如何确保执行supervisorctl stop时能终止Supervisor启动的服务?
问题背景
我的Supervisor配置如下:
[program:myprogram] command=/usr/local/bin/run_script.sh directory=/usr/local/bin user=myuser numprocs=1 process_name=%(program_name)s stdout_logfile=/var/log/run_script.log stderr_logfile=/var/log/run_script.log autostart=true autorestart=true startsecs=10 stopsignal=KILL
其中run_script.sh会先完成Shell初始化操作,再启动阻塞运行的Python脚本run_script.py,该脚本通过joblib.Parallel启动多进程。
启动服务时一切正常,但执行sudo supervisorctl stop myprogram后,Supervisor显示服务已停止,但run_script.sh和run_script.py及其子进程仍在运行。
原因分析
核心问题在于Supervisor仅管理它直接启动的进程:
- Supervisor启动的是
run_script.sh对应的Bash进程,执行stop命令时,只会向这个Bash进程发送指定的KILL信号。 - 默认情况下,Bash不会将收到的终止信号转发给它启动的子进程(即Python脚本进程)。当Bash进程被杀死后,Python进程及其
joblib创建的多进程会变成孤儿进程,被系统的init或systemd进程接管,继续运行。 - 即使指定了
stopsignal=KILL,也只是终止了顶层的Bash进程,无法触及其子进程链。
解决方案
方案1:让Python进程取代Shell进程(推荐)
修改run_script.sh,用exec命令启动Python脚本,这样Python进程会直接替换当前的Bash进程,Supervisor将直接管理Python进程:
# run_script.sh 修改后内容 # 保留你的Shell初始化操作 # ... # 用exec启动Python脚本,替换当前Bash进程 exec /usr/local/bin/run_script.py
这样当执行supervisorctl stop时,KILL信号会直接发送给Python进程,Python进程退出时会自动终止其所有子进程(包括joblib创建的多进程)。
方案2:在Shell脚本中添加信号转发逻辑
如果必须保留Shell脚本的独立进程(比如需要复杂的初始化操作),可以在run_script.sh中添加信号捕获和转发逻辑,确保终止信号能传递给Python进程:
# run_script.sh 修改后内容 # 保留你的Shell初始化操作 # ... # 定义信号处理函数:将收到的终止信号转发给Python进程 trap 'kill -KILL $PYTHON_PID' TERM INT KILL # 启动Python脚本并记录其PID /usr/local/bin/run_script.py & PYTHON_PID=$! # 等待Python进程结束,确保Shell脚本不会提前退出 wait $PYTHON_PID
当Bash进程收到KILL信号时,会触发trap定义的逻辑,向Python进程发送KILL信号,wait命令会让Shell脚本等到Python进程完全终止后再退出,避免子进程成为孤儿。
补充说明
对于joblib.Parallel创建的多进程,只要父Python进程被正常终止,这些子进程通常会随之退出(因为父进程退出后,子进程会收到SIGHUP信号,默认行为是终止)。所以关键是确保Supervisor能正确终止父Python进程,无论是通过exec让Supervisor直接管理,还是通过Shell转发信号。
内容的提问来源于stack exchange,提问作者Cerin

