Skip to content

Commit db2dd25

Browse files
Refactor GC bridge to use UCO callback instead of wait_for_processing
- Pass UCO callback to native during initialization - Native mark_cross_references calls the UCO callback directly - UCO stores args in ConcurrentQueue and signals SemaphoreSlim - C# background thread waits on semaphore and processes from queue - Remove clr_gc_bridge_wait_for_processing P/Invoke - Remove semaphore and atomic from C++ code - Regenerate pinvoke-tables.include Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
1 parent 838dbaf commit db2dd25

File tree

8 files changed

+44
-45
lines changed

8 files changed

+44
-45
lines changed

src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,13 @@ internal unsafe static class RuntimeNativeMethods
8686
internal static extern bool clr_typemap_java_to_managed (string java_type_name, out IntPtr managed_assembly_name, out uint managed_type_token_id);
8787

8888
/// <summary>
89-
/// Initialize GC bridge for managed processing mode (no native background thread).
89+
/// Initialize GC bridge for managed processing mode.
90+
/// Takes a callback that will be invoked when mark_cross_references is called by the GC.
9091
/// Returns the mark_cross_references function pointer to pass to JavaMarshal.Initialize.
9192
/// </summary>
9293
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
93-
internal static extern delegate* unmanaged<MarkCrossReferencesArgs*, void> clr_gc_bridge_initialize_for_managed_processing ();
94-
95-
/// <summary>
96-
/// Wait for the next set of cross references to process. Blocks until mark_cross_references is called by the GC.
97-
/// </summary>
98-
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
99-
internal static extern MarkCrossReferencesArgs* clr_gc_bridge_wait_for_processing ();
94+
internal static extern delegate* unmanaged<MarkCrossReferencesArgs*, void> clr_gc_bridge_initialize_for_managed_processing (
95+
delegate* unmanaged[Cdecl]<MarkCrossReferencesArgs*, void> onMarkCrossReferencesCallback);
10096

10197
/// <summary>
10298
/// Trigger Java garbage collection.

src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,29 @@ class ManagedValueManager : JniRuntime.JniValueManager
2828
bool disposed;
2929

3030
static readonly SemaphoreSlim bridgeProcessingSemaphore = new (1, 1);
31+
static readonly SemaphoreSlim pendingArgsSemaphore = new (0, int.MaxValue);
32+
static readonly ConcurrentQueue<IntPtr> pendingArgsQueue = new ();
3133
static Thread? bridgeProcessingThread;
3234

3335
static Lazy<ManagedValueManager> s_instance = new (() => new ManagedValueManager ());
3436
public static ManagedValueManager GetOrCreateInstance () => s_instance.Value;
3537

38+
/// <summary>
39+
/// UCO callback invoked by native code when mark_cross_references is called.
40+
/// This stores the args pointer in a queue and signals the processing thread.
41+
/// </summary>
42+
[UnmanagedCallersOnly (CallConvs = [typeof (CallConvCdecl)])]
43+
static unsafe void OnMarkCrossReferences (MarkCrossReferencesArgs* args)
44+
{
45+
pendingArgsQueue.Enqueue ((IntPtr)args);
46+
pendingArgsSemaphore.Release ();
47+
}
48+
3649
unsafe ManagedValueManager ()
3750
{
38-
// Initialize GC bridge for managed processing mode (C# background thread instead of C++)
39-
var mark_cross_references_ftn = RuntimeNativeMethods.clr_gc_bridge_initialize_for_managed_processing ();
51+
// Initialize GC bridge with our UCO callback that will be invoked when mark_cross_references is called
52+
delegate* unmanaged[Cdecl]<MarkCrossReferencesArgs*, void> callback = &OnMarkCrossReferences;
53+
var mark_cross_references_ftn = RuntimeNativeMethods.clr_gc_bridge_initialize_for_managed_processing (callback);
4054
JavaMarshal.Initialize (mark_cross_references_ftn);
4155

4256
// Start the managed bridge processing thread
@@ -446,11 +460,16 @@ public static void Free (ref HandleContext* context)
446460
static unsafe void ManagedBridgeProcessingLoop ()
447461
{
448462
while (true) {
449-
// Wait for mark_cross_references to be called by the GC
450-
MarkCrossReferencesArgs* args = RuntimeNativeMethods.clr_gc_bridge_wait_for_processing ();
463+
// Wait for OnMarkCrossReferences to be called by the GC via our UCO callback
464+
pendingArgsSemaphore.Wait ();
465+
466+
if (!pendingArgsQueue.TryDequeue (out IntPtr argsPtr)) {
467+
throw new InvalidOperationException ("pendingArgsQueue was signaled but no args were available");
468+
}
451469

470+
MarkCrossReferencesArgs* args = (MarkCrossReferencesArgs*)argsPtr;
452471
if (args == null) {
453-
throw new InvalidOperationException ("clr_gc_bridge_wait_for_processing returned null");
472+
throw new InvalidOperationException ("MarkCrossReferencesArgs pointer was null");
454473
}
455474

456475
// Start processing - acquire semaphore and validate contexts

src/native/clr/host/gc-bridge.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept
5252
abort_unless (args->CrossReferences != nullptr || args->CrossReferenceCount == 0, "CrossReferences must not be null if CrossReferenceCount is greater than 0");
5353
log_mark_cross_references_args_if_enabled (args);
5454

55-
shared_args.store (args);
56-
shared_args_semaphore.release ();
55+
// Invoke the managed callback to queue the args for processing
56+
abort_unless (on_mark_cross_references_callback != nullptr, "on_mark_cross_references_callback must be set before mark_cross_references is called");
57+
on_mark_cross_references_callback (args);
5758
}
5859

5960
[[gnu::always_inline]]

src/native/clr/host/internal-pinvokes-shared.cc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,9 @@ void _monodroid_weak_gref_delete (jobject handle, char type, const char *threadN
3434
OSBridge::_monodroid_weak_gref_delete (handle, type, threadName, threadId, from);
3535
}
3636

37-
BridgeProcessingFtn clr_gc_bridge_initialize_for_managed_processing () noexcept
37+
BridgeProcessingFtn clr_gc_bridge_initialize_for_managed_processing (OnMarkCrossReferencesCallback callback) noexcept
3838
{
39-
return GCBridge::initialize_for_managed_processing ();
40-
}
41-
42-
MarkCrossReferencesArgs* clr_gc_bridge_wait_for_processing () noexcept
43-
{
44-
return GCBridge::wait_for_processing ();
39+
return GCBridge::initialize_for_managed_processing (callback);
4540
}
4641

4742
void clr_gc_bridge_trigger_java_gc () noexcept

src/native/clr/include/host/gc-bridge.hh

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#pragma once
22

3-
#include <atomic>
43
#include <jni.h>
5-
#include <semaphore>
64

75
#include <shared/cpp-util.hh>
86
#include <host/os-bridge.hh>
@@ -48,6 +46,7 @@ struct MarkCrossReferencesArgs
4846
};
4947

5048
using BridgeProcessingFtn = void (*)(MarkCrossReferencesArgs*);
49+
using OnMarkCrossReferencesCallback = void (*)(MarkCrossReferencesArgs*);
5150

5251
namespace xamarin::android {
5352
class GCBridge
@@ -56,22 +55,16 @@ namespace xamarin::android {
5655
static void initialize_on_onload (JNIEnv *env) noexcept;
5756
static void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept;
5857

58+
// Initialize GC bridge for managed processing mode.
59+
// Takes a callback that will be invoked when mark_cross_references is called by the GC.
60+
// The callback is expected to queue the args and signal a managed thread to process them.
5961
// Returns the mark_cross_references function pointer for JavaMarshal.Initialize.
60-
// All GC bridge processing is done in managed code (C#).
61-
// This function just provides the callback pointer for the GC to invoke.
62-
static BridgeProcessingFtn initialize_for_managed_processing () noexcept
62+
static BridgeProcessingFtn initialize_for_managed_processing (OnMarkCrossReferencesCallback callback) noexcept
6363
{
64+
on_mark_cross_references_callback = callback;
6465
return mark_cross_references;
6566
}
6667

67-
// Wait for the next set of cross references to process (for managed processing mode)
68-
// Blocks until mark_cross_references is called by the GC
69-
static MarkCrossReferencesArgs* wait_for_processing () noexcept
70-
{
71-
shared_args_semaphore.acquire ();
72-
return shared_args.load ();
73-
}
74-
7568
static void trigger_java_gc (JNIEnv *env) noexcept;
7669

7770
// Trigger Java GC using the cached Runtime instance (for managed processing mode)
@@ -82,8 +75,7 @@ namespace xamarin::android {
8275
}
8376

8477
private:
85-
static inline std::binary_semaphore shared_args_semaphore{0};
86-
static inline std::atomic<MarkCrossReferencesArgs*> shared_args;
78+
static inline OnMarkCrossReferencesCallback on_mark_cross_references_callback = nullptr;
8779

8880
static inline jobject Runtime_instance = nullptr;
8981
static inline jmethodID Runtime_gc = nullptr;

src/native/clr/include/runtime-base/internal-pinvokes.hh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ extern "C" {
1717
bool clr_typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept;
1818

1919
// Functions for managed GC bridge processing mode
20-
BridgeProcessingFtn clr_gc_bridge_initialize_for_managed_processing () noexcept;
21-
MarkCrossReferencesArgs* clr_gc_bridge_wait_for_processing () noexcept;
20+
BridgeProcessingFtn clr_gc_bridge_initialize_for_managed_processing (OnMarkCrossReferencesCallback callback) noexcept;
2221
void clr_gc_bridge_trigger_java_gc () noexcept;
2322

2423
void monodroid_log (xamarin::android::LogLevel level, LogCategories category, const char *message) noexcept;

src/native/clr/pinvoke-override/generate-pinvoke-tables.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ const std::vector<std::string> internal_pinvoke_names = {
7070
"clr_typemap_managed_to_java",
7171
"clr_typemap_java_to_managed",
7272
"clr_gc_bridge_initialize_for_managed_processing",
73-
"clr_gc_bridge_wait_for_processing",
7473
"clr_gc_bridge_trigger_java_gc",
7574
"_monodroid_weak_gref_delete",
7675
"_monodroid_weak_gref_get",

src/native/clr/pinvoke-override/pinvoke-tables.include

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace {
1212
#if INTPTR_MAX == INT64_MAX
1313
//64-bit internal p/invoke table
14-
std::array<PinvokeEntry, 26> internal_pinvokes {{
14+
std::array<PinvokeEntry, 25> internal_pinvokes {{
1515
{0x34f89602cc44d0, "clr_gc_bridge_initialize_for_managed_processing", reinterpret_cast<void*>(&clr_gc_bridge_initialize_for_managed_processing)},
1616
{0x3b2467e7eadd4a6a, "_monodroid_lref_log_new", reinterpret_cast<void*>(&_monodroid_lref_log_new)},
1717
{0x423c8f539a2c56d2, "_monodroid_lookup_replacement_type", reinterpret_cast<void*>(&_monodroid_lookup_replacement_type)},
@@ -20,7 +20,6 @@ namespace {
2020
{0x5f0b4e426eff086b, "_monodroid_detect_cpu_and_architecture", reinterpret_cast<void*>(&_monodroid_detect_cpu_and_architecture)},
2121
{0x9099a4b95e3c3a89, "_monodroid_lref_log_delete", reinterpret_cast<void*>(&_monodroid_lref_log_delete)},
2222
{0x9187e6bc6294cacf, "clr_typemap_managed_to_java", reinterpret_cast<void*>(&clr_typemap_managed_to_java)},
23-
{0x9857d0d4f620e7d2, "clr_gc_bridge_wait_for_processing", reinterpret_cast<void*>(&clr_gc_bridge_wait_for_processing)},
2423
{0x9a946dfe9916a942, "clr_typemap_java_to_managed", reinterpret_cast<void*>(&clr_typemap_java_to_managed)},
2524
{0xa6ec846592d99536, "_monodroid_weak_gref_delete", reinterpret_cast<void*>(&_monodroid_weak_gref_delete)},
2625
{0xa7f58f3ee428cc6b, "_monodroid_gref_log_delete", reinterpret_cast<void*>(&_monodroid_gref_log_delete)},
@@ -545,8 +544,7 @@ constexpr hash_t system_security_cryptography_native_android_library_hash = 0x18
545544
constexpr hash_t system_globalization_native_library_hash = 0x28b5c8fca080abd5;
546545
#else
547546
//32-bit internal p/invoke table
548-
std::array<PinvokeEntry, 26> internal_pinvokes {{
549-
{0x6ba8719, "clr_gc_bridge_wait_for_processing", reinterpret_cast<void*>(&clr_gc_bridge_wait_for_processing)},
547+
std::array<PinvokeEntry, 25> internal_pinvokes {{
550548
{0xb7a486a, "monodroid_TypeManager_get_java_class_name", reinterpret_cast<void*>(&monodroid_TypeManager_get_java_class_name)},
551549
{0x1d6638a0, "clr_gc_bridge_initialize_for_managed_processing", reinterpret_cast<void*>(&clr_gc_bridge_initialize_for_managed_processing)},
552550
{0x21bee2a0, "clr_gc_bridge_trigger_java_gc", reinterpret_cast<void*>(&clr_gc_bridge_trigger_java_gc)},
@@ -1079,6 +1077,6 @@ constexpr hash_t system_security_cryptography_native_android_library_hash = 0x93
10791077
constexpr hash_t system_globalization_native_library_hash = 0xa66f1e5a;
10801078
#endif
10811079

1082-
constexpr size_t internal_pinvokes_count = 26;
1080+
constexpr size_t internal_pinvokes_count = 25;
10831081
constexpr size_t dotnet_pinvokes_count = 491;
10841082
} // end of anonymous namespace

0 commit comments

Comments
 (0)