0

一段时间以来,我一直在寻找一种干净而健壮的方式来优雅地关闭 nodejs express 服务器。我为此使用了 terminus.js,并提出了以下代码:

#! /usr/bin/env node
const {once} = require('ramda'); 
const http = require('http');
const {createLogger} = require('./util/logger.js');
const loadConfig = require('./load-config.js');
const {createTerminus} = require('@godaddy/terminus');
const createApp = require('./app');

const config = loadConfig();
const apiPort = config?.apiServer?.port || 3335;
const logger = createLogger(config.logging);
const expressApp = createApp({ config, logger });
const server = http.createServer(expressApp);

const uncaughtHandler = once(async error => { 
  logger.info('Uncaught exception, attempting graceful shutdown...', { error });
  try {
    await shutdown();
    logger.info('Shutdown completed. Exit cleanly.');
  } catch(error) {
    logger.error(error);
    logger.error('Shutdown timedout or failed. Exit abruptly.');
  } finally {
    process.exit(1);
  }
});
process.on('unhandledRejection', error => { throw error });  // Forward as uncaughtExeption
process.on('uncaughtException', uncaughtHandler);

const terminusConfiguration = Object.freeze({
  timeout: 20000, 
  signals: ['SIGINT', 'SIGTERM', 'SIGQUIT'],
  onSignal: async () => await shutdown(),
  onShutdown: () => logger.info('Cleanup finished. Exitting'),
  logger: logger.error,
});

const shutdown = once(function() {
  return new Promise(async (resolve, reject) => {
    // After 10 seconds the cleanup times out.
    setTimeout(reject, 10000).unref();
    
    await cleanup();
    resolve();
  });
});
async function cleanup() {
  return new Promise(async (resolve, reject) => {
    // Note depending on whether the shutdown was initiated by terminus
    // or the by the uncaughtHandler, we might still need to close
    // the server ourselves.
    if(server && server.listening) await closeServer(server);

    // simulate other cleanup tasks.
    setTimeout(resolve, 3000); 
  });
}
async function closeServer(server) {
  return new Promise(resolve => server.close(resolve));
}

createTerminus(server, terminusConfiguration);

server.listen(apiPort, () => logger.info(`API started on port ${apiPort}`));

Terminus 通过关闭 http 服务器然后调用 onSignal() 来处理信号,然后调用关闭函数。因为我希望在有未捕获异常的情况下重新使用关闭功能,所以关闭功能也将尝试关闭 http 服务器(如果它正在侦听)。

我使用oncefrom ramda 来确保关闭函数只执行一次,并且随后对它的任何调用都将被忽略。这在首先有一个 SIGINT 信号并且在服务器关闭时有一个 uncaughtException 的情况下效果很好,因为后者将被忽略。但是,当我们首先遇到触发关闭功能的 uncaughtException 并且在我们关闭时有一个信号时,就会出现问题。这将使总站尝试关闭此时可能已经关闭的服务器,从而导致ERR_SERVER_NOT_RUNNING错误。

有关改进此代码的任何提示?难道没有什么方法可以让 terminus 也处理 uncaughtExeptions 吗?

4

0 回答 0