1. 程式人生 > >linux訊號處理及libcurl的坑

linux訊號處理及libcurl的坑

前言:       最近有個專案, 需要訪問第三方服務. 該服務是通過http的形式訪問的, 為了安全和加密, 對方提供了一個加密用的C/C++庫, 用於對引數進行處理.  鑑於此, 選用了C/C++語言, 以libcurl作為http類庫來編寫該服務模組. 這為後續的坑埋下了伏筆.   

狀況簡述:
  程式採用Reactor模型, IO執行緒專做IO事件讀寫, 以及請求的dispatch分發, 後端執行緒池用於業務的同步操作. 對libcurl的使用, 也穿插在多執行緒中. 
  當程式功能完成後, 對其進行壓力測試. 過了大致20分鐘, 程式crash並出core.

  

  gdb調core後, 發現其在epoll_wait之上的呼叫棧出問題了.
  根據經驗, 本著誰肇事, 誰擦屁股

的原則, 對該執行緒以及epoll呼叫方做了艱苦卓絕, 卻步履維艱的排查. 後面的事實證明, 這是條不歸路, duang duang duang.....

逆向推測:
  起初的排查沒有效果, 於是乎, 採用逆向思維. 從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需要了解更多的細節, 擡高門檻的同時, 也增加開發難度和開發時間, 未免有點得不償失. 這篇文章不是語言之爭, 就此打住.

寫在最後:
  
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫部落格能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.