記一次 .NET 某企業OA後端服務 卡死分析

来源:https://www.cnblogs.com/huangxincheng/archive/2022/10/14/16790444.html
-Advertisement-
Play Games

一:背景 1.講故事 前段時間有位朋友微信找到我,說他生產機器上的 Console 服務看起來像是卡死了,也不生成日誌,對方也收不到我的httpclient請求,不知道程式出現什麼情況了,特來尋求幫助。 哈哈,一般來說卡死的情況在窗體程式(WinForm,WPF) 上特別多,在 Console,We ...


一:背景

1.講故事

前段時間有位朋友微信找到我,說他生產機器上的 Console 服務看起來像是卡死了,也不生成日誌,對方也收不到我的httpclient請求,不知道程式出現什麼情況了,特來尋求幫助。

哈哈,一般來說卡死的情況在窗體程式(WinForm,WPF) 上特別多,在 Console,WebApi 中相對較少,既然找到我,那就上 WinDbg 分析吧。

二:WinDbg 分析

1. 程式真的卡死了嗎

程式之所以能跑的梭梭響,全靠線程幫忙,如果玩不轉可能就是線程上出了點什麼問題,接下來使用 !t 展示下線程列表。


0:000> !t
ThreadCount:      124
UnstartedThread:  0
BackgroundThread: 105
PendingThread:    0
DeadThread:       18
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     65ec 000002097A4BF390    2a020 Preemptive  0000000000000000:0000000000000000 000002097a4bea20 -00001 MTA 
   3    2    15afc 000002097A50BA10    2b220 Preemptive  00000209060BE8F8:00000209060BEBE8 000002097a4bea20 -00001 MTA (Finalizer) 
   4    3    11d20 000002097A524EC0  102a220 Preemptive  0000000000000000:0000000000000000 000002097a4bea20 -00001 MTA (Threadpool Worker) 
   5    9     b8a4 000002097DE10290  3029220 Preemptive  0000000000000000:0000000000000000 000002097a4bea20 -00001 MTA (Threadpool Worker) 
   6   13    1b22c 000002097DE0ADB0  1029220 Preemptive  0000000000000000:0000000000000000 000002097a4bea20 -00001 MTA (Threadpool Worker) 
   7   16    1b0e8 000002097DE1A030  202b220 Preemptive  00000209063CB630:00000209063CC1F8 000002097a4bea20 -00001 MTA 
   8   17    138c8 000002097DE175C0  202b220 Preemptive  00000209063F71C0:00000209063F7BC8 000002097a4bea20 -00001 MTA 
   9   18    1afd0 000002097DE181E0  202b220 Preemptive  00000209064627E0:0000020906463598 000002097a4bea20 -00001 MTA 
  10   19    1ac48 000002097DE13310  202b220 Preemptive  000002090632D6C8:000002090632E0D8 000002097a4bea20 -00001 MTA 
  11   20    18704 000002097DE16390  202b220 Preemptive  00000209063FB5A8:00000209063FBBC8 000002097a4bea20 -00001 MTA 
  12   21    1ade4 000002097DE187F0  202b220 Preemptive  00000209062EA138:00000209062EA708 000002097a4bea20 -00001 MTA 
  13   22    13164 000002097DE13920  202b220 Preemptive  0000020906392108:0000020906392A38 000002097a4bea20 -00001 MTA 
  14   23    1b334 000002097DE169A0  202b220 Preemptive  00000209063CD9E8:00000209063CE1F8 000002097a4bea20 -00001 MTA 
  ...
 106  168    19e18 0000020927066770  a029220 Preemptive  0000000000000000:0000000000000000 000002097a4bea20 -00001 MTA (Threadpool Completion Port) 
 108  136     af74 0000020928590290  8029220 Preemptive  00000209063B60E0:00000209063B6408 000002097a4bea20 -00001 MTA (Threadpool Completion Port) 
 107   84     e7f0 000002097AD6B600  8029220 Preemptive  00000209063E3300:00000209063E3DD8 000002097a4bea20 -00001 MTA (Threadpool Completion Port) 

一般來說卦中的 Lock Count 列表示當前線程所持有的托管鎖個數,現在顯示的 -00001 應該是命令不起效果了。。。不過沒關係,我們還可以通過 !syncblk 來看下 lock 的情況,畢竟 95% 的鎖場景都會用到它。


0:000> !syncblk
Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
  176 000002097E0DA1D8           17         1 00000209216A3500 132c8  71   00000209002c6a68 System.Object
  191 000002097E0DAC88           27         1 000002097ADC54F0 1ae90  53   00000209000185e8 System.IO.TextWriter+SyncTextWriter
-----------------------------
Total           270
CCW             0
RCW             0
ComClassFactory 0
Free            32

從卦中的 MonitorHeld=27 來看,表示這個 SyncTextWriter 對象當前有 13 個線程在等待,有 1 個線程在持有,那這個線程為什麼沒有退出呢? 接下來可以切到 53 號線程上,查看下它的線程棧。


0:053> ~~[1ae90]s
ntdll!NtWriteFile+0x14:
00007ffd`a70df774 c3              ret
0:053> !clrstack 
OS Thread Id: 0x1ae90 (53)
        Child SP               IP Call Site
00000070505BDE88 00007ffda70df774 [InlinedCallFrame: 00000070505bde88] Interop+Kernel32.WriteFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
00000070505BDE88 00007ffd32a7cbe2 [InlinedCallFrame: 00000070505bde88] Interop+Kernel32.WriteFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
00000070505BDE50 00007ffd32a7cbe2 ILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
00000070505BDF20 00007ffd3a2d6971 System.ConsolePal+WindowsConsoleStream.WriteFileNative(IntPtr, Byte[], Int32, Int32, Boolean)
00000070505BDF80 00007ffd3a2d672f System.ConsolePal+WindowsConsoleStream.Write(Byte[], Int32, Int32) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 1131]
00000070505BDFD0 00007ffd377c05d9 System.IO.StreamWriter.Flush(Boolean, Boolean) [/_/src/libraries/System.Private.CoreLib/src/System/IO/StreamWriter.cs @ 260]
00000070505BE050 00007ffd3a2d6687 System.IO.StreamWriter.WriteLine(System.String)
00000070505BE0D0 00007ffd3a2d6472 System.IO.TextWriter+SyncTextWriter.WriteLine(System.String) [/_/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs @ 880]
00000070505BE130 00007ffd3a2d640d System.Console.WriteLine(System.String) [/_/src/libraries/System.Console/src/System/Console.cs @ 716]
00000070505BE160 00007ffd3a2375ab HCloud.xxx+d__2.MoveNext()
00000070505BEAE0 00007ffd37e19365 System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[[System.__Canon, System.Private.CoreLib]](System.__Canon ByRef) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs @ 63]
00000070505BEB40 00007ffd3a235cc3 HCloud.xxxx.Execute(Quartz.IJobExecutionContext)
00000070505BEBB0 00007ffd3a231264 Quartz.xxx+d__9.MoveNext()
00000070505BEE90 00007ffd3a230183 System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[[Quartz.Core.JobRunShell+d__9, Quartz]](d__9 ByRef) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs @ 63]
00000070505BEF10 00007ffd3a2300db System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[[Quartz.Core.JobRunShell+d__9, Quartz]](d__9 ByRef) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilder.cs @ 33]
00000070505BEF40 00007ffd3a230082 Quartz.xxxxl.Run(System.Threading.CancellationToken)
00000070505BF020 00007ffd3a22ffc3 Quartz.Core.QuartzSchedulerThread+c__DisplayClass28_0.b__0()
00000070505BF060 00007ffd3939b71a System.Threading.Tasks.Task`1[[System.__Canon, System.Private.CoreLib]].InnerInvoke() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @ 507]
00000070505BF0C0 00007ffd37d54431 System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 300]
00000070505BF110 00007ffd37d53657 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2352]
00000070505BF1C0 00007ffd37d50e04 System.Threading.ThreadPoolWorkQueue.Dispatch() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @ 677]
00000070505BF690 00007ffd925ea7a3 [DebuggerU2MCatchHandlerFrame: 00000070505bf690] 

仔細觀察線程棧會很驚訝的發現,居然還能卡在 System.Console.WriteLine 方法上,挺奇怪的,為了探究原因,我們使用 k 命令看下非托管棧。


0:053> k 5
 # Child-SP          RetAddr               Call Site
00 00000070`505bddd8 00007ffd`a32febda     ntdll!NtWriteFile+0x14
01 00000070`505bdde0 00007ffd`32a7cbe2     KERNELBASE!WriteFile+0x7a
02 00000070`505bde50 00007ffd`3a2d6971     0x00007ffd`32a7cbe2
03 00000070`505bdf20 00007ffd`3a2d672f     System_Console!System.ConsolePal.WindowsConsoleStream.WriteFileNative+0x61
04 00000070`505bdf80 00007ffd`377c05d9     System_Console!System.ConsolePal.WindowsConsoleStream.Write+0x3f

0:053> ub ntdll!NtWriteFile+0x14
ntdll!NtDeviceIoControlFile+0x15:
00007ffd`a70df755 cd2e            int     2Eh
00007ffd`a70df757 c3              ret
00007ffd`a70df758 0f1f840000000000 nop     dword ptr [rax+rax]
ntdll!NtWriteFile:
00007ffd`a70df760 4c8bd1          mov     r10,rcx
00007ffd`a70df763 b808000000      mov     eax,8
00007ffd`a70df768 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffd`a70df770 7503            jne     ntdll!NtWriteFile+0x15 (00007ffd`a70df775)
00007ffd`a70df772 0f05            syscall

從上面的 syscall 系統調用關鍵詞看,代碼是將用戶態的 ntdll!NtWriteFile 切到入了內核態的 nt!NtWriteFile 方法,那進入了內核態為什麼沒有返回呢? 這又是一個值得思索的問題。

2. 為什麼進入了內核態無返回

其實 ntdll!NtWriteFile 這個 win32 api 方法的第一個參數是一個 handle 的文件句柄,簽名如下。


__kernel_entry NTSYSCALLAPI NTSTATUS NtWriteFile(
  [in]           HANDLE           FileHandle,
  [in, optional] HANDLE           Event,
  [in, optional] PIO_APC_ROUTINE  ApcRoutine,
  [in, optional] PVOID            ApcContext,
  [out]          PIO_STATUS_BLOCK IoStatusBlock,
  [in]           PVOID            Buffer,
  [in]           ULONG            Length,
  [in, optional] PLARGE_INTEGER   ByteOffset,
  [in, optional] PULONG           Key
);

可能 handle 在內核中被別人占用了,可以用 !handle 查看下 rcx 寄存器。


0:053> r
rax=0000000000000008 rbx=00000070505bdf50 rcx=0000000000000418
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000418
rip=00007ffda70df774 rsp=00000070505bddd8 rbp=00000070505bdf10
 r8=0000000002000805  r9=0000000000000000 r10=0000020906191620
r11=00000070505bc8f8 r12=0000000000000100 r13=0000000000000053
r14=0000000000000077 r15=000002097adc54f0
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtWriteFile+0x14:
00007ffd`a70df774 c3              ret
0:053> !handle 0000000000000418 f
Handle 0000000000000418
  Type         	File
  Attributes   	0
  GrantedAccess	0x120196:
         ReadControl,Synch
         Write/Add,Append/SubDir/CreatePipe,WriteEA,ReadAttr,WriteAttr
  HandleCount  	3
  PointerCount 	65483
  No object specific information available

哈哈,其實也看不出什麼,也沒法進入內核態,所以下一步只能到網上搜搜看,其實有經驗的朋友肯定猜出來了,應該是控制台啟用了 快捷編輯視窗 ,截圖如下:

快捷編輯視窗 簡而言之就是用戶可以在控制臺上獨占這個視窗,編輯一些內容, 可一旦被用戶獨占,那程式側就沒法輸出內容到 控制台視窗 上了,只能在 內核態 傻傻等等,這應該就是形成原因,畫個圖大概就像下麵這樣。

將信息告訴朋友後,朋友說他用的是 Windows 服務部署,但不管是什麼模式部署,註釋掉 Console.WriteLine 肯定沒錯。

感謝如家的 鄧工 提供的禁用 快捷編輯視窗 代碼。


    #region 關閉控制台 快速編輯模式、插入模式
    const int STD_INPUT_HANDLE = -10;
    const uint ENABLE_QUICK_EDIT_MODE = 0x0040;
    const uint ENABLE_INSERT_MODE = 0x0020;
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetStdHandle(int hConsoleHandle);
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint mode);
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint mode);

    public static void DisbleQuickEditMode()
    {
        IntPtr hStdin = GetStdHandle(STD_INPUT_HANDLE);
        uint mode;
        GetConsoleMode(hStdin, out mode);
        mode &= ~ENABLE_QUICK_EDIT_MODE;//移除快速編輯模式
        mode &= ~ENABLE_INSERT_MODE;      //移除插入模式
        SetConsoleMode(hStdin, mode);
    }
    #endregion

三:總結

這次卡死的事故,主要還是開發人員大量的使用 Console.WriteLine 來輸出日誌,在某個時刻輸出端視窗因為各種原因被他人獨占,導致程式側無法輸出內容到視窗而一直被迫等待,之後朋友將日誌輸出切換到文件模式,問題得以解決。

其實這個問題很多新手朋友都會犯,特此記錄下來。

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 緩存就是記憶體中的數據,常常來自對資料庫查詢結果的保存。使用緩存,我們可以避免頻繁的與資料庫進行交互,進而提高響應速度MyBatis也提供了對緩存的支持,分為一級緩存和二級緩存,可以通過下圖來理解: ①、一級緩存是SqlSession級別的緩存。在操作資料庫時需要構造sqlSession對象,在對象中 ...
  • AttribteError: ‘module’ object has no attribute xxx’ 描述:模塊沒有相關屬性。可能出現的原因:1.命名.py文件時,使用了Python保留字或者與模塊名等相同。解決:修改文件名2…pyc文件中緩存了沒有更新的代碼。解決:刪除該庫的.pyc 文件 A ...
  • 某寶秒殺,用毫秒級的精準度來搶購! 還記得前段時間情人節,各種產品活動秒殺。結果自然少不了被對象一番折磨 (註意:不是new出來的哈,也不是橡膠的,實實在在的女朋友) 於是乎徹底激發了我的求生欲,在這種關頭我是必鬚髮揮出自己的才能了,這才有了這篇毫秒級秒殺的精品出來,話不多說直接進入主題 項目環境​ ...
  • 2022-10-11 10:58:41 😀 前言 本文開始流程式控制制方面的學習,主要包括用戶交互和流程式控制制語句,適合新手學習。 1 用戶交互Scanner 1.1 Scanner對象 Java提供了一個可以獲取用戶輸入的Scanner工具類 基本語法: Scanner s = new Scanner ...
  • 各種語言用到的編輯器 python開發:pycharm(收費),vscode(免費),sublintext, go開發:goland(收費),vscode,國產的 java:idea(收費),eclipse(免費),MyEclipse(收費) android:androidstudio(免費),ec ...
  • 大家好,我是三友~~ 最近突然心血來潮(就是閑的)就想著擼一個簡單的配置中心,順便也照葫蘆畫瓢給整合到SpringCloud。 本文大綱 配置中心的概述 隨著歷史的車輪不斷的前進,技術不斷的進步,單體架構的系統都逐漸轉向微服務架構。雖然微服務架構有諸多優點,但是隨著越來越多的服務實例的數量,配置的不 ...
  • 在一個項目中,客戶要求對報表中的簽名進行仿手寫的簽名處理,因此我們原先只是顯示相關人員的姓名的地方,需要採用手寫方式簽名,我們的報表是利用FastReport處理的,在利用楷體處理的時候,開發展示倒是正常效果,不過實際上在伺服器運行的時候,出來的確實正規的宋體格式,相應的字體都已經安裝,不過還是沒有... ...
  • 1.進程 進程的概念從字義上理解相對還是比較抽象的,但進程實際上對我們並不陌生,可以說它無時不刻的伴隨著我們的生活。當你每天上班打開電腦,運行微信與好友通訊、運行瀏覽器閱讀網頁新聞等,這一些將程式運行起來的操作,都屬於創建了一個進程。並且我們可以對同一種程式重覆運行多次,這意味著一個程式可以創建多個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...