2015-11-03 126 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