Tomcat進程意外退出的問題分析

節前某個部門的測試環境反饋tomcat會意外退出,我們到實際環境排查后發現不是jvm crash,日志里有進程銷毀的記錄,從pause到destory的整個過程:

org.apache.coyote.AbstractProtocol pause
Pausing ProtocolHandler
org.apache.catalina.core.StandardService stopInternal
Stopping service Catalina
org.apache.coyote.AbstractProtocol stop
Stopping ProtocolHandler
org.apache.coyote.AbstractProtocol destroy
Destroying ProtocolHandler

從上面日志來可以判斷:

  1. tomcat不是通過腳本正常關閉(viaport: 即通過8005端口發送shutdown指令),因為正常關閉(viaport)的話會在 pause 之前有這樣的一句warn日志:
org.apache.catalina.core.StandardServer await
A valid shutdown command was received via the shutdown port. Stopping the Server instance.

然后才是 pause -> stop -> destory

  1. tomcat的shutdownhook被觸發,執行了銷毀邏輯,而這又有兩種情況,一是應用代碼里有地方用System.exit來退出jvm,二是系統發的信號(kill -9除外,SIGKILL信號JVM不會有機會執行shutdownhook)。

先通過排查代碼,應用方和中間件團隊都排查了System.exit在這個應用中使用的可能。那就只剩下Signal的情況了;經過一番排查后,發現每次tomcat意外退出的時間與ssh會話結束的時間正好吻合。

有了這個線索之后,立刻看了一下對方測試環境的腳本,簡化后如下:

$ cat test.sh
#!/bin/bash
cd /data/server/tomcat/bin/
./catalina.sh start
tail -f /data/server/tomcat/logs/catalina.out

tomcat啟動后,當前shell進程并沒有退出,而是掛住在tail進程,往終端輸出日志內容。這種情況下,如果用戶直接關閉ssh終端的窗口(用鼠標或快捷鍵),則java進程也會退出。而如果先ctrl-c終止test.sh進程,然后再關閉ssh終端的話,則java進程不會退出。

這是一個有趣的現象,catalina.sh start方式啟動的tomcat會把java進程掛到init(進程id為1)的父進程下,已經與當前test.sh進程脫離了父子關系,也與ssh進程沒有關系,為什么關閉ssh終端窗口會導致java進程退出?

我們的推測是ssh窗口在關閉時,對當前交互的shell以及正在運行的test.sh等子進程發送某個退出的Signal,找了一臺裝有systemtap的機器來驗證,所用的stop腳本都是copy的:

function time_str: string () {
    return ctime(gettimeofday_s() + 8 * 60 * 60);
}

probe begin {
    printdln(" ", time_str(), "BEGIN");
}

probe end {
    printdln(" ", time_str(), "END");
}

probe signal.send {
    if (sig_name == "SIGHUP" || sig_name == "SIGQUIT" || 
        sig_name=="SIGINT" || sig_name=="SIGKILL" || sig_name=="SIGABRT") {
        printd(" ", time_str(), sig_name, "[", uid(), pid(), cmdline_str(), 
                "] -> [", task_uid(task), sig_pid, pid_name, "], ");
        task = pid2task(pid());
        while (task_pid(task) > 0) {
            printd(" ", "[", task_uid(task), task_pid(task), task_execname(task), "]");
            task = task_parent(task);
        }
        println("");
    }
}

模擬時的進程層級(pstree)大致如下,tomcat啟動后java進程已經脫離test.sh,掛在init下:

|-sshd(1622)
 -+-sshd(11681)
  ---sshd(11699)
  ---bash(11700)
  ---test.sh(13285)
  ---tail(13299)

最終,我們發現:
a) 用 ctrl-c 終止當前test.sh進程時,系統events進程向 java 和 tail 兩個進程發送了SIGINT 信號

SIGINT [ 0 11 ] -> [ 0 20629 tail ]
SIGINT [ 0 11 ] -> [ 0 20628 java ]
SIGINT [ 0 11 ] -> [ 0 20615 test.sh ]
注pid 11是events進程

b) 關閉ssh終端窗口時,sshd向下游進程發送SIGHUP, 為何java進程也會收到?

SIGHUP [ 0 11681 sshd: hongjiang.wanghj [priv] ] -> [ 57316 11700 bash ]
SIGHUP [ 57316 11700 -bash ] -> [ 57316 11700 bash ]
SIGHUP [ 57316 11700 ] -> [ 0 13299 tail ]
SIGHUP [ 57316 11700 ] -> [ 0 13298 java ]
SIGHUP [ 57316 11700 ] -> [ 0 13285 test.sh ]

確定了是由signal引起的之后,我的疑惑變成了:

  1. 為什么SIGINT (kill -2) 不會讓tomcat進程退出?
  2. 為什么SIGHUP (kill -1) 會讓tomcat進程退出?

Shell在非交互模式下對后臺進程處理SIGINT信號時設置的是IGNORE。交互模式與非交互模式對作業控制(job control)默認方式不同。

為什么在交互模式下shell不會對后臺進程處理SIGINT信號設置為忽略,而非交互模式下會設置為忽略呢?還是比較好理解的,舉例來說,我們先某個前臺進程運行時間太長,可以ctrl-z中止一下,然后通過bg %n把這個進程放入后臺,同樣也可以把一個cmd &方式啟動的后臺進程,通過fg %n放回前臺,然后在ctrl-c停止它,當然不能忽略SIGINT。

為何交互模式下的后臺進程會設置一個自己的進程組ID呢?因為默認如果采用父進程的進程組ID,父進程會把收到的鍵盤事件比如ctrl-c之類的SIGINT傳播給進程組中的每個成員,假設后臺進程也是父進程組的成員,因為作業控制的需要不能忽略SIGINT,你在終端隨意ctrl-c就可能導致所有的后臺進程退出,顯然這樣是不合理的;所以為了避免這種干擾后臺進程設置為自己的pgid。

而非交互模式下,通常是不需要作業控制的,所以作業控制在非交互模式下默認也是關閉的(當然也可以在腳本里通過選項set -m打開作業控制選項)。不開啟作業控制的話,腳本里的后臺進程可以通過設置忽略SIGINT信號來避免父進程對組中成員的傳播,因為對它來說這個信號已經沒有意義。

回到tomcat的例子,catalina.sh腳本通過start參數啟動的時候,就是以非交互方式后臺啟動,java進程也被shell設置了忽略SIGINT信號,因此在ctrl-c結束test.sh進程時,系統發送的SIGINT對java沒有影響。

SIGHUP (kill -1) 讓tomcat進程退出的原因。

在非交互模式下,shell對java進程設置了SIGINT,SIGQUIT信號設置了忽略,但并沒有對SIGHUP信號設為忽略。再看一下當時的進程層級:

|-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 /home/admin/tt/tomcat/bin/
./catalina.sh start
tail -f /home/admin/tt/tomcat/logs/catalina.out

此時java后臺進程繼承父進程catalina.sh的pgid,而catalina.sh不再使用test.sh的進程組,而是自己的pid作為pgid,catalina.sh進程在執行完退出后,java進程掛到了init下,java與test.sh進程就完全脫離關系了,bash也不會再向它發送信號。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容