php死鎖問題

2021-07-23 21:18:18 字數 3076 閱讀 5162

背景:對於死鎖的問題,人們往往想到出現一些關於訪問很緩慢,有白頁現象,要是測試環境(我就真實遇到測試環境有本文談及一樣的問題)你也就重啟一下php的php-fpm程序發現又好了,隔一段時間又出類似的問題,你會看下日誌,你會發現有很多日誌是「max execution timeout of 60 seconds exceeded」,你會發現這可能是一些php的守護程序導致的,你為了解決測試環境的問題,於是覺得應該把那個php-fpm的程序數開多點,可能會好一些,於是你開多了,一直沒有面對這個問題的原因,為什麼呢,因為公司裝php的是運維裝的,你沒有辦法或時間去裝乙個debug版本的php,你說這個問題讓運維的人來查,你覺得能查出來?so,這個問題一拖再拖,但就是沒解決,但是有一天你發現磁碟滿了,用du去看整體時發現滿了,但是如果乙個個目錄去看發現並沒有占用多少,也萬萬沒有想到php的死鎖還會導致磁碟空間占用太多,上面這種情況我就真實遇到過,後來重新reboot作業系統,磁碟又回來了,所以,我認為是一篇好文章,所以轉了此文,也想說明對於php的擴充套件這方面**質量把關需要嚴格,再就是php本身關於鎖這塊要弱化(除開cookie/session和cache鎖外,其它能不用就不用),盡可能少用鎖,這是博主一點小看法,下面言歸正傳。

引子:本期我們邀請到了 雲盤服務端 團隊的技術達人- 徐鐵成,乙個隱蔽已久的php死鎖問題被層層掘出,感謝鐵成為我們帶來這次暢快的體驗,小夥伴們,準備好這次技術之旅了麼?

發現問題:

近期發現線上很多機器的磁碟空間報警, 且日誌檔案已經清理,但是磁碟空間沒有釋放。通過ps aux | grep php-cgi 發現, 很多程序的啟動時間在幾天到幾周甚至幾個月之前。我們線上的php-cgi都有最大執行次數的。一般在1天內都會重啟一次。初步結論,這些cgi程序有問題。

通過lsof -p [pid] 發現, 啟動時間很久的cgi程序中開啟了一些日誌檔案控制代碼,並且沒有關閉。這些日誌檔案在檔案系統中已經刪除了。但是控制代碼沒關閉,導致磁碟空間沒有釋放。到此,磁碟空間異常的問題基本確定。是由於cgi沒有關閉檔案控制代碼造成的。

進一步分析程序, strace -p [pid], 發現所有異常的程序都阻塞與 fmutex 狀態。換句話所,異常的cgi程序死鎖了。程序死鎖導致開啟的檔案控制代碼沒有關閉,所以導致磁碟空間異常。

為什麼cgi程序會死鎖呢?

什麼是死鎖

學過作業系統的通同學,都了解多執行緒的概念。在多執行緒中訪問公共資源,需要對資源加鎖。訪問結束後,釋放鎖。如果沒有釋放鎖,那麼下乙個執行緒來獲取資源的時候就會永遠都無法獲取資源的鎖,於是這個執行緒死鎖了。那麼cgi是多執行緒的公共資源訪問導致的死鎖嗎? 答案是no。

1. cgi 是單執行緒程序,通過ps 就能看到。(程序狀態 sl的才是多執行緒程序)。

2. 即使是多執行緒的,死鎖發生在php的shutdown過程中呼叫glibc 中time 函式的位置,不是php模組造成的。而glibc 中的time相關函式是執行緒安全的,不會產生死鎖。

那是什麼導致的死鎖呢?

通過分析linux中死鎖產生的機制,發現除了多執行緒會產生死鎖外,訊號處理函式同樣會產生死鎖。那麼cgi是由於訊號處理導致的死鎖嗎?在這之前介紹乙個感念。

函式的可重入性與訊號安全

函式可重入是指,無論第幾次進入該函式,函式都能正常執行並返回結果。那麼執行緒安全函式是可重入的嗎?答案是no。 執行緒安全函式,在第一次訪問公共資源時,會獲取全域性鎖。如果函式沒有執行完成,鎖還沒釋放,此時程序被中斷。那麼在中斷處理函式中,再次訪問該函式,就會產生死鎖。那麼什麼樣的函式才可以在中斷處理函式中訪問呢? 除了沒有使用全域性鎖的函式,還有一些signal safe的系統呼叫可以使用。呼叫任何其他的非signal safe的函式都會產生不可預知的後果(比如 死鎖)。 詳見 man signal。在分析死鎖的原因前,我們先看看cgi執行的流程,分析其中有沒有產生死鎖的可能。

php-cgi的執行流程

glibc中的時間函式使用到了全域性鎖,保證函式的執行緒安全,但沒***訊號安全(signal safe)。經過之前的分析,我們初步懷疑死鎖是由於php-cgi程序接收到了乙個訊號,然後在signal handle中執行了非signal safe的函式。主流程在中斷前,正在執行glibc中的時間函式。在函式獲取的鎖沒釋放前,進入中斷流程。而中斷過程中又訪問了glibc中的時間函式。於是導致了死鎖。

進一步分析發現,所有死鎖的cgi程序的sapi_global中都記錄了乙個錯誤資訊

「max execution timeout of 60 seconds exceeded」.

60s 是我們php-cgi中設定執行超時。所以我們確認了,cig在執行過程中的確產生了超時異常,然後由於longjmp進入了shutdown過程。在shutdown過程中訪問了glibc中的時間函式。導致了死鎖。

void zend_set_timeout(long seconds)

……setitimer(itimer_prof, &t_r, null);

signal(sigprof, zend_timeout); // 此處會呼叫zend異常處理函式

sigemptyset(&sigset);

sigaddset(&sigset, sigprof);……}

通過gdb除錯發現,所有php-cgi都阻塞在zend_request_shutdown中。zend_request_shutdown會呼叫使用者自定義的php指令碼中實現的shutdown函式。如果cgi執行超市,那麼定時器會產生sigprof訊號使執行流程中斷。如果此時指令碼剛好處於呼叫時間函式的狀態,且還沒有釋放鎖資源。然後執行流程進入了 timeout 函式,繼續跳轉到zend_request_shutdown。此時如果自定義的shutdown函式中訪問了時間函式。就會產生死鎖。我們從**中找到了證據:

register_shutdown_function ('******websvc:: shutdown』);

我們在php**中使用qalarm系統,qalarm系統會在cgi執行結束(shutdown)的時候,注入乙個鉤子函式,來分析cgi執行是否正常,如果不正常,則傳送報警資訊。而剛好qalarm的報警處理函式中訪問了時間函式。於是就有一定的概率產生死鎖。

結論通過上面的分析,我們找到了cgi死鎖產生的原因,是應為在signal handler中使用了非signal safe的函式,導致了死鎖。

解決辦法

去掉或簡化qalarm註冊到shutdown中的鉤子函式。避免不安全的函式呼叫。

**:

Linux 死鎖問題

之前的部落格都多次提到了死鎖問題,那麼我們先來了解一下。什麼是死鎖?其實死鎖是指在多道程式系統中,一組程序中的每乙個程序均無期限的等待被該組程序中的另乙個程序所占有且永遠不會釋放的資源,執行緒一樣。這種現象稱系統處於死鎖狀態,簡稱死鎖。處於死鎖狀態的程序稱為死鎖程序。產生死鎖的四個必要條件 其實死鎖...

SPIN LOCK死鎖問題

板卡公升級版本後,初始化疑似未執行完,ssh無法連線,可以ping通。通過檢視控制台的列印,可以看到,初始化執行到了57一半,就卡住了。兩次控制台輸入i 這個命令是我們內部實現的,功能和vxworks的類似,列印一些任務狀態 只有pid為1785的程式執行時間在增長,懷疑任務陷入死迴圈。控制台檢視其...

執行緒死鎖問題

所謂死鎖是指多個執行緒因競爭資源而造成的一種僵局,多個執行緒被無限的阻塞,執行緒之間相互等待所需的資源,若無外力作用,這些程序都將無法向前推進。死鎖的產生,通常是下面的兩種情況 1.如果執行緒試圖對同乙個互斥量加鎖兩次,那麼它自身就會陷入死鎖狀態,使用互斥量時,2.如果程式使用多個互斥量時,如果允許...