[runtime] Cycle the finalizer thread if there are too many outstanding hazard pointer...
authorRodrigo Kumpera <kumpera@gmail.com>
Thu, 31 Mar 2016 00:38:43 +0000 (17:38 -0700)
committerRodrigo Kumpera <kumpera@gmail.com>
Thu, 31 Mar 2016 00:38:43 +0000 (17:38 -0700)
We add a callback to the HP so the GC can monitor the queue size and act on it.

The queue size was picked to keep the overhead limited even under high load.

mono/metadata/gc.c
mono/utils/hazard-pointer.c
mono/utils/hazard-pointer.h

index 98769adf537abfb29dae81c873ee5586fa37fcf9..36f896ce929b4bb80b9a79c740a63c08438481a7 100644 (file)
@@ -39,6 +39,7 @@
 #include <mono/utils/mono-threads.h>
 #include <mono/utils/atomic.h>
 #include <mono/utils/mono-coop-semaphore.h>
+#include <mono/utils/hazard-pointer.h>
 
 #ifndef HOST_WIN32
 #include <pthread.h>
@@ -638,6 +639,41 @@ mono_gc_finalize_notify (void)
        mono_coop_sem_post (&finalizer_sem);
 }
 
+/*
+This is the number of entries allowed in the hazard free queue before
+we explicitly cycle the finalizer thread to trigger pumping the queue.
+
+It was picked empirically by running the corlib test suite in a stress
+scenario where all hazard entries are queued.
+
+In this extreme scenario we double the number of times we cycle the finalizer
+thread compared to just GC calls.
+
+Entries are usually in the order of 100's of bytes each, so we're limiting
+floating garbage to be in the order of a dozen kb.
+*/
+static gboolean finalizer_thread_pulsed;
+#define HAZARD_QUEUE_OVERFLOW_SIZE 20
+
+static void
+hazard_free_queue_is_too_big (size_t size)
+{
+       if (size < HAZARD_QUEUE_OVERFLOW_SIZE)
+               return;
+
+       if (finalizer_thread_pulsed || InterlockedCompareExchange (&finalizer_thread_pulsed, TRUE, FALSE))
+               return;
+
+       mono_gc_finalize_notify ();
+}
+
+static void
+hazard_free_queue_pump (void)
+{
+       mono_thread_hazardous_try_free_all ();
+       finalizer_thread_pulsed = FALSE;
+}
+
 #ifdef HAVE_BOEHM_GC
 
 static void
@@ -713,6 +749,9 @@ finalizer_thread (gpointer unused)
 {
        gboolean wait = TRUE;
 
+       /* Register a hazard free queue pump callback */
+       mono_hazard_pointer_install_free_queue_size_callback (hazard_free_queue_is_too_big);
+
        while (!finished) {
                /* Wait to be notified that there's at least one
                 * finaliser to run
@@ -757,7 +796,7 @@ finalizer_thread (gpointer unused)
 
                reference_queue_proccess_all ();
 
-               mono_thread_hazardous_try_free_all ();
+               hazard_free_queue_pump ();
 
                /* Avoid posting the pending done event until there are pending finalizers */
                if (mono_coop_sem_timedwait (&finalizer_sem, 0, MONO_SEM_FLAGS_NONE) == 0) {
index 0613e9d18c930088c7e41feec6434a933f09bc7d..de3dc113fb16170a056369d1254d82ea84c1e5b9 100644 (file)
@@ -42,6 +42,7 @@ typedef struct {
 
 static volatile int hazard_table_size = 0;
 static MonoThreadHazardPointers * volatile hazard_table = NULL;
+static MonoHazardFreeQueueSizeCallback queue_size_cb;
 
 /*
  * Each entry is either 0 or 1, indicating whether that overflow small
@@ -351,9 +352,19 @@ mono_thread_hazardous_queue_free (gpointer p, MonoHazardousFreeFunc free_func)
        InterlockedIncrement (&hazardous_pointer_count);
 
        mono_lock_free_array_queue_push (&delayed_free_queue, &item);
+
+       guint32 queue_size = delayed_free_queue.num_used_entries;
+       if (queue_size && queue_size_cb)
+               queue_size_cb (queue_size);
 }
 
 
+void
+mono_hazard_pointer_install_free_queue_size_callback (MonoHazardFreeQueueSizeCallback cb)
+{
+       queue_size_cb = cb;
+}
+
 void
 mono_thread_hazardous_try_free_all (void)
 {
index d756c311e0f8166be37ce38f983b72f51a703fae..4805eede2bf3135475602bca4b45f14aea56ebc5 100644 (file)
@@ -58,6 +58,9 @@ int mono_thread_small_id_alloc (void);
 int mono_hazard_pointer_save_for_signal_handler (void);
 void mono_hazard_pointer_restore_for_signal_handler (int small_id);
 
+typedef void (*MonoHazardFreeQueueSizeCallback)(size_t size);
+void mono_hazard_pointer_install_free_queue_size_callback (MonoHazardFreeQueueSizeCallback cb);
+
 void mono_thread_smr_init (void);
 void mono_thread_smr_cleanup (void);
 #endif /*__MONO_HAZARD_POINTER_H__*/