server: Fix handling of errors during startup and shutdown

Before, an unhandled rejection or uncaught exception during startup
would cause `exports.exit()` to wait forever for startup completion.
Similarly, an error during shutdown would cause `exports.exit()` to
wait forever for shutdown to complete. Now any error during startup or
shutdown triggers an immediate exit.
pull/4740/head
Richard Hansen 2021-02-09 00:03:05 -05:00 committed by John McLear
parent 5999d8cd44
commit ebdb2798ff
1 changed files with 64 additions and 46 deletions

View File

@ -59,6 +59,7 @@ const State = {
STOPPED: 5,
EXITING: 6,
WAITING_FOR_EXIT: 7,
STATE_TRANSITION_FAILED: 8,
};
let state = State.INITIAL;
@ -85,13 +86,15 @@ exports.start = async () => {
break;
case State.STARTING:
await startDoneGate;
// fall through
// Retry. Don't fall through because it might have transitioned to STATE_TRANSITION_FAILED.
return await exports.start();
case State.RUNNING:
return express.server;
case State.STOPPING:
case State.STOPPED:
case State.EXITING:
case State.WAITING_FOR_EXIT:
case State.STATE_TRANSITION_FAILED:
throw new Error('restart not supported');
default:
throw new Error(`unknown State: ${state.toString()}`);
@ -99,7 +102,7 @@ exports.start = async () => {
logger.info('Starting Etherpad...');
startDoneGate = new Gate();
state = State.STARTING;
try {
// Check if Etherpad version is up-to-date
UpdateCheck.check();
@ -141,6 +144,12 @@ exports.start = async () => {
logger.debug(`Installed hooks:\n${plugins.formatHooks()}`);
await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('createServer');
} catch (err) {
logger.error('Error occurred while starting Etherpad');
state = State.STATE_TRANSITION_FAILED;
startDoneGate.resolve();
return await exports.exit(err);
}
logger.info('Etherpad is running');
state = State.RUNNING;
@ -166,6 +175,7 @@ exports.stop = async () => {
case State.STOPPED:
case State.EXITING:
case State.WAITING_FOR_EXIT:
case State.STATE_TRANSITION_FAILED:
return;
default:
throw new Error(`unknown State: ${state.toString()}`);
@ -173,6 +183,7 @@ exports.stop = async () => {
logger.info('Stopping Etherpad...');
let stopDoneGate = new Gate();
state = State.STOPPING;
try {
let timeout = null;
await Promise.race([
hooks.aCallAll('shutdown'),
@ -181,6 +192,12 @@ exports.stop = async () => {
}),
]);
clearTimeout(timeout);
} catch (err) {
logger.error('Error occurred while stopping Etherpad');
state = State.STATE_TRANSITION_FAILED;
stopDoneGate.resolve();
return await exports.exit(err);
}
logger.info('Etherpad stopped');
state = State.STOPPED;
stopDoneGate.resolve();
@ -214,6 +231,7 @@ exports.exit = async (err = null) => {
return await exports.exit();
case State.INITIAL:
case State.STOPPED:
case State.STATE_TRANSITION_FAILED:
break;
case State.EXITING:
await exitGate;