Skip to content

Commit

Permalink
Fix delay between server starting and showing "=> Server restarted"
Browse files Browse the repository at this point in the history
Creating the watcher can take up to 12+ seconds in small - medium apps, and uses sync fs calls.
The server would start right away, but the tool process wouldn't know about it until the watcher finished setting up. Also, the proxy doesn't forward requests until "=> Server restarted" is shown.
A new async option is added to Watcher which prevents it from blocking the event loop too long.
Also, the watcher and legacy bundle are only created after the server has started, or 3 seconds has passed.
  • Loading branch information
zodern committed Jan 1, 2019
1 parent 2ae55e8 commit 21f976d
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 45 deletions.
58 changes: 45 additions & 13 deletions tools/fs/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ export class Watcher {
constructor(options) {
var self = this;

// Run initial check asyncly
self._async = options.async;

// The set to watch.
self.watchSet = options.watchSet;
if (! self.watchSet) {
Expand Down Expand Up @@ -431,14 +434,10 @@ export class Watcher {
}

_startFileWatches() {
var self = this;

// Set up a watch for each file
_.each(self.watchSet.files, function (hash, absPath) {
if (self.stopped) {
return;
}
const self = this;
const keys = Object.keys(self.watchSet.files);

self._processBatches(keys, absPath => {
if (! self.justCheckOnce) {
self._watchFileOrDirectory(absPath);
}
Expand Down Expand Up @@ -640,19 +639,52 @@ export class Watcher {

return stat;
}

// Iterates over the array, calling handleItem for each item
// When this._async is true, it pauses ocassionally to avoid blocking for too long
// Stops iterating after watcher is stopped
_processBatches(array, handleItem) {
const self = this;
const async = self._async;
const amountPerBatch = async ? 50 : array.length;
let index = 0;

function processBatch() {
const stop = Math.min(index + amountPerBatch, array.length);
for(; index < stop; index++) {
if (self.stopped) {
return;
}

handleItem(array[index]);
}

if (index < array.length) {
if (async) {
setImmediate(processBatch);
} else {
processBatch();
}
}
}

processBatch();
}
_checkDirectories() {
var self = this;
const self = this;
const dirs = self.watchSet.directories.sort((dir1, dir2) => {
// Check node_modules directories last since they are the least likely to change
const dir1Value = dir1.absPath.indexOf('node_modules') > -1 ? 0 : 1
const dir2Value = dir2.absPath.indexOf('node_modules') > -1 ? 0 : 1;

return dir2Value - dir1Value;
})

if (self.stopped) {
return;
}

_.each(self.watchSet.directories, function (info) {
if (self.stopped) {
return;
}

self._processBatches(dirs, info => {
if (! self.justCheckOnce) {
self._watchFileOrDirectory(info.absPath);
}
Expand Down
83 changes: 51 additions & 32 deletions tools/runners/run-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ _.extend(AppRunner.prototype, {
Promise.await(self.runPromise);

var runPromise = self.runPromise = self._makePromise("run");
var listenPromise = self._makePromise("listen");

// Run the program
options.beforeRun && options.beforeRun();
Expand All @@ -719,6 +720,7 @@ _.extend(AppRunner.prototype, {
self.proxy.setMode("proxy");
options.onListen && options.onListen();
self._resolvePromise("start");
self._resolvePromise("listen");
},
nodeOptions: getNodeOptionsFromEnvironment(),
settings: settings,
Expand Down Expand Up @@ -761,17 +763,6 @@ _.extend(AppRunner.prototype, {
var serverWatcher;
var clientWatcher;

if (self.watchForChanges) {
serverWatcher = new watch.Watcher({
watchSet: serverWatchSet,
onChange: function () {
self._resolvePromise("run", {
outcome: 'changed'
});
}
});
}

var setupClientWatcher = function () {
clientWatcher && clientWatcher.stop();
clientWatcher = new watch.Watcher({
Expand All @@ -781,26 +772,10 @@ _.extend(AppRunner.prototype, {
? 'changed-refreshable' // only a client asset has changed
: 'changed'; // both a client and server asset changed
self._resolvePromise('run', { outcome: outcome });
}
},
async: true
});
};
if (self.watchForChanges && canRefreshClient) {
setupClientWatcher();
}

function pauseClient(arch) {
return appProcess.proc.sendMessage("webapp-pause-client", { arch });
}

async function refreshClient(arch) {
if (typeof arch === "string") {
// This message will reload the client program and unpause it.
await appProcess.proc.sendMessage("webapp-reload-client", { arch });
}
// If arch is not a string, the receiver of this message should
// assume all clients need to be refreshed.
await appProcess.proc.sendMessage("client-refresh");
}

function runPostStartupCallbacks(bundleResult) {
const callbacks = bundleResult.postStartupCallbacks;
Expand Down Expand Up @@ -833,15 +808,59 @@ _.extend(AppRunner.prototype, {
}
}

Console.enableProgressDisplay(false);
Promise.race([
listenPromise,
new Promise(resolve => setTimeout(resolve, 3000))
]).then(() => {
if (self.watchForChanges) {
serverWatcher = new watch.Watcher({
watchSet: serverWatchSet,
onChange: function () {
self._resolvePromise("run", {
outcome: 'changed'
});
},
async: true
});
}

if (self.watchForChanges && canRefreshClient) {
setupClientWatcher();
}
Console.enableProgressDisplay(false);
const postStartupResult = runPostStartupCallbacks(bundleResult);

if (postStartupResult) {
self._resolvePromise('run', {
...postStartupResult,
postStartupResult: true
})
return postStartupResult;
}
});

const postStartupResult = runPostStartupCallbacks(bundleResult);
if (postStartupResult) return postStartupResult;
function pauseClient(arch) {
return appProcess.proc.sendMessage("webapp-pause-client", { arch });
}

async function refreshClient(arch) {
if (typeof arch === "string") {
// This message will reload the client program and unpause it.
await appProcess.proc.sendMessage("webapp-reload-client", { arch });
}
// If arch is not a string, the receiver of this message should
// assume all clients need to be refreshed.
await appProcess.proc.sendMessage("client-refresh");
}

// Wait for either the process to exit, or (if watchForChanges) a
// source file to change. Or, for stop() to be called.
var ret = runPromise.await();

if (ret.postStartupResult) {
return ret;
}

try {
while (ret.outcome === 'changed-refreshable') {
if (! canRefreshClient) {
Expand Down

0 comments on commit 21f976d

Please sign in to comment.