Skip to content

Commit d764578

Browse files
[release/6.0] Notify Reference Tracker runtime of disconnect at the right time (#58533)
* Notify Reference Tracker runtime of disconnect at the right time Based on misunderstanding of SyncBlock clean-up modes the indication of when an Native Object Wrapper was being collected was being done after the wrapper's Finalizer was run. However, this information must be conveyed prior to wrapper finalization and synchronously during the GC. * Review feedback. Co-authored-by: Aaron Robinson <arobins@microsoft.com>
1 parent 6be45d2 commit d764578

9 files changed

Lines changed: 69 additions & 21 deletions

File tree

src/coreclr/interop/comwrappers.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -791,17 +791,6 @@ void NativeObjectWrapperContext::Destroy(_In_ NativeObjectWrapperContext* wrappe
791791
{
792792
_ASSERTE(wrapper != nullptr);
793793

794-
// Check if the tracker object manager should be informed prior to being destroyed.
795-
IReferenceTracker* trackerMaybe = wrapper->GetReferenceTracker();
796-
if (trackerMaybe != nullptr)
797-
{
798-
// We only call this during a GC so ignore the failure as
799-
// there is no way we can handle it at this point.
800-
HRESULT hr = TrackerObjectManager::BeforeWrapperDestroyed(trackerMaybe);
801-
_ASSERTE(SUCCEEDED(hr));
802-
(void)hr;
803-
}
804-
805794
// Manually trigger the destructor since placement
806795
// new was used to allocate the object.
807796
wrapper->~NativeObjectWrapperContext();

src/coreclr/interop/comwrappers.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ class TrackerObjectManager
212212
// Called after wrapper has been created.
213213
static HRESULT AfterWrapperCreated(_In_ IReferenceTracker* obj);
214214

215-
// Called before wrapper is about to be destroyed (the same lifetime as short weak handle).
216-
static HRESULT BeforeWrapperDestroyed(_In_ IReferenceTracker* obj);
215+
// Called before wrapper is about to be finalized (the same lifetime as short weak handle).
216+
static HRESULT BeforeWrapperFinalized(_In_ IReferenceTracker* obj);
217217

218218
public:
219219
// Begin the reference tracking process for external objects.

src/coreclr/interop/inc/interoplib.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,12 @@ namespace InteropLib
8787
_In_ size_t contextSize,
8888
_Out_ ExternalWrapperResult* result) noexcept;
8989

90-
// Destroy the supplied wrapper.
91-
void DestroyWrapperForExternal(_In_ void* context) noexcept;
90+
// Inform the wrapper it is being collected.
91+
void NotifyWrapperForExternalIsBeingCollected(_In_ void* context) noexcept;
92+
93+
// Destroy the supplied wrapper.
94+
// Optionally notify the wrapper of collection at the same time.
95+
void DestroyWrapperForExternal(_In_ void* context, _In_ bool notifyIsBeingCollected = false) noexcept;
9296

9397
// Separate the supplied wrapper from the tracker runtime.
9498
void SeparateWrapperFromTrackerRuntime(_In_ void* context) noexcept;

src/coreclr/interop/interoplib.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,37 @@ namespace InteropLib
166166
return S_OK;
167167
}
168168

169-
void DestroyWrapperForExternal(_In_ void* contextMaybe) noexcept
169+
void NotifyWrapperForExternalIsBeingCollected(_In_ void* contextMaybe) noexcept
170+
{
171+
NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe);
172+
173+
// A caller should not be destroying a context without knowing if the context is valid.
174+
_ASSERTE(context != nullptr);
175+
176+
// Check if the tracker object manager should be informed of collection.
177+
IReferenceTracker* trackerMaybe = context->GetReferenceTracker();
178+
if (trackerMaybe != nullptr)
179+
{
180+
// We only call this during a GC so ignore the failure as
181+
// there is no way we can handle it at this point.
182+
HRESULT hr = TrackerObjectManager::BeforeWrapperFinalized(trackerMaybe);
183+
_ASSERTE(SUCCEEDED(hr));
184+
(void)hr;
185+
}
186+
}
187+
188+
void DestroyWrapperForExternal(_In_ void* contextMaybe, _In_ bool notifyIsBeingCollected) noexcept
170189
{
171190
NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe);
172191

173192
// A caller should not be destroying a context without knowing if the context is valid.
174193
_ASSERTE(context != nullptr);
175194

176-
NativeObjectWrapperContext::Destroy(context);
177-
}
195+
if (notifyIsBeingCollected)
196+
NotifyWrapperForExternalIsBeingCollected(contextMaybe);
197+
198+
NativeObjectWrapperContext::Destroy(context);
199+
}
178200

179201
void SeparateWrapperFromTrackerRuntime(_In_ void* contextMaybe) noexcept
180202
{

src/coreclr/interop/trackerobjectmanager.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,13 @@ HRESULT TrackerObjectManager::AfterWrapperCreated(_In_ IReferenceTracker* obj)
305305
return S_OK;
306306
}
307307

308-
HRESULT TrackerObjectManager::BeforeWrapperDestroyed(_In_ IReferenceTracker* obj)
308+
HRESULT TrackerObjectManager::BeforeWrapperFinalized(_In_ IReferenceTracker* obj)
309309
{
310310
_ASSERTE(obj != nullptr);
311311

312312
HRESULT hr;
313313

314-
// Notify tracker runtime that we are about to destroy a wrapper
314+
// Notify tracker runtime that we are about to finalize a wrapper
315315
// (same timing as short weak handle) for this object.
316316
// They need this information to disconnect weak refs and stop firing events,
317317
// so that they can avoid resurrecting the object.

src/coreclr/vm/interoplibinterface_comwrappers.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ namespace
176176
if (Result.Context != NULL)
177177
{
178178
GCX_PREEMP();
179-
InteropLib::Com::DestroyWrapperForExternal(Result.Context);
179+
// We also request collection notification since this holder is presently
180+
// only used for new activation of wrappers therefore the notification won't occur
181+
// at the typical time of before finalization.
182+
InteropLib::Com::DestroyWrapperForExternal(Result.Context, /* notifyIsBeingCollected */ true);
180183
}
181184
}
182185
InteropLib::Com::ExternalWrapperResult* operator&()
@@ -478,7 +481,11 @@ namespace
478481
if (!cxt->IsSet(ExternalObjectContext::Flags_Detached)
479482
&& !GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(cxt->GetObjectRef())))
480483
{
484+
// Indicate the wrapper entry has been detached.
481485
cxt->MarkDetached();
486+
487+
// Notify the wrapper it was not promoted and is being collected.
488+
InteropLib::Com::NotifyWrapperForExternalIsBeingCollected(cxt);
482489
}
483490
}
484491
}

src/tests/Interop/COM/ComWrappers/API/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,9 @@ static int Main(string[] doNotUse)
624624
ValidateQueryInterfaceAfterManagedObjectCollected();
625625
ValidateAggregationWithComObject();
626626
ValidateAggregationWithReferenceTrackerObject();
627+
628+
// Ensure all objects have been cleaned up.
629+
ForceGC();
627630
}
628631
catch (Exception e)
629632
{

src/tests/Interop/COM/ComWrappers/Common.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ public static AllocationCountResult CountTrackerObjectAllocations()
156156

157157
[DllImport(nameof(MockReferenceTrackerRuntime))]
158158
extern public static int TrackerTarget_ReleaseFromReferenceTracker(IntPtr ptr);
159+
160+
// Suppressing the GC transition here as we want to make sure we are in-sync
161+
// with the GC which is setting the connected value.
162+
[SuppressGCTransition]
163+
[DllImport(nameof(MockReferenceTrackerRuntime))]
164+
extern public static byte IsTrackerObjectConnected(IntPtr instance);
159165
}
160166

161167
[Guid("42951130-245C-485E-B60B-4ED4254256F8")]
@@ -219,6 +225,12 @@ static IntPtr CreateInstance(IntPtr outer, out IntPtr inner)
219225
}
220226
else
221227
{
228+
byte isConnected = MockReferenceTrackerRuntime.IsTrackerObjectConnected(this.classNative.Instance);
229+
if (isConnected != 0)
230+
{
231+
throw new Exception("TrackerObject should be disconnected prior to finalization");
232+
}
233+
222234
ComWrappersHelper.Cleanup(ref this.classNative);
223235
}
224236
}

src/tests/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/ReferenceTrackerRuntime.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ namespace
218218
}
219219
}
220220

221+
bool IsConnected()
222+
{
223+
return _connected;
224+
}
225+
221226
STDMETHOD(AddObjectRef)(_In_ IUnknown* c, _Out_ int* id)
222227
{
223228
assert(c != nullptr && id != nullptr);
@@ -546,6 +551,12 @@ extern "C" DLL_EXPORT int STDMETHODCALLTYPE Trigger_NotifyEndOfReferenceTracking
546551
return TrackerRuntimeManager.NotifyEndOfReferenceTrackingOnThread();
547552
}
548553

554+
extern "C" DLL_EXPORT bool STDMETHODCALLTYPE IsTrackerObjectConnected(IUnknown* inst)
555+
{
556+
auto trackerObject = reinterpret_cast<TrackerObject::TrackerObjectImpl*>(inst);
557+
return trackerObject->IsConnected();
558+
}
559+
549560
extern "C" DLL_EXPORT void* STDMETHODCALLTYPE TrackerTarget_AddRefFromReferenceTrackerAndReturn(IUnknown *obj)
550561
{
551562
assert(obj != nullptr);

0 commit comments

Comments
 (0)