server: Simplify gating of state transition waiters

pull/4740/head
Richard Hansen 2021-02-08 23:48:20 -05:00 committed by John McLear
parent 105f8b0ccb
commit 5b327b63ac
1 changed files with 20 additions and 9 deletions

View File

@ -63,6 +63,14 @@ const State = {
let state = State.INITIAL; let state = State.INITIAL;
class Gate extends Promise {
constructor() {
let res;
super((resolve) => { res = resolve; });
this.resolve = res;
}
}
const removeSignalListener = (signal, listener) => { const removeSignalListener = (signal, listener) => {
logger.debug(`Removing ${signal} listener because it might interfere with shutdown tasks. ` + logger.debug(`Removing ${signal} listener because it might interfere with shutdown tasks. ` +
`Function code:\n${listener.toString()}\n` + `Function code:\n${listener.toString()}\n` +
@ -70,13 +78,13 @@ const removeSignalListener = (signal, listener) => {
process.off(signal, listener); process.off(signal, listener);
}; };
const runningCallbacks = []; let startDoneGate;
exports.start = async () => { exports.start = async () => {
switch (state) { switch (state) {
case State.INITIAL: case State.INITIAL:
break; break;
case State.STARTING: case State.STARTING:
await new Promise((resolve) => runningCallbacks.push(resolve)); await startDoneGate;
// fall through // fall through
case State.RUNNING: case State.RUNNING:
return express.server; return express.server;
@ -89,6 +97,7 @@ exports.start = async () => {
throw new Error(`unknown State: ${state.toString()}`); throw new Error(`unknown State: ${state.toString()}`);
} }
logger.info('Starting Etherpad...'); logger.info('Starting Etherpad...');
startDoneGate = new Gate();
state = State.STARTING; state = State.STARTING;
// Check if Etherpad version is up-to-date // Check if Etherpad version is up-to-date
@ -135,13 +144,13 @@ exports.start = async () => {
logger.info('Etherpad is running'); logger.info('Etherpad is running');
state = State.RUNNING; state = State.RUNNING;
while (runningCallbacks.length > 0) setImmediate(runningCallbacks.pop()); startDoneGate.resolve();
// Return the HTTP server to make it easier to write tests. // Return the HTTP server to make it easier to write tests.
return express.server; return express.server;
}; };
const stoppedCallbacks = []; let stopDoneGate;
exports.stop = async () => { exports.stop = async () => {
switch (state) { switch (state) {
case State.STARTING: case State.STARTING:
@ -151,7 +160,7 @@ exports.stop = async () => {
case State.RUNNING: case State.RUNNING:
break; break;
case State.STOPPING: case State.STOPPING:
await new Promise((resolve) => stoppedCallbacks.push(resolve)); await stopDoneGate;
// fall through // fall through
case State.INITIAL: case State.INITIAL:
case State.STOPPED: case State.STOPPED:
@ -162,6 +171,7 @@ exports.stop = async () => {
throw new Error(`unknown State: ${state.toString()}`); throw new Error(`unknown State: ${state.toString()}`);
} }
logger.info('Stopping Etherpad...'); logger.info('Stopping Etherpad...');
let stopDoneGate = new Gate();
state = State.STOPPING; state = State.STOPPING;
let timeout = null; let timeout = null;
await Promise.race([ await Promise.race([
@ -173,10 +183,10 @@ exports.stop = async () => {
clearTimeout(timeout); clearTimeout(timeout);
logger.info('Etherpad stopped'); logger.info('Etherpad stopped');
state = State.STOPPED; state = State.STOPPED;
while (stoppedCallbacks.length > 0) setImmediate(stoppedCallbacks.pop()); stopDoneGate.resolve();
}; };
const exitCallbacks = []; let exitGate;
let exitCalled = false; let exitCalled = false;
exports.exit = async (err = null) => { exports.exit = async (err = null) => {
/* eslint-disable no-process-exit */ /* eslint-disable no-process-exit */
@ -206,7 +216,7 @@ exports.exit = async (err = null) => {
case State.STOPPED: case State.STOPPED:
break; break;
case State.EXITING: case State.EXITING:
await new Promise((resolve) => exitCallbacks.push(resolve)); await exitGate;
// fall through // fall through
case State.WAITING_FOR_EXIT: case State.WAITING_FOR_EXIT:
return; return;
@ -214,8 +224,9 @@ exports.exit = async (err = null) => {
throw new Error(`unknown State: ${state.toString()}`); throw new Error(`unknown State: ${state.toString()}`);
} }
logger.info('Exiting...'); logger.info('Exiting...');
exitGate = new Gate();
state = State.EXITING; state = State.EXITING;
while (exitCallbacks.length > 0) setImmediate(exitCallbacks.pop()); exitGate.resolve();
// Node.js should exit on its own without further action. Add a timeout to force Node.js to exit // Node.js should exit on its own without further action. Add a timeout to force Node.js to exit
// just in case something failed to get cleaned up during the shutdown hook. unref() is called on // just in case something failed to get cleaned up during the shutdown hook. unref() is called on
// the timeout so that the timeout itself does not prevent Node.js from exiting. // the timeout so that the timeout itself does not prevent Node.js from exiting.