参考:http://hongjiang.info/why-kill-2-cannot-stop-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 awaitA valid shutdown command was received via the shutdown port. Stopping the Server instance.然后才是 pause -> stop -> destroy
2) 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的机器来验证,所用的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进程已经脱离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进程退出?

我第一反应可能是jvm在某些参数下(或因为某些jni)对os的信号处理会不同,看了一下应用的jvm参数,没有看出问题,也排除了tomcat使用apr/tcnative的情况。

我们看一下默认情况下,jvm进程对SIGINTSIGHUP是怎么处理的,用scala的repl模拟一下:

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

对这个java进程分别用kill -2kill -1发现都会导致jvm进程退出,并且也触发shutdownhook。这也符合oracle对hotspot虚拟机处理Signal的说明,参考这里,SIGTERM,SIGINT,SIGHUP三种信号都会触发shutdownhook

看来并不是jvm的事,继续猜测是否与进程的状态有关?catalina.sh脚本里并没有使用start-stop-daemon之类的方式启动java进程,start参数的执行方式简化后脚本相当于:

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

就是简单的把java放到后台执行。当catalina.sh自身进程退出后,java进程的ppid变成了1

花了很多的时间猜测可能是OS层面的原因,后来发现并没有关系。春节后回来让少明和涧泉也一起分析这个问题,因为他们有c的背景,对系统底层知道的多一些,用了大半天时间,不断猜测和验证,最后确认了是Shell的原因。

SIGINT (kill -2) 不会让后台java进程退出的原因

为了简便,我们用sleep来模拟进程,当我们在交互模式下:

$ sleep 1000 & $ ps -opid,pgid,ppid,stat,cmd -C sleepPID  PGID  PPID STAT CMD9897  9897  9813 S    sleep 1000

注意,进程sleep 1000的pid与pgid(进程组)是相同的,这时我们用kill -2是可以杀掉sleep 1000进程的。

现在我们把sleep进程放到一个脚本里后台执行:

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

运行a.sh脚本之后,sleep 4400进程的pid与pgid是不同的,pgid是其父进程的id,即已经退出了的a.sh进程

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

这时我们用kill -2是杀不掉sleep 4400进程的。

到了这一步,已经非常接近原因了,一定是shell对后台进程signal_handler做了什么手脚。少明实现了一个自定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做手脚是在执行用户逻辑之前,也就是脚本在fork出子进程的时候就设置了。按照这个线索我们google后了解到: 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进程设置了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里设置开启作业控制的话,就不会让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也不会再向它发送信号。

tomcat进程意外退出的问题分析相关推荐

  1. PHP运行环境之IIS FastCGI 进程意外退出解决办法

    本机做了系统,结果之前装好的APACHE环境什么的都没了,不想费事了,这次直接使用WIN8自带的IIS功能了,安装完毕后提示FastCGI 进程意外退出解决办法,这是由于某些加载库加载失败的原因,这里 ...

  2. wamp php http 1.0500,HTTP 错误 500.0 - Internal Server Error C:\php-5.3.5\php.exe - FastCGI 进程意外退出...

    满意答案 hzxbz181818 2013.10.05 采纳率:58%    等级:12 已帮助:29291人 最常见的出错提示:500 SERVER ERROR 主要是由于IWAM账号(在我的计算机 ...

  3. php cgi 挂掉,zend opcache引起iis/Apache 不时挂掉 php-cgi.exe - FastCGI 进程意外退出

    windows程序日志报错如下: 无法找到来自源 Zend OPcache 的事件 ID 487 的描述.本地计算机上未安装引发此事件的组件,或者安装已损坏.可以安装或修复本地计算机上的组件. 详细错 ...

  4. iis php 0xc0000135,Django-IIS - FastCGI意外退出

    我遇到了此意外退出的FastCGI,我不知道如何解决此错误.我已经查看了解决方案的解决方案,但是建议的解决方案不适用或者没有答案提供给问题.我想使用带有WFastCGI的IIS服务器运行非常基本的dj ...

  5. 为应用程序池**提供服务的进程意外终止。进程ID是**。进程退出代码是'0x80'

    2010-8-26 11:39  事件类型: 警告  事件来源: W3SVC  事件种类: 无  事件 ID: 7034  日期: 2010-8-XX  事件: XX:XX:XX  用户: XX  计 ...

  6. .NET 6 “目标进程已退出,但未引发 CoreCLR 启动事件。请确保将目标进程配置为使用 .NET Core。如果目标进程未运行 .NET Core,则发生这种情况并不意外。”

    Mac M1 在 .NET 6 上调试.NET 5 的Web应用程序出现程序闪退问题 首先看了下本地 .NET 的环境 ➜ ~ dotnet --list-sdks 6.0.200 [/usr/loc ...

  7. sdk缺失”目标进程已退出,但未引发 CoreCLR 启动事件。请确保将目标进程配置为使用 .NET Core。如果目标进程未运行 .NET Core,则发生这种情况并不意外。 程序“[16780]

    问题:项目运行后出现"目标进程已退出,但未引发 CoreCLR 启动事件.请确保将目标进程配置为使用 .NET Core.如果目标进程未运行 .NET Core,则发生这种情况并不意外. 程 ...

  8. 为应用程序池 ''DefaultAppPool'' 提供服务的进程意外终止。进程 ID 是 ''xxx''问题的解决方法...

    网上提供了很多办法,都未解决. 解决过程一波三折,依次用了下列方法: 1.解决办法 点击"开始"-"控制面板"-"管理工具"-"组 ...

  9. 子进程及时知道父进程已经退出的最简单方案

    [精彩] 子进程及时知道父进程已经退出的最简单方案? http://www.chinaunix.net 作者:yuonunix  发表于:2003-10-31 10:14:14 [发表评论] [查看原 ...

最新文章

  1. 长途每分钟只要9分钱!
  2. Windows8 解决VMware与Hyper-V不兼容共存方法
  3. 解决Linux中使用google chrome浏览器出现:ERR_PROXY_CONNECTION_FAILED 代理错误,导致不能够上网
  4. hdu2604 矩阵快速幂
  5. 数据存储四种常见方式
  6. 假如古代有了云计算,延禧攻略里的各位嫔妃要如何宫斗
  7. mysql round 0.1111_听说Mysql你很豪横?-------------分分钟带你玩转SQL高级查询语句(库函数,存储过程)...
  8. UI设计灵感|移动应用的数据表盘都是怎么设计的?
  9. 使用Redis和Apache Kafka处理时间序列数据
  10. Word vba之遍历段落、识别固定段头、设置样式
  11. 【面朝大厂】万字+图解 Redis,面试不用愁了!
  12. numpy中相关系数
  13. 老师的经典口头禅,这一句最扎心
  14. Bridging the Gap Between Anchor-based and Anchor-free Detection
  15. 内网安全-域横向CobaltStrikeSPNRDP
  16. 在ASP中实现RsA加密与解密
  17. 微信自定义菜单以及消息and事件通知
  18. 详细介绍如何使用GPU(显卡)跑MATLAB代码
  19. PHP生成PDF——mpdf用法小结
  20. 八,如何检测PC1,PC2直接通信?

热门文章

  1. 多数大数据项目都以失败而告终的原因
  2. Linux chkconfig命令详解
  3. PostgreSQL 8.2.5 安装为 Windows 服务 (Service)
  4. 随机生成十六进制颜色
  5. KVM — 内存虚拟化
  6. Kolla 让 OpenStack 部署更贴心
  7. 关于IR21的自举电路
  8. STM32 KEIL里的MAP文件分析
  9. 浅谈工程师的调试法宝(5) -JScope的应用_MCU
  10. 外卖流量红利期已过:正从补贴战进入AI赛道