终端断开导致Tomcat进程被kill问题分析
在测试环境中,我们经常会写类似这样的tomcat启动脚本,通过tail
日志来检查启动是否成功
|
|
有的时候我们会遇到tomcat进程启动完一段时间后就退出了的情况。通过测试发现,如果不退出脚本就直接关闭终端,tomcat就会在终端关闭后退出
TL;DR
有如下几种解决方式
|
|
|
|
|
|
原因分析
看下tomcat自身的启动脚本,没发现什么特殊的地方
startup.sh
通过exec
将自己替换为catalina.sh
catalina.sh
直接以后台方式&
启动了java
,并且只有当OS为HP-UX
时才会加上nohup
catalina.sh
退出,java
进程ppid变为1
用前面我们自己写的脚本启动一下,观察启动完以后的进程状态
|
|
用strace
跟踪一下tomcat进程,然后关闭终端,可以看到是SIGHUP
信号导致了tomcat退出
|
|
既然是SIGHUP
导致的退出,那么最直接的解决方式自然是用nohup
了
那为什么以
&
方式后台启动的tomcat会收到SIGHUP
信号,但是退出启动脚本后再关闭终端又不会有问题呢
这个问题先放一下,我们尝试退出脚本后再看下进程状态,这时start.sh
, tail
等进程都结束了,只剩下tomcat
|
|
对比上面的状态图可以看到tomcat进程状态STAT
发生了变化,从Sl+
变成了Sl
通过man ps
我们可以找到+
号的意义
+ is in the foreground process group
继续查找原因需要了解一些进程和Job Control
相关的知识
理解Unix进程
APUE - Chapter 9. Process Relationships
When does kernel send SIGHUP
Terminally confused (part six)
Job是交互式shell管理进程的一种抽象手段,借此shell可以批量操作进程,例如使用Ctrl-C
会停止tail -f foo | grep bar
这个job中所有的进程
Job通过进程组Process Group
来实现,进程组是进程的集合,Session
是进程组的集合,Session
可以有一个控制终端Controlling Terminal(ctty)
。
- 进程组分为一个前台进程组
foreground process group
和多个后台进程组background process groups
- 前台进程组接收终端触发的信号,如
SIGINT
,以及连接中断时的SIGHUP
Session Leader
(建立session的进程,例如login shell)接收SIGHUP
信号,并决定如何处理。Bash
的处理方式是转发给所有的jobs,无论前后台Orphaned Process Group
简单来说就是组内进程脱离了和Session Leader
的联系的进程组,例如tomcat启动脚本,启动java后自身退出,java成为孤儿进程,ppid为1,进程组也成为孤儿进程组
简单做个实验,启动两组tail
+grep
的命令
|
|
|
|
可以看到两行命令形成了两个进程组,PGID
分别为2085
和2087
,其中2087
是前台进程组(STAT
中有+
)
用strace
跟踪,并输入Ctrl-C
,可以看到前台进程组里所有的进程都收到了信号
|
|
关闭终端,后台进程组中的进程收到bash
转发的连接中断信号
|
|
现在可以回答前面那个问题了
为什么以
&
方式后台启动的tomcat会收到SIGHUP
信号,但是退出启动脚本后再关闭终端又不会有问题呢
因为启动脚本未退出时,tomcat位于前台进程组中,退出后tomcat位于孤儿进程组中
继续,为什么启动脚本中所有的命令都在同一个进程组中
因为执行脚本时(非交互shell)默认不启用Job Control
,因此脚本内所有命令的PGID都同脚本本身一致
如果在脚本中启用
Job Control
会怎样
我们实验下,在脚本中加上set -m
,现在每个命令都有了自己的进程组,并且只有tail
是前台进程组,那tomcat自然不会收到SIGHUP
了
|
|
还有其他途径吗
最彻底的,也是linux中启动守护进程的方法,就是启动tomcat时干脆就不和login shell在一个session里
实验下通过setsid
来启动tomcat,计划通~
|
|