SIGPIPE(Signal 13, Code 0) 异常排查及处理
作者:yuec
问题现象
最近一个版本 APP
更新之后,sentry
大量异常数据上报,影响用户的数量非常夸张 10w +
,具体报错如下
排查过程
首先查看 SIGPIPE 的报错原因, 在官网搜索到了相关信息
大意是 Socket
连接关闭后,如果客户端仍在发送数据,这个时候就会产生 SIGPIPE
信号,如果信号没有被处理就会产生崩溃,这里截取了部分关键信息。
文档上说可以使用 signal(SIGPIPE, SIG_IGN)
全局忽略,确认客户端添加了该逻辑,但是异常还是上报到了 sentry
。signal
这个函数是给信号关联一个 handler
,收到这个信号的时候去执行。 SIG_IGN
是系统提供的忽略信号的处理方式,定义如下:
#define SIG_IGN (void (*)(int))1
尝试手动触发 SIGPIPE
, 运行后可以正常输出。
void signalHandler(int signal) { printf("bingo"); } int main(int argc, char * argv[]) { signal(SIGPIPE, signalHandler); kill(getpid(), SIGPIPE); }
多次添加 handler
继续尝试, 控制台输出 333
, 也就是说只有最后添加的 handler
会执行到,比较容易理解一个信号只能关联一个 handler
。
void signalHandler(int signal) { printf("111"); } void signalHandler2(int signal) { printf("222"); } void signalHandler3(int signal) { printf("333"); } int main(int argc, char * argv[]) { signal(SIGPIPE, signalHandler); signal(SIGPIPE, signalHandler2); signal(SIGPIPE, signalHandler3); kill(getpid(), SIGPIPE); }
现状是 sentry
可以捕获并处理这个异常,所以此时怀疑是 sentry
把客户端的处理给覆盖了。
查看 sentry
里面的逻辑,sentry
使用了 sigaction
函数关联 handler
,这个函数与 signal
函数一样,可以设置与信号 sig
关联的动作,而 oact
如果不是空指针的话,就用它来保存原先对该信号的动作的位置,act
则用于设置指定信号的动作。sentry
关联了自己的处理 handleSignal
并且会把之前的handler
存储到数组 g_previousSignalHandlers
里面。
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); // sentry 关联的 action 为 handleSignal sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i])
sentry
在 handleSignal
里面上报异常并且执行了了 sentrycrashcm_handleException
,然后使用 raise
重新抛出这个信号。
static void handleSignal(int sigNum, siginfo_t *signalInfo, void *userContext) { SentryCrashLOG_DEBUG("Trapped signal %d", sigNum); if (g_isEnabled) { // 这里省略上报逻辑 sentrycrashcm_handleException(); } SentryCrashLOG_DEBUG("Re-raising signal for regular handlers to catch."); // This is technically not allowed, but it works in OSX and iOS. raise(sigNum); }
查看 handleException
简化后的调用栈:
void sentrycrashcm_handleException(**struct** SentryCrash_MonitorContext *context) { sentrycrashcm_setActiveMonitors(SentryCrashMonitorTypeNone); } void sentrycrashcm_setActiveMonitors(SentryCrashMonitorType monitorTypes) { // isEnabled = false setMonitorEnabled(monitor, isEnabled); } static inline void setMonitorEnabled(Monitor *monitor, bool isEnabled) { uninstallSignalHandler(); } static void uninstallSignalHandler(void) { sigaction(fatalSignals[i], &g_previousSignalHandlers[i], **NULL**); }
可以看到 handleException
这个函数最终会重新关联保存在 g_previousSignalHandlers
里面的 handler
,也就是客户端设置的 SIG_IGN
默认忽略。sentry 关联的函数 handleSignal
会在处理完会重新抛出信号,这个信号会触发 SIG_IGN
,所以这里并不存在覆盖关系,sentry
不会影响到客户端默认忽略的逻辑。
综上客户端设置的 SIG_IGN
是会生效的,sentry
只是上报了异常,并没有崩溃产生。在 APP
里面手动触发 SIGPIPE
,Charles
抓包可以看到 sentry
上报,APP
未出现崩溃。
原因与处理
和多个业务方确认这个版本并没有 socket
相关的改动,那为什么在这个版本之后突然有大量异常上报呢?
后面 diff
代码发现是改动了 sentry
的初始时机造成的。之前的逻辑是 sentry
初始化,客户端调用 signal
关联 SIG_IGN
,这个时候 SIG_IGN
覆盖了 sentry
的 signalHandler
,并且没有保存和恢复之前 handler
的逻辑,sentry
捕获不到信号不会上报,当前版本的改动使这个顺序颠倒了,导致了大量异常数据上报。后续尝试去定位具体的 socket
无果,重新修改了顺序 SIG_IGN
在 sentry
初始化之后关联,之后的版本不再有异常数据上报。
以上就是SIGPIPE(Signal 13, Code 0) 异常排查及处理的详细内容,更多关于SIGPIPE异常排查的资料请关注脚本之家其它相关文章!