tomcat进程意外退出的问题分析(转)
原文链接: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 await
A 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进程对SIGINT
和SIGHUP
是怎么处理的,用scala的repl模拟一下:
scala> Runtime.getRuntime().addShutdownHook(
new Thread() { override def run() { println("ok") } })
对这个java进程分别用kill -2
和kill -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 sleep
PID PGID PPID STAT CMD
9897 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 63376
PID 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进程设置了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也不会 再向它发送信号。
tomcat进程意外退出的问题分析(转)的更多相关文章
- Tomcat进程意外退出的问题分析
http://ifeve.com/why-kill-2-cannot-stop-tomcat/?tdsourcetag=s_pctim_aiomsg tail后关掉客户端 正常执行shutdown.s ...
- (转)tomcat进程意外退出的问题分析
节前某个部门的测试环境反馈tomcat会意外退出,我们到实际环境排查后发现不是jvm crash,日志里有进程销毁的记录,从pause到destory的整个过程: org.apache.coyote. ...
- "php-cgi.exe - FastCGI 进程意外退出" 解决办法
问题描述: win7下iis中php-cgi.exe - FastCGI 进程意外退出 错误提示: HTTP 错误 500.0 - Internal Server Error D:\phpStudy\ ...
- 【转载】IIS出现“HTTP 错误 500.0,C:\php\php-cgi.exe - FastCGI 进程意外退出”解决方法
昨天给大家介绍了在windows+iis的平台上搭建支持php+mysql平台的文章,教程步骤都是笔者一步一个操作然后一个记录介绍给大家的,实机演练,教程绝对切实可用,但是不同的同学在不同的环境下按照 ...
- PHP运行环境之IIS FastCGI 进程意外退出解决办法
本机做了系统,结果之前装好的APACHE环境什么的都没了,不想费事了,这次直接使用WIN8自带的IIS功能了,安装完毕后提示FastCGI 进程意外退出解决办法,这是由于某些加载库加载失败的原因,这里 ...
- 32位win7系统下配置IIS遇到php-cgi.exe - FastCGI 进程意外退出问题的解决的方法
今天重装了一下系统,是32位的WIN7.装完系统后想把IIS装回来,由于有时候须要用到笔记本处理一些事情.结果WEBserver正常了.但IIS的FASTCGI模块始终不能解析PHP,一直报php-c ...
- iis8 php-cgi.exe - FastCGI 进程意外退出 500错误解决办法
今天iis服务环境下的网站突然显示200错误php-cgi.exe - FastCGI 进程意外退出,昨天还好好的网站正常,这个问题一直偶尔出现几次,不是很频繁,但是偶尔会出现: 这是由于某些加载库加 ...
- WIN服务器出现 php-cgi.exe - FastCGI 进程意外退出
既然是不能解析PHP,那就直接运行一下PHP,看会报什么错,再对症下药,于是,在命令提示符窗口进入php安装的根目录,然后运行php -v的命令,这时窗口弹出计算机丢失msvcr110.dll的错误, ...
- 对于tomcat通过catalina.sh停止服务后,tomcat进程没有退出问题解决办法
例:tomcat路径及名称为/data/apache-tomcat-7.0.67/ vim /data/apache-tomcat-7.0.67/bin/catalina.sh 找到org.apach ...
随机推荐
- SQL:CASE WHEN ELSE END用法
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 WHEN 条件3 THEN 结果3 WHEN 条件4 THEN 结果4......... ...
- android基础---->AccessibilityService的简单使用(一)
AccessibilityService类可以帮助我们实现监听手机上别的应用,以下做一个简单的总结.我总是勇敢的离开一个人 却不懂如何巧妙的靠近一个人. AccessibilityService的使用 ...
- ORA-28000: the account is locked
1.用system账号登录 2.执行一下sql: ALTER USER username ACCOUNT UNLOCK; 此处username 可能为scott或者你要解锁的用户名. ........ ...
- java中Logger.getLogger(Test.class),即log4日志的使用
log4的使用方法: log4是具有日志记录功能,主要通过一个配置文件来对程序进行监测有两种配置方式:一种程序配置,一种文件配置有三个主要单元要了解,Logger,appender,layout. l ...
- 160229-02、Sublime Text 3 快捷键总结
选择类 Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本. Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑.举个栗子:快速选中并更改所有相同的变量名.函数 ...
- 160517、nginx负载均衡详解
1:什么是负载均衡 负载平衡(Load balancing)是一种计算机网络技术,用来在多个计算机(计算机集群).网络连接.CPU.磁盘驱动器或其他资源中分配负载,以达到最佳化资源使用.最大化吞吐率. ...
- linux 定时备份mysql数据库
首先要先搞清楚两个概念: ①.mysqldump,mysqldump是mysql的逻辑备份工具,它不是linux的命令,工作原理类似产生一些列sql语句,对数据库进行指定的逻辑备份. 最简洁的形式是: ...
- Servlet------>jsp自定义标签3(不显示余下jsp内容)
2.自定义标签控制,jsp页面余下标签是否继续执行 EndTag.java package tag; import javax.servlet.jsp.JspException; import jav ...
- h5+的Downloader下载网络图片缓存到本地的案例
之前展示图片都是通过<img src="网络图片地址"> , 每次都请求服务器, 加载比较慢;如何做到显示图片的时候先从本地获取,没有则联网下载,缓存到本地;下次直接从 ...
- Bootstrap插件架构 基于元素自定义属性的布局规则
w HTML布局规则 Javascript实现步骤 插件调用方法