Ready needs to be able to handle asynchronous code
Summary:
A previous diff introduced the isReady state as to make it possible to serve HTML content sooner than we are ready.
This worked great on debugging. As I was testing a release, it was discovered that there was a race condition and the server was not ready when it was already handling upgrade events.
To solve this, I've added another state flag in the form of a promise.
This one, can be waited on. This is used then during upgrade events as we can safely wait until the server is ready to accept incoming connections before proceeding with the upgrade.
Problem is shown below:
{F1080003241}
{F1080003356}
Reviewed By: passy
Differential Revision: D48829453
fbshipit-source-id: e148a392bbe66dd91710e32871e270c8950e25c2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
072d618681
commit
457767c7cc
@@ -72,7 +72,17 @@ const verifyAuthToken = (req: http.IncomingMessage): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following two variables are used to control when
|
||||||
|
* the server is ready to accept incoming connections.
|
||||||
|
* - isReady, is used to synchronously check if the server is
|
||||||
|
* ready or not.
|
||||||
|
* - isReadyWaitable achieves the same but is used by
|
||||||
|
* asynchronous functions which may want to wait until the
|
||||||
|
* server is ready.
|
||||||
|
*/
|
||||||
let isReady = false;
|
let isReady = false;
|
||||||
|
let isReadyWaitable: Promise<void> | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orchestrates the creation of the HTTP server, proxy, and WS server.
|
* Orchestrates the creation of the HTTP server, proxy, and WS server.
|
||||||
@@ -134,6 +144,17 @@ async function startHTTPServer(config: Config): Promise<{
|
|||||||
|
|
||||||
server.listen(config.port);
|
server.listen(config.port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the promise which can be waited on. In this case,
|
||||||
|
* a reference to resolve is kept outside of the body of the promise
|
||||||
|
* so that other asynchronous functions can resolve the promise
|
||||||
|
* on its behalf.
|
||||||
|
*/
|
||||||
|
let isReadyResolver: (value: void | PromiseLike<void>) => void;
|
||||||
|
isReadyWaitable = new Promise((resolve, _reject) => {
|
||||||
|
isReadyResolver = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
console.log(`Starting server on http://localhost:${config.port}`);
|
console.log(`Starting server on http://localhost:${config.port}`);
|
||||||
const readyForIncomingConnections = (
|
const readyForIncomingConnections = (
|
||||||
@@ -141,11 +162,18 @@ async function startHTTPServer(config: Config): Promise<{
|
|||||||
companionEnv: FlipperServerCompanionEnv,
|
companionEnv: FlipperServerCompanionEnv,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
attachSocketServer(socket, serverImpl, companionEnv);
|
attachSocketServer(socket, serverImpl, companionEnv);
|
||||||
|
/**
|
||||||
|
* At this point, the server is ready to accept incoming
|
||||||
|
* connections. Change the isReady state and resolve the
|
||||||
|
* promise so that other asychronous function become unblocked.
|
||||||
|
*/
|
||||||
isReady = true;
|
isReady = true;
|
||||||
|
isReadyResolver();
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
tracker.track('server-started', {
|
tracker.track('server-started', {
|
||||||
port: config.port,
|
port: config.port,
|
||||||
});
|
});
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -214,8 +242,14 @@ function attachWS(server: http.Server, config: Config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wss = new WebSocketServer(options);
|
const wss = new WebSocketServer(options);
|
||||||
server.on('upgrade', function upgrade(request, socket, head) {
|
server.on('upgrade', async function upgrade(request, socket, head) {
|
||||||
const {pathname} = parse(request.url!);
|
if (!request.url) {
|
||||||
|
console.log('[flipper-server] No request URL available');
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {pathname} = parse(request.url);
|
||||||
|
|
||||||
// Handled by Metro
|
// Handled by Metro
|
||||||
if (pathname === '/hot') {
|
if (pathname === '/hot') {
|
||||||
@@ -223,13 +257,18 @@ function attachWS(server: http.Server, config: Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pathname === '/') {
|
if (pathname === '/') {
|
||||||
|
// Wait until the server is ready to accept incoming connections.
|
||||||
|
await isReadyWaitable;
|
||||||
wss.handleUpgrade(request, socket, head, function done(ws) {
|
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||||
wss.emit('connection', ws, request);
|
wss.emit('connection', ws, request);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('addWebsocket.upgrade -> unknown pathname', pathname);
|
console.error(
|
||||||
|
'[flipper-server] Unable to upgrade, unknown pathname',
|
||||||
|
pathname,
|
||||||
|
);
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user