C# 调试CLR挂起

C# 调试CLR挂起,c#,odbc,clr,deadlock,C#,Odbc,Clr,Deadlock,我上传了WinDBG会话的日志,我将参考: 因此,我正在调试一个客户报告的挂起。复制机是一个小型C#程序: 我们出售一个框架来构建ODBC驱动程序,客户正在测试一个使用该框架构建的ODBC驱动程序。一个可能相关的细节是,他们正在使用一个允许用C#编写其业务逻辑的组件,该组件是用C++/CLI编写的,以在我们的本机代码和客户代码之间架起桥梁(因此,ODBC驱动程序DLL是一个混合模式DLL,它向ODBC驱动程序管理器公开了一个C接口) (如果需要,我也可以上传驱动程序二进制文件。) 此复制程序(必

我上传了WinDBG会话的日志,我将参考:

因此,我正在调试一个客户报告的挂起。复制机是一个小型C#程序:

我们出售一个框架来构建ODBC驱动程序,客户正在测试一个使用该框架构建的ODBC驱动程序。一个可能相关的细节是,他们正在使用一个允许用C#编写其业务逻辑的组件,该组件是用C++/CLI编写的,以在我们的本机代码和客户代码之间架起桥梁(因此,ODBC驱动程序DLL是一个混合模式DLL,它向ODBC驱动程序管理器公开了一个C接口)

(如果需要,我也可以上传驱动程序二进制文件。)

此复制程序(必须在使用的DSN上启用连接池的情况下运行)中发生的情况是,该进程最终挂起一个带有堆栈的线程,堆栈如下所示:

RetAddr           : Args to Child                                                           : Call Site
000007fe`fcea10dc : 00000000`00470000 00000000`770d0290 00000000`00000000 00000000`009ae8e0 : ntdll!ZwWaitForSingleObject+0xa
000007fe`f0298407 : 00000000`00999a98 00000000`770d5972 00000000`00000000 00000000`00000250 : KERNELBASE!WaitForSingleObjectEx+0x79
000007fe`f0294d04 : 00000000`00999a98 00000000`00a870e0 00000000`00999a68 00000000`00991a10 : comsvcs!UTSemReadWrite::LockWrite+0x90
000007fe`f0294ca8 : 00000000`00999a68 00000000`00999a98 00000000`00999a20 00000000`7717ba58 : comsvcs!CDispenserManager::~CDispenserManager+0x2c
000007fe`f02932a8 : 00000000`00999a20 00000000`00a871c0 00000000`77182e70 00000000`7717ba58 : comsvcs!ATL::CComObjectCached<ATL::CComClassFactorySingleton<CDispenserManager> >::`scalar deleting destructor'+0x68
000007fe`f0293a00 : 000007fe`f0290000 00000000`00000001 00000000`00000001 00000000`00a87198 : comsvcs!ATL::CComObjectCached<ATL::CComClassFactorySingleton<CDispenserManager> >::Release+0x20
000007fe`f02949aa : 00000000`00000000 00000000`00a87188 00000000`00992c20 00000000`00992c30 : comsvcs!ATL::CComModule::Term+0x35
000007fe`f0293543 : 00000000`00000000 00000000`00a87190 00000000`00000001 00000000`00a87278 : comsvcs!`dynamic atexit destructor for 'g_ModuleWrapper''+0x22
000007fe`f029355b : 00000000`00000001 00000000`00000000 000007fe`f0290000 00000000`76f515aa : comsvcs!CRT_INIT+0x96
00000000`7708778b : 000007fe`f0290000 00000000`00000000 00000000`00000001 00000000`7717ba58 : comsvcs!__DllMainCRTStartup+0x187
00000000`7708c1e0 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrShutdownProcess+0x1db
000007fe`efb4ee58 : 00000000`00486b10 00000000`00000001 00000000`00482460 00000000`00000000 : ntdll!RtlExitUserProcess+0x90
000007fe`efb4efd4 : 00000000`00000000 000007fe`efb4efc0 ffffffff`00000000 00000000`004868a0 : mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes+0x287
000007fe`eefa9535 : 00000000`0042f4b8 000007fe`ef53d6c0 00000000`0042f488 00000000`004868a0 : mscoreei!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x14
000007fe`eefa9495 : 00000000`00000000 00000000`0042f488 00000000`00000000 00000000`00000000 : clr!EEPolicy::ExitProcessViaShim+0x95
000007fe`eee83336 : 00000000`00000006 00000000`0042f870 00000000`00000000 00000000`00000000 : clr!SafeExitProcess+0x9d
000007fe`eee61c51 : 00000000`01000000 00000000`0042f870 00000000`00000000 00000000`00000000 : clr!HandleExitProcessHelper+0x3e
000007fe`eee62034 : ffffffff`ffffffff 000007fe`eee62020 00000000`00000000 00000000`00000000 : clr!_CorExeMainInternal+0x101
000007fe`efb47b2d : 00000000`00000000 00000000`00000091 00000000`00000000 00000000`0042f7c8 : clr!CorExeMain+0x14
000007fe`efbe5b21 : 00000000`00000000 000007fe`eee62020 00000000`00000000 00000000`00000000 : mscoreei!CorExeMain+0x112
00000000`76f4556d : 000007fe`efb40000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!CorExeMain_Exported+0x57
00000000`770a385d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0xd
00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x1d
(这带来了一个问题;那么ntdll!ZwTerminateProcess实际上并没有终止进程?因为它显然是返回的,并调用atexit处理程序……我想这是一个同名的不同函数?)

所以,我的问题是,我是否正确解释了调试器显示的内容?这实际上是CLR中的错误吗?CLR不应该首先优雅地结束线程吗

客户注意到,如果他在驱动程序中创建一个线程作为后台线程,则不会发生挂起,这很奇怪,因为卸载驱动程序时,即使是前台线程也应该很快停止(通过终结器在驱动程序的句柄上调用SQLFreeHandle()),我猜,除非终结器线程被什么东西拖慢了

发送给我们的复制机驱动程序中的背景线程基本上是

public Driver()
{
     this.tokenSource= new CancellationTokenSource();
            this.token = this.tokenSource.Token;
            this.worker= new Thread(this.DoWork) { IsBackground = false };
            this.worker.Start();
}

public override void Dispose()
        {
            this.tokenSource.Cancel();
            this.worker.Join();
            this.tokenSource.Dispose();

            base.Dispose();
        }

private void DoWork() {
            while (!this.token.WaitHandle.WaitOne(200)) {
                log(this.Log, "Doing some work....");
            }
            log(this.Log, "Done with work.");
        }
并正确调用Dispose(),然后退出

我不知道下一步该怎么做

编辑:阅读后,我觉得这是CLR的一个bug/怪癖。在我的场景中,最后一个前台.NET线程位于ODBC驱动程序中。当ODBC驱动程序管理器调用
sqlfreehold
卸载驱动程序时(从windows线程池中的某个线程或由驱动程序管理器本身拥有的线程卸载,不确定),这会导致驱动程序终止最后一个前台线程。根据我从那篇文章中获得的对CLR关闭过程的理解,CLR最终将在有机会实际返回之前终止调用
sqlfreehold
的线程,这是预期的行为

但该线程似乎持有UTSemReadWrite锁,所以稍后在atexit处理期间,它将死锁

如果这实际上是CLR的错误,我唯一的想法就是在最后一次调用
sqlfreehold
时启动另一个(前台).NET线程,该线程将在超时后(希望足够长的时间让
sqlfreehold
线程释放它持有的所有锁),以延迟CLR关闭。如果这最终导致应用程序关闭,则不太理想


编辑:事实上,即使这种想法也不起作用,因为这意味着ODBC驱动程序管理器可能会在线程执行代码时卸载驱动程序,从而导致崩溃。

我与microsoft支持人员进行了交谈,他们确认这是comsvcs组件的问题,他们可能会在未来版本的windows中修复。如果他们告诉我他们已经修复了,我会更新它。

UTSemReadWrite堆栈中的内容与.NET无关,它是COMSVCS(“COM/COM+服务”)的一部分。VIPER是MicrosoftTransactionServer的代码名,COMSVCS是其中的一部分。NET开发人员可能只是出于他们自己的目的而吸收了这个类的源代码,这就是为什么您在.NET源代码中看到它。在没有可复制项目的情况下很难提供帮助。堆栈看起来像很多。挂起可能来自许多问题,包括任何会扰乱进程的本机dll。你的家人呢?在过去的几天里,我读了很多遍,这很难。如果你需要一只手,我们需要一个或垃圾场和私人标志。你已经找到了很多东西。由于它是一个COM+组件,您可以添加一个关键的终结器来强制释放拥有连接池的COM对象,然后在进程分离过程中销毁全局变量,显然其他线程丢失了?有没有办法跳过销毁连接池的过程?我们没有任何COM(或COM+,但确实确定其中的区别)代码。COM内容要么来自ODBC驱动程序管理器,要么来自CLR。从某种意义上说,.Net ODBC实现使用的ODBC环境句柄将“拥有”连接池,但它存储在一个静态变量中,因此在最后一个前台线程死亡之前,它不会最终确定,我认为这太晚了
RetAddr           : Args to Child                                                           : Call Site
00000000`7708c198 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!ZwTerminateProcess+0xa
000007fe`efb4ee58 : 00000000`00486b10 00000000`00000001 00000000`00482460 00000000`00000000 : ntdll!RtlExitUserProcess+0x48
000007fe`efb4efd4 : 00000000`00000000 000007fe`efb4efc0 ffffffff`00000000 00000000`004868a0 : mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes+0x287
000007fe`eefa9535 : 00000000`0042f4b8 000007fe`ef53d6c0 00000000`0042f488 00000000`004868a0 : mscoreei!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x14
000007fe`eefa9495 : 00000000`00000000 00000000`0042f488 00000000`00000000 00000000`00000000 : clr!EEPolicy::ExitProcessViaShim+0x95
000007fe`eee83336 : 00000000`00000006 00000000`0042f870 00000000`00000000 00000000`00000000 : clr!SafeExitProcess+0x9d
000007fe`eee61c51 : 00000000`01000000 00000000`0042f870 00000000`00000000 00000000`00000000 : clr!HandleExitProcessHelper+0x3e
000007fe`eee62034 : ffffffff`ffffffff 000007fe`eee62020 00000000`00000000 00000000`00000000 : clr!_CorExeMainInternal+0x101
000007fe`efb47b2d : 00000000`00000000 00000000`00000091 00000000`00000000 00000000`0042f7c8 : clr!CorExeMain+0x14
000007fe`efbe5b21 : 00000000`00000000 000007fe`eee62020 00000000`00000000 00000000`00000000 : mscoreei!CorExeMain+0x112
00000000`76f4556d : 000007fe`efb40000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!CorExeMain_Exported+0x57
00000000`770a385d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0xd
00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x1d
public Driver()
{
     this.tokenSource= new CancellationTokenSource();
            this.token = this.tokenSource.Token;
            this.worker= new Thread(this.DoWork) { IsBackground = false };
            this.worker.Start();
}

public override void Dispose()
        {
            this.tokenSource.Cancel();
            this.worker.Join();
            this.tokenSource.Dispose();

            base.Dispose();
        }

private void DoWork() {
            while (!this.token.WaitHandle.WaitOne(200)) {
                log(this.Log, "Doing some work....");
            }
            log(this.Log, "Done with work.");
        }