microsoft / microsoft/CsWinRT

ObjectReferenceWithContext.Release() dispatched into an apartment that is uninitializing, faulting inside the callee

Open
#2,532 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
665
Forks
134
Avg merge
1d 3h
Merged PRs (30d)
32

Description

Summary

When a UWP secondary view is closed, ObjectReferenceWithContext<T>.Release() running on the finalizer thread marshals the Release back into that view's apartment through IContextCallback. The apartment is inside ApartmentUninitializeWaitForPendingGitRegistrations, which still pumps, so the call is serviced — but the callee (XAML) has already torn down its core, and the release faults with an access violation inside it.

From the caller's point of view the context still looks valid, so I can't see a way for the app to detect the situation. I'd like to know whether CsWinRT can, and what the recommended pattern is.

Environment
  • .NET 10, CsWinRT 2.x (Windows SDK 10.0.26100.0 ref pack), x64
  • UWP XAML app (Windows.UI.Xaml), UseUwp, not NativeAOT in this dump
  • Secondary view created with CoreApplication.CreateNewView(), i.e. its own ASTA thread
  • Reproduces reliably a moment after closing that view
The two stacks

Finalizer thread (MTA), making the call:

System.GC.RunFinalizers()
WinRT.IObjectReference.Finalize()
WinRT.ObjectReferenceWithContext`1[[WinRT.Interop.IUnknownVftbl]].Release()
WinRT.Interop.Platform.CoGetContextToken(IntPtr*)
  combase!CObjectContext::ContextCallback+0xa8
  combase!CObjectContext::InternalContextCallback+0x1b1
  combase!ObjectStubless+0x42
  combase!ObjectStublessClient+0x146
  rpcrt4!NdrpClientCall3+0x431
  combase!CSyncClientCall::SendReceive+0x3fb
  combase!MTAThreadDispatchCrossApartmentCall
  combase!MTAThreadWaitForCall+0xf4          <-- blocked, waiting for the callee

The view's thread, servicing it while uninitializing:

SHCore!_WrapperThreadProc
twinapi_appcore!<lambda_...>::operator()
combase!RoUninitialize+0x9
combase!CoUninitialize+0x19f
combase!wCoUninitialize+0x2e0
combase!ApartmentUninitialize+0x253
  combase!CComApartment::WaitForPendingGitRegistrations+0x4b
  combase!ASTAState::WaitForPendingGitRegistrations+0x44
combase!CoWaitForMultipleHandles+0xe2
combase!ModernSTAThreadWaitForHandles+0x9a
combase!ModernSTAWaitInNewContext+0xd4
combase!ModernSTAWaitContext::Wait+0x61a
combase!ModernSTAState::HandleMessage+0x3c
combase!ThreadDispatch+0x3ef
combase!ComInvokeWithLockAndIPID+0xd0c
combase!ASTAInvokeInApartment+0xaf
combase!ServerCall::ContextInvoke+0x28f
combase!StubInvoke+0x138
combase!DefaultStubInvoke+0x376
combase!CStdStubBuffer_Invoke+0x7d
rpcrt4!NdrStubCall3+0xc0
rpcrt4!Ndr64StubWorker+0x6ee
rpcrt4!Invoke+0x73
combase!CRemoteUnknown::DoCallback+0xc7
  [managed frames]
Windows_UI_Xaml!ctl::interface_forwarder<Windows::UI::Xaml::Core::Direct::IXamlDirectObject,CDependencyObject>::Release+0x45
Windows_UI_Xaml!CDependencyObject::Release+0x1cf
Windows_UI_Xaml!CDependencyObject::ReleaseImpl+0x19d
Windows_UI_Xaml!CDependencyObject::ResetReferencesFromChildren+0x17b
Windows_UI_Xaml!CDependencyObject::ResetReferenceFromChild+0xbb
Windows_UI_Xaml!CMultiParentShareableDependencyObject::RemoveParent+0x8c
Windows_UI_Xaml!CDependencyObject::OnParentChange+0x5
Windows_UI_Xaml!CDependencyObject::GetContext+0x5      <-- AV, core pointer is null
Analysis

The object being released is a CDependencyObject created through XamlDirect (IXamlDirectObject), so it has no framework peer. XAML's shutdown (DXamlCore::DeinitializeInstance) severs every entry in its peer table in ShutdownAllPeers() — explicitly "so that when we start to release everything, we're not bothering to call back into the framework from the core" — and then releases CCoreServices. Objects that were never in the peer table are not severed, so a later Release on one still runs XAML code, and that code dereferences the now-destroyed core.

Every participant is behaving reasonably in isolation:

  • CsWinRT marshals the release into the creating apartment, which is the apartment-correct thing to do for a non-agile object.
  • COM services the call, because WaitForPendingGitRegistrations deliberately pumps to drain pending work.
  • XAML released its core after severing everything it knew about.

The combination is what fails, and the app has no visibility into any of it.

Why this does not reproduce on .NET Native

The same app on the .NET Native toolchain never hits this. Its interop releases the interface pointer directly from the finalizer thread, with no context callback and therefore no cross-apartment call — so nothing re-enters the dying apartment. The garbage and its timing are identical; only the release path differs.

Question

Is there anything CsWinRT can do here? Specifically:

  1. Can ObjectReferenceWithContext detect that the target apartment is uninitializing and skip the marshal (leaking the reference, which is harmless at that point)? From the app side the context token still resolves and the callback still succeeds, so I could not find a check that works.
  2. Would an opt-out — an AppContext switch to release non-agile objects directly, matching the older behaviour — be considered? For an app whose objects all die with the process it would be strictly safer.
  3. If neither, is deterministic disposal on the owning thread the recommended guidance? ((IWinRTObject)obj).NativeObject.Dispose() from the UI thread releases inline rather than through the context callback, and appears to avoid the problem entirely — but it requires the app to know every RCW it holds and drop them all before the view closes, which is hard to guarantee.

Happy to share the dump or a minimal repro if that would help.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing ObjectReferenceWithContext.Release, IContextCallback, and the two provided apartment-uninitialization stacks. Compare the direct NativeObject.Dispose() path with the marshaled release path, then investigate whether CsWinRT can detect shutdown or support the proposed opt-out; done means a supported mitigation or clear guidance, ideally validated with the offered dump or minimal repro.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.