天天看點

記一次 .NET 某電商無貨源後端服務 死鎖分析

這個月初,星球裡的一位朋友找到我,說他的程式出現了死鎖,懷疑是自己的某些寫法導緻mongodb出現了如此尴尬的情況,截圖如下:

記一次 .NET 某電商無貨源後端服務 死鎖分析

說實話,看過這麼多dump,還是第一次遇到真實的死鎖,這tmd的頓時就有了興趣。。。 上 windbg 說話。

既然朋友說死鎖,我得先驗證一下,可以用指令 <code>!syncblk</code> 檢視同步塊表。

從同步塊表中可得知如下資訊。

25号線程正持有 <code>000000f7b853d480</code> 鎖對象。

63号線程正持有 <code>000000f7b853de48</code> 鎖對象。

我們知道所謂的 <code>死鎖</code> 就是兩個線程都渴望得到對方持有的鎖資源,誰也不讓步所造成的一種僵局,如果不明白,我就畫一張圖:

記一次 .NET 某電商無貨源後端服務 死鎖分析

上圖就是一種死鎖的僵局,順便提一下, 在 sqlserver 中也常會遇到這種情況,那它會怎麼處理的呢? 這就有點意思了,sqlserver 内部有一個調停的線程周期性執行,當檢測到這種死鎖僵局的時候,它會把優先級低的線程kill掉,這樣另外一個線程就能順利擷取鎖,被 kill 掉的線程就會出現如下異常資訊:

哈哈,是不是似曾相識,好了,對死鎖有了一定認識之後,我們假設一下,如果存在

25号線程想擷取 <code>000000f7b853de48</code> 鎖對象。

63号線程想擷取 <code>000000f7b853d480</code> 鎖對象。

的情況下,必然就會死鎖, 對吧,接下來怎麼用 windbg 驗證呢? 切到 25 号線程檢視線程棧及棧對象。

可以清楚的看到 <code>ReliableEnter</code> 正在擷取 <code>000000f7b853de48</code> 鎖對象時被卡住,再切到 63号線程檢視。

可以清楚的看到 <code>ReliableEnter</code> 正在擷取 <code>000000f7b853d480</code>, 這就表明确實産生了死鎖,沒毛病。

要想追究死鎖的原因,隻能仔細推敲 <code>線程棧</code> + <code>線程棧對象</code>。

由于這代碼到處都是 <code>await,async</code> ,是以看這反編譯後的線程棧真的頭大,經過仔細比對,發現代碼流程大概是:

從處理 Mongodb 的異步請求回調開始 (System.Threading.OverlappedData)。

在 <code>MongoDB.Driver.Core.Operations.FindOperation</code> 時不知為啥抛了取消異常 <code>OperationCanceled</code>,然後調用 <code>RetryableReadContext.Dispose()</code>。

記一次 .NET 某電商無貨源後端服務 死鎖分析

在 <code>ListConnectionHolder.Return()</code> 方法中擷取 <code>000000f7b853de48</code> 鎖對象。

記一次 .NET 某電商無貨源後端服務 死鎖分析

在 <code>SignalOrReset() -&gt; SemaphoreSlimSignalable.Signal()</code> 方法中執行一些注冊handler邏輯。

記一次 .NET 某電商無貨源後端服務 死鎖分析

注意:在事件觸發中并沒有退出 <code>lock</code> 區域。

在幾個handler痙攣過程中進入了另外一個線程池的 <code>ListConnectionHolder.Acquire()</code> 方法中,希望能得到該池中的 <code>000000f7b853d480</code> 鎖對象。

記一次 .NET 某電商無貨源後端服務 死鎖分析

同時 25号線程正在反向做這個操作,由于大家都是雙重 lock,是以最終導緻 死鎖 的發生。

從<code>線程棧對象</code>看,應該也看到了有兩個線程池 <code>ExclusiveConnectionPool</code>。

這是由于朋友的 <code>mongodb 連接配接串</code> 用的是雙IP的副本集模式。

從堆棧資訊看并不是程式員的鍋,是 mongodb 在接收異步回調時,由于某種情況發生了 <code>OperationCanceled</code> 異常,面對異常的後續處理邏輯時出現了死鎖bug。

這個mongodb 官方驅動是 <code>2.13.1.0</code>,也就是 <code>2021-8-15</code> 釋出的,截至最新的是 10月份釋出的 <code>2.13.2.0</code>。

記一次 .NET 某電商無貨源後端服務 死鎖分析

了解這些資訊後,和朋友做了溝通,朋友說他給 mongodb 社群送出 issue,幾天後,官方給的回答是在最新的 <code>v2.14.beta1</code> 中做了處理。

https://github.com/mongodb/mongo-csharp-driver/commit/b961b81cb7dc1ffe7262c55a227afad0aab5a994

https://jira.mongodb.org/browse/CSHARP-3815

也就是說在未來的 <code>release v2.14.0</code> 版本中會得到解決,目前也隻能等一等啦! 期待中。。。哈哈😁😁😁

總的來說,這是 mongodb 底層的一個 bug 導緻的死鎖問題,dump的分析過程也幾經波折,雖是官方權威的 <code>MongoDB.Driver</code> 包,但同樣值得懷疑,而不要一味的深深懷疑自己... 最後期待即将釋出的 <code>release v2.14.0</code> 吧。

記一次 .NET 某電商無貨源後端服務 死鎖分析
記一次 .NET 某電商無貨源後端服務 死鎖分析

繼續閱讀