Ceph中的信号处理、调用栈打印机制分析及超时未自杀情况下线程调用栈打印功能




本文基于H版本代码分析(为啥老是H?因为生产用的就是这个)。

我们想在osd io线程卡住的时候,知道卡在什么地方了(还未自杀的情况),因此想通过打印调用栈的方式获取该信息。

超出一定时间osd进程就自杀了(通过assert异常)。

相关配置项:
1. osd_op_thread_timeout = 15
2. osd_op_thread_suicide_timeout = 150

自杀的情况,社区在新版本已经加入了调用栈打印功能,具体参考commit c90ccbf227c。

信号处理

信号注册

信号注册分为两类,一类是杂项信号:

第二类是3个重要信号,SIGHUP、SIGINT、SIGTERM:

SIGHUP是用来重新打开日志文件的,比如在定期滚动压缩日志文件之后刷新一下日志文件句柄。

另外两个是通知进程退出的,比如stop service的时候。

对于第二类信号来说但是上面的流程并不是真正的信号处理函数,或者说处理的并不是第一手的信号,而是经过一次传递之后的信号。

第一手的信号处理函数在这里注册:

信号传递

通过上面的代码分析可以看出,ceph中信号(SIGHUP、SIGINT、SIGTERM)并不是直接传递给信号处理函数的,二是经过了一个自定义的传递管道(pipe),相关传递过程如下:

有信息写入管道,就得有对应的线程从管道读取信息,才能传递给最终的处理函数,相关的线程就是:

信号处理

杂项信号处理函数是handle_fatal_signal,这个是直接处理,不经过pipe转发:

SIGTERM/SIGINT信号则经过一次转发,当然最终的处理函数还是main中指定的:

osd进程正常退出之前,有很多工作要做,比如:
1. 通知mon osd down、close pg
2. superblock、FileJournal刷盘
3. 清理各种线程、messenger、handler
4. 调高日志级别并将内存中保存日志到日志文件

上述操作都是在OSD::shutdown()中处理的。

调用栈打印

进程正常退出,如kill -SIGTERM/-SIGINT等,是不会打印调用栈的,除非退出过程中出现其他异常,例如遇到Segmentation fault。

进程异常退出打印

异常退出分为两种情况,1是遇到assert(*)错误,2是收到kill信号(上面讲到的杂项信号),这两种情况都会打印线程调用栈。

首先看assert错误:

收到杂项信号之后的流程上面已经分析过,杂项信号处理函数是handle_fatal_signal,都是在这里面处理的。

打印其他线程的调用栈

Ceph OSD IO线程健康状态检查机制

线程超时导致进程自杀前会打印超时线程的调用栈(这个c90ccbf227c commit之前只是通过assert打印出心跳检查线程本身的调用栈,而不是IO线程osd_op_tp的,所以没有意义)。

参考上面的提交,我们在超时情况下,但还没有达到配置的osd进程自杀时间时,主动打印超时线程的调用栈:

另外通过kill -USR1 $pid,也可以主动触发打印线程调用栈。

需要注意的是,打印出的调用栈可能并不是线程当前实际阻塞的位置,例如第二次打印的时候可能是在等锁或者限流场景(其他位置阻塞导致的)。第一次打印可能是准确的。

效果:

2019-05-13 17:31:35.045017 7f9de6c96700 -1 *** Caught signal (User defined signal 1) **
in thread 7f9de6c96700

ceph version 0.94.6-18-g3c711c4 (3c711c45d990b5fe3fbbedb32659af282de5b7d7)
1: /usr/bin/ceph-osd() [0xa21fa4]
2: (()+0xf890) [0x7f9e078bf890]
3: (ReplicatedPG::do_op(std::tr1::shared_ptr<oprequest>&)+0) [0x8510f0]
4: (ReplicatedPG::do_request(std::tr1::shared_ptr<oprequest>&, ThreadPool::TPHandle&)+0x604) [0x7ea074]
5: (OSD::dequeue_op(boost::intrusive_ptr<pg>, std::tr1::shared_ptr<oprequest>, ThreadPool::TPHandle&)+0x3cf) [0x64107f]
6: (OSD::ShardedOpWQ::_process(unsigned int, ceph::heartbeat_handle_d*)+0x338) [0x6415b8]
7: (ShardedThreadPool::shardedthreadpool_worker(unsigned int)+0x89e) [0xb10bce]
8: (ShardedThreadPool::WorkThreadSharded::entry()+0x10) [0xb12d30]
9: (()+0x8064) [0x7f9e078b8064]
10: (clone()+0x6d) [0x7f9e05e1062d]