-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
base: main
Are you sure you want to change the base?
Conversation
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.CoreCLR.cs
Outdated
Show resolved
Hide resolved
e4fade9
to
190552f
Compare
40e5e75
to
ff4ea6c
Compare
ff4ea6c
to
f3e49ae
Compare
From Aaron's implementation.
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.
ccb99ad
to
4d61fbe
Compare
Create reset event during javamarshal initialize
99fb057
to
28a2b21
Compare
I think I've addressed all review up to this point and there doesn't seem to be any related failures on CI. Any additional feedback so we can move this forward ? |
@@ -0,0 +1,277 @@ | |||
// Licensed to the .NET Foundation under one or more agreements. | |||
// The .NET Foundation licenses this file to you under the MIT license. | |||
using System; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test should be enabled only where JavaMarshal is supported. I do not see any code to do that. Do We need to add it?
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 viaTriggerClientBridgeProcessing
that will end up calling the mark cross reference callback provided byJavaMarshal.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.
The gcbridge doesn't consume much memory. A collection for a heavier app can end with hundreds of SCCs and xrefs. For such a scenario, the gcbridge is expected to consume hundreds of KBs. Most of this memory is represented by data for ScanData, ColorData and stacks used by tarjan algorithm. These data structures have their capacity increased when necessary, so for most collections there is no new memory allocation, the existing storage is reused. For a few other data structures, like xrefs arrays and data allocated to be passed to the bridge client, new allocations from scratch happen at each collection. While the gc bridge can end up consuming hundreds of KB for heavy scenarios, maybe a few MB in extreme theoretical cases, less than 10% of this memory is expected to be allocated during collection, the rest should be reused.