我收到了来自虚拟化环境中的故障的多个转储,其中应用程序只是挂断了说(没有响应)的 Windows。虚拟化环境的供应商认为这是一个应用程序问题,尽管客户无法在实际客户端机器上重现挂起但几乎可以在虚拟化环境中按需重复它。
小型转储文件显示相同的挂起;GUI 线程以 NtUserPeekMessage(通过 winforms Application.DoEvents)作为堆栈顶部停止。
应用程序代码如下所示(伪代码):
while MsgWaitForMultipleObjects(1, new[]{eventhandle}, false, INFINITY, 0x4FF) != 0
Application.DoEvents() ;; HANG HERE
栈顶:
win32u.dll!NtUserPeekMessage() + 20 bytes
user32.dll!PeekMessageW() + 254 bytes
System.Windows.Forms.Ni.dll!(no symbol)
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.WIndows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessagLoop()
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner()
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop()
[Function given above]
Application.DoEvents 实际上并没有出现在堆栈上;看起来它的尾巴被叫出来了。
该应用程序是用 99.9% 的托管代码编写的,十多年来没有出现堆损坏错误。
我认为这永远不会发生是对的吗?
21 年 6 月 29 日的新信息(可能会使现有答案无效):
在没有 WS_CHILD 标志的窗口上使用 SetParent() 调用时,实际上并不附加输入队列。我能够在我自己的机器上观察到 AttachThreadInput() 在尝试删除不存在的附件时实际上会产生错误;但是在现场没有观察到错误,这意味着某些东西正在附加不应该的输入队列。
根据包括非常值得信赖的 Raymond Chen 的 Old New Thing 在内的多个在线消息来源,当多个线程附加到同一个输入队列时,所有消息队列功能都会成为阻塞同步操作。
在这里展示:
该开发人员进行了调试工作,发现此确切堆栈跟踪上的挂起是由附加的输入队列引起的。
我推测PeekMessage
需要为共享输入队列获取互斥锁......当前由另一个线程持有的互斥锁。同步对象的争用会导致响应速度很差,直至彻底死锁。要进一步调试,你必须查看共享同一输入队列的其他线程正在做什么。
直接跟进;另一个 SetParent 是否删除附加的输入队列?如果为真,这将缩短搜索空间。
@Joshua:我无法想象它会,因为在相同的两个线程之间可能存在多个父/子或所有者/拥有的关系。但是,可以使用显式调用
AttachThreadQueue
来中断附件关系。所以你确实回答了我的问题。我回去用 AttachThreadQueue 手动分离线程,现场仍然发生死锁。那好吧。
@Joshua:您是否检查了返回值
AttachThreadQueue
以确保分离成功?您对确定所有与您的线程相关联的线程有多大信心?此外,我不确定在建立关系后导致队列连接的机制——该机制可能是“自我修复”并抵制分离的尝试。我在 SetParent(handle, 0); 之后添加了对 AttachThreadQueue 的调用以分离线程 但我有点怀疑真正的问题在于托管环境将第 3 方 dll 加载到我们的进程中。