Skip to content

Add CoreCLR support for android GC bridge #116310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

BrzVlad
Copy link
Member

@BrzVlad BrzVlad commented Jun 4, 2025

This change adds runtime support for the GCBridge api described in #115506 and to be used on android. It includes most of initial work from #114184.

When the GCBridge feature is used, at the start of the application JavaMarshal.Initialize is called. This will provide to the runtime a native callback (markCrossReferences) to be called during the GC when the collection takes place. During GC, we compute the set of strongly connected components containing bridge objects that are dead in the .NET space. These SCCs are passed to the callback so the .NET android implementation would reproduce the links between the java counterparts in order to determine whether the .NET object needs to be collected or not (The constraint is that the C# peer keeps the Java Peer alive and vice verse. We make no effort to handle finalization, so a resurrected C# object can have the Java Peer collected). Once the .NET Android runtime does the java collection it will report back to the runtime with the list of bridge objects that can be freed and with the previously passed SCC related pointers to be freed.

A bridge object is an object that can have a JavaPeer. The CoreCLR runtime has no insight into this, the only thing it understands are cross reference handles. These are GCHandles that have an additional pointer associated with them, so additional information related to the java peer can be attached. Objects that have a cross reference handle allocated, will always survive the current GC collection, because we can't collect them until we get permission from the Java world. Once the cross reference gchandle is freed, the associated object becomes ordinary, detached from any java peer, and it is free for collection in the .NET heap.

At the end of mark phase, during GC, we iterate over all cross reference handles. When we encounter a handle with target that hasn't yet been marked, we add it to a list (these objects will have to be marked so they remain alive after this collection, given we need to probe the java world first). Once we obtained the set of dead bridge objects, we apply the tarjan algorithm (this algorithm is ported directly from mono's implementation). This algorithm will operate on the dead object graph, reachable from the initial set of dead bridge objects. In order to implement this secondary scanning mechanism, for objects that we reach, we hijack the object header with a ScanData that contains all information relevant to the SCC building algorithm. Once we finished building the SCCs, while still in the GC, we callback into the .NET Android via TriggerClientBridgeProcessing that will end up calling the mark cross reference callback provided by JavaMarshal.Initialize. This callback will have to dispatch the neceesary work for another thread to run, since it needs to return quickly, for the C# GC to continue its execution.

Because the world gets resumed without having decided yet whether the bridge objects will be alive or dead, for weak references, we would need to wait for the java bridge processing to finish before we can resolve the Target. Aside from the general problem of resurrecting a C# peer that has the Java Peer collected, this mechanism will be used internally by the .NET Android in order to manually manage liveness of these bridge objects, in the scenario of calling Dispose on an object. This synchronisation will be used at the core of .NET Android Runtime interop. In order to implement this, weak refs for bridge objects are not nulled during GC (these objects are promoted during collection) but rather at the finishing stage of bridge processing. This change is conservative and adds bridge waiting only for WeakReference, not when using GCHandle, following the existing approach in COM.

This PR adds a few tests in the runtime tests. The tests have a native counterpart that acts as the client bridge, not doing anything, just doing random sleep instead of doing the Java collection. The test creates a set of objects with certain links between then, creates weak refs to the BridgeObjects and then doesn't reference anything else. Depending on the built graph, it expects a certain number of SCCs and cross refs constructed by the tarjan algorithm, and then reports all bridge objects as alive or dead. The test will also check to see if the Target for all the weak refs is the expected one.

@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jun 4, 2025
@BrzVlad BrzVlad added area-Interop-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jun 4, 2025
BrzVlad added 13 commits June 12, 2025 09:54
From Aaron's implementation
Checking if the object is promoted was validating the next object header in debug builds. During bridge tarjan computation, we patch the object header for some objects in order to store data used by the bridge algorithm, so we need to disable this validation.
HANDLE_MAX_INTERNAL_TYPES value

new instead of malloc

assert for allocation failure

Reuse memory for ColorData and ScanData between collections. We still do alloc/free for other type of data, for example for arrays representing edges between SCCs.

Actually print class name when enabling tarjan bridge logs.

Add separate IsPromoted method to the gc interface

Rename TriggerGCBridge to TriggerClientBridgeProcessing to be more specific about what it is doing.
BrzVlad added 2 commits June 13, 2025 19:51
Include classes that are part of the Java Marshal API on mono as well

Disable test on mono and nativeaot.
@BrzVlad BrzVlad force-pushed the feature-clr-gcbridge branch from ccb99ad to 4d61fbe Compare June 13, 2025 20:52
@BrzVlad BrzVlad marked this pull request as ready for review June 14, 2025 15:52
@BrzVlad BrzVlad force-pushed the feature-clr-gcbridge branch from 99fb057 to 28a2b21 Compare June 19, 2025 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants