linux訊號處理及libcurl的坑
狀況簡述:
程式採用Reactor模型, IO執行緒專做IO事件讀寫, 以及請求的dispatch分發, 後端執行緒池用於業務的同步操作. 對libcurl的使用, 也穿插在多執行緒中.
當程式功能完成後, 對其進行壓力測試. 過了大致20分鐘, 程式crash並出core.
gdb調core後, 發現其在epoll_wait之上的呼叫棧出問題了.
根據經驗, 本著誰肇事, 誰擦屁股
逆向推測:
起初的排查沒有效果, 於是乎, 採用逆向思維. 從SIGABRT 6訊號出發, 看看什麼情況下能觸發該訊號.
指標的重複刪除會引發該錯誤, 如下面例子:
其他的記憶體操作, 如野指標的使用, 越界, 往往對應的是SIGSEGV 11.
於是乎, 尋找疑似double delete的情況, 結果還是一無所獲.
訊號認識:
真相只有一個, 可惜在那裡呢? 是那個環節出錯呢?
無意中, 再次掃了下堆疊, 徒然發現 <signal handler called
看來epoll_wait的執行緒, 是被訊號中斷, 並執行了該訊號回撥函式(其來源標明瞭libcurl.so.4), 並在該訊號處理函式中出SIGABRT 6.
讓我們回憶下, 訊號的處理.
raise函式, 會把訊號傳送給本執行緒. 而其他函式(如kill, alarm), 向程序傳送訊號, 但具體執行該訊號處理函式的執行緒是不確定的.
現在離真相已經很接近了: 某執行緒觸發訊號, epoll_wait執行緒則執行了該訊號處理函式, 可惜在這個處理函式中, 出了異常. 和epoll_wait無關, 與其呼叫方也沒關係.
那某函式(黑衣人)是誰呢? 和libcurl有關係不?
撥開雲霧:
通過搜尋引擎, 發現"libcurl 多執行緒使用注意事項", "Libcurl多執行緒crash問題"
這兩篇文章.
真相終於浮現出來了, 原來libcurl的超時機制預設是通過訊號sigalrm, setjmp/longjmp函式來實現的, 在多執行緒情況下, 會導致程式crash.
這官方的說明, 也解釋了core棧上的異常資訊.
最終的解決方案就是, CURLOPT_NOSIGNAL設定為1.
1 |
curl_easy_setopt(curl,
CURLOPT_NOSIGNAL, 1L);
|
總結:
甲方虐我千百遍, 我待甲方如初戀. 雖然被libcurl坑, 但對訊號的理解更深刻了, 終是好事. 但C/C++和Java實現同樣的業務功能時, C/C++ Coder需要了解更多的細節, 擡高門檻的同時, 也增加開發難度和開發時間, 未免有點得不償失. 這篇文章不是語言之爭, 就此打住.
寫在最後:
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫部落格能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.