当前位置: 首页 > 科技观察

进程无故消失的破案历程

时间:2023-03-20 19:22:33 科技观察

过程无故消失的破案过程总结。前段时间公司有一个系统,进程总是无故退出。出来还是有规矩的,只是这个规矩比较特殊。看完下面的内容,大家就会明白,真的很特别!初步分析进程Crash?当同事找到我的时候,我的第一反应是进程是不是崩溃了。如果是crash,一般都会有我查看了crashlog,没有留下log。当然,有时候JVM在crash之后并没有留下crashlog,但这种情况非常少见,大部分是人为操作。被操作系统杀死?既然不是Crash,是不是系统内存泄露,被OSkill掉了,不过很快通过dmesg排除,因为没有kill的迹象。系统退出?排除了以上两个因素后,我马上怀疑是不是有代码执行了System.exit,于是重新编译了一个JDK,在System的exit方法处打印了一些日志,等待奇迹的发生。.令人兴奋的是,进程真的消失了,可惜我们埋下的日志并没有出现。这让我又开始思考了。返回源码从日志看确实调用了ShutdownHook,于是找到addShutdownHook的源码,再次查看了JDK的源码。除了正常退出,System.在Shutdown.runHooks方法中。继续等待事情再次发生。果然,没多久。下午又发生了。打印了如下日志,表示此时收到了SIGHUP信号。这个信号最终会导致进程退出。JVM没有对这个信号进行特殊处理,所以我们没有看到崩溃日志。那么下一步就是找出为什么会收到这个信号,以及是谁发出了这个信号。找出SIGHUP信号的信号源。最重要的场景是,当Shell终端关闭一个Session时,它会向Session关联的进程发送一个SIGHUP信号。这个信号默认会退出进程。为此,我还特地下载了ITerm2的源码(我和同事都是mac,使用的是iTerm2终端)。我真的找到了一些发送SIGHUP信号的代码。看名字PTYTask就可以猜到。这应该是一个sesion任务,于是深入代码看到主要有两种方式向子进程发送SIGHUP信号,分别是dealloc和stop,其中stop会通过sendSignal函数向子进程发送SIGHUP信号。对于像我这样喜欢自虐的人,我通常会思考如何确定这就是我要找的代码。我以前从未编写过Object-C代码。不知道有没有类似java的jmap的工具可以看看。对象在内存中的情况,暂时没有找到,但是在mac自带的Activity工具中发现了一些迹象,于是在Activity中找到了iTerm2进程,然后采样其内存数据到看看我能不能抓住它。很难模拟类似PTYTask的调用栈。dealloc或者stop,因为有时间差。当我点击sample的时候,很快就结束了,还没来得及关闭session。在看采样报告的时候,偶然看到了/usr/bin/sample这个命令。原来Activity是用这个命令来采样的,于是摸索了一下,果然奏效了。采样时间可自定义,间隔1ms。这样可以让我有足够的时间去操作,所以开始采样后,我继续打开一个session,启动一个进程,然后关闭,重复几次。采样完成后,我查看采样的输出。当我到达PTYTask.stop的调用堆栈时,它也验证了当我关闭会话时,我确实会向相应的子进程发送一个SIGHUP信号。至此我们可以确认进程退出是因为收到了SIGHUP信号,而SIGHUP的发生是因为终端SessionClose。是这样吗?重新审视问题现场似乎是不可能的,因为我们的shell在脚本中执行java的时候,会带上&,这样进程就会在后台运行,不会出现这样的session问题。看了脚本,确实是带&的,我也模拟过几次,在一个shell中用&调用java命令,关闭终端java进程不会退出。是因为我同事的终端配置和我的不一样吗?后来让同事把iTerm2里面的seesion配置发给我,和我的一模一样,比较奇怪。于是回过头去看之前进程消失时的日志(希望同事保留),看到那些进程退出的时间点,分别是2019-01-1420:42:522019-01-1518:34:002019-01-1800:57:582019-01-1817:34:30这些时间点看似完全不规律,但突然想起2019-01-18是周五,同事在在上海出差,17:34:30,应该快下班了,要不就是从上海开车回杭州,所以我问同事那个时候回杭州吗点,同事说确实是我打开电脑准备开车回杭州,于是我又问其他时间是不是同时关闭电脑,结果是这样的巧合。如果是,说明我们的判断方向是正确的。真相大白后,突然发现同事修改了启动脚本,在脚本底部添加了一行命令javaxxxx&cd$DIR_LOG&&tail-fcommon-*.log。这个命令是鬼吗?突然想到一个问题,父子进程的问题,如果说我们在shell中添加&运行的时候,当父进程运行完毕后,会直接挂在init进程下,也就是如果看到它的父进程通过ps-ef,它会是1号进程,但是如果发现父进程还没有运行完,那么父进程还是原来的进程,所以当我们执行这个脚本的时候,它会总是被执行等待日志输出。这样,如果我们关闭当前窗口,就意味着父进程会退出,然后给子进程发送SIGHUP信号,导致java进程退出,但是如果我们在taillog过程中按ctrlc,则java进程会被init进程通过,所以此时我们关闭窗口时不会向它发送SIGHUP信号。经核实,确实如此。至此,消失的过程终于解释清楚了。好吧,幸运的是,这不是我们代码的问题。这是令人欣慰的。毕竟我们的代码是跑在客户端的,一定要保证消费的稳定性。【本文为专栏作家李嘉鹏原创文章,转载请微信公众号(你个假笨蛋,id:lovestblog)联系作者授权转载】点此查看本作者更多好文