Allow bridge callbacks to be set after GC init
authorAndi McClure <andi.mcclure@xamarin.com>
Tue, 12 Jul 2016 22:31:58 +0000 (18:31 -0400)
committerAndi McClure <andi.mcclure@xamarin.com>
Tue, 12 Jul 2016 22:31:58 +0000 (18:31 -0400)
This is cleanup to the previous emergency fix for bug 42469. The goal of that fix was to defer bridge processor initialization until after sgen init, but it introduced a requirement mono_gc_register_bridge_callback be called before sgen init. This breaks Xamarin Designer, which sets up callbacks late.

This patch introduces an sgen_init_bridge () which is called both at startup and after callbacks are set, and performs the initialization once all pieces are in place. Additional concurrency and style issues are also addressed.

mono/metadata/sgen-bridge-internals.h
mono/metadata/sgen-bridge.c
mono/metadata/sgen-bridge.h
mono/sgen/sgen-gc.c
mono/sgen/sgen-gc.h

index 129582ed778f3e95c4588983d85f37bf8416403c..0597bdd7882fdd994391eadbe65c84ca58c0c181 100644 (file)
@@ -64,7 +64,7 @@ void sgen_new_bridge_init (SgenBridgeProcessor *collector);
 void sgen_tarjan_bridge_init (SgenBridgeProcessor *collector);
 void sgen_set_bridge_implementation (const char *name);
 void sgen_bridge_set_dump_prefix (const char *prefix);
-void sgen_init_bridge_processor();
+void sgen_init_bridge (void);
 
 #endif
 
index a1f0f51fbbb940f3494b108dc9a7be3dea5f3cf7..bf0c523399918da0ad7e6012ff2cc7992a29cd83 100644 (file)
 #include "sgen/sgen-hash-table.h"
 #include "sgen/sgen-qsort.h"
 #include "utils/mono-logger-internals.h"
-
+#include "utils/mono-once.h"
+
+typedef enum {
+       BRIDGE_PROCESSOR_INVALID,
+       BRIDGE_PROCESSOR_OLD,
+       BRIDGE_PROCESSOR_NEW,
+       BRIDGE_PROCESSOR_TARJAN,
+       BRIDGE_PROCESSOR_DEFAULT = BRIDGE_PROCESSOR_TARJAN
+} BridgeProcessorSelection;
+
+// Bridge processor type pending / in use
+static BridgeProcessorSelection bridge_processor_selection = BRIDGE_PROCESSOR_DEFAULT;
+// Most recently requested callbacks
+static MonoGCBridgeCallbacks pending_bridge_callbacks;
+// Currently-in-use callbacks
 MonoGCBridgeCallbacks bridge_callbacks;
+
+// Bridge processor state
 static SgenBridgeProcessor bridge_processor;
+// This is used for a special debug feature
 static SgenBridgeProcessor compare_to_bridge_processor;
 
 volatile gboolean bridge_processing_in_progress = FALSE;
 
+// Mutex guarding pending_bridge_callbacks
+// Must use OS mutex because can be used before runtime init
+static mono_mutex_t pending_bridge_callbacks_mutex;
+
+static void
+pending_bridge_callbacks_mutex_init ()
+{
+       mono_os_mutex_init (&pending_bridge_callbacks_mutex);
+}
+
+static void
+pending_bridge_callbacks_mutex_lock ()
+{
+       static mono_once_t init_once=MONO_ONCE_INIT;
+       mono_once (&init_once, pending_bridge_callbacks_mutex_init);
+       mono_os_mutex_lock (&pending_bridge_callbacks_mutex);
+}
+
+static void
+pending_bridge_callbacks_mutex_unlock ()
+{
+       mono_os_mutex_unlock (&pending_bridge_callbacks_mutex);
+}
+
+// FIXME: The current usage pattern for this function is unsafe. Bridge processing could start immediately after unlock
 void
 mono_gc_wait_for_bridge_processing (void)
 {
@@ -39,46 +81,105 @@ mono_gc_wait_for_bridge_processing (void)
        sgen_gc_unlock ();
 }
 
+// This function is currently called only once, but it can be called at any time, including before or during sgen initialization.
 void
 mono_gc_register_bridge_callbacks (MonoGCBridgeCallbacks *callbacks)
 {
        if (callbacks->bridge_version != SGEN_BRIDGE_VERSION)
                g_error ("Invalid bridge callback version. Expected %d but got %d\n", SGEN_BRIDGE_VERSION, callbacks->bridge_version);
 
-       bridge_callbacks = *callbacks;
+       // Defer assigning to bridge_callbacks until we have the gc lock.
+       pending_bridge_callbacks_mutex_lock ();
+       pending_bridge_callbacks = *callbacks;
+       pending_bridge_callbacks_mutex_unlock ();
+
+       // If sgen has started, will assign bridge callbacks and init bridge
+       sgen_init_bridge ();
 }
 
-static gboolean
-init_bridge_processor_by_name (SgenBridgeProcessor *processor, const char *name)
+static BridgeProcessorSelection
+bridge_processor_name (const char *name)
 {
        if (!strcmp ("old", name)) {
-               memset (processor, 0, sizeof (SgenBridgeProcessor));
-               sgen_old_bridge_init (processor);
+               return BRIDGE_PROCESSOR_OLD;
        } else if (!strcmp ("new", name)) {
-               memset (processor, 0, sizeof (SgenBridgeProcessor));
-               sgen_new_bridge_init (processor);
+               return BRIDGE_PROCESSOR_NEW;
        } else if (!strcmp ("tarjan", name)) {
-               memset (processor, 0, sizeof (SgenBridgeProcessor));
-               sgen_tarjan_bridge_init (processor);
+               return BRIDGE_PROCESSOR_TARJAN;
        } else {
-               return FALSE;
+               return BRIDGE_PROCESSOR_INVALID;
        }
-       return TRUE;
 }
 
+// Initialize a single bridge processor
+static void
+init_bridge_processor (SgenBridgeProcessor *processor, BridgeProcessorSelection selection)
+{
+       memset (processor, 0, sizeof (SgenBridgeProcessor));
+
+       switch (selection) {
+               case BRIDGE_PROCESSOR_OLD:
+                       sgen_old_bridge_init (processor);
+                       break;
+               case BRIDGE_PROCESSOR_NEW:
+                       sgen_new_bridge_init (processor);
+                       break;
+               case BRIDGE_PROCESSOR_TARJAN:
+                       sgen_tarjan_bridge_init (processor);
+                       break;
+               default:
+                       g_assert_not_reached ();
+       }
+}
+
+/*
+ * Initializing the sgen bridge consists of setting the bridge callbacks,
+ * and initializing the bridge processor. Init should follow these rules:
+ *
+ *   - Init happens only after sgen is initialized (because we don't
+ *     know which bridge processor to initialize until then, and also
+ *     to allow bridge processor init to interact with sgen if it wants)
+ *
+ *   - Init happens only after mono_gc_register_bridge_callbacks is called
+ *
+ *   - Init should not happen concurrently with a GC (because a GC will
+ *     call sgen_need_bridge_processing at various times)
+ *
+ *   - Initializing the bridge processor should happen only once
+ *
+ * We call sgen_init_bridge when the callbacks are set, and also when sgen
+ * is done initing. Actual initialization then only occurs if it is ready.
+ */
 void
-sgen_init_bridge_processor()
+sgen_init_bridge ()
 {
-       // If a bridge was registered but there is no bridge processor yet, init defaults
-       if (bridge_callbacks.cross_references && !bridge_processor.reset_data)
-               sgen_tarjan_bridge_init (&bridge_processor);
+       if (sgen_gc_initialized ()) {
+               // This lock is not initialized until the GC is
+               sgen_gc_lock ();
+
+               pending_bridge_callbacks_mutex_lock ();
+               bridge_callbacks = pending_bridge_callbacks;
+               pending_bridge_callbacks_mutex_unlock ();
+
+               // If a bridge was registered but there is no bridge processor yet
+               if (bridge_callbacks.cross_references && !bridge_processor.reset_data)
+                       init_bridge_processor (&bridge_processor, bridge_processor_selection);
+
+               sgen_gc_unlock ();
+       }
 }
 
 void
 sgen_set_bridge_implementation (const char *name)
 {
-       if (!init_bridge_processor_by_name (&bridge_processor, name))
-               g_warning ("Invalid value for bridge implementation, valid values are: 'new', 'old' and 'tarjan'.");
+       BridgeProcessorSelection selection = bridge_processor_name (name);
+
+       if (selection == BRIDGE_PROCESSOR_INVALID)
+               g_warning ("Invalid value for bridge processor implementation, valid values are: 'new', 'old' and 'tarjan'.");
+       else if (bridge_processor.reset_data)
+               g_warning ("Cannot set bridge processor implementation once bridge has already started");
+       else
+               bridge_processor_selection = selection;
 }
 
 gboolean
@@ -593,11 +694,10 @@ sgen_bridge_handle_gc_debug (const char *opt)
                set_dump_prefix (prefix);
        } else if (g_str_has_prefix (opt, "bridge-compare-to=")) {
                const char *name = strchr (opt, '=') + 1;
-               if (init_bridge_processor_by_name (&compare_to_bridge_processor, name)) {
-                       if (compare_to_bridge_processor.reset_data == bridge_processor.reset_data) {
-                               g_warning ("Cannot compare bridge implementation to itself - ignoring.");
-                               memset (&compare_to_bridge_processor, 0, sizeof (SgenBridgeProcessor));
-                       }
+               BridgeProcessorSelection selection = bridge_processor_name (name);
+
+               if (selection != BRIDGE_PROCESSOR_INVALID) {
+                       init_bridge_processor (&compare_to_bridge_processor, selection);
                } else {
                        g_warning ("Invalid bridge implementation to compare against - ignoring.");
                }
index 2292225e08369ee5bbf8da799ddfa259a1b655c9..c1cb4ac1def76fcbe78d21520998cbbee0bf34c9 100644 (file)
@@ -95,7 +95,6 @@ typedef struct {
        void (*cross_references) (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs);
 } MonoGCBridgeCallbacks;
 
-// Clients should call before initializing runtime.
 MONO_API void mono_gc_register_bridge_callbacks (MonoGCBridgeCallbacks *callbacks);
 
 MONO_API void mono_gc_wait_for_bridge_processing (void);
index b510a4fc43b77c222100da152262c457f507d79e..3dac1df3d8dfb82cc4c835eba8d0b59c4c5ad603 100644 (file)
@@ -3142,9 +3142,15 @@ sgen_gc_init (void)
 
        sgen_register_root (NULL, 0, sgen_make_user_root_descriptor (sgen_mark_normal_gc_handles), ROOT_TYPE_NORMAL, MONO_ROOT_SOURCE_GC_HANDLE, "normal gc handles");
 
-       sgen_init_bridge_processor();
-
        gc_initialized = 1;
+
+       sgen_init_bridge ();
+}
+
+gboolean
+sgen_gc_initialized ()
+{
+       return gc_initialized > 0;
 }
 
 NurseryClearPolicy
index 192c570f08b23c655bbb5d3dff83d131de14a7e6..003c3a5fa1dfe4de3f53d3534c7473505c0e9df9 100644 (file)
@@ -303,6 +303,8 @@ void sgen_check_section_scan_starts (GCMemSection *section);
 
 void sgen_conservatively_pin_objects_from (void **start, void **end, void *start_nursery, void *end_nursery, int pin_type);
 
+gboolean sgen_gc_initialized (void);
+
 /* Keep in sync with description_for_type() in sgen-internal.c! */
 enum {
        INTERNAL_MEM_PIN_QUEUE,