SIGPIPE(Signal?13,?Code?0)?異常排查及處理
問題現(xiàn)象
最近一個(gè)版本 APP 更新之后,sentry 大量異常數(shù)據(jù)上報(bào),影響用戶的數(shù)量非??鋸?10w +,具體報(bào)錯(cuò)如下

排查過程
首先查看 SIGPIPE 的報(bào)錯(cuò)原因, 在官網(wǎng)搜索到了相關(guān)信息
大意是 Socket 連接關(guān)閉后,如果客戶端仍在發(fā)送數(shù)據(jù),這個(gè)時(shí)候就會(huì)產(chǎn)生 SIGPIPE 信號,如果信號沒有被處理就會(huì)產(chǎn)生崩潰,這里截取了部分關(guān)鍵信息。

文檔上說可以使用 signal(SIGPIPE, SIG_IGN) 全局忽略,確認(rèn)客戶端添加了該邏輯,但是異常還是上報(bào)到了 sentry。signal 這個(gè)函數(shù)是給信號關(guān)聯(lián)一個(gè) handler,收到這個(gè)信號的時(shí)候去執(zhí)行。 SIG_IGN 是系統(tǒng)提供的忽略信號的處理方式,定義如下:
#define SIG_IGN (void (*)(int))1
嘗試手動(dòng)觸發(fā) SIGPIPE, 運(yùn)行后可以正常輸出。
void signalHandler(int signal) {
printf("bingo");
}
int main(int argc, char * argv[]) {
signal(SIGPIPE, signalHandler);
kill(getpid(), SIGPIPE);
}
多次添加 handler 繼續(xù)嘗試, 控制臺(tái)輸出 333, 也就是說只有最后添加的 handler 會(huì)執(zhí)行到,比較容易理解一個(gè)信號只能關(guān)聯(lián)一個(gè) 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);
}
現(xiàn)狀是 sentry 可以捕獲并處理這個(gè)異常,所以此時(shí)懷疑是 sentry 把客戶端的處理給覆蓋了。
查看 sentry 里面的邏輯,sentry 使用了 sigaction 函數(shù)關(guān)聯(lián) handler,這個(gè)函數(shù)與 signal 函數(shù)一樣,可以設(shè)置與信號 sig 關(guān)聯(lián)的動(dòng)作,而 oact 如果不是空指針的話,就用它來保存原先對該信號的動(dòng)作的位置,act 則用于設(shè)置指定信號的動(dòng)作。sentry 關(guān)聯(lián)了自己的處理 handleSignal 并且會(huì)把之前的handler 存儲(chǔ)到數(shù)組 g_previousSignalHandlers 里面。
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); // sentry 關(guān)聯(lián)的 action 為 handleSignal sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i])
sentry 在 handleSignal 里面上報(bào)異常并且執(zhí)行了了 sentrycrashcm_handleException,然后使用 raise 重新拋出這個(gè)信號。
static void handleSignal(int sigNum, siginfo_t *signalInfo, void *userContext)
{
SentryCrashLOG_DEBUG("Trapped signal %d", sigNum);
if (g_isEnabled) {
// 這里省略上報(bào)邏輯
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 簡化后的調(diào)用棧:
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è)函數(shù)最終會(huì)重新關(guān)聯(lián)保存在 g_previousSignalHandlers里面的 handler,也就是客戶端設(shè)置的 SIG_IGN 默認(rèn)忽略。sentry 關(guān)聯(lián)的函數(shù) handleSignal 會(huì)在處理完會(huì)重新拋出信號,這個(gè)信號會(huì)觸發(fā) SIG_IGN,所以這里并不存在覆蓋關(guān)系,sentry 不會(huì)影響到客戶端默認(rèn)忽略的邏輯。
綜上客戶端設(shè)置的 SIG_IGN 是會(huì)生效的,sentry 只是上報(bào)了異常,并沒有崩潰產(chǎn)生。在 APP 里面手動(dòng)觸發(fā) SIGPIPE,Charles 抓包可以看到 sentry 上報(bào),APP 未出現(xiàn)崩潰。
原因與處理
和多個(gè)業(yè)務(wù)方確認(rèn)這個(gè)版本并沒有 socket 相關(guān)的改動(dòng),那為什么在這個(gè)版本之后突然有大量異常上報(bào)呢?
后面 diff 代碼發(fā)現(xiàn)是改動(dòng)了 sentry 的初始時(shí)機(jī)造成的。之前的邏輯是 sentry 初始化,客戶端調(diào)用 signal 關(guān)聯(lián) SIG_IGN,這個(gè)時(shí)候 SIG_IGN 覆蓋了 sentry 的 signalHandler,并且沒有保存和恢復(fù)之前 handler 的邏輯,sentry 捕獲不到信號不會(huì)上報(bào),當(dāng)前版本的改動(dòng)使這個(gè)順序顛倒了,導(dǎo)致了大量異常數(shù)據(jù)上報(bào)。后續(xù)嘗試去定位具體的 socket 無果,重新修改了順序 SIG_IGN 在 sentry 初始化之后關(guān)聯(lián),之后的版本不再有異常數(shù)據(jù)上報(bào)。
以上就是SIGPIPE(Signal 13, Code 0) 異常排查及處理的詳細(xì)內(nèi)容,更多關(guān)于SIGPIPE異常排查的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
混合棧跳轉(zhuǎn)導(dǎo)致Flutter頁面事件卡死問題解決
這篇文章主要為大家介紹了混合棧跳轉(zhuǎn)導(dǎo)致Flutter頁面事件卡死問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
ios啟動(dòng)頁強(qiáng)制豎屏(進(jìn)入App后允許橫屏與豎屏)
最近工作遇到這樣一個(gè)需要,當(dāng)進(jìn)入啟動(dòng)頁需要強(qiáng)制豎屏,而進(jìn)入APP后就允許橫屏與豎屏,通過查找相關(guān)的資料找到了解決的方法,所以將實(shí)現(xiàn)的方法整理后分享出來,需要的朋友們可以參考借鑒,下面來一起看看吧。2017-03-03
iOS逆向教程之動(dòng)態(tài)調(diào)試詳解
這篇文章主要給大家介紹了關(guān)于iOS逆向教程之動(dòng)態(tài)調(diào)試的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06
Framework中實(shí)現(xiàn)OC和Swift的混編方案
這篇文章主要為大家介紹了Framework中實(shí)現(xiàn)OC和Swift的混編方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
iOS 實(shí)現(xiàn)多代理的方法及實(shí)例代碼
這篇文章主要介紹了iOS 實(shí)現(xiàn)多代理的方法及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10
IOS 開發(fā)之PickerView文字和隨機(jī)數(shù)的使用
這篇文章主要介紹了IOS 開發(fā)之PickerView文字和隨機(jī)數(shù)的使用的相關(guān)資料,這里提供實(shí)例幫助大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-08-08
iOS 將系統(tǒng)自帶的button改裝成上圖片下文字的樣子
這篇文章主要介紹了 iOS 將系統(tǒng)自帶的button改裝成上圖片下文字的樣子,代碼是通過繼承UIButton,然后再重寫layoutSubviews方法,對自帶的圖片和titleLabel進(jìn)行重新的layout。下面通過本文給大家分享下實(shí)現(xiàn)代碼2016-12-12
iOS App中UILabel的自定義及在Auto Layout中的使用
這篇文章主要介紹了iOS App中UILabel的自定義及在Auto Layout中的使用,示例代碼為傳統(tǒng)的Objective-C語言,需要的朋友可以參考下2016-03-03

