之前在網上看過一篇文章,是講Tomcat進程意外退出的,我看完感覺好奇,自己也測試了下,果然是有這種問題,所以自己也借此總結一下。
先簡單說下測試過程,先創建一個web服務啟動 test.sh,內容如下:
#!/bin/bash cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start tail -f /usr/software/tomcat/apache-tomcat-7.0.81/logs/catalina.out
然后啟動該腳本,服務起來了,可以正常訪問。
tomcat啟動之后,當前shell進程並沒有退出,而是掛住在tail進程,往終端輸出日志內容。這種情況下:
1)、如果我先直接關掉ssh窗口后,Java進程會退出,服務不可用。
2)、而我如果先 用ctrl-c
終止test.sh進程,然后再關閉ssh終端的話,這時Java進程不會退出。服務沒有受到影響,仍然可用。
3)、下面我又把最后tail這一行去掉,發現直接關掉ssh終端窗口,Java進程也不會退出,服務不受影響。
#!/bin/bash cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start
4)、最后我再次恢復tail這一行,然后在開頭再加上 set -m, 直接關掉ssh窗口Java進程同樣也不會退出,服務同樣也不受影響。
#!/bin/bash set -m cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start tail -f /usr/software/tomcat/apache-tomcat-7.0.81/logs/catalina.out
到這里是不是有點暈了,不急,待我慢慢道出原因來。
這是一個有趣的現象,catalina.sh start
方式啟動的tomcat會把java進程掛到init
(進程id為1)的父進程下,已經與當前test.sh
進程脫離了父子關系,也與ssh進程沒有關系,為什么關閉ssh終端窗口會導致java進程退出?
經過測試,有發現:
a) 用 ctrl-c 終止當前test.sh進程時,系統events進程向 java 和 tail 兩個進程發送了SIGINT
信號
b) 關閉ssh終端窗口時,sshd向下游進程發送SIGHUP
, java進程也會收到。
后來通過google后了解到: shell在非交互模式下對后台進程處理SIGINT
信號時設置的是IGNORE
。
交互模式與非交互模式對作業控制(job control)默認方式不同:
I)在交互模式下,因為作業控制的需要,
shell不會對后台進程處理SIGINT
信號設置為忽略。因此這樣的話,父進程會把收到的鍵盤事件比如ctrl-c
之類的SIGINT
傳播給進程組中的每個成員(假設后台進程也是父進程組的成員),那么終端隨意ctrl-c
就可能導致所有的后台進程退出,顯然這樣是不合理的。因此,在交互模式下的后台進程會設置一個自己的進程組ID。
II)而非交互模式下,通常是不需要作業控制的,所以作業控制在非交互模式下默認也是關閉的(當然也可以在腳本里通過選項set -m
打開作業控制選項)。不開啟作業控制的話,腳本里的后台進程可以通過設置忽略SIGINT
信號來避免父進程對組中成員的傳播,因為對它來說這個信號已經沒有意義。
在非交互模式下,shell對java進程設置了SIGINT
,SIGQUIT
信號設置了忽略,但並沒有對SIGHUP
信號設為忽略,回頭看上面說的,直接關閉ssh終端窗口時,sshd向下游進程發送SIGHUP
, java進程也會收到。
再看一下當時的進程層級:
|-sshd(1622)-+-sshd(11681)---sshd(11699)---bash(11700)---test.sh(13285)---tail(13299)
sshd把SIGHUP
傳遞給bash進程后,bash會把SIGHUP
傳遞給它的子進程,並且對於其子進程test.sh,bash還會對test.sh的進程組里的成員都傳播一遍SIGHUP
。因為java后台進程從父進程catalina.sh(又是從其父進程test.sh)繼承的pgid,所以java進程仍屬於test.sh進程組里的成員,收到SIGHUP
后退出。
如果我們在test.sh里設置開啟作業控制的話,就不會讓java進程退出了
#!/bin/bash set -m cd /usr/software/tomcat/apache-tomcat-7.0.81/bin/ ./catalina.sh start tail -f /usr/software/tomcat/apache-tomcat-7.0.81/logs/catalina.out
此時java后台進程繼承父進程catalina.sh的pgid,而catalina.sh不再使用test.sh的進程組,而是自己的pid作為pgid,catalina.sh進程在執行完退出后,java進程掛到了init下,java與test.sh進程就完全脫離關系了,bash也不會再向它發送信號。
注: 如果把tail 打印的這一行去掉,為什么Java進程也不會退出,我猜原因應該是,如果沒有tail的話, Java進程就不是他們的子進程了,自然怎么操作都不會受影響。