2015-11-03 121 views
1

我在C#中編寫了一個Windows服務,目標是.NET 4.0,當我嘗試停止服務時,它將在奇怪的時候完全掛起。我注意到從一個轉儲文件中看到一些線程被掛起,儘管我並沒有在我的代碼中自行掛起它們。服務在停止時偶爾掛起:掛起的線程

環境是Windows Server 2008R2 64bit,儘管我在Windows 7 64bit上觀察到相同的掛起。 .NET 4.0是安裝的最新版本。

有很多的代碼,所以我只是發佈一些希望相關的片段,如果需要我可以發佈更多。

基本設計:

的Main()開始一個新的線程來處理記錄到文件(該代碼是在一個單獨的DLL),然後啓動該服務。

public static void Main(string[] args) 
{ 
    ... 
    else if (Args.RunService) 
    { 
     Logger.Options.LogToFile = true; 
     MSPO.Logging.Logger.Start(); 
     RunService(); 
     MSPO.Logging.Logger.Stop(); 
    } 
    ... 
} 

private static void RunService() 
{ 
    service = new ProcessThrottlerService(); 
    System.ServiceProcess.ServiceBase.Run(service); 
} 

該線程停留在那裏,直到ServiceBase.Run返回。

服務中的OnStart()創建一個新線程並啓動它。

protected override void OnStart(string[] args) 
{ 
    serviceThread = new MainServiceThread(); 
    serviceThread.StartThread(); 
    base.OnStart(args); 
} 

創建其被用作用於程序的其餘部分停止信號一個ManualResetEventSlim。 OnStop()設置事件。

protected override void OnStop() 
{ 
    if (serviceThread != null) 
    { 
     serviceThread.StopThread(); // Event is signalled in there 
     serviceThread.WaitForThreadToReturn(); // This calls thread.Join() on the MainServiceThread thread 
    } 
    base.OnStop(); 
} 

「MainServiceThread」創建事件,再次啓動一個新線程,並等待事件。

private void StartHandlerAndWaitForServiceStop() 
{ 
    processHandler.Start(serviceStopEvent); 
    serviceStopEvent.Wait(); 
    processHandler.Stop(); 
} 

的processHandler線程同意這樣的WMI查詢:

watcher = new ManagementEventWatcher(new ManagementScope("root\\CIMV2"), 
    new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace")); 
watcher.EventArrived += HandleNewProcessCreated; 

如果新的進程名很感興趣,我創建了一個新的「節流」的線程,有效地只是暫停過程中,睡覺,簡歷這個過程中,又一次睡上一個循環:

while (true) 
{ 
    ntresult = Ntdll.NtResumeProcess(processHandle); 
    if (ntresult != Ntdll.NTSTATUS.STATUS_SUCCESS) 
    { 
     if (ntresult != Ntdll.NTSTATUS.STATUS_PROCESS_IS_TERMINATING) 
      LogSuspendResumeFailure("resume", ntresult); 
     break; 
    } 
    Thread.Sleep(resumeTime); 

    ntresult = Ntdll.NtSuspendProcess(processHandle); 
    if (ntresult != Ntdll.NTSTATUS.STATUS_SUCCESS) 
    { 
     if (ntresult != Ntdll.NTSTATUS.STATUS_PROCESS_IS_TERMINATING) 
      LogSuspendResumeFailure("suspend", ntresult); 
     break; 
    } 
    Thread.Sleep(suspendTime); 

    if (++loop >= loopsBeforeCheckingStopEvent) 
    { 
     if (stopEvent.IsSet) break; 
     loop = 0; 
    } 
} 

如果服務接收到停止命令,它將設置ManualResetEventSlim事件。任何線程「限制」進程都會在1秒內看到它並跳出循環/返回。進程處理程序線程將等待所有這些線程返回,然後返回。此時,上面發佈的StartHandlerAndWaitForServiceStop()方法將返回,並且其他已經在這裏等待的線程返回。

絕大多數時候我停止了服務,它停止沒有任何問題。這與我是否有0或500個throttler線程在運行無關,而且無論服務運行時是否創建過任何線程。

然而,當我試圖阻止它(通過services.msc)時,它會掛起。昨天我設法在這個狀態下創建了一個完整的過程轉儲。我使用Process Explorer創建了轉儲。

轉儲文件顯示了一些我的線程被掛起:

0:010> ~ 
    0 Id: 1840.c34 Suspend: 0 Teb: 000007ff`fffdd000 Unfrozen 
    1 Id: 1840.548 Suspend: 0 Teb: 000007ff`fffdb000 Unfrozen 
    2 Id: 1840.9c0 Suspend: 0 Teb: 000007ff`fffd9000 Unfrozen 
    3 Id: 1840.1da8 Suspend: 0 Teb: 000007ff`fffd7000 Unfrozen 
    4 Id: 1840.b08 Suspend: 3 Teb: 000007ff`fffd5000 Unfrozen 
    5 Id: 1840.1b5c Suspend: 0 Teb: 000007ff`ffef6000 Unfrozen 
    6 Id: 1840.af0 Suspend: 2 Teb: 000007ff`ffef2000 Unfrozen 
    7 Id: 1840.c60 Suspend: 0 Teb: 000007ff`ffef0000 Unfrozen 
    8 Id: 1840.1d94 Suspend: 4 Teb: 000007ff`ffeee000 Unfrozen 
    9 Id: 1840.1cd8 Suspend: 4 Teb: 000007ff`ffeec000 Unfrozen 
. 10 Id: 1840.1c64 Suspend: 0 Teb: 000007ff`ffefa000 Unfrozen 
    11 Id: 1840.1dc8 Suspend: 0 Teb: 000007ff`fffd3000 Unfrozen 
    12 Id: 1840.8f4 Suspend: 0 Teb: 000007ff`ffefe000 Unfrozen 

這關係了什麼,我在Process Explorer中看到 - 這兩個過程我是「節流」的,一個是永久停權,另一個永久恢復。因此,那些調節器線程已被有效掛起,因爲他們不再工作。它們應該不會被暫停而停止,因爲我有錯誤處理纏繞它,任何異常都會導致這些線程記錄信息並返回。加上他們的調用堆棧顯示沒有錯誤。由於一些錯誤,他們並沒有永久睡眠,因爲兩次睡眠的睡眠時間分別爲22和78毫秒,並且在我試圖停止服務之前它工作正常。

所以我想了解這些線程可能會被暫停。我唯一的懷疑是GC,因爲它會在回收/壓縮內存時掛起線程。

我已經貼eestack的內容和〜* kb的位置:http://pastebin.com/rfQK0Ak8

我要提到我沒有符號,因爲我已經在我的時間重建了應用程序的次數創建了轉儲。但是,因爲它是.NET,我認爲它不是一個問題?

從eestack,這些都是我所相信的是「我」的主題:

  • 主題0:主服務線程,它仍然在ServiceBase.Run方法。
  • 線程4:這是我的記錄器線程。該線程將花費其大部分時間在阻塞隊列中等待。
  • 線程6:我的MainServiceThread線程,它正在等待要設置的事件。
  • 主題8 & 9:兩者都是「throttler」線程,執行我上面發佈的循環。
  • 線程10:此線程似乎正在執行OnStop()方法,因此處理service stop命令。

就是這樣,根據轉儲文件掛起線程4,6,8和9。所以除了主線程和處理OnStop()方法的線程之外,所有「我的」線程都被暫停。

現在我不太瞭解GC和調試.NET的東西,但線程10看起來不友好。從調用堆棧摘錄:

Thread 10 
Current frame: ntdll!NtWaitForMultipleObjects+0xa 
Child-SP   RetAddr   Caller, Callee 
000000001a83d670 000007fefdd41420 KERNELBASE!WaitForMultipleObjectsEx+0xe8, calling ntdll!NtWaitForMultipleObjects 
000000001a83d6a0 000007fef4dc3d7c clr!CExecutionEngine::ClrVirtualAlloc+0x3c, calling kernel32!VirtualAllocStub 
000000001a83d700 000007fefdd419bc KERNELBASE!WaitForMultipleObjectsEx+0x224, calling ntdll!RtlActivateActivationContextUnsafeFast 
000000001a83d710 000007fef4e9d3aa clr!WKS::gc_heap::grow_heap_segment+0xca, calling clr!StressLog::LogOn 
000000001a83d730 000007fef4e9cc98 clr!WKS::gc_heap::adjust_limit_clr+0xec, calling clr!memset 
000000001a83d740 000007fef4df398d clr!COMNumber::FormatInt32+0x8d, calling clr!LazyMachStateCaptureState 
000000001a83d750 000007fef4df398d clr!COMNumber::FormatInt32+0x8d, calling clr!LazyMachStateCaptureState 
000000001a83d770 00000000778a16d3 kernel32!WaitForMultipleObjectsExImplementation+0xb3, calling kernel32!WaitForMultipleObjectsEx 
000000001a83d7d0 000007fef4e9ce73 clr!WKS::gc_heap::allocate_small+0x158, calling clr!WKS::gc_heap::a_fit_segment_end_p 
000000001a83d800 000007fef4f8f8e1 clr!WaitForMultipleObjectsEx_SO_TOLERANT+0x91, calling kernel32!WaitForMultipleObjectsExImplementation 
000000001a83d830 000007fef4dfb798 clr!Thread::GetApartment+0x34, calling clr!GetThread 
000000001a83d860 000007fef4f8f6ed clr!Thread::GetFinalApartment+0x1a, calling clr!Thread::GetApartment 
000000001a83d890 000007fef4f8f6ba clr!Thread::DoAppropriateAptStateWait+0x56, calling clr!WaitForMultipleObjectsEx_SO_TOLERANT 
000000001a83d8d0 000007fef4f8f545 clr!Thread::DoAppropriateWaitWorker+0x1b1, calling clr!Thread::DoAppropriateAptStateWait 
000000001a83d990 000007fef4ecf167 clr!ObjectNative::Pulse+0x147, calling clr!HelperMethodFrameRestoreState 
000000001a83d9d0 000007fef4f8f63b clr!Thread::DoAppropriateWait+0x73, calling clr!Thread::DoAppropriateWaitWorker 
000000001a83da50 000007fef4f0ff6a clr!Thread::JoinEx+0xa6, calling clr!Thread::DoAppropriateWait 
000000001a83dac0 000007fef4defd90 clr!GCHolderBase<0,0,0,0>::EnterInternal+0x3c, calling clr!Thread::EnablePreemptiveGC 
000000001a83daf0 000007fef4f1039a clr!ThreadNative::DoJoin+0xd8, calling clr!Thread::JoinEx 
000000001a83db20 000007fef45f86f3 (MethodDesc 000007fef3cbe8d8 +0x1a3 System.Threading.SemaphoreSlim.Release(Int32)), calling 000007fef4dc31b0 (stub for System.Threading.Monitor.Exit(System.Object)) 
000000001a83db60 000007fef4dfb2a6 clr!FrameWithCookie<HelperMethodFrame_1OBJ>::FrameWithCookie<HelperMethodFrame_1OBJ>+0x36, calling clr!GetThread 
000000001a83db90 000007fef4f1024d clr!ThreadNative::Join+0xfd, calling clr!ThreadNative::DoJoin 
000000001a83dc40 000007ff001723f5 (MethodDesc 000007ff001612c0 +0x85 MSPO.Logging.MessageQueue.EnqueueMessage(System.String)), calling (MethodDesc 000007fef30fde88 +0 System.Collections.Concurrent.BlockingCollection`1[[System.__Canon, mscorlib]].TryAddWithNoTimeValidation(System.__Canon, Int32, System.Threading.CancellationToken)) 
000000001a83dcf0 000007ff001720e9 (MethodDesc 000007ff00044bb0 +0xc9 ProcessThrottler.Logging.Logger.Log(LogLevel, System.String)), calling (MethodDesc 000007ff00161178 +0 MSPO.Logging.MessageFormatter.QueueFormattedOutput(System.String, System.String)) 
000000001a83dd10 000007fef4f101aa clr!ThreadNative::Join+0x5a, calling clr!LazyMachStateCaptureState 
000000001a83dd30 000007ff0018000b (MethodDesc 000007ff00163e10 +0x3b ProcessThrottler.Service.MainServiceThread.WaitForThreadToReturn()), calling 000007fef4f10150 (stub for System.Threading.Thread.JoinInternal()) 
000000001a83dd60 000007ff0017ff44 (MethodDesc 000007ff00049f30 +0xc4 ProcessThrottler.Service.ProcessThrottlerService.OnStop()), calling 000007ff0004d278 (stub for ProcessThrottler.Service.MainServiceThread.WaitForThreadToReturn()) 
000000001a83dda0 000007fef63fcefb (MethodDesc 000007fef63d65e0 +0xbb System.ServiceProcess.ServiceBase.DeferredStop()) 

我可以發佈更多的代碼顯示了每個在我的職務是幹什麼的,但我真的不認爲這是我的代碼死鎖,因爲線程不會成爲懸浮在案件。所以我正在查看上面的調用堆棧,並在我告訴它將一個字符串記錄到一個隊列後看到它正在執行一些GC的東西。但是沒有一個GC的東西看起來不可靠,至少沒有與我在http://blogs.msdn.com/b/tess/archive/2008/02/11/hang-caused-by-gc-xml-deadlock.aspx中看到的相比較。我有一個配置文件來告訴它使用gcServer,但我幾乎可以肯定它沒有使用該設置,因爲在我之前的測試中GCSettings.IsServerGC總是返回錯誤。

所以......有沒有人有任何建議,爲什麼我的線程被暫停?

這是我的OpenProcess方法BTW它獲取的句柄進程暫停/恢復,響應漢斯的評論:

private void GetProcessHandle(CurrentProcessDetails process) 
{ 
    IntPtr handle = Kernel32.OpenProcess(
     process.Settings.RequiredProcessAccessRights, 
     false, 
     (uint)process.ID 
     ); 
    if (handle == IntPtr.Zero) 
     throw new Win32ExceptionWrapper(
      string.Format("Failed to open process {0} {1}", 
      process.Settings.ProcessNameWithExt, process.IDString)); 
    process.Handle = handle; 
} 
+0

你是黑客NtSuspendProcess()和覺得奇怪,你的整個業務掛起。這只是我看到的共同點?我的水晶球說你在OpenProcess()上的錯誤檢查是不夠的,你最終會得到一個NULL處理句柄。切腹,你會暫停自己:) –

+0

@HansPassant LOL是,但我敢肯定,這一切都巧合。我很確定我正確使用OpenProcess(),並將結果與​​IntPtr.Zero進行比較。另外,如果失敗則有件事情我不暫停/恢復它會失敗,就像從GetModuleFileNameEx獲得的文件名比較我所期望的,等 – eurotrash

回答

1

我發現原因。它與我的代碼無關。這是Process Explorer中的一個錯誤。

我的程序是針對.NET 4.0編寫的。如果我使用Process Explorer查看我的任何線程的調用堆棧,Process Explorer將暫停該線程並且不會恢復它。它應該做的是在獲取調用堆棧的同時掛起線程,然後立即恢復。但它不會恢復線程 - 不管我的託管線程如何。

我可以用這個非常簡單的代碼複製它:

using System; 

namespace Test 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      for (int i = 0; i < int.MaxValue; i++) 
      { 
       Console.WriteLine(i.ToString()); 
      } 
     } 
    } 
} 

如果我編譯的目標.NET 4.0或更高版本,運行和使用Process Explorer中打開線程運行的循環中,線程將被暫停。恢復按鈕將變爲可用,我可以點擊它恢復線程。多次打開線程會導致多次掛起;我通過使用Windbg查看線程的暫停計數來確認這一點。

如果我編譯它到目標低於4.0(2.0試過和3.5),線程我打開進程瀏覽器版本不保持暫停狀態。

+1

和更新之前:我登錄這個馬克Russinovich和他親切地固定它, MS網站上即將推出固定版本。 – eurotrash