關於多線程情況下Net-SNMP v3 版本導致進程假死情況的跟蹤與分析


1、問題描述

  在使用net-snmp對交換機進行掃描的時候經常會出現進程假死的情況(就是進程並沒有死掉,但是看不到它與外界進行任何的數據交互)。這時候不知道進程內部發生了什么,雖然有日志信息,但進程已經很長時間沒有動靜,根本不知道這段時間做了什么。用gdb att進去發現,進行snmp發送的線程已經被阻塞了。但是阻塞的情況並不是每次都發生,而是經常發生,這就導致很難捕捉問題。通過觀察日志和 tcpdump 抓包,發現這種情況只在v3版本的時候出現,那就是v3版本有什么特別的地方。


 

2、調試跟蹤

  觀察 gdb att 后的情況,發現每次都會掛在 recvmsg() 這個函數上。剛開始以為是在進行接收的時候出的問題,多看了幾層棧才發現,原來在發送函數里面就卡住了,傷心啊……

  下面是gdb看到的棧調用情況:

  在snmp v3 協議中,要想能夠請求數據,首先需要獲取SNMP 協議引擎,也就是engineID號,然后根據這個再去請求索要的數據。目前遇到的情況就是在請求這個引擎ID的時候卡住了。看了下net-snmp的源碼,在請求引擎ID的時候是沒有設置超時的,也就是死等……

在 snmp_client.c 源文件中的 snmp_sess_synch_response() 函數中的源代碼:

numfds = 0;
        FD_ZERO(&fdset);
        block = NETSNMP_SNMPBLOCK;
        tvp = &timeout;
        timerclear(tvp);
        snmp_sess_select_info(sessp, &numfds, &fdset, tvp, &block);
        if (block == 1)
            tvp = NULL;         /* block without timeout */
        count = select(numfds, &fdset, 0, 0, tvp);
        if (count > 0) {
            snmp_sess_read(sessp, &fdset);
        } 

而且是用的 select !!!我們發送和接收用的可是多線程呀……

然后搜到這么一篇討論 net-snmp 對多線程支持情況的文章:《Is Net-SNMP thread safe?

譯文在這里:《Net-SNMP是線程安全的嗎

文章開篇是這么說的,我忍不住要截圖呀 !!!

問:Net-SNMP是線程安全的嗎?

答:確切的說,不是!

我勒個去,多么干脆的回答,簡直不忍直視……

雖然在文末給出了v3不支持多線程的原因,我還是感到不開心……

Unfortunately, the SNMPv3 support was added about the same time as the thread support and since they occurred in parallel the SNMPv3 support was never checked for multi-threading correctness. It is most likely that it is not thread-safe at this time.


 

3、分析多線程下net-snmp v3出現卡死的原因

  在多線程下,因為我的程序對交換機的發送和接收是在不同線程的,導致一個很嚴重的問題就是線程間的同步。

  我們假設有這樣一種情況:

  我們有兩個線程,發送線程T1 和 接收線程 T2。T1 只負責調用 net-snmp 的發送函數接口,向交換機請求信息;T2只負責進行接收,並且是select 方式進行接收:

  步驟:

    1)T1線程 發送一個請求,然后就不管了,接收工作就交給T2線程 select吧

    2)那么,此時這個發送線程 T1 可以繼續向另一個snmp v3版本的交換機發送請求,在發送的過程中需要首先請求SNMP引擎,及請求engineID

    3)T1線程發送完了,進行阻塞調用 select,直到有消息返回才會結束阻塞狀態

  注意,此時就出現問題了。我們發送請求和接收請求的時候使用兩個不同的線程,也就是說,那個只負責接收的線程一直在進行select。這個時候就要看是誰能夠接收到這個請求engineID的UDP包了,如果是此時的這個發送線程T1,那么萬事大吉,程序繼續向下走。如果這個請求engineID的包被只負責接收的線程 T2 收到了呢?那 T1 線程就沒得接收了,那就只好等待了,由於沒有設置超時,還是以阻塞方式進行調用的,結果就是死等……

  所以現在遇到的問題是,發送線程 T1 不僅僅只是進行了發送,還進行了接收,並且在發送過程中出現的接收動作不是我們能控制的。當然我們也可以修改源代碼,增加線程鎖,但那需要更多的精力去研究 net-snmp 更多的代碼,還不如在自己代碼里加鎖。


 

4、解決辦法

  還能有什么解決辦法?只能加線程鎖了。加鎖的目的在於,使發送線程 T1 的發送過程和接收線程 T2 的select過程互斥的進行,不允許接收線程 T2 搶奪發送線程 T1 的數據。

  解決方法:

    1)在 T1 線程進行發送之前先加鎖,發送完成后解鎖

    2)在 T2 線程 select 之前加鎖,seelct結束之后解鎖

  結果:運行了很長事件了,沒有再出現v3卡死的情況

  但目前這種處理還是比較粗糙的,會導致T2線程在沒有接收到返回數據時進行超時等待處理,一次超時等待需要3s,這對性能的損耗還是比較大的。

 


 

5、總結

  開發的時候總會遇到各種各樣的奇聞異事,習慣了就好了。今天把問題的發現及解決過程記錄一下,按照茨威格在《昨日的世界》中所說的“倘若你想完全領悟偉大的傑作,你不僅要看到過它們的成品,而且必須了解到它們形成的過程”。所以我要記錄下每一個問題的過程,不是因為這是什么傑作,而是因為這是我生命中的一件故事,當我垂垂暮年,回首往事,看到自己一路上記錄的點滴,想起自己年輕時努力的身影,也許,這才是我送給未來自己最好的禮物吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM