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

感謝同事宏江投遞本稿。

節(jié)前某個部門的測試環(huán)境反饋tomcat會意外退出燎斩,我們到實際環(huán)境排查后發(fā)現(xiàn)不是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端口發(fā)送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 

2) tomcat的shutdownhook被觸發(fā),執(zhí)行了銷毀邏輯

而這又有兩種情況项滑,一是應用代碼里有地方用System.exit來退出jvm依沮,二是系統(tǒng)發(fā)的信號(kill -9除外,SIGKILL信號JVM不會有機會執(zhí)行shutdownhook)

先通過排查代碼枪狂,應用方和中間件團隊都排查了System.exit在這個應用中使用的可能危喉。那就只剩下Signal的情況了;經(jīng)過一番排查后州疾,發(fā)現(xiàn)每次tomcat意外退出的時間與ssh會話結束的時間正好吻合辜限。

有了這個線索之后,銀時同學立刻看了一下對方測試環(huán)境的腳本严蓖,簡化后如下:

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

tomcat啟動為后薄嫡,當前shell進程并沒有退出氧急,而是掛住在tail進程,往終端輸出日志內(nèi)容毫深。這種情況下吩坝,如果用戶直接關閉ssh終端的窗口(用鼠標或快捷鍵),則java進程也會退出哑蔫。而如果先ctrl-c終止test.sh進程钉寝,然后再關閉ssh終端的話,則java進程不會退出闸迷。

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

我們的推測是ssh窗口在關閉時今阳,對當前交互的shell以及正在運行的test.sh等子進程發(fā)送某個退出的Signal师溅,找了一臺裝有systemtap的機器來驗證,所用的stap腳本是從澗泉同學那里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進程已經(jīng)脫離test.sh险胰,掛在init下:

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

經(jīng)過內(nèi)核組伯俞的協(xié)助,我們發(fā)現(xiàn)

a) 用 ctrl-c 終止當前test.sh進程時矿筝,系統(tǒng)events進程向 java 和 tail 兩個進程發(fā)送了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向下游進程發(fā)送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 ] 

不過伯俞很忙沒有繼續(xù)協(xié)助分析這個問題(他給出了一些猜測窖维,但后來證明并不是那樣)榆综。

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

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

我第一反應可能是jvm在某些參數(shù)下(或因為某些jni)對os的信號處理會不同鼻疮,看了一下應用的jvm參數(shù),沒有看出問題琳轿,也排除了tomcat使用apr/tcnative的情況判沟。

我們看一下默認情況下,jvm進程對SIGINTSIGHUP是怎么處理的崭篡,用scala的repl模擬一下:

scala> Runtime.getRuntime().addShutdownHook(
            new Thread() { override def run() { println("ok") } })

對這個java進程分別用kill -2kill -1發(fā)現(xiàn)都會導致jvm進程退出挪哄,并且也觸發(fā)shutdownhook皂林。這也符合oracle對hotspot虛擬機處理Signal的說明六荒,參考這里帘靡,SIGTERM,SIGINT,SIGHUP三種信號都會觸發(fā)shutdownhook

看來并不是jvm的事杀捻,繼續(xù)猜測是否與進程的狀態(tài)有關?catalina.sh腳本里并沒有使用start-stop-daemon之類的方式啟動java進程副瀑,start參數(shù)的執(zhí)行方式簡化后腳本相當于:

eval '"/pathofjdk/bin/java"' 'params' org.apache.catalina.startup.Bootstrap start '&'

就是簡單的把java放到后臺執(zhí)行晰搀。當catalina.sh自身進程退出后蕉拢,java進程的ppid變成了1

花了很多的時間猜測可能是OS層面的原因,后來發(fā)現(xiàn)并沒有關系刻两。春節(jié)后回來讓少明和澗泉也一起分析這個問題增蹭,因為他們有c的背景,對系統(tǒng)底層知道的多一些闹伪,用了大半天時間沪铭,不斷猜測和驗證壮池,最后確認了是Shell的原因偏瓤。

SIGINT (kill -2) 不會讓后臺java進程退出的原因

為了簡便,我們用sleep來模擬進程椰憋,當我們在交互模式下:

$ sleep 1000 & 

$ ps -opid,pgid,ppid,stat,cmd -C sleep
  PID  PGID  PPID STAT CMD
 9897  9897  9813 S    sleep 1000   

注意厅克,進程sleep 1000的pid與pgid(進程組)是相同的,這時我們用kill -2是可以殺掉sleep 1000進程的橙依。

現(xiàn)在我們把sleep進程放到一個腳本里后臺執(zhí)行:

$ cat a.sh
#!/bin/sh
sleep 4400 &
echo "shell exit"

運行a.sh腳本之后证舟,sleep 4400進程的pid與pgid是不同的,pgid是其父進程的id窗骑,即已經(jīng)退出了的a.sh進程

$ ps -opid,pgid,ppid,comm -p 63376
  PID  PGID  PPID COMM
63376 63375     1 sleep

這時我們用kill -2是殺不掉sleep 4400進程的女责。

到了這一步,已經(jīng)非常接近原因了创译,一定是shell對后臺進程signal_handler做了什么手腳抵知。少明實現(xiàn)了一個自定handler的命令看看是否對kill -2有效:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void my_handler(int sig) {
    printf("handler aaa\n");
    exit(0);
}

int main() {
    signal(SIGINT, my_handler);
    for(;;) { }
    return 0;
}

我們把編譯后的a.out命令在腳本里以后臺方式運行:

$ cat a.sh
#!/bin/sh
/tmp/a.out &

這次再嘗試用kill -2去殺a.out進程,是可以的软族。這說明shell對signal_handler做手腳是在執(zhí)行用戶邏輯之前刷喜,也就是腳本在fork出子進程的時候就設置了。按照這個線索我們google后了解到: shell在非交互模式下對后臺進程處理SIGINT信號時設置的是IGNORE立砸。

交互模式與非交互模式對作業(yè)控制(job control)默認方式不同

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

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

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

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

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

在非交互模式下凌外,shell對java進程設置了SIGINTSIGQUIT信號設置了忽略涛浙,但并沒有對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里設置開啟作業(yè)控制的話仓手,就不會讓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進程在執(zhí)行完退出后呀伙,java進程掛到了init下,java與test.sh進程就完全脫離關系了添坊,bash也不會再向它發(fā)送信號剿另。

原創(chuàng)文章,轉載請注明: 轉載自并發(fā)編程網(wǎng) – ifeve.com本文鏈接地址: Tomcat進程意外退出的問題分析

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雨女,隨后出現(xiàn)的幾起案子谚攒,更是在濱河造成了極大的恐慌,老刑警劉巖氛堕,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馏臭,死亡現(xiàn)場離奇詭異,居然都是意外死亡讼稚,警方通過查閱死者的電腦和手機括儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锐想,“玉大人帮寻,你說我怎么就攤上這事⊥匆校” “怎么了规婆?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝉稳。 經(jīng)常有香客問我,道長掘鄙,這世上最難降的妖魔是什么耘戚? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮操漠,結果婚禮上收津,老公的妹妹穿的比我還像新娘。我一直安慰自己浊伙,他們只是感情好撞秋,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嚣鄙,像睡著了一般吻贿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哑子,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天舅列,我揣著相機與錄音,去河邊找鬼卧蜓。 笑死帐要,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的弥奸。 我是一名探鬼主播榨惠,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赠橙?” 一聲冷哼從身側響起伸蚯,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎简烤,沒想到半個月后剂邮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡横侦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年挥萌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枉侧。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡引瀑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出榨馁,到底是詐尸還是另有隱情憨栽,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布翼虫,位于F島的核電站屑柔,受9級特大地震影響,放射性物質發(fā)生泄漏珍剑。R本人自食惡果不足惜掸宛,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望招拙。 院中可真熱鬧唧瘾,春花似錦、人聲如沸别凤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽规哪。三九已至求豫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間由缆,已是汗流浹背注祖。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留均唉,地道東北人是晨。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像舔箭,于是被迫代替她去往敵國和親罩缴。 傳聞我的和親對象是個殘疾皇子蚊逢,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容