SIGPIPE(Signal 13, Code 0) 异常排查及处理
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>问题现象</li><li>排查过程</li><li>原因与处理</li></ul></div><p class="maodian"></p><h2>问题现象</h2><p>最近一个版本 <code>APP</code> 更新之后,<code>sentry</code> 大量异常数据上报,影响用户的数量非常夸张 <code>10w +</code>,具体报错如下</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202301/20230129084949039.png" /></p>
<p class="maodian"></p><h2>排查过程</h2>
<p>首先查看 SIGPIPE 的报错原因, 在官网搜索到了相关信息</p>
<p>大意是 <code>Socket</code> 连接关闭后,如果客户端仍在发送数据,这个时候就会产生 <code>SIGPIPE</code> 信号,如果信号没有被处理就会产生崩溃,这里截取了部分关键信息。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202301/20230129084949040.png" /></p>
<p>文档上说可以使用 <code>signal(SIGPIPE, SIG_IGN)</code> 全局忽略,确认客户端添加了该逻辑,但是异常还是上报到了 <code>sentry</code>。<code>signal</code> 这个函数是给信号关联一个 <code>handler</code>,收到这个信号的时候去执行。 <code>SIG_IGN</code> 是系统提供的忽略信号的处理方式,定义如下:</p>
<div class="jb51code"><pre class="brush:cpp;">#define SIG_IGN (void (*)(int))1
</pre></div>
<p>尝试手动触发 <code>SIGPIPE</code>, 运行后可以正常输出。</p>
<div class="jb51code"><pre class="brush:cpp;">void signalHandler(int signal) {
printf("bingo");
}
int main(int argc, char * argv[]) {
signal(SIGPIPE, signalHandler);
kill(getpid(), SIGPIPE);
}
</pre></div>
<p>多次添加 <code>handler</code> 继续尝试, 控制台输出 <code>333</code>, 也就是说只有最后添加的 <code>handler</code> 会执行到,比较容易理解一个信号只能关联一个 <code>handler</code>。</p>
<div class="jb51code"><pre class="brush:cpp;">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);
}
</pre></div>
<p>现状是 <code>sentry</code> 可以捕获并处理这个异常,所以此时怀疑是 <code>sentry</code> 把客户端的处理给覆盖了。</p>
<p>查看 <code>sentry</code> 里面的逻辑,<code>sentry</code> 使用了 <code>sigaction</code> 函数关联 <code>handler</code>,这个函数与 <code>signal</code> 函数一样,可以设置与信号 <code>sig</code> 关联的动作,而 <code>oact</code> 如果不是空指针的话,就用它来保存原先对该信号的动作的位置,<code>act</code> 则用于设置指定信号的动作。<code>sentry</code> 关联了自己的处理 <code>handleSignal</code> 并且会把之前的<code>handler</code> 存储到数组 <code>g_previousSignalHandlers</code> 里面。</p>
<div class="jb51code"><pre class="brush:cpp;">int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
// sentry 关联的 action 为 handleSignal
sigaction(fatalSignals, &action, &g_previousSignalHandlers)
</pre></div>
<p><code>sentry</code> 在 <code>handleSignal</code> 里面上报异常并且执行了了 <code>sentrycrashcm_handleException</code>,然后使用 <code>raise</code> 重新抛出这个信号。</p>
<div class="jb51code"><pre class="brush:cpp;">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);
}
</pre></div>
<p>查看 <code>handleException</code> 简化后的调用栈:</p>
<div class="jb51code"><pre class="brush:cpp;">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, &g_previousSignalHandlers, **NULL**);
}
</pre></div>
<p>可以看到 <code>handleException</code> 这个函数最终会重新关联保存在 <code>g_previousSignalHandlers</code>里面的 <code>handler</code>,也就是客户端设置的 <code>SIG_IGN</code> 默认忽略。sentry 关联的函数 <code>handleSignal</code> 会在处理完会重新抛出信号,这个信号会触发 <code>SIG_IGN</code>,所以这里并不存在覆盖关系,<code>sentry</code> 不会影响到客户端默认忽略的逻辑。</p>
<p>综上客户端设置的 <code>SIG_IGN</code> 是会生效的,<code>sentry</code> 只是上报了异常,并没有崩溃产生。在 <code>APP</code> 里面手动触发 <code>SIGPIPE</code>,<code>Charles</code> 抓包可以看到 <code>sentry</code> 上报,<code>APP</code> 未出现崩溃。</p>
<p class="maodian"></p><h2>原因与处理</h2>
<p>和多个业务方确认这个版本并没有 <code>socket</code> 相关的改动,那为什么在这个版本之后突然有大量异常上报呢?</p>
<p>后面 <code>diff</code> 代码发现是改动了 <code>sentry</code> 的初始时机造成的。之前的逻辑是 <code>sentry</code> 初始化,客户端调用 <code>signal</code> 关联 <code>SIG_IGN</code>,这个时候 <code>SIG_IGN</code> 覆盖了 <code>sentry</code> 的 <code>signalHandler</code>,并且没有保存和恢复之前 <code>handler</code> 的逻辑,<code>sentry</code> 捕获不到信号不会上报,当前版本的改动使这个顺序颠倒了,导致了大量异常数据上报。后续尝试去定位具体的 <code>socket</code> 无果,重新修改了顺序 <code>SIG_IGN</code> 在 <code>sentry</code> 初始化之后关联,之后的版本不再有异常数据上报。</p>
<p>以上就是SIGPIPE(Signal 13, Code 0) 异常排查及处理的详细内容,更多关于SIGPIPE异常排查的资料请关注琼殿技术社区其它相关文章!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>iOS之异常与信号使用场景分析</li><li>iOS横屏弹键盘的高度错误异常解决</li><li>axios对请求各种异常情况处理的封装方法</li><li>iOS中程序异常Crash友好化处理详解</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]