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
51 changes: 47 additions & 4 deletions src/native/clr/host/gc-bridge.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#include <cerrno>
#include <pthread.h>
#include <semaphore.h>

#include <host/gc-bridge.hh>
#include <host/bridge-processing.hh>
#include <host/os-bridge.hh>
Expand All @@ -7,6 +11,41 @@

using namespace xamarin::android;

void GCBridge::initialize_shared_args_semaphore () noexcept
{
int ret = sem_init (&shared_args_semaphore, 0, 0);
abort_unless (ret == 0, "Failed to initialize GC bridge semaphore");
}

void GCBridge::start_bridge_processing_thread () noexcept
{
pthread_t thread {};
int ret = pthread_create (&thread, nullptr, bridge_processing_thread_entry, nullptr);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 💡 Native C++ — Consider creating the worker already-detached via pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED) before pthread_create, matching the sibling pattern in assembly-store.cc (start_writer_locked). The current create-then-pthread_detach sequence works, but a single-step detached creation is consistent with the rest of the CLR host and avoids a separate failure/abort path.

While here, pthread_setname_np (thread, "gc-bridge") would make this worker identifiable in logcat/tombstones and traces — helpful given it runs the GC bridge on every collection.

(Rule: Consistency with repo native patterns)

abort_unless (ret == 0, "Failed to create GC bridge processing thread");

ret = pthread_detach (thread);
abort_unless (ret == 0, "Failed to detach GC bridge processing thread");
}

void GCBridge::publish_shared_args (MarkCrossReferencesArgs *args) noexcept
{
__atomic_store_n (&shared_args, args, __ATOMIC_RELEASE);

int ret = sem_post (&shared_args_semaphore);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 💡 Native C++sem_post/sem_wait already establish a happens-before relationship, so the explicit __ATOMIC_RELEASE/__ATOMIC_ACQUIRE on shared_args are technically redundant for this single-slot handoff (which the header comment nicely documents). Keeping them as belt-and-suspenders is fine, but a one-line note here that the semaphore is the real synchronization point — and the atomics just make the intent explicit — would prevent a future reader from assuming lock-free concurrent access is intended.

(Rule: Documentation of concurrency intent)

abort_unless (ret == 0, "Failed to release GC bridge semaphore");
}

auto GCBridge::wait_for_shared_args () noexcept -> MarkCrossReferencesArgs*
{
int ret;
do {
ret = sem_wait (&shared_args_semaphore);
} while (ret == -1 && errno == EINTR);
abort_unless (ret == 0, "Failed to acquire GC bridge semaphore");

return __atomic_load_n (&shared_args, __ATOMIC_ACQUIRE);
}

void GCBridge::initialize_on_onload (JNIEnv *env) noexcept
{
abort_if_invalid_pointer_argument (env, "env");
Expand Down Expand Up @@ -55,8 +94,7 @@ void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept
abort_unless (args->CrossReferences != nullptr || args->CrossReferenceCount == 0, "CrossReferences must not be null if CrossReferenceCount is greater than 0");
log_mark_cross_references_args_if_enabled (args);

shared_args.store (args);
shared_args_semaphore.release ();
publish_shared_args (args);
}

void GCBridge::bridge_processing () noexcept
Expand All @@ -66,8 +104,7 @@ void GCBridge::bridge_processing () noexcept

while (true) {
// wait until mark cross references args are set by the GC callback
shared_args_semaphore.acquire ();
MarkCrossReferencesArgs *args = shared_args.load ();
MarkCrossReferencesArgs *args = wait_for_shared_args ();

bridge_processing_started_callback (args);

Expand All @@ -78,6 +115,12 @@ void GCBridge::bridge_processing () noexcept
}
}

auto GCBridge::bridge_processing_thread_entry ([[maybe_unused]] void *arg) noexcept -> void*
{
bridge_processing ();
return nullptr;
}

[[gnu::always_inline]]
void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept
{
Expand Down
26 changes: 15 additions & 11 deletions src/native/clr/include/host/gc-bridge.hh
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#pragma once

#include <atomic>
#include <semaphore.h>

#include <jni.h>
#include <thread>
#include <semaphore>
#include <shared_mutex>
#include <unordered_map>

#include <shared/cpp-util.hh>

Expand Down Expand Up @@ -72,27 +69,34 @@ namespace xamarin::android {
GCBridge::bridge_processing_started_callback = bridge_processing_started;
GCBridge::bridge_processing_finished_callback = bridge_processing_finished;

bridge_processing_thread = std::thread { GCBridge::bridge_processing };
bridge_processing_thread.detach ();
initialize_shared_args_semaphore ();
start_bridge_processing_thread ();

return mark_cross_references;
}

static void trigger_java_gc (JNIEnv *env) noexcept;

private:
static inline std::thread bridge_processing_thread {};

static inline std::binary_semaphore shared_args_semaphore{0};
static inline std::atomic<MarkCrossReferencesArgs*> shared_args;
static inline sem_t shared_args_semaphore {};
// JavaMarshal serializes bridge rounds: it does not publish another argument block until
// bridge_processing_finished_callback has completed. This is therefore a single-slot
// handoff; the semaphore signals availability but does not queue distinct argument blocks.
static inline MarkCrossReferencesArgs *shared_args = nullptr;

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

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

static void initialize_shared_args_semaphore () noexcept;
static void start_bridge_processing_thread () noexcept;
static void publish_shared_args (MarkCrossReferencesArgs *args) noexcept;
static auto wait_for_shared_args () noexcept -> MarkCrossReferencesArgs*;

static void bridge_processing () noexcept;
static auto bridge_processing_thread_entry (void *arg) noexcept -> void*;
static void mark_cross_references (MarkCrossReferencesArgs *args) noexcept;

static void log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept;
Expand Down
Loading