[sgen] Implement a simple thread pool and do concurrent sweep with it.
authorMark Probst <mark.probst@gmail.com>
Fri, 13 Feb 2015 17:58:02 +0000 (09:58 -0800)
committerMark Probst <mark.probst@gmail.com>
Thu, 2 Apr 2015 23:41:30 +0000 (16:41 -0700)
We cannot start threads during stop-the-world pauses (some thread APIs,
including pthreads, can deadlock), and we want to do marking and sweeping
with the same threads.  This new thread pool facility will allow that.

man/mono.1
mono/metadata/Makefile.am
mono/metadata/sgen-marksweep.c
mono/metadata/sgen-thread-pool.c [new file with mode: 0644]
mono/metadata/sgen-thread-pool.h [new file with mode: 0644]

index 17ec6e294460d086b2082f88c209996ff536c1cb..7cd68eaef4add3d6cf223f5625812c422cef23cf 100644 (file)
@@ -1129,9 +1129,16 @@ to 100 percent.  A value of 0 turns evacuation off.
 .TP
 \fB(no-)lazy-sweep\fR
 Enables or disables lazy sweep for the Mark&Sweep collector.  If
-enabled, the sweep phase of the garbage collection is done piecemeal
-whenever the need arises, typically during nursery collections.  Lazy
-sweeping is enabled by default.
+enabled, the sweeping of individual major heap blocks is done
+piecemeal whenever the need arises, typically during nursery
+collections.  Lazy sweeping is enabled by default.
+.TP
+\fB(no-)concurrent-sweep\fR
+Enables or disables concurrent sweep for the Mark&Sweep collector.  If
+enabled, the iteration of all major blocks to determine which ones can
+be freed and which ones have to be kept and swept, is done
+concurrently with the running program.  Concurrent sweeping is enabled
+by default.
 .TP
 \fBstack-mark=\fImark-mode\fR
 Specifies how application threads should be scanned. Options are
index 0a6e4067d19b5f9fdf0d342c92d98c5d7c8f4919..0cbcf73f826fe2d00c8e2c518b5309b6588e36ce 100644 (file)
@@ -291,6 +291,8 @@ sgen_sources = \
        sgen-layout-stats.h     \
        sgen-qsort.c    \
        sgen-qsort.h    \
+       sgen-thread-pool.c      \
+       sgen-thread-pool.h      \
        sgen-tagged-pointer.h
 
 libmonoruntime_la_SOURCES = $(common_sources) $(gc_dependent_sources) $(null_gc_sources) $(boehm_sources)
index 892dcfbb3af110cf104d72066b5fe3bd2326cc31..57a52e19176de1171c3a6713c1f46d530acea9f7 100644 (file)
@@ -43,6 +43,7 @@
 #include "metadata/sgen-pointer-queue.h"
 #include "metadata/sgen-pinning.h"
 #include "metadata/sgen-workers.h"
+#include "metadata/sgen-thread-pool.h"
 
 #if defined(ARCH_MIN_MS_BLOCK_SIZE) && defined(ARCH_MIN_MS_BLOCK_SIZE_SHIFT)
 #define MS_BLOCK_SIZE  ARCH_MIN_MS_BLOCK_SIZE
@@ -188,6 +189,7 @@ enum {
 static volatile int sweep_state = SWEEP_STATE_SWEPT;
 
 static gboolean concurrent_mark;
+static gboolean concurrent_sweep = TRUE;
 
 #define BLOCK_IS_TAGGED_HAS_REFERENCES(bl)     SGEN_POINTER_IS_TAGGED_1 ((bl))
 #define BLOCK_TAG_HAS_REFERENCES(bl)           SGEN_POINTER_TAG_1 ((bl))
@@ -1517,12 +1519,11 @@ ensure_block_is_checked_for_sweeping (int block_index, gboolean wait, gboolean *
        return !!tagged_block;
 }
 
-static mono_native_thread_return_t
-sweep_loop_thread_func (void *dummy)
+static void
+sweep_job_func (SgenThreadPoolJob *job)
 {
        int block_index;
        int num_blocks = num_major_sections_before_sweep;
-       int small_id = mono_thread_info_register_small_id ();
 
        SGEN_ASSERT (0, sweep_in_progress (), "Sweep thread called with wrong state");
        SGEN_ASSERT (0, num_blocks <= allocated_blocks.next_slot, "How did we lose blocks?");
@@ -1556,10 +1557,6 @@ sweep_loop_thread_func (void *dummy)
        sgen_pointer_queue_remove_nulls (&allocated_blocks);
 
        sweep_finish ();
-
-       mono_thread_small_id_free (small_id);
-
-       return NULL;
 }
 
 static void
@@ -1593,7 +1590,7 @@ sweep_finish (void)
        set_sweep_state (SWEEP_STATE_SWEPT, SWEEP_STATE_COMPACTING);
 }
 
-static MonoNativeThreadId sweep_loop_thread;
+static SgenThreadPoolJob sweep_job;
 
 static void
 major_sweep (void)
@@ -1607,14 +1604,11 @@ major_sweep (void)
        num_major_sections_before_sweep = num_major_sections;
        num_major_sections_freed_in_sweep = 0;
 
-       if (TRUE /*concurrent_mark*/) {
-               /*
-                * FIXME: We can't create a thread while the world is stopped because it
-                * might deadlock.  `finalizer-wait.exe` exposes this.
-                */
-               mono_native_thread_create (&sweep_loop_thread, sweep_loop_thread_func, NULL);
+       if (concurrent_sweep) {
+               sgen_thread_pool_job_init (&sweep_job, sweep_job_func);
+               sgen_thread_pool_job_enqueue (&sweep_job);
        } else {
-               sweep_loop_thread_func (NULL);
+               sweep_job_func (NULL);
        }
 }
 
@@ -2035,6 +2029,12 @@ major_handle_gc_param (const char *opt)
        } else if (!strcmp (opt, "no-lazy-sweep")) {
                lazy_sweep = FALSE;
                return TRUE;
+       } else if (!strcmp (opt, "concurrent-sweep")) {
+               concurrent_sweep = TRUE;
+               return TRUE;
+       } else if (!strcmp (opt, "no-concurrent-sweep")) {
+               concurrent_sweep = FALSE;
+               return TRUE;
        }
 
        return FALSE;
@@ -2047,6 +2047,7 @@ major_print_gc_param_usage (void)
                        ""
                        "  evacuation-threshold=P (where P is a percentage, an integer in 0-100)\n"
                        "  (no-)lazy-sweep\n"
+                       "  (no-)concurrent-sweep\n"
                        );
 }
 
@@ -2468,6 +2469,9 @@ sgen_marksweep_init_internal (SgenMajorCollector *collector, gboolean is_concurr
        mono_mutex_init (&scanned_objects_list_lock);
 #endif
 
+       if (concurrent_sweep)
+               sgen_thread_pool_init (1);
+
        SGEN_ASSERT (0, SGEN_MAX_SMALL_OBJ_SIZE <= MS_BLOCK_FREE / 2, "MAX_SMALL_OBJ_SIZE must be at most MS_BLOCK_FREE / 2");
 
        /*cardtable requires major pages to be 8 cards aligned*/
diff --git a/mono/metadata/sgen-thread-pool.c b/mono/metadata/sgen-thread-pool.c
new file mode 100644 (file)
index 0000000..2243b93
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * sgen-thread-pool.c: Threadpool for all concurrent GC work.
+ *
+ * Copyright (C) 2015 Xamarin Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License 2.0 as published by the Free Software Foundation;
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License 2.0 along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "config.h"
+#ifdef HAVE_SGEN_GC
+
+#include "mono/metadata/sgen-gc.h"
+#include "mono/metadata/sgen-thread-pool.h"
+#include "mono/metadata/sgen-pointer-queue.h"
+#include "mono/utils/mono-mutex.h"
+#include "mono/utils/mono-threads.h"
+
+static mono_mutex_t lock;
+static mono_cond_t work_cond;
+static mono_cond_t done_cond;
+
+static MonoNativeThreadId thread;
+
+/* Only accessed with the lock held. */
+static SgenPointerQueue job_queue;
+
+enum {
+       STATE_WAITING,
+       STATE_IN_PROGRESS,
+       STATE_DONE
+};
+
+/* Assumes that the lock is held. */
+static SgenThreadPoolJob*
+get_job (void)
+{
+       for (size_t i = 0; i < job_queue.next_slot; ++i) {
+               SgenThreadPoolJob *job = job_queue.data [i];
+               if (job->state == STATE_WAITING)
+                       return job;
+       }
+       return NULL;
+}
+
+/* Assumes that the lock is held. */
+static void
+remove_job (SgenThreadPoolJob *job)
+{
+       gboolean found = FALSE;
+       SGEN_ASSERT (0, job->state == STATE_DONE, "Why are we removing a job that's not done?");
+       for (size_t i = 0; i < job_queue.next_slot; ++i) {
+               if (job_queue.data [i] == job) {
+                       job_queue.data [i] = NULL;
+                       found = TRUE;
+                       break;
+               }
+       }
+       SGEN_ASSERT (0, found, "Why is the job we're trying to remove not in the queue?");
+       sgen_pointer_queue_remove_nulls (&job_queue);
+}
+
+static mono_native_thread_return_t
+thread_func (void *arg)
+{
+       mono_thread_info_register_small_id ();
+
+       mono_mutex_lock (&lock);
+       for (;;) {
+               SgenThreadPoolJob *job;
+
+               while (!(job = get_job ()))
+                       mono_cond_wait (&work_cond, &lock);
+               SGEN_ASSERT (0, job->state == STATE_WAITING, "The job we got is in the wrong state.  Should be waiting.");
+               job->state = STATE_IN_PROGRESS;
+               mono_mutex_unlock (&lock);
+
+               job->func (job);
+
+               mono_mutex_lock (&lock);
+               SGEN_ASSERT (0, job->state == STATE_IN_PROGRESS, "The job should still be in progress.");
+               job->state = STATE_DONE;
+               remove_job (job);
+               /*
+                * Only the main GC thread will ever wait on the done condition, so we don't
+                * have to broadcast.
+                */
+               mono_cond_signal (&done_cond);
+       }
+}
+
+void
+sgen_thread_pool_init (int num_threads)
+{
+       SGEN_ASSERT (0, num_threads == 1, "We only support 1 thread pool thread for now.");
+
+       mono_mutex_init (&lock);
+       mono_cond_init (&work_cond, NULL);
+       mono_cond_init (&done_cond, NULL);
+
+       mono_native_thread_create (&thread, thread_func, NULL);
+}
+
+void
+sgen_thread_pool_job_init (SgenThreadPoolJob *job, SgenThreadPoolJobFunc func)
+{
+       job->state = STATE_WAITING;
+       job->func = func;
+}
+
+void
+sgen_thread_pool_job_enqueue (SgenThreadPoolJob *job)
+{
+       mono_mutex_lock (&lock);
+
+       sgen_pointer_queue_add (&job_queue, job);
+       /*
+        * FIXME: We could check whether there is a job in progress.  If there is, there's
+        * no need to signal the condition, at least as long as we have only one thread.
+        */
+       mono_cond_signal (&work_cond);
+
+       mono_mutex_unlock (&lock);
+}
+
+void
+sgen_thread_pool_wait_for_all_jobs (void)
+{
+       mono_mutex_lock (&lock);
+
+       while (!sgen_pointer_queue_is_empty (&job_queue))
+               mono_cond_wait (&done_cond, &lock);
+
+       mono_mutex_unlock (&lock);
+}
+
+#endif
diff --git a/mono/metadata/sgen-thread-pool.h b/mono/metadata/sgen-thread-pool.h
new file mode 100644 (file)
index 0000000..bee74b6
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * sgen-thread-pool.h: Threadpool for all concurrent GC work.
+ *
+ * Copyright (C) 2015 Xamarin Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License 2.0 as published by the Free Software Foundation;
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License 2.0 along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __MONO_SGEN_THREAD_POOL_H__
+#define __MONO_SGEN_THREAD_POOL_H__
+
+typedef struct _SgenThreadPoolJob SgenThreadPoolJob;
+
+typedef void (*SgenThreadPoolJobFunc) (SgenThreadPoolJob *job);
+
+struct _SgenThreadPoolJob {
+       SgenThreadPoolJobFunc func;
+       volatile gint32 state;
+};
+
+void sgen_thread_pool_init (int num_threads);
+
+void sgen_thread_pool_job_init (SgenThreadPoolJob *job, SgenThreadPoolJobFunc func);
+void sgen_thread_pool_job_enqueue (SgenThreadPoolJob *job);
+
+void sgen_thread_pool_wait_for_all_jobs (void);
+
+#endif