2011-10-21 66 views
0

我的.NET Windows服务存在一个主要问题。它运行在配置完全不同的多台服务器上。该服务似乎易受某些服务器上的崩溃影响,但在其他服务器上保持稳定。最近引入了不稳定性,但迄今为止条件未知。我们有运行Windows 2003/Windows 2003 R2/Windows 2008的服务器。它们中的大多数都已完全更新。.NET Windows服务在调度Windows消息时崩溃

我们试着针对不同的目标框架版本(2.0/3.5/4.0)构建服务,但它没有什么区别。对于每个版本的框架,具有不稳定服务的机器都不稳定。我试过修复.NET框架,但这也没有什么不同。据我所知,整个服务及其依赖关系都在托管代码中。

我也尝试在命令行版本中运行服务器代码。这似乎运行稳定。我们现在使用这个作为解决方法。但是,该问题与用户帐户无关。该服务通常作为“本地服务”运行。我试图让它在本地管理员帐户下运行,这是我用来运行命令行版本的帐户。但服务仍然不稳定。

到目前为止,我已经能够在其中一台服务器上创建可重现的情况: - 在服务器上启动服务。 - 作为域用户登录到同一台服务器上的新RDP会话中。 - 启动我们的客户端软件,该软件通过该会话中的TCP远程访问来访问我们的服务。 - 关闭客户端和会话。 - 在服务器上与域用户打开一个新的RDP会话。 - 服务即时崩溃!

请注意,服务在域用户登录到新的RDP会话时崩溃。当时我们的客户端软件尚未在该会话中运行。如果我在第一个会话中未打开客户端并使用TCP远程访问服务,则该服务在第二次登录期间不会崩溃。如果我以本地管理员身份打开会话,则该服务也不会崩溃。

我已经能够将本机调试器(OllyDbg)附加到崩溃服务。尝试在地址0x4bcdcee9处执行时,它会因访问冲突而崩溃。该地址在所有服务器和配置上都是相同的(我在eventlog中每次都看到该地址)。我已经看过了崩溃线程的堆栈。该线程似乎是在崩溃之前创建的。首先它会尝试加载Ole32.dll。它运行从OLE32一些代码,然后我看到被称为以下功能:

  • User32.SetTimer
  • User32.GetMessageW
  • User32.TranslateMessage
  • User32.DispatchMessageW

崩溃在DispatchMessageW中。我可以在堆栈上看到DispatchMessageW的* MSG参数。它看起来像这样被传递:

  • 的hWnd = 0x00090082
  • 消息= 0X0000001E
  • 的wParam = 00000000
  • 的lParam = 00000000

我试过间谍++。但它似乎没有检测到Windows服务中的任何hWnd。

因此,该服务收到此消息,试图解析和调度它,每次最终调用0x4bc4cee9,这是未映射的内存,并崩溃。

编辑:根据汉斯的建议,我调查了系统事件。我调试了该服务。我为服务可执行文件添加了额外的服务,以便我可以启动帮助程序服务,然后附加调试程序,然后启动真实服务。这样我就能够调试服务的OnStart。我在SetWindowsHookA,SetWindowsHookW,SetWindowsHookExA和SetWindowsHookExW上放置了断点,但是没有一个被击中!

编辑2:我检查了我所有的笔记,发现我跳到错误的结论,因为我在我的笔记有一个错字:-S反正崩溃的地址是0x4bc4cee9。在执行的某个时候,msado15.dll被加载到那里。我可以看到,当客户端与服务器断开连接时,调试器中有2个受管异常。不久之后,我看到一个WM_Timer消息,它由调度程序处理并调用CoFreeUnusedLibraries()。这导致卸载msado15.dll。我在反汇编程序中打开了msado15.dll并加载了来自Microsoft的符号。该DLL是Microsoft数据访问组件(MDAC)2.8 SP1的一部分。该版本是2.82.4795.0,表示它是2011年1月发布的最新版本。ADOConnection和ADORecordset有Advise()和Unadvise()函数。 Advise()调用InitAsyncEvents()并调用RegisterClassEx()。传递给RegisterClassEx()的WndProc是FireEventOnMainThread(),它位于0x4bc4cee9!我可以看到那里的功能!应该发生的是,当对象被处置时,应调用Unadvise()和DestroyAsyncEvents()和UnregisterClassEx()。但不知何故,这并未发生。 DLL可以在取消注册类之前被卸载。这导致下一个事件发生崩溃。这可能以某种方式与2个管理的例外相关。我会进一步调查。

堆栈跟踪:http://pastebin.com/dsSjMe4Y

登录:http://pastebin.com/qD2MXvHd

我真的很感激在这个问题上提供一些指导。比如,哪个流程可以发送此消息?而这种服务怎么可能将这个完全错误的发送出去呢?如何避免这种情况?

谢谢 希刺克厉夫

+0

非常长的问题,大部分信息都是有用的。在崩溃和异常信息时有一个调用堆栈会很有用。确保你有符号加载正确。 –

+0

WM_TIMECHANGE是一个广播消息。去所有的顶级窗口。听起来就像你曾经有一个,然后与窗口过程的DLL被卸载,没有很好地关闭窗口。 .NET中的SystemEvents btw,需要明确注销的静态事件。 –

+0

@SevaTitov我加了一个堆栈跟踪。所有其他信息似乎与我有关。 – Heathcliff

回答

0

我发现这个问题。我花了将近8天的时间来制定一个解决方案!

所有ADODB版本高达6.0都有一个严重的错误! ADODB 2.8是MDAC 2.8(用于XP和Win2003)的一部分,ADODB 6.0是Vista/Win2008的一部分,ADODB 6.1是Win7/Win2008R2的一部分。核心DLL是msado15.dll。当一个Connection或Recordset类被实例化时,它被注册到RegisterClass(),并且它有一个名为__FireEventOnMainThread()的WndProc。在所有COM对象再次被处理后,引用计数被设置为0.当调用Ole32!CoFreeUnusedLibraries()时,它将调用所有COM DLL的DllCanUnloadNow()。 DllCanUnloadNow()检查引用计数,当它为0时,它将返回0,表示它可以卸载。在ADODB 6.1(仅针对Win7和Win2008R2发布)中,Microsoft在DllCanUnloadNow()中实施了修复。他们检查AsyncEventsWnd,如果它仍然存在,它们不会卸载DLL。但是COM对象处置中仍存在真正的bug。引用计数减少,但由于某种原因UnregisterClass()未被调用。当DLL被卸载并且广播事件被发送时,应用程序将运行到访问冲突,因为WndProc不在内存中了。崩溃!在服务的情况下,Ole32!CDllHost被实例化(不知道在哪里)。该类使用TimerProc STAHostTimerProc()启动一个定时器,每300秒触发一次。 STAHostTimerProc()调用CoFreeUnusedLibraries()。有许多不同的广播消息。例如,当一个新的用户会话在终端服务器上启动时,它将广播WM_TIMECHANGE。因此,在应用程序创建ADODB.Connection或ADODB.Recordset并创建一个Ole32!CDllHost,然后部署所有COM对象,然后等待计时器卸载msado15.dll,然后再在Windows到高达Vista/Win2008的计算机上等待广播消息,该应用程序将崩溃!

微软在MDAC 6.1中解决这个问题很可怕,但他们并没有为早期版本发布修复程序。所有较旧的操作系统都受影响。

作为解决方法,我们将通过创建静态ADODB.Connection对象来避免ADO COM对象的引用计数变为0。