Welcome to MSDN Blogs Sign in | Join | Help

TechEd2008 .NET 應用程式除錯秘技系列(4) 應用程式沒有回應(Idle Hang) 下

終於進入到正題囉~馬上來看一下這個Idle Hang的問題如何重現?

重現步驟:

1. 執行應用程式並開啟WinDbg,在WinDbg中直接附加到HangDemo.exe這個process。忘記的請參考前一篇文章。

2. 在轉帳旁邊的TextBox輸入一個金額(Ex, 2000),按一下"轉帳"鈕,然後很快的切到"支票轉存款帳戶",再按一次"轉帳"

034

3. 此時您再去按顯示餘額(無論支票或存款)都會變成灰色且無回應。

035

4. 這時候我們已經確定應用程式Hang住了~可以回到WinDbg裏,在Debug=>Break或按一下snap026 圖示將程式中斷下來。

5. 載入SOS.dll (輸入 ".loadby sos mscorwks")

6. 輸入"!syncblk",如果不知道指令的意思及用法,可以用!help <command>取得說明。例如 !help syncblk 會得到如下結果:

0:015> !help syncblk
-------------------------------------------------------------------------------
!SyncBlk [-all | <syncblk number>]

A SyncBlock is a holder for extra information that doesn't need to be created
for every object. It can hold COM Interop data, HashCodes, and locking
information for thread-safe operations.

When called without arguments, !SyncBlk will print the list of SyncBlocks
corresponding to objects that are owned by a thread. For example, a

    lock(MyObject)
    {
        .... (略)

輸入!syncblk後會得到以下資訊:

0:015> !syncblk
Index SyncBlock MonitorHeld     Recursion      Owning  Thread Info     SyncBlock Owner
     34   003fdad4             7         1 0044a0b8          19b0           9              029c3774 HangDemo.Account
     36   003fdb34             5         1 0044da00            9fc           8             029729c0 HangDemo.Account
-----------------------------
Total           37
CCW             2
RCW             0
ComClassFactory 0
Free            0

以上有幾個資訊要注意,一是Owning Thread,另一個是Own住的物件位址及型別。 Syncblk指令主要會列出目前lock的資訊。

7. 使用 "!threads"可以列出所有threads。使用"~<n>s" 指令可以切換到不同thread,以此例來看,我們應該看第8及第9條thread在做什麼,以致於應用程式沒有回應。

8. 輸入 "~8s" 切換到第8條thread。然後輸入 "!clrstack",這個指令可以顯示當前thread的call stack,若加上 -a的參數,則可以顯示相關的參數及區域變數。執行後得到的結果如下:

0:009> ~8s
eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=77d60bc5 esp=05faef64 ebp=05faeffc iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtWaitForMultipleObjects+0x15:
77d60bc5 c21400          ret     14h
0:008> !clrstack
OS Thread Id: 0x9fc (8)
ESP       EIP    
05faf1f0 77d60bc5 [GCFrame: 05faf1f0]
05faf32c 77d60bc5 [HelperMethodFrame: 05faf32c] System.Threading.Monitor.Enter(System.Object)
05faf380 008622ad HangDemo.Form1.SavToChk()
05faf3a4 793b0d1f System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
05faf3ac 79373ecd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
05faf3c4 793b0c68 System.Threading.ThreadHelper.ThreadStart()
05faf5ec 79e7c74b [GCFrame: 05faf5ec]

我們看到程式在執行SavToChk函式後,呼叫了Monitor.Enter方法。

9. 切換到第9條thread,同樣來看看它的call stack

0:008> ~9s
eax=00000004 ebx=00000001 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=77d60bc5 esp=0619f144 ebp=0619f1dc iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtWaitForMultipleObjects+0x15:
77d60bc5 c21400          ret     14h
0:009> !clrstack
OS Thread Id: 0x19b0 (9)
ESP       EIP    
0619f3d0 77d60bc5 [GCFrame: 0619f3d0]
0619f50c 77d60bc5 [HelperMethodFrame: 0619f50c] System.Threading.Monitor.Enter(System.Object)
0619f560 0086254d HangDemo.Form1.ChkToSav()
0619f584 793b0d1f System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
0619f58c 79373ecd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0619f5a4 793b0c68 System.Threading.ThreadHelper.ThreadStart()
0619f7cc 79e7c74b [GCFrame: 0619f7cc]

這call stack看起來好眼熟喔~但仔細一看,這裏執行的是ChkToSav (第8條執行的是SavToChk),對照我們剛剛在程式裏操作的步驟:支票轉存款,然後存款轉支票~Bingo!!好像離答案愈來愈近了。

既然有source code,當然是拿來看看這兩個方法在做什麼最快了!請看以下程式碼:

   
void ChkToSav()
        {
            double amount = Convert.ToDouble(transf_textBox.Text);
            Monitor.Enter(acnt_checking);
            acnt_checking.withdraw_checking(123, amount);
            Thread.Sleep(5000);
            Monitor.Enter(acnt_saving);
            acnt_saving.deposit_saving(123, amount);
            Monitor.Exit(acnt_saving);
            Monitor.Exit(acnt_checking);
        }
 
        void SavToChk()
        {
            double amount = Convert.ToDouble(transf_textBox.Text);
            Monitor.Enter(acnt_saving);
            acnt_saving.withdraw_saving(123, amount);
            Thread.Sleep(5000);
            Monitor.Enter(acnt_checking);
            acnt_checking.deposit_checking(123, amount);
            Monitor.Exit(acnt_checking);
            Monitor.Exit(acnt_saving);
        }

看到了嗎? ChkToSav是支票轉存款,它會先lock acnt_checking,然後sleep 5秒後再lock acnt_saving. SavToChk則剛好相反。所以我們就是利用這5秒鐘造成一個deaklcok。

可是~~剛剛我們在dump裏看到的不是物件位址029729c0029c3774  嗎? 怎麼確定它就是acnt_checking和acnt_saving呢? 給各位一個提示,用!do指令去看看Form物件裏有哪些欄位吧!

另外要思考的是,我們找出deadlock的原因了,但怎麼解決呢? 也許我們應該要思考的是,轉帳這個動作用lock適合嗎?有沒有其他的做法呢? 留給各位當練習囉~

Published Friday, October 10, 2008 2:41 AM by Terry Lin
Filed under: , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker