首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >调试CLR挂起

调试CLR挂起
EN

Stack Overflow用户
提问于 2019-07-19 23:12:59
回答 1查看 485关注 0票数 12

我已经上传了一个WinDBG会话的日志,我将参考:https://pastebin.com/TvYD9500

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

代码语言:javascript
复制
using System;
using System.Data.Odbc;
using System.Threading;

namespace ConnectionPoolingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            String connString = "DSN=DotNetUltraLightDSII";
            using (OdbcConnection connnection = new OdbcConnection(connString))
            {
                connnection.Open();
                connnection.Close();
            }
        }
    }
}

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

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

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

代码语言:javascript
复制
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

我找到了一些UTSemReadWrite类的源代码(但它似乎与我实际运行的有点不同):https://github.com/dotnet/coreclr/blob/616fea550548af750b575f3c304d1a9b4b6ef9a6/src/utilcode/utsem.cpp

将一个断点放入UTSemReadWrite::LockW区,我能够调试挂起的最后一个调用,发现原因是m_dwFlag (用于原子性的)不是零,所以它会等待一个事件(当它释放它时,拥有一个线程来向它发出信号),并且它通过调用UTSemReadW区::GetWriteWaiterEvent来完成这个任务,但是这个调用会创建事件(并且在这里没有其他线程.),然后等待它。砰,僵局。

通过程序集调试,我可以推断出m_dwFlag在对象中偏移了4个字节,并将一个断点放入UTSemReadW区::UTSemReadW区,我能够获得挂起的UTSemReadWrite实例的地址,并在m_dwFlag上放置一个数据断点。

这样做,我可以看到,一个线程函数comsvcs!PingThread调用comsvcs!UTSemReadW区::LockRead(并可能得到了锁),然后在调用comsvcs之前被杀死!UTSemReadW区::UnlockRead。我以前见过这样的情况,它是由一个未处理的SEH异常杀死PingThread引起的,但是这个应用程序防止使用setunhandledexceptionfilter()崩溃,所以我想可能有一些异常正在杀死线程,但原来是CLR本身!

代码语言:javascript
复制
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

(这就引出了一个问题;那么ntdll!ZwTerminateProcess实际上不会终止这个进程?因为它显然会被返回并调用atexit处理程序..。我想这是一个不同的函数,同名?https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntddk/nf-ntddk-zwterminateprocess)

所以,我的问题是,我是否正确地解释了调试器向我展示了什么?这实际上是CLR中的一个bug吗?CLR不应该首先优雅地结束线程吗?

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

发送给我们的复制器驱动程序中的后台线程基本上是

代码语言:javascript
复制
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驱动程序管理器调用SQLFreeHandle卸载驱动程序(从windows线程池中的某个线程或驱动程序管理器本身拥有的某个线程(不确定))时,这将导致驱动程序终止最后一个前景线程。根据我对从那篇文章中获得的CLR关闭过程的理解,CLR将在它获得实际返回的机会之前终止调用SQLFreeHandle的线程,这是预期的行为。

但是该线程似乎持有UTSemReadWrite锁,因此稍后在atexit处理期间它将陷入死锁。

如果这实际上是CLR的错误,那么我关于如何解决这个问题的唯一想法是在对.NET的最后调用中启动另一个(前台) SQLFreeHandle线程,这个线程在超时后会自动结束(希望足够长的时间让SQLFreeHandle线程释放它持有的任何锁),延迟CLR的关闭。如果这样会阻碍应用程序的关闭,那就不太理想了…

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

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-11-06 00:39:11

我已经和微软的支持部门谈过了,他们已经确认这是comsvcs组件的一个问题,他们可能会在windows的未来版本中修复这个问题。如果他们告诉我他们已经修好了我会更新的。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57120511

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档