How can I find the reason for a hung finalizer queue?
Asked Answered
B

1

9

I have an application that experiences a slow memory leak from the word go.

Using ANTS Memory Profiler I can see that all of the leaked memory is being held by the finalizer queue's GC root.

I suspect what may have happened is that the finalizer is deadlocked waiting on a lock to become available.

None of our classes implement explicit finalizers, we avoid them as a rule, this makes me think the lock might related to a system or library class.

I've used SOS.dll to take a look at the contents of the finalizer queue and if I am interpreting it correctly then it reports the first item to be an instance System.Threading.Thread However I am unsure if the head of the queue actually represents the currently being disposed object or the next object to be disposed.

  • Are there any tricks I can use to find out what is being finalized?
  • Is there a way I can find out what lock the finalizer thread is waiting on?
  • Can any extra debugging be turned on to trace the finalizer thread's actions?
  • What else can I look at?

Update

The finalizer thread's stack appears as follows:

ntdll.dll!_ZwWaitForSingleObject@12()  + 0x15 bytes  
ntdll.dll!_ZwWaitForSingleObject@12()  + 0x15 bytes  
user32.dll!_NtUserPostMessage@16()  + 0x15 bytes     

kernel32.dll!_WaitForSingleObjectExImplementation@12()  + 0x43 bytes     
kernel32.dll!_WaitForSingleObject@8()  + 0x12 bytes  
ole32.dll!GetToSTA()  + 0x72 bytes   

ole32.dll!CRpcChannelBuffer::SwitchAptAndDispatchCall()  - 0x1939 bytes  
ole32.dll!CRpcChannelBuffer::SendReceive2()  + 0xa6 bytes    
ole32.dll!CAptRpcChnl::SendReceive()  + 0x5b7 bytes  
ole32.dll!CCtxComChnl::SendReceive()  - 0x14b97 bytes    
ole32.dll!NdrExtpProxySendReceive()  + 0x43 bytes    
rpcrt4.dll!@NdrpProxySendReceive@4()  + 0xe bytes    
rpcrt4.dll!_NdrClientCall2()  + 0x144 bytes  
ole32.dll!_ObjectStublessClient@8()  + 0x7a bytes    
ole32.dll!_ObjectStubless@0()  + 0xf bytes   

ole32.dll!CObjectContext::InternalContextCallback()  - 0x511f bytes  
ole32.dll!CObjectContext::ContextCallback()  + 0x8f bytes    
clr.dll!CtxEntry::EnterContext()  + 0x119 bytes  

clr.dll!RCWCleanupList::ReleaseRCWListInCorrectCtx()  + 0x2bb bytes  

clr.dll!RCWCleanupList::CleanupAllWrappers()  - 0x20fb0 bytes    
clr.dll!SyncBlockCache::CleanupSyncBlocks()  + 0x1ec6 bytes  
clr.dll!Thread::DoExtraWorkForFinalizer()  + 0x411b5 bytes   

clr.dll!WKS::GCHeap::FinalizerThreadWorker()  + 0x8b bytes   
clr.dll!Thread::DoExtraWorkForFinalizer()  + 0xb6e76 bytes   
clr.dll!Thread::ShouldChangeAbortToUnload()  - 0x5f8 bytes   
clr.dll!Thread::ShouldChangeAbortToUnload()  - 0x53d bytes   
clr.dll!ManagedThreadBase_NoADTransition()  + 0x35 bytes     
clr.dll!ManagedThreadBase::FinalizerBase()  + 0xf bytes  
clr.dll!WKS::GCHeap::FinalizerThreadStart()  + 0xfb bytes    
clr.dll!Thread::intermediateThreadProc()  + 0x48 bytes   
kernel32.dll!@BaseThreadInitThunk@12()  + 0x12 bytes     
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes    
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes     
Basket answered 9/10, 2011 at 16:14 Comment(6)
Why are you avoiding finalizers as a rule? Proper implementation of the disposable pattern requires finalizer.Passion
@Passion - A note on MSDN re. implementing IDisposable says "Leave out the finalizer altogether if a class doesn't own unmanaged resources itself"Basket
@Passion - chillitom is correct. Quoting Framework Design Guidelines: "You really don’t want to write a finalizer if you can help it."Diatropism
@Passion I can't think of many situations that require a classical finalizer. You usually use a SafeHandle for unmanaged resources, and managed resources don't need a finalizer.Timbuktu
To be absolutely clear then, you are avoiding destructors . They are part of the implicit finaliztion pattern. Dispose() is the explicit form.Excitable
@HenkHolterman - yes, we make use of IDisposable frequently but haven't yet had need to implement destructors on our classes as we don't own any unmanaged resources.Basket
A
11

Looks to me you are having a problem with a COM server. The call stack shows it is trying to make the IUnknown::Release() call on a single-threaded COM object. The ReleaseRCWListInCorrectCtx() call sets it off, the _NtUserPostMessage@16() is the call that marshals the request to the STA that owns the COM object.

The typical cause is creating COM objects and not pumping a message loop. A hard requirement for STA threads. You avoid it by creating them on the main UI thread and never blocking it.

Artificial answered 9/10, 2011 at 17:49 Comment(2)
spot on, the program's main method was marked [STAThread] to accommodate a GUI mode but would normally run as a windows service. Recently a COM component must have been introduced, likely candidate a Timer. As you predicted with no UI thread the service's finalizer just hangs.Basket
FYI, If your main thread is doing something like Console.ReadLine, but you want it to be pumping messages, you can spin up a new Thread to do the readline, and then use .Join on it from your Main thread. Internally Thread.Join when called on an STA thread will pump COM messages, which should enable your objects to use it.Pottery

© 2022 - 2024 — McMap. All rights reserved.