如题,打日志发现函数确实注册成功了,但是SIGTERM和SIGINT信号关闭应用时都不执行。目前没有发现能够正常执行钩子的关闭方法。
关闭的钩子用 app.beforeClose 不要手动监听信号量,在 egg-cluster 里面已经拦截了。
之前看错题目。
并没有手动监听信号量,就是用app.beforeClose注册的钩子
是不会执行,在单元测试用的,一般进程退出不需要做什么。
我们在做eureka的集成,目前这个组件需要在程序退出的时候挂钩子做优雅关闭的,不排除有其他类型插件同样需要优雅关闭。
最好不要这样做,如果 kill -9 的话是不会执行的
-9是异常退出场景,正常的退出行为都是SIGINT或者SIGTERM退出,node语言层面也提供了支持异步的进程退出钩子,个人认为框架层应该提供优雅关闭的方法。
我试过kill是可以正确触发退出钩子
平时开发在Terminal里面只有Ctrl + \ (SIGQUIT) 能正常退出
用Ctrl + c都无法正常退出
我加一个
beforeClose 不会执行的话, https://github.com/eggjs/egg/blob/master/lib/egg.js#L103 这里是不是会有问题?
cc @popomore @gxcsoccer
因为我之前也受这个问题困扰,所以当时看了一下源码,有一些分析整理了一下
constructor(options) {
// ...
// https://nodejs.org/api/process.html#process_signal_events
// https://en.wikipedia.org/wiki/Unix_signal
// kill(2) Ctrl-C
process.once('SIGINT', this.onSignal.bind(this, 'SIGINT'));
// kill(3) Ctrl-\
process.once('SIGQUIT', this.onSignal.bind(this, 'SIGQUIT'));
// kill(15) default
process.once('SIGTERM', this.onSignal.bind(this, 'SIGTERM'));
// ...
}
// ...
/**
* close agent worker, App Worker will closed by cluster
*
* https://www.exratione.com/2013/05/die-child-process-die/
* make sure Agent Worker exit before master exit
*/
killAgentWorker() {
if (this.agentWorker) {
this.log('[master] kill agent worker with signal SIGTERM');
this.agentWorker.removeAllListeners();
this.agentWorker.kill('SIGTERM');
}
}
killAppWorkers() {
for (const id in cluster.workers) {
const worker = cluster.workers[id];
worker.disableRefork = true;
worker.process.kill('SIGTERM');
}
}
// ...
onSignal(signal) {
if (this.closed) return;
this.logger.info('[master] receive signal %s, closing', signal);
this.close();
}
// ...
close() {
this.closed = true;
// kill app workers
// kill agent worker
// exit itself
this.killAppWorkers();
this.killAgentWorker();
// sleep 100ms to make sure SIGTERM send to the child processes
this.log('[master] send kill SIGTERM to app workers and agent worker, will exit with code:0 after 100ms');
setTimeout(() => {
this.log('[master] close done, exiting with code:0');
process.exit(0);
}, 100);
}
从egg-cluster中能我看到 master 监听了SIGINT SIGQUIT SIGTERM 三个事件
触发事件后,master 会向 agent 和 app 发 SIGTERM
beforeClose注册在 https://github.com/eggjs/egg-core/blob/b36c6854fdbd8d1cebe4c0a1da5f02d78476a0a4/lib/egg.js#L67
226~263行
/**
* Close all, it will close
* - callbacks registered by beforeClose
* - emit `close` event
* - remove add listeners
*
* If error is thrown when it's closing, the promise will reject.
* It will also reject after following call.
* @return {Promise} promise
* @since 1.0.0
*/
close() {
if (this[CLOSE_PROMISE]) return this[CLOSE_PROMISE];
this[CLOSE_PROMISE] = co(function* closeFunction() {
// close in reverse order: first created, last closed
const closeFns = Array.from(this[CLOSESET]);
for (const fn of closeFns.reverse()) {
yield utils.callFn(fn);
this[CLOSESET].delete(fn);
}
// Be called after other close callbacks
this.emit('close');
this.removeAllListeners();
this[ISCLOSE] = true;
}.bind(this));
return this[CLOSE_PROMISE];
}
/**
* Register a function that will be called when app close
* @param {Function} fn - the function that can be generator function or async function
*/
beforeClose(fn) {
assert(is.function(fn), 'argument should be function');
this[CLOSESET].add(fn);
}
可以看到close方法返回的就是触发这些callback的promise
我没有找到任何触发这个close方法的地方
根据上面的代码,app 和 agent 应该要监听 SIGTERM,并且把close的promise执行完,但我没有在任何地方看到监听的代码,那么所有的包括默认注册的beforeClose都不会执行
我现在的解决方案是在app和agnet中直接监听SIGTERM
值得注意的是,用kill发信号的时候会正常工作
而在控制台中用Ctrl+c和Ctrl+\ 这种快捷键的时候,master 和 worker 都能收到对应的信号,这样就会触发两次事件
补个示例,可以向 master 进程发送信号,和快捷键发送信号
看close.log
看了下不太好实现,先搁置。
我觉得这个问题导致注册beforeClose没有意义了,希望尽快修复一下。
每个worker执行完beforeClose后发信号给master,master确保每个worker都已经退出再关闭
这个 api 的目的是给单元测试用的,不是没有意义。
原来是不支持阿, 折腾了半天准备提 issue 了才搜索到这个。
https://eggjs.org/zh-cn/basics/app-start.html 文档应该注明下,或者干脆删除掉关于 beforeClose 的。
备注下使用 docker 时无法触发的情景
情况: 使用 docker 时,entrypoint: 是 npm start 或 npm run dev
docker stop 时 app.js 中 beforeClose 无法被正确触发,因为收到 SIGTERM 信号的进程不是 egg-scripts;如果直接 kill egg-scripts,能正常触发 beforeClose。
解决方案:
不要把 npm start 作为 entrypoint,eggjs 改成
entrypoint: node node_modules/.bin/egg-scripts start --title=title
就能在 docker stop 时正常触发 beforeClose
是不是你 npm start 里面有 --daemon
没有 daemon,egg 应用在线上跑了很多年了,加 --daemon 都无法正常启动 。
搜到类似这种文章明白了原理:https://juejin.im/post/5d9c5d3451882541391a2b7f
Most helpful comment
-9是异常退出场景,正常的退出行为都是SIGINT或者SIGTERM退出,node语言层面也提供了支持异步的进程退出钩子,个人认为框架层应该提供优雅关闭的方法。