Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder)
builder.TypeManager ??= CreateDefaultTypeManager ();
#endif // NET

builder.ValueManager ??= JavaMarshalValueManager.Instance;
builder.ValueManager ??= new JavaMarshalValueManager ();
Comment thread
simonrozsival marked this conversation as resolved.
builder.ObjectReferenceManager ??= new Android.Runtime.AndroidObjectReferenceManager ();

if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)
Expand Down
6 changes: 5 additions & 1 deletion src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ internal static JniRuntime.JniValueManager CreateValueManager ()
return new AndroidValueManager ();
}

if (RuntimeFeature.IsCoreClrRuntime || RuntimeFeature.IsNativeAotRuntime) {
if (RuntimeFeature.IsCoreClrRuntime) {
return new JavaMarshalValueManager ();
}

if (RuntimeFeature.IsNativeAotRuntime) {
return new JavaMarshalValueManager ();
Comment on lines +190 to 195
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Code organization — These two branches have identical bodies. Unless there's a planned divergence (different JavaMarshalValueManager configuration per runtime), this could stay as the original if (RuntimeFeature.IsCoreClrRuntime || RuntimeFeature.IsNativeAotRuntime). If there is a plan to diverge, a comment explaining why they're split would help future readers.

Rule: Remove unused code / YAGNI

}

Expand Down
3 changes: 2 additions & 1 deletion src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ internal unsafe static partial class RuntimeNativeMethods
[LibraryImport (RuntimeConstants.InternalDllName)]
[UnmanagedCallConv (CallConvs = new[] { typeof (CallConvCdecl) })]
internal static partial delegate* unmanaged<MarkCrossReferencesArgs*, void> clr_initialize_gc_bridge (
IntPtr java_marshal_value_manager_handle,
delegate* unmanaged<MarkCrossReferencesArgs*, void> bridge_processing_started_callback,
delegate* unmanaged<MarkCrossReferencesArgs*, void> bridge_processing_finished_callback);
delegate* unmanaged<IntPtr, MarkCrossReferencesArgs*, void> bridge_processing_finished_callback);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void monodroid_unhandled_exception (Exception javaException);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,11 @@ class JavaMarshalValueManager : JniRuntime.JniValueManager

bool disposed;

static JavaMarshalValueManager? s_instance;

public static JavaMarshalValueManager Instance =>
s_instance ?? throw new InvalidOperationException ("JavaMarshalValueManager has not been initialized. Call the constructor first.");

unsafe internal JavaMarshalValueManager ()
public unsafe JavaMarshalValueManager ()
{
var previous = Interlocked.CompareExchange (ref s_instance, this, null);
Debug.Assert (previous is null, "JavaMarshalValueManager must only be created once.");

// There can only be one instance because JavaMarshal.Initialize can only be called once.
var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingStarted, &BridgeProcessingFinished);
var javaMarshalValueManagerHandle = new GCHandle<JavaMarshalValueManager> (this);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 ⚠️ Resource management — The GCHandle<JavaMarshalValueManager> is created here but never stored as a field, so it can never be freed in Dispose(). This keeps the managed object pinned for the entire process lifetime.

Since there's only ever one JavaMarshalValueManager and it lives for the app's lifetime, this is probably acceptable — but it should be explicit. Consider storing it as a field and freeing it in Dispose(bool), or at minimum adding a comment explaining why the handle is intentionally leaked (e.g., "the native side holds this handle for the process lifetime; freeing it in Dispose would race with GC bridge callbacks").

Rule: Resource management (Postmortem #11)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the same as before, we could do this in Dispose(), but that will never get called anyway.

var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (
GCHandle<JavaMarshalValueManager>.ToIntPtr (javaMarshalValueManagerHandle), &BridgeProcessingStarted, &BridgeProcessingFinished);
JavaMarshal.Initialize (mark_cross_references_ftn);
}

Expand Down Expand Up @@ -356,6 +349,12 @@ public static GCHandle GetAssociatedGCHandle (HandleContext* context)

public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr)
{
// This call site is reachable on all platforms. 'MarkCrossReferencesArgs.ComponentCount' is only supported on: 'android'.
// This call site is reachable on all platforms. 'MarkCrossReferencesArgs.Components' is only supported on: 'android'.
// This call site is reachable on all platforms. 'StronglyConnectedComponent.Count' is only supported on: 'android'.
// This call site is reachable on all platforms. 'StronglyConnectedComponent.Contexts' is only supported on: 'android'.
#pragma warning disable CA1416

lock (referenceTrackingHandles) {
for (nuint i = 0; i < mcr->ComponentCount; i++) {
StronglyConnectedComponent component = mcr->Components [i];
Expand All @@ -375,7 +374,9 @@ static void EnsureContextIsOurs (IntPtr context)
if (!referenceTrackingHandles.ContainsKey (context)) {
throw new InvalidOperationException ("Unknown reference tracking handle.");
}
}
}

#pragma warning restore CA1416
}

public static HandleContext* Alloc (IJavaPeerable peer)
Expand Down Expand Up @@ -422,20 +423,32 @@ static unsafe void BridgeProcessingStarted (MarkCrossReferencesArgs* mcr)
}

[UnmanagedCallersOnly]
static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr)
static unsafe void BridgeProcessingFinished (IntPtr javaMarshalValueManagerHandle, MarkCrossReferencesArgs* mcr)
{
if (mcr == null) {
throw new ArgumentNullException (nameof (mcr), "MarkCrossReferencesArgs should never be null.");
}

ReadOnlySpan<GCHandle> handlesToFree = ProcessCollectedContexts (mcr);
JavaMarshalValueManager instance = GCHandle<JavaMarshalValueManager>.FromIntPtr (javaMarshalValueManagerHandle).Target;

ReadOnlySpan<GCHandle> handlesToFree = instance.ProcessCollectedContexts (mcr);


Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Formatting — Extra blank line here (two consecutive blank lines).

Rule: Minimal diffs

// This call site is reachable on all platforms. 'JavaMarshal.FinishCrossReferenceProcessing(MarkCrossReferencesArgs*, ReadOnlySpan<GCHandle>)' is only supported on: 'android'.
#pragma warning disable CA1416
JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree);
#pragma warning restore CA1416
}

static unsafe ReadOnlySpan<GCHandle> ProcessCollectedContexts (MarkCrossReferencesArgs* mcr)
unsafe ReadOnlySpan<GCHandle> ProcessCollectedContexts (MarkCrossReferencesArgs* mcr)
{
List<GCHandle> handlesToFree = [];
JavaMarshalValueManager instance = Instance;

// This call site is reachable on all platforms. 'MarkCrossReferencesArgs.ComponentCount' is only supported on: 'android'.
// This call site is reachable on all platforms. 'MarkCrossReferencesArgs.Components' is only supported on: 'android'.
// This call site is reachable on all platforms. 'StronglyConnectedComponent.Count' is only supported on: 'android'.
// This call site is reachable on all platforms. 'StronglyConnectedComponent.Contexts' is only supported on: 'android'.
#pragma warning disable CA1416

for (int i = 0; (nuint)i < mcr->ComponentCount; i++) {
StronglyConnectedComponent component = mcr->Components [i];
Expand All @@ -444,6 +457,8 @@ static unsafe ReadOnlySpan<GCHandle> ProcessCollectedContexts (MarkCrossReferenc
}
}

#pragma warning restore CA1416

void ProcessContext (HandleContext* context)
{
if (context == null) {
Expand All @@ -460,7 +475,7 @@ void ProcessContext (HandleContext* context)
// Note: modifying the RegisteredInstances dictionary while processing the collected contexts
// is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free
// them later outside of the bridge processing loop.
instance.CollectedContexts.Enqueue ((IntPtr)context);
CollectedContexts.Enqueue ((IntPtr)context);

// important: we must not free the handle before passing it to JavaMarshal.FinishCrossReferenceProcessing
handlesToFree.Add (handle);
Expand Down
2 changes: 1 addition & 1 deletion src/native/clr/host/gc-bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void GCBridge::bridge_processing () noexcept
BridgeProcessing bridge_processing {args};
bridge_processing.process ();

bridge_processing_finished_callback (args);
bridge_processing_finished_callback (java_marshal_value_manager_handle, args);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/native/clr/host/internal-pinvokes-shared.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ void _monodroid_weak_gref_delete (jobject handle, char type, const char *threadN
}

BridgeProcessingFtn clr_initialize_gc_bridge (
intptr_t java_marshal_value_manager_handle,
BridgeProcessingStartedFtn bridge_processing_started_callback,
BridgeProcessingFinishedFtn bridge_processing_finished_callback) noexcept
{
return GCBridge::initialize_callback (bridge_processing_started_callback, bridge_processing_finished_callback);
return GCBridge::initialize_callback (java_marshal_value_manager_handle, bridge_processing_started_callback, bridge_processing_finished_callback);
}

void monodroid_log (LogLevel level, LogCategories category, const char *message) noexcept
Expand Down
5 changes: 4 additions & 1 deletion src/native/clr/include/host/gc-bridge.hh
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct MarkCrossReferencesArgs
};

using BridgeProcessingStartedFtn = void (*)(MarkCrossReferencesArgs*);
using BridgeProcessingFinishedFtn = void (*)(MarkCrossReferencesArgs*);
using BridgeProcessingFinishedFtn = void (*)(intptr_t, MarkCrossReferencesArgs*);
using BridgeProcessingFtn = void (*)(MarkCrossReferencesArgs*);

namespace xamarin::android {
Expand All @@ -61,6 +61,7 @@ namespace xamarin::android {
static void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept;

static BridgeProcessingFtn initialize_callback (
intptr_t java_marshal_value_manager,
BridgeProcessingStartedFtn bridge_processing_started,
BridgeProcessingFinishedFtn bridge_processing_finished) noexcept
{
Expand All @@ -69,6 +70,7 @@ namespace xamarin::android {
abort_unless (GCBridge::bridge_processing_started_callback == nullptr, "GC bridge processing started callback is already set");
abort_unless (GCBridge::bridge_processing_finished_callback == nullptr, "GC bridge processing finished callback is already set");

GCBridge::java_marshal_value_manager_handle = java_marshal_value_manager;
GCBridge::bridge_processing_started_callback = bridge_processing_started;
GCBridge::bridge_processing_finished_callback = bridge_processing_finished;

Expand All @@ -89,6 +91,7 @@ namespace xamarin::android {
static inline jobject Runtime_instance = nullptr;
static inline jmethodID Runtime_gc = nullptr;

static inline intptr_t java_marshal_value_manager_handle;
static inline BridgeProcessingStartedFtn bridge_processing_started_callback = nullptr;
static inline BridgeProcessingFinishedFtn bridge_processing_finished_callback = nullptr;

Expand Down
1 change: 1 addition & 0 deletions src/native/clr/include/runtime-base/internal-pinvokes.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extern "C" {
const char* clr_typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept;
bool clr_typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept;
BridgeProcessingFtn clr_initialize_gc_bridge (
intptr_t java_marshal_value_manager_handle,
BridgeProcessingStartedFtn bridge_processing_started_callback,
BridgeProcessingFinishedFtn mark_cross_references_callback) noexcept;
void monodroid_log (xamarin::android::LogLevel level, LogCategories category, const char *message) noexcept;
Expand Down