DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • Logging Best Practices Revisited [Video]
  • Merge GraphQL Schemas Using Apollo Server and Koa
  • Why You Should Consider Using React Router V6: An Overview of Changes
  • MLOps: Definition, Importance, and Implementation

Trending

  • Logging Best Practices Revisited [Video]
  • Merge GraphQL Schemas Using Apollo Server and Koa
  • Why You Should Consider Using React Router V6: An Overview of Changes
  • MLOps: Definition, Importance, and Implementation
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. STA Objects and the Finalizer Thread: Tale of a Deadlock

STA Objects and the Finalizer Thread: Tale of a Deadlock

Sasha Goldshtein user avatar by
Sasha Goldshtein
·
Jun. 30, 10 · News
Like (0)
Save
Tweet
Share
6.27K Views

Join the DZone community and get the full member experience.

Join For Free

Here’s a non-trivial deadlock that manifests from using a non-pumping wait API and a finalizer. It is another example of why finalizers are a dangerous cleanup mechanism and why you should avoid them at all costs.

Let’s say that you have an STA COM object called NativeComObject that your managed application is using, and you wrap the COM object with a class called FinalizableResource. This latter class has a finalizer that cleans up resources associated with the COM object by calling a cleanup method on it, or even by deterministically releasing the object with Marshal.FinalReleaseComObject.

Note that the object is STA, meaning that if you created it in an application thread, the finalizer thread won’t be able to access the object directly—it will have to send a Windows message to the object’s STA thread and use it to call the method. This completes the picture of a possible deadlock—if the STA thread waits for a resource acquired by the finalizer thread, and the finalizer thread performs a COM method call into the STA, the two threads are blocked waiting for one another.

Fortunately, most .NET synchronization APIs use the moral equivalent of MsgWaitForMultipleObjects (or CoWaitForMultipleHandles), which are APIs that perform message pumping while waiting. However, if you resort to native synchronization APIs (for example, if your STA thread is now in unmanaged code which uses a wait API), you might encounter this deadlock.

This is some sample code that reproduces the problem (assuming, of course, that you have an STA COM object called SimpleComObject on your hands).

namespace ManagedApp
{
class FinalizableResource
{
ISimpleComObject _obj;
EventWaitHandle _signalWhenDone;

public FinalizableResource(EventWaitHandle signalWhenDone)
{
_obj = new SimpleComObject();
_signalWhenDone = signalWhenDone;
}

~FinalizableResource()
{
//Deadlock here:

Marshal.FinalReleaseComObject(_obj);

_signalWhenDone.Set();
}
}

class Program
{
[DllImport("kernel32.dll")]
static extern uint WaitForSingleObject(
IntPtr handle, uint timeout);

[STAThread]
static void Main(string[] args)
{
ManualResetEvent waitOn = new ManualResetEvent(false);
FinalizableResource r = new FinalizableResource(waitOn);
r = null;
GC.Collect(); //The finalizer will be called soon


//Deadlock here:
WaitForSingleObject(waitOn.Handle, 100000);
}
}
}

(Note that the “r = null” line might seem redundant because the local variable is no longer used after the line where it is declared, but in Debug builds, local variables are considered GC roots until the end of the scope.)

Here’s what it looks like in the debugger:

0:000> kc 20
ntdll!NtWaitForSingleObject
KERNELBASE!WaitForSingleObjectEx
KERNEL32!WaitForSingleObjectExImplementation
KERNEL32!WaitForSingleObject
0x0
clr!CallDescrWorker
clr!SigParser::GetElemType
clr!MetaSig::MetaSig
0x0
clr!MethodDesc::GetSigFromMetadata

~0s0:002> kc 20
ntdll!NtWaitForSingleObject
KERNELBASE!WaitForSingleObjectEx
KERNEL32!WaitForSingleObjectExImplementation
KERNEL32!WaitForSingleObject
ole32!GetToSTA
ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall
ole32!CRpcChannelBuffer::SendReceive2
ole32!CAptRpcChnl::SendReceive
ole32!CCtxComChnl::SendReceive
ole32!NdrExtpProxySendReceive
RPCRT4!NdrpProxySendReceive
RPCRT4!NdrClientCall2
ole32!ObjectStublessClient
ole32!ObjectStubless
ole32!CObjectContext::InternalContextCallback
ole32!CObjectContext::ContextCallback
clr!CtxEntry::EnterContext
clr!RCW::ReleaseAllInterfacesCallBack
clr!RCW::Cleanup
clr!RCW::FinalExternalRelease
clr!MarshalNative::FinalReleaseComObject
mscorlib_ni
clr!MethodTable::SetObjCreateDelegate
clr!MethodTable::SetObjCreateDelegate
clr!MethodTable::CallFinalizer
clr!WKS::CallFinalizer
clr!WKS::GCHeap::TraceGCSegments
clr!WKS::GCHeap::TraceGCSegments
clr!WKS::GCHeap::FinalizerThreadWorker
clr!Thread::DoExtraWorkForFinalizer
clr!Thread::ShouldChangeAbortToUnload
clr!Thread::ShouldChangeAbortToUnload

I.e, the main thread is calling WaitForSingleObject directly, and the finalizer thread, in its attempt to release a COM object, needs to perform a cross-thread call to the STA thread. Both threads are waiting for each other.

Object (computer science) Finalizer Threading

Published at DZone with permission of Sasha Goldshtein, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • Logging Best Practices Revisited [Video]
  • Merge GraphQL Schemas Using Apollo Server and Koa
  • Why You Should Consider Using React Router V6: An Overview of Changes
  • MLOps: Definition, Importance, and Implementation

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: