[sgen] Workers use thread pool.
[mono.git] / mono / metadata / sgen-workers.c
index 1147aefc103376b1680d8838bca2bdac57462028..f54362656101a765c0a40309c9b90e301846d95e 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "metadata/sgen-gc.h"
 #include "metadata/sgen-workers.h"
+#include "metadata/sgen-thread-pool.h"
 #include "utils/mono-counters.h"
 
 static int workers_num;
@@ -33,280 +34,177 @@ static void *workers_gc_thread_major_collector_data = NULL;
 static SgenSectionGrayQueue workers_distribute_gray_queue;
 static gboolean workers_distribute_gray_queue_inited;
 
-static volatile gboolean workers_marking = FALSE;
-static gboolean workers_started = FALSE;
+enum {
+       STATE_NOT_WORKING,
+       STATE_WORKING,
+       STATE_NURSERY_COLLECTION
+} WorkersStateName;
 
 typedef union {
        gint32 value;
        struct {
-               /*
-                * Decremented by the main thread and incremented by
-                * worker threads.
-                */
-               guint32 num_waiting : 8;
-               /* Set by worker threads and reset by the main thread. */
-               guint32 done_posted : 1;
-               /* Set by the main thread. */
-               guint32 gc_in_progress : 1;
+               guint state : 4; /* WorkersStateName */
        } data;
 } State;
 
 static volatile State workers_state;
 
-static MonoSemType workers_waiting_sem;
-static MonoSemType workers_done_sem;
-
-static volatile int workers_job_queue_num_entries = 0;
-static volatile JobQueueEntry *workers_job_queue = NULL;
-static LOCK_DECLARE (workers_job_queue_mutex);
-static int workers_num_jobs_enqueued = 0;
-static volatile int workers_num_jobs_finished = 0;
-
-static long long stat_workers_stolen_from_self_lock;
-static long long stat_workers_stolen_from_self_no_lock;
-static long long stat_workers_stolen_from_others;
-static long long stat_workers_num_waited;
+static guint64 stat_workers_num_finished;
 
 static gboolean
 set_state (State old_state, State new_state)
 {
+       if (old_state.data.state == STATE_NURSERY_COLLECTION)
+               SGEN_ASSERT (0, new_state.data.state != STATE_NOT_WORKING, "Can't go from nursery collection to not working");
+
        return InterlockedCompareExchange (&workers_state.value,
                        new_state.value, old_state.value) == old_state.value;
 }
 
 static void
-workers_wake_up (int max)
+assert_not_working (State state)
 {
-       int i;
+       SGEN_ASSERT (0, state.data.state == STATE_NOT_WORKING, "Can only signal enqueue work when in no work state");
 
-       for (i = 0; i < max; ++i) {
-               State old_state, new_state;
-               do {
-                       old_state = new_state = workers_state;
-                       /*
-                        * We must not wake workers up once done has
-                        * been posted.
-                        */
-                       if (old_state.data.done_posted)
-                               return;
-                       if (old_state.data.num_waiting == 0)
-                               return;
-                       --new_state.data.num_waiting;
-               } while (!set_state (old_state, new_state));
-               MONO_SEM_POST (&workers_waiting_sem);
-       }
 }
 
 static void
-workers_wake_up_all (void)
+assert_working (State state, gboolean from_worker)
 {
-       workers_wake_up (workers_num);
+       SGEN_ASSERT (0, state.data.state == STATE_WORKING, "A worker can't wait without being in working state");
 }
 
-void
-sgen_workers_wake_up_all (void)
+static void
+assert_nursery_collection (State state, gboolean from_worker)
 {
-       g_assert (workers_state.data.gc_in_progress);
-       workers_wake_up_all ();
+       SGEN_ASSERT (0, state.data.state == STATE_NURSERY_COLLECTION, "Must be in the nursery collection state");
 }
 
 static void
-workers_wait (void)
+assert_working_or_nursery_collection (State state)
 {
-       State old_state, new_state;
-       ++stat_workers_num_waited;
-       do {
-               old_state = new_state = workers_state;
-               /*
-                * Only the last worker thread awake can set the done
-                * posted flag, and since we're awake and haven't set
-                * it yet, it cannot be set.
-                */
-               g_assert (!old_state.data.done_posted);
-               ++new_state.data.num_waiting;
-               /*
-                * This is the only place where we use
-                * workers_gc_in_progress in the worker threads.
-                */
-               if (new_state.data.num_waiting == workers_num && !old_state.data.gc_in_progress)
-                       new_state.data.done_posted = 1;
-       } while (!set_state (old_state, new_state));
-       mono_memory_barrier ();
-       if (new_state.data.done_posted)
-               MONO_SEM_POST (&workers_done_sem);
-       MONO_SEM_WAIT (&workers_waiting_sem);
+       if (state.data.state == STATE_WORKING)
+               assert_working (state, TRUE);
+       else
+               assert_nursery_collection (state, TRUE);
 }
 
-static gboolean
-collection_needs_workers (void)
+static void
+workers_signal_enqueue_work (gboolean from_nursery_collection)
 {
-       return sgen_collection_is_parallel () || sgen_collection_is_concurrent ();
-}
+       State old_state = workers_state;
+       State new_state = old_state;
+       gboolean did_set_state;
 
-void
-sgen_workers_enqueue_job (JobFunc func, void *data)
-{
-       int num_entries;
-       JobQueueEntry *entry;
+       if (from_nursery_collection)
+               assert_nursery_collection (old_state, FALSE);
+       else
+               assert_not_working (old_state);
 
-       if (!collection_needs_workers ()) {
-               func (NULL, data);
-               return;
-       }
-
-       g_assert (workers_state.data.gc_in_progress);
+       new_state.data.state = STATE_WORKING;
 
-       entry = sgen_alloc_internal (INTERNAL_MEM_JOB_QUEUE_ENTRY);
-       entry->func = func;
-       entry->data = data;
+       did_set_state = set_state (old_state, new_state);
+       SGEN_ASSERT (0, did_set_state, "Nobody else should be mutating the state");
 
-       mono_mutex_lock (&workers_job_queue_mutex);
-       entry->next = workers_job_queue;
-       workers_job_queue = entry;
-       num_entries = ++workers_job_queue_num_entries;
-       ++workers_num_jobs_enqueued;
-       mono_mutex_unlock (&workers_job_queue_mutex);
+       sgen_thread_pool_idle_signal ();
+}
 
-       workers_wake_up (num_entries);
+static void
+workers_signal_enqueue_work_if_necessary (void)
+{
+       if (workers_state.data.state == STATE_NOT_WORKING)
+               workers_signal_enqueue_work (FALSE);
 }
 
 void
-sgen_workers_wait_for_jobs (void)
+sgen_workers_ensure_awake (void)
 {
-       // FIXME: implement this properly
-       while (workers_num_jobs_finished < workers_num_jobs_enqueued) {
-               State state = workers_state;
-               g_assert (state.data.gc_in_progress);
-               g_assert (!state.data.done_posted);
-               if (state.data.num_waiting == workers_num)
-                       workers_wake_up_all ();
-               g_usleep (1000);
-       }
+       SGEN_ASSERT (0, workers_state.data.state != STATE_NURSERY_COLLECTION, "Can't wake workers during nursery collection");
+       workers_signal_enqueue_work_if_necessary ();
 }
 
-static gboolean
-workers_dequeue_and_do_job (WorkerData *data)
+static void
+worker_finish (void)
 {
-       JobQueueEntry *entry;
-
-       /*
-        * At this point the GC might not be running anymore.  We
-        * could have been woken up by a job that was then taken by
-        * another thread, after which the collection finished, so we
-        * first have to successfully dequeue a job before doing
-        * anything assuming that the collection is still ongoing.
-        */
-
-       if (!workers_job_queue_num_entries)
-               return FALSE;
-
-       mono_mutex_lock (&workers_job_queue_mutex);
-       entry = (JobQueueEntry*)workers_job_queue;
-       if (entry) {
-               workers_job_queue = entry->next;
-               --workers_job_queue_num_entries;
-       }
-       mono_mutex_unlock (&workers_job_queue_mutex);
-
-       if (!entry)
-               return FALSE;
+       State old_state, new_state;
 
-       g_assert (collection_needs_workers ());
+       ++stat_workers_num_finished;
 
-       entry->func (data, entry->data);
-       sgen_free_internal (entry, INTERNAL_MEM_JOB_QUEUE_ENTRY);
+       do {
+               new_state = old_state = workers_state;
 
-       SGEN_ATOMIC_ADD (workers_num_jobs_finished, 1);
+               assert_working_or_nursery_collection (old_state);
 
-       return TRUE;
+               /* We are the last thread to go to sleep. */
+               if (old_state.data.state == STATE_WORKING)
+                       new_state.data.state = STATE_NOT_WORKING;
+       } while (!set_state (old_state, new_state));
 }
 
 static gboolean
-workers_steal (WorkerData *data, WorkerData *victim_data, gboolean lock)
+collection_needs_workers (void)
 {
-       SgenGrayQueue *queue = &data->private_gray_queue;
-       int num, n;
-
-       g_assert (!queue->first);
+       return sgen_collection_is_concurrent ();
+}
 
-       if (!victim_data->stealable_stack_fill)
-               return FALSE;
+void
+sgen_workers_enqueue_job (SgenThreadPoolJob *job)
+{
+       if (!collection_needs_workers ()) {
+               job->func (NULL, job);
+               sgen_thread_pool_job_free (job);
+               return;
+       }
 
-       if (lock && mono_mutex_trylock (&victim_data->stealable_stack_mutex))
-               return FALSE;
+       sgen_thread_pool_job_enqueue (job);
+}
 
-       num = (victim_data->stealable_stack_fill + 1) / 2;
-       if(num > 0 && GRAY_OBJECT_IS_PARTIAL(victim_data->stealable_stack [victim_data->stealable_stack_fill - num]))
-               num++;
-       n = num;
-       /* We're stealing num entries. */
+void
+sgen_workers_wait_for_jobs_finished (void)
+{
+       sgen_thread_pool_wait_for_all_jobs ();
+}
 
-       while (n > 0) {
-               int m = MIN (SGEN_GRAY_QUEUE_SECTION_SIZE, n);
-               if(GRAY_OBJECT_IS_PARTIAL(victim_data->stealable_stack [victim_data->stealable_stack_fill - num +n -m]))
-                       m--;
-               n -= m;
+void
+sgen_workers_signal_start_nursery_collection_and_wait (void)
+{
+       State old_state, new_state;
 
-               sgen_gray_object_alloc_queue_section (queue);
-               memcpy (queue->first->objects,
-                               victim_data->stealable_stack + victim_data->stealable_stack_fill - num + n,
-                               sizeof (char*) * m);
-               queue->first->size = m;
+       do {
+               new_state = old_state = workers_state;
 
-               /*
-                * DO NOT move outside this loop
-                * Doing so trigger "assert not reached" in sgen-scan-object.h : we use the queue->cursor
-                * to compute the size of the first section during section allocation (via alloc_prepare_func
-                * -> workers_gray_queue_share_redirect -> sgen_gray_object_dequeue_section) which will be then
-                * set to 0, because queue->cursor is still pointing to queue->first->objects [-1], thus
-                * losing objects in the gray queue.
-                */
-               queue->cursor = (char**)queue->first->objects + queue->first->size - 1;
-       }
+               new_state.data.state = STATE_NURSERY_COLLECTION;
 
-       victim_data->stealable_stack_fill -= num;
+               if (old_state.data.state == STATE_NOT_WORKING) {
+                       assert_not_working (old_state);
+               } else {
+                       assert_working (old_state, FALSE);
+               }
+       } while (!set_state (old_state, new_state));
 
-       if (lock)
-               mono_mutex_unlock (&victim_data->stealable_stack_mutex);
+       sgen_thread_pool_idle_wait ();
 
-       if (data == victim_data) {
-               if (lock)
-                       stat_workers_stolen_from_self_lock += num;
-               else
-                       stat_workers_stolen_from_self_no_lock += num;
-       } else {
-               stat_workers_stolen_from_others += num;
-       }
+       old_state = workers_state;
+       assert_nursery_collection (old_state, FALSE);
+}
 
-       return num != 0;
+void
+sgen_workers_signal_finish_nursery_collection (void)
+{
+       assert_nursery_collection (workers_state, FALSE);
+       workers_signal_enqueue_work (TRUE);
 }
 
 static gboolean
 workers_get_work (WorkerData *data)
 {
        SgenMajorCollector *major;
-       int i;
 
        g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
 
-       /* Try to steal from our own stack. */
-       if (workers_steal (data, data, TRUE))
-               return TRUE;
-
-       /* From another worker. */
-       for (i = data->index + 1; i < workers_num + data->index; ++i) {
-               WorkerData *victim_data = &workers_data [i % workers_num];
-               g_assert (data != victim_data);
-               if (workers_steal (data, victim_data, TRUE))
-                       return TRUE;
-       }
-
-       /*
-        * If we're concurrent or parallel, from the workers
-        * distribute gray queue.
-        */
+       /* If we're concurrent, steal from the workers distribute gray queue. */
        major = sgen_get_major_collector ();
-       if (major->is_concurrent || major->is_parallel) {
+       if (major->is_concurrent) {
                GrayQueueSection *section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
                if (section) {
                        sgen_gray_object_enqueue_section (&data->private_gray_queue, section);
@@ -319,55 +217,6 @@ workers_get_work (WorkerData *data)
        return FALSE;
 }
 
-static void
-workers_gray_queue_share_redirect (SgenGrayQueue *queue)
-{
-       GrayQueueSection *section;
-       WorkerData *data = queue->alloc_prepare_data;
-
-       if (data->stealable_stack_fill) {
-               /*
-                * There are still objects in the stealable stack, so
-                * wake up any workers that might be sleeping
-                */
-               if (workers_state.data.gc_in_progress)
-                       workers_wake_up_all ();
-               return;
-       }
-
-       /* The stealable stack is empty, so fill it. */
-       mono_mutex_lock (&data->stealable_stack_mutex);
-
-       while (data->stealable_stack_fill < STEALABLE_STACK_SIZE &&
-                       (section = sgen_gray_object_dequeue_section (queue))) {
-               int num = MIN (section->size, STEALABLE_STACK_SIZE - data->stealable_stack_fill);
-               if(num == STEALABLE_STACK_SIZE - data->stealable_stack_fill && GRAY_OBJECT_IS_PARTIAL(section->objects[section->size -num]))
-                       num--;
-
-               memcpy (data->stealable_stack + data->stealable_stack_fill,
-                               section->objects + section->size - num,
-                               sizeof (char*) * num);
-
-               section->size -= num;
-               data->stealable_stack_fill += num;
-
-               if (section->size){
-                       sgen_gray_object_enqueue_section (queue, section);
-                       break;
-               }
-               else
-                       sgen_gray_object_free_queue_section (section);
-       }
-
-       if (sgen_gray_object_queue_is_empty (queue))
-               workers_steal (data, data, FALSE);
-
-       mono_mutex_unlock (&data->stealable_stack_mutex);
-
-       if (workers_state.data.gc_in_progress)
-               workers_wake_up_all ();
-}
-
 static void
 concurrent_enqueue_check (char *obj)
 {
@@ -379,67 +228,66 @@ concurrent_enqueue_check (char *obj)
 static void
 init_private_gray_queue (WorkerData *data)
 {
-       sgen_gray_object_queue_init_with_alloc_prepare (&data->private_gray_queue,
-                       sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
-                       workers_gray_queue_share_redirect, data);
+       sgen_gray_object_queue_init (&data->private_gray_queue,
+                       sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
 }
 
-static mono_native_thread_return_t
-workers_thread_func (void *data_untyped)
+static void
+thread_pool_init_func (void *data_untyped)
 {
        WorkerData *data = data_untyped;
        SgenMajorCollector *major = sgen_get_major_collector ();
 
        mono_thread_info_register_small_id ();
 
+       if (!major->is_concurrent)
+               return;
+
        if (major->init_worker_thread)
                major->init_worker_thread (data->major_collector_data);
 
        init_private_gray_queue (data);
+}
 
-       for (;;) {
-               gboolean did_work = FALSE;
-
-               while (workers_dequeue_and_do_job (data)) {
-                       did_work = TRUE;
-                       /* FIXME: maybe distribute the gray queue here? */
-               }
+static gboolean
+marker_idle_func (void *data_untyped)
+{
+       WorkerData *data = data_untyped;
+       SgenMajorCollector *major = sgen_get_major_collector ();
 
-               if (workers_marking && (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data))) {
-                       SgenObjectOperations *ops = sgen_concurrent_collection_in_progress ()
-                               ? &major->major_concurrent_ops
-                               : &major->major_ops;
-                       ScanCopyContext ctx = { ops->scan_object, NULL, &data->private_gray_queue, ops->scan_work };
+       if (workers_state.data.state != STATE_WORKING)
+               return FALSE;
 
-                       g_assert (!sgen_gray_object_queue_is_empty (&data->private_gray_queue));
+       SGEN_ASSERT (0, sgen_get_current_collection_generation () != GENERATION_NURSERY, "Why are we doing work while there's a nursery collection happening?");
 
-                       while (!sgen_drain_gray_stack_work_item (32, ctx))
-                               workers_gray_queue_share_redirect (&data->private_gray_queue);
-                       g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
+       if (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data)) {
+               SgenObjectOperations *ops = sgen_concurrent_collection_in_progress ()
+                       ? &major->major_concurrent_ops
+                       : &major->major_ops;
+               ScanCopyContext ctx = { ops->scan_object, NULL, &data->private_gray_queue };
 
-                       init_private_gray_queue (data);
+               SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data->private_gray_queue), "How is our gray queue empty if we just got work?");
 
-                       did_work = TRUE;
-               }
+               sgen_drain_gray_stack (32, ctx);
 
-               if (!did_work)
-                       workers_wait ();
+               return TRUE;
        }
 
-       /* dummy return to make compilers happy */
-       return NULL;
+       worker_finish ();
+
+       return FALSE;
 }
 
 static void
-init_distribute_gray_queue (gboolean locked)
+init_distribute_gray_queue (void)
 {
        if (workers_distribute_gray_queue_inited) {
                g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
-               g_assert (!workers_distribute_gray_queue.locked == !locked);
+               g_assert (workers_distribute_gray_queue.locked);
                return;
        }
 
-       sgen_section_gray_queue_init (&workers_distribute_gray_queue, locked,
+       sgen_section_gray_queue_init (&workers_distribute_gray_queue, TRUE,
                        sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
        workers_distribute_gray_queue_inited = TRUE;
 }
@@ -447,19 +295,21 @@ init_distribute_gray_queue (gboolean locked)
 void
 sgen_workers_init_distribute_gray_queue (void)
 {
-       if (!collection_needs_workers ())
-               return;
-
-       init_distribute_gray_queue (sgen_get_major_collector ()->is_concurrent || sgen_get_major_collector ()->is_parallel);
+       SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent && collection_needs_workers (),
+                       "Why should we init the distribute gray queue if we don't need it?");
+       init_distribute_gray_queue ();
 }
 
 void
 sgen_workers_init (int num_workers)
 {
        int i;
+       void *workers_data_ptrs [num_workers];
 
-       if (!sgen_get_major_collector ()->is_parallel && !sgen_get_major_collector ()->is_concurrent)
+       if (!sgen_get_major_collector ()->is_concurrent) {
+               sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL);
                return;
+       }
 
        //g_print ("initing %d workers\n", num_workers);
 
@@ -468,10 +318,7 @@ sgen_workers_init (int num_workers)
        workers_data = sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
        memset (workers_data, 0, sizeof (WorkerData) * num_workers);
 
-       MONO_SEM_INIT (&workers_waiting_sem, 0);
-       MONO_SEM_INIT (&workers_done_sem, 0);
-
-       init_distribute_gray_queue (sgen_get_major_collector ()->is_concurrent || sgen_get_major_collector ()->is_parallel);
+       init_distribute_gray_queue ();
 
        if (sgen_get_major_collector ()->alloc_worker_data)
                workers_gc_thread_major_collector_data = sgen_get_major_collector ()->alloc_worker_data ();
@@ -479,204 +326,85 @@ sgen_workers_init (int num_workers)
        for (i = 0; i < workers_num; ++i) {
                workers_data [i].index = i;
 
-               /* private gray queue is inited by the thread itself */
-               mono_mutex_init (&workers_data [i].stealable_stack_mutex);
-               workers_data [i].stealable_stack_fill = 0;
-
                if (sgen_get_major_collector ()->alloc_worker_data)
                        workers_data [i].major_collector_data = sgen_get_major_collector ()->alloc_worker_data ();
-       }
-
-       LOCK_INIT (workers_job_queue_mutex);
-
-       sgen_register_fixed_internal_mem_type (INTERNAL_MEM_JOB_QUEUE_ENTRY, sizeof (JobQueueEntry));
-
-       mono_counters_register ("Stolen from self lock", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_workers_stolen_from_self_lock);
-       mono_counters_register ("Stolen from self no lock", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_workers_stolen_from_self_no_lock);
-       mono_counters_register ("Stolen from others", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_workers_stolen_from_others);
-       mono_counters_register ("# workers waited", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_workers_num_waited);
-}
 
-/* only the GC thread is allowed to start and join workers */
+               workers_data_ptrs [i] = &workers_data [i];
+       }
 
-static void
-workers_start_worker (int index)
-{
-       g_assert (index >= 0 && index < workers_num);
+       sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, workers_data_ptrs);
 
-       g_assert (!workers_data [index].thread);
-       mono_native_thread_create (&workers_data [index].thread, workers_thread_func, &workers_data [index]);
+       mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
 }
 
 void
 sgen_workers_start_all_workers (void)
 {
-       State old_state, new_state;
-       int i;
-
        if (!collection_needs_workers ())
                return;
 
        if (sgen_get_major_collector ()->init_worker_thread)
                sgen_get_major_collector ()->init_worker_thread (workers_gc_thread_major_collector_data);
 
-       old_state = new_state = workers_state;
-       g_assert (!old_state.data.gc_in_progress);
-       new_state.data.gc_in_progress = TRUE;
-
-       workers_marking = FALSE;
-
-       g_assert (workers_job_queue_num_entries == 0);
-       workers_num_jobs_enqueued = 0;
-       workers_num_jobs_finished = 0;
-
-       if (workers_started) {
-               g_assert (old_state.data.done_posted);
-               if (old_state.data.num_waiting != workers_num) {
-                       g_error ("Expecting all %d sgen workers to be parked, but only %d are",
-                                       workers_num, old_state.data.num_waiting);
-               }
-
-               /* Clear the done posted flag */
-               new_state.data.done_posted = 0;
-               if (!set_state (old_state, new_state))
-                       g_assert_not_reached ();
-
-               workers_wake_up_all ();
-               return;
-       }
-
-       g_assert (!old_state.data.done_posted);
-
-       if (!set_state (old_state, new_state))
-               g_assert_not_reached ();
-
-       for (i = 0; i < workers_num; ++i)
-               workers_start_worker (i);
-
-       workers_started = TRUE;
-}
-
-gboolean
-sgen_workers_have_started (void)
-{
-       return workers_state.data.gc_in_progress;
-}
-
-void
-sgen_workers_start_marking (void)
-{
-       if (!collection_needs_workers ())
-               return;
-
-       g_assert (workers_started && workers_state.data.gc_in_progress);
-       g_assert (!workers_marking);
-
-       workers_marking = TRUE;
-
-       workers_wake_up_all ();
+       workers_signal_enqueue_work (FALSE);
 }
 
 void
 sgen_workers_join (void)
 {
-       State old_state, new_state;
        int i;
 
        if (!collection_needs_workers ())
                return;
 
-       do {
-               old_state = new_state = workers_state;
-               g_assert (old_state.data.gc_in_progress);
-               g_assert (!old_state.data.done_posted);
+       sgen_thread_pool_wait_for_all_jobs ();
 
-               new_state.data.gc_in_progress = 0;
-       } while (!set_state (old_state, new_state));
+       for (;;) {
+               SGEN_ASSERT (0, workers_state.data.state != STATE_NURSERY_COLLECTION, "Can't be in nursery collection when joining");
+               sgen_thread_pool_idle_wait ();
+               assert_not_working (workers_state);
 
-       if (new_state.data.num_waiting == workers_num) {
                /*
-                * All the workers have shut down but haven't posted
-                * the done semaphore yet, or, if we come from below,
-                * haven't done all their work yet.
-                *
-                * It's not a big deal to wake them up again - they'll
-                * just do one iteration of their loop trying to find
-                * something to do and then go back to waiting again.
+                * Checking whether there is still work left and, if not, going to sleep,
+                * are two separate actions that are not performed atomically by the
+                * workers.  Therefore there's a race condition where work can be added
+                * after they've checked for work, and before they've gone to sleep.
                 */
-       reawaken:
-               workers_wake_up_all ();
-       }
-       MONO_SEM_WAIT (&workers_done_sem);
-
-       old_state = new_state = workers_state;
-       g_assert (old_state.data.num_waiting == workers_num);
-       g_assert (old_state.data.done_posted);
+               if (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
+                       break;
 
-       if (workers_job_queue_num_entries || !sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue)) {
-               /*
-                * There's a small race condition that we avoid here.
-                * It's possible that a worker thread runs out of
-                * things to do, so it goes to sleep.  Right at that
-                * moment a new job is enqueued, but the thread is
-                * still registered as running.  Now the threads are
-                * joined, and we wait for the semaphore.  Only at
-                * this point does the worker go to sleep, and posts
-                * the semaphore, because workers_gc_in_progress is
-                * already FALSE.  The job is still in the queue,
-                * though.
-                *
-                * Clear the done posted flag.
-                */
-               new_state.data.done_posted = 0;
-               if (!set_state (old_state, new_state))
-                       g_assert_not_reached ();
-               goto reawaken;
+               workers_signal_enqueue_work (FALSE);
        }
 
        /* At this point all the workers have stopped. */
 
-       workers_marking = FALSE;
-
        if (sgen_get_major_collector ()->reset_worker_data) {
                for (i = 0; i < workers_num; ++i)
                        sgen_get_major_collector ()->reset_worker_data (workers_data [i].major_collector_data);
        }
 
-       g_assert (workers_job_queue_num_entries == 0);
        g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
-       for (i = 0; i < workers_num; ++i) {
-               g_assert (!workers_data [i].stealable_stack_fill);
+       for (i = 0; i < workers_num; ++i)
                g_assert (sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue));
-       }
 }
 
 gboolean
 sgen_workers_all_done (void)
 {
-       State state = workers_state;
-       /*
-        * Can only be called while the collection is still in
-        * progress, i.e., before done has been posted.
-        */
-       g_assert (state.data.gc_in_progress);
-       g_assert (!state.data.done_posted);
-       return state.data.num_waiting == workers_num;
+       return workers_state.data.state == STATE_NOT_WORKING;
 }
 
 gboolean
-sgen_is_worker_thread (MonoNativeThreadId thread)
+sgen_workers_are_working (void)
 {
-       int i;
-
-       if (sgen_get_major_collector ()->is_worker_thread && sgen_get_major_collector ()->is_worker_thread (thread))
-               return TRUE;
+       return workers_state.data.state == STATE_WORKING;
+}
 
-       for (i = 0; i < workers_num; ++i) {
-               if (workers_data [i].thread == thread)
-                       return TRUE;
-       }
-       return FALSE;
+void
+sgen_workers_wait (void)
+{
+       sgen_thread_pool_idle_wait ();
+       SGEN_ASSERT (0, sgen_workers_all_done (), "Why are the workers not done after we wait for them?");
 }
 
 SgenSectionGrayQueue*