Skip to content

[Mono.Android] call new Java "GC Bridge" APIs #10125

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ Makefile eol=lf
*.wixproj eol=crlf
*.wxs eol=crlf
*.rtf eol=crlf

packages/* filter=lfs diff=lfs merge=lfs -text
25 changes: 19 additions & 6 deletions Documentation/workflow/DevelopmentTips.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,26 @@ A second (better) way is to add this MSBuild target to your Android
`.csproj` file:

```xml
<Target Name="UpdateMonoRuntimePacks" BeforeTargets="ProcessFrameworkReferences">
<Target Name="UpdateRuntimeVersion" BeforeTargets="ProcessFrameworkReferences">
<PropertyGroup>
<!-- This could be a version I built myself -->
<_Version>10.0.0-dev</_Version>
</PropertyGroup>
<ItemGroup>
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net6.0' "
LatestRuntimeFrameworkVersion="6.0.0-preview.7.21364.3"
/>
<!-- For runtime packs only -->
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net10.0' "
LatestRuntimeFrameworkVersion="$(_Version)"
/>
<!-- For new .NET APIs -->
<KnownFrameworkReference
Update="Microsoft.NETCore.App"
Condition=" '%(KnownFrameworkReference.TargetFramework)' == 'net10.0' "
DefaultRuntimeFrameworkVersion="$(_Version)"
LatestRuntimeFrameworkVersion="$(_Version)"
TargetingPackVersion="$(_Version)"
/>
</ItemGroup>
</Target>
```
Expand Down
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<!-- End: Package sources from dotnet-android -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<!-- ensure only the sources defined below are used -->
<add key="local-packages" value="packages" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" protocolVersion="3" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" protocolVersion="3" />
<!-- This is for packages needed by debugger-libs -->
Expand Down
21 changes: 21 additions & 0 deletions build-tools/scripts/custom-runtime.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project>
<!-- Use version in local packages folder -->
<Target Name="UpdateRuntimeVersion" BeforeTargets="ProcessFrameworkReferences">
<PropertyGroup>
<_Version>10.0.0-dev</_Version>
</PropertyGroup>
<ItemGroup>
<KnownFrameworkReference Update="Microsoft.NETCore.App"
Condition=" '%(KnownFrameworkReference.TargetFramework)' == 'net10.0' "
DefaultRuntimeFrameworkVersion="$(_Version)"
LatestRuntimeFrameworkVersion="$(_Version)"
TargetingPackVersion="$(_Version)"
/>
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net10.0' "
LatestRuntimeFrameworkVersion="$(_Version)"
/>
</ItemGroup>
</Target>
</Project>
3 changes: 3 additions & 0 deletions packages/Microsoft.NETCore.App.Ref.10.0.0-dev.nupkg
Git LFS file not shown
Git LFS file not shown
65 changes: 46 additions & 19 deletions src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Java;
using System.Threading;
using Android.Runtime;
using Java.Interop;
Expand All @@ -20,10 +21,11 @@ class ManagedValueManager : JniRuntime.JniValueManager
{
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

Dictionary<int, List<IJavaPeerable>>? RegisteredInstances = new Dictionary<int, List<IJavaPeerable>>();
Dictionary<int, List<GCHandle>>? RegisteredInstances = new ();

internal ManagedValueManager ()
internal unsafe ManagedValueManager ()
{
JavaMarshal.Initialize (&FinishBridgeProcessing);
}

public override void WaitForGCBridgeProcessing ()
Expand All @@ -35,7 +37,7 @@ public override void CollectPeers ()
if (RegisteredInstances == null)
throw new ObjectDisposedException (nameof (ManagedValueManager));

var peers = new List<IJavaPeerable> ();
var peers = new List<GCHandle> ();

lock (RegisteredInstances) {
foreach (var ps in RegisteredInstances.Values) {
Expand All @@ -48,7 +50,8 @@ public override void CollectPeers ()
List<Exception>? exceptions = null;
foreach (var peer in peers) {
try {
peer.Dispose ();
if (peer.Target is IDisposable disposable)
disposable.Dispose ();
}
catch (Exception e) {
exceptions = exceptions ?? new List<Exception> ();
Expand All @@ -74,33 +77,35 @@ public override void AddPeer (IJavaPeerable value)
}
int key = value.JniIdentityHashCode;
lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers)) {
peers = new List<IJavaPeerable> () {
value,
peers = new List<GCHandle> () {
CreateReferenceTrackingHandle (value)
};
RegisteredInstances.Add (key, peers);
return;
}

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (!JniEnvironment.Types.IsSameObject (p.PeerReference, value.PeerReference))
if (p.Target is not IJavaPeerable peer)
continue;
if (!JniEnvironment.Types.IsSameObject (peer.PeerReference, value.PeerReference))
continue;
if (Replaceable (p)) {
peers [i] = value;
peers [i] = CreateReferenceTrackingHandle (value);
} else {
WarnNotReplacing (key, value, p);
WarnNotReplacing (key, value, peer);
}
return;
}
peers.Add (value);
peers.Add (CreateReferenceTrackingHandle (value));
}
}

static bool Replaceable (IJavaPeerable peer)
static bool Replaceable (GCHandle handle)
{
if (peer == null)
if (handle.Target is not IJavaPeerable peer)
return true;
return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable);
}
Expand Down Expand Up @@ -132,14 +137,14 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
int key = GetJniIdentityHashCode (reference);

lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers))
return null;

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (JniEnvironment.Types.IsSameObject (reference, p.PeerReference))
return p;
if (p.Target is IJavaPeerable peer && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference))
return peer;
}
if (peers.Count == 0)
RegisteredInstances.Remove (key);
Expand All @@ -157,14 +162,15 @@ public override void RemovePeer (IJavaPeerable value)

int key = value.JniIdentityHashCode;
lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers))
return;

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (object.ReferenceEquals (value, p)) {
if (object.ReferenceEquals (value, p.Target)) {
peers.RemoveAt (i);
FreeHandle (p);
}
}
if (peers.Count == 0)
Expand Down Expand Up @@ -251,13 +257,34 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
var peers = new List<JniSurfacedPeerInfo> (RegisteredInstances.Count);
foreach (var e in RegisteredInstances) {
foreach (var p in e.Value) {
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (p)));
if (p.Target is not IJavaPeerable peer)
continue;
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (peer)));
}
}
return peers;
}
}

static GCHandle CreateReferenceTrackingHandle (IJavaPeerable value) =>
JavaMarshal.CreateReferenceTrackingHandle (value, value.JniObjectReferenceControlBlock);

static unsafe void FreeHandle (GCHandle handle)
{
IntPtr context = JavaMarshal.GetContext (handle);
NativeMemory.Free ((void*) context);
}

[UnmanagedCallersOnly]
internal static unsafe void FinishBridgeProcessing (nint sccsLen, StronglyConnectedComponent* sccs, nint ccrsLen, ComponentCrossReference* ccrs)
{
Java.Lang.JavaSystem.Gc ();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the C# binding of java.lang.System.gc(): https://developer.android.com/reference/java/lang/System#gc()


JavaMarshal.ReleaseMarkCrossReferenceResources (
new Span<StronglyConnectedComponent> (sccs, (int) sccsLen),
new Span<ComponentCrossReference> (ccrs, (int) ccrsLen));
}

const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<Import Project="..\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems" Label="Shared" Condition="Exists('..\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems')" />
<Import Project="Mono.Android.targets" />
<Import Project="..\..\build-tools\scripts\JavaCallableWrappers.targets" />
<Import Project="..\..\build-tools\scripts\custom-runtime.targets" />
<Import Project="$(IntermediateOutputPath)mcw\Mono.Android.projitems" Condition="Exists('$(IntermediateOutputPath)mcw\Mono.Android.projitems')" />

<ItemGroup>
Expand Down
Loading