Add a new GC handle and use it to simplify NativeAOT corelib#99512
Add a new GC handle and use it to simplify NativeAOT corelib#99512AustinWise wants to merge 1 commit into
Conversation
Support is for this features is added to ConditionalWeakTable and used in NativeAOT. This replaces the pattern of re-registering for finalization repeatedly until the primary object is collected. # Conflicts: # src/coreclr/nativeaot/Runtime/HandleTableHelpers.cpp # src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs
|
The GC handle that you are proposing provides a first-class functional equivalent of phantom references. It is possible to emulate it using combo of existing GC handles, but this emulation has overhead. If we were to build a first-class functional equivalent of phantom references, I think we would want to design and expose them as a proper public API. I do not think that it is worth it for the few internal uses. In general, phantom references can be used in interop as a safehandles alternative with different perf characteristics and lifetime guarantees. Regular CoreCLR CoreLib has a few places where these can be used as well. For example: |
Thanks for taking a look, I suspected as much. I'll close this PR. Also thanks for the pointer to phantom references. |
Problem
In the .NET runtime, particularly in its interop systems, it is common to attached some extra state
to an object. In CoreCLR, this data is often stored in the
SyncBlockof an object. In NativeAOT, thisis often accomplished using a
ConditionalWeakTable.A disadvantage of using
ConditionalWeakTablecompared to theSyncBlockis the behavior aroundfinalization. The
SyncBlocksystem hooks into the GC to detect when an object is collected and waits untilthat moment to schedule the deallocation of data associated with an object. When using a
ConditionalWeakTable,the value in a table is queued for finalization as soon as the key is queued for finalization.
The NativeAOT corelib has to ensure that the key in the table has been fully collected and call
GC.ReRegisterForFinalizeto defer finialization if it has not. Failure to check for this conditionhas cause a couple of bugs in the past:
#86882
#99185
This PR's solution
With a small tweak, the dependant handle and the
ConditionalWeakTablecan make writing thesesorts of finalizers in the corelib easier. If the dependant handle always promotes its secondary
as long as the primary was a alive, the secondary object will not be put on the finalization queue
until the primary object has been fully collected. This PR implements a new type of GC handle with
this behavior called a "defer finalize dependant handle".
Use cases
This PR uses the handle in 4 places in the NativeAOT corelib:
an extra GC handle allocation per tracked object.
I've identified a couple places in CoreCLR where this handle could be used, but have not implemented
it in this PR:
Conceivably, functionality that currently is placed on the SyncBlock in CoreCLR could be moved to use
this new handle to manage the lifetime of data. This might allow for more sharing between CoreCLR
and NativeAOT. Longer term, the
SyncBlockand its custom handle table could removed in favor of usingthis new GC handle.
Analysis
Here are some of the pros and cons I can think of. Please let me know if there are other considerations
or if there are any best practices to quantify the performance impact of a change like this.
Pros:
Cons:
TODO