[sgen] Write barrier nursery checks might be needed
[mono.git] / mono / metadata / sgen-workers.c
1 /*
2  * sgen-workers.c: Worker threads for parallel and concurrent GC.
3  *
4  * Copyright 2001-2003 Ximian, Inc
5  * Copyright 2003-2010 Novell, Inc.
6  * Copyright (C) 2012 Xamarin Inc
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License 2.0 as published by the Free Software Foundation;
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License 2.0 along with this library; if not, write to the Free
19  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "config.h"
23 #ifdef HAVE_SGEN_GC
24
25 #include "metadata/sgen-gc.h"
26 #include "metadata/sgen-workers.h"
27 #include "utils/mono-counters.h"
28
29 static int workers_num;
30 static WorkerData *workers_data;
31 static void *workers_gc_thread_major_collector_data = NULL;
32
33 static SgenSectionGrayQueue workers_distribute_gray_queue;
34 static gboolean workers_distribute_gray_queue_inited;
35
36 static gboolean workers_started = FALSE;
37
38 enum {
39         STATE_NOT_WORKING,
40         STATE_WORKING,
41         STATE_NURSERY_COLLECTION
42 } WorkersStateName;
43
44 /*
45  * | state                    | num_awake | num_posted                 | post_done |
46  * |--------------------------+-----------+----------------------------+-----------|
47  * | STATE_NOT_WORKING        | 0         | *                          |         0 |
48  * | STATE_WORKING            | > 0       | <= workers_num - num_awake |         * |
49  * | STATE_NURSERY_COLLECTION | *         | <= workers_num - num_awake |         1 |
50  * | STATE_NURSERY_COLLECTION | 0         | 0                          |         0 |
51  */
52 typedef union {
53         gint32 value;
54         struct {
55                 guint state : 4; /* WorkersStateName */
56                 /* Number of worker threads awake. */
57                 guint num_awake : 8;
58                 /* The state of the waiting semaphore. */
59                 guint num_posted : 8;
60                 /* Whether to post `workers_done_sem` */
61                 guint post_done : 1;
62         } data;
63 } State;
64
65 static volatile State workers_state;
66
67 static MonoSemType workers_waiting_sem;
68 static MonoSemType workers_done_sem;
69
70 static volatile int workers_job_queue_num_entries = 0;
71 static volatile JobQueueEntry *workers_job_queue = NULL;
72 static LOCK_DECLARE (workers_job_queue_mutex);
73 static int workers_num_jobs_enqueued = 0;
74 static volatile int workers_num_jobs_finished = 0;
75
76 static guint64 stat_workers_stolen_from_self_lock;
77 static guint64 stat_workers_stolen_from_self_no_lock;
78 static guint64 stat_workers_stolen_from_others;
79 static guint64 stat_workers_num_waited;
80
81 static gboolean
82 set_state (State old_state, State new_state)
83 {
84         if (old_state.data.state == STATE_NURSERY_COLLECTION)
85                 SGEN_ASSERT (0, new_state.data.state != STATE_NOT_WORKING, "Can't go from nursery collection to not working");
86
87         return InterlockedCompareExchange (&workers_state.value,
88                         new_state.value, old_state.value) == old_state.value;
89 }
90
91 static void
92 assert_not_working (State state)
93 {
94         SGEN_ASSERT (0, state.data.state == STATE_NOT_WORKING, "Can only signal enqueue work when in no work state");
95         SGEN_ASSERT (0, state.data.num_awake == 0, "No workers can be awake when not working");
96         SGEN_ASSERT (0, state.data.num_posted == 0, "Can't have posted already");
97         SGEN_ASSERT (0, !state.data.post_done, "post_done can only be set when working");
98
99 }
100
101 static void
102 assert_working (State state, gboolean from_worker)
103 {
104         SGEN_ASSERT (0, state.data.state == STATE_WORKING, "A worker can't wait without being in working state");
105         if (from_worker)
106                 SGEN_ASSERT (0, state.data.num_awake > 0, "How can we be awake, yet we are not counted?");
107         else
108                 SGEN_ASSERT (0, state.data.num_awake + state.data.num_posted > 0, "How can we be working, yet no worker threads are awake or to be awoken?");
109         SGEN_ASSERT (0, state.data.num_awake + state.data.num_posted <= workers_num, "There are too many worker threads awake");
110 }
111
112 static void
113 assert_nursery_collection (State state, gboolean from_worker)
114 {
115         SGEN_ASSERT (0, state.data.state == STATE_NURSERY_COLLECTION, "Must be in the nursery collection state");
116         if (from_worker) {
117                 SGEN_ASSERT (0, state.data.num_awake > 0, "We're awake, but num_awake is zero");
118                 SGEN_ASSERT (0, state.data.post_done, "post_done must be set in the nursery collection state");
119         }
120         SGEN_ASSERT (0, state.data.num_awake <= workers_num, "There are too many worker threads awake");
121         if (!state.data.post_done) {
122                 SGEN_ASSERT (0, state.data.num_awake == 0, "Once done has been posted no threads can be awake");
123                 SGEN_ASSERT (0, state.data.num_posted == 0, "Once done has been posted no thread must be awoken");
124         }
125 }
126
127 static void
128 assert_working_or_nursery_collection (State state)
129 {
130         if (state.data.state == STATE_WORKING)
131                 assert_working (state, TRUE);
132         else
133                 assert_nursery_collection (state, TRUE);
134 }
135
136 static void
137 workers_signal_enqueue_work (int num_wake_up, gboolean from_nursery_collection)
138 {
139         State old_state = workers_state;
140         State new_state = old_state;
141         int i;
142         gboolean did_set_state;
143
144         SGEN_ASSERT (0, num_wake_up <= workers_num, "Cannot wake up more workers than are present");
145
146         if (from_nursery_collection)
147                 assert_nursery_collection (old_state, FALSE);
148         else
149                 assert_not_working (old_state);
150
151         new_state.data.state = STATE_WORKING;
152         new_state.data.num_posted = num_wake_up;
153
154         did_set_state = set_state (old_state, new_state);
155         SGEN_ASSERT (0, did_set_state, "Nobody else should be mutating the state");
156
157         for (i = 0; i < num_wake_up; ++i)
158                 MONO_SEM_POST (&workers_waiting_sem);
159 }
160
161 static void
162 workers_signal_enqueue_work_if_necessary (int num_wake_up)
163 {
164         if (workers_state.data.state == STATE_NOT_WORKING)
165                 workers_signal_enqueue_work (num_wake_up, FALSE);
166 }
167
168 void
169 sgen_workers_ensure_awake (void)
170 {
171         SGEN_ASSERT (0, workers_state.data.state != STATE_NURSERY_COLLECTION, "Can't wake workers during nursery collection");
172         workers_signal_enqueue_work_if_necessary (workers_num);
173 }
174
175 static void
176 workers_wait (void)
177 {
178         State old_state, new_state;
179         gboolean post_done;
180
181         ++stat_workers_num_waited;
182
183         do {
184                 new_state = old_state = workers_state;
185
186                 assert_working_or_nursery_collection (old_state);
187
188                 --new_state.data.num_awake;
189                 post_done = FALSE;
190                 if (!new_state.data.num_awake && !new_state.data.num_posted) {
191                         /* We are the last thread to go to sleep. */
192                         if (old_state.data.state == STATE_WORKING)
193                                 new_state.data.state = STATE_NOT_WORKING;
194
195                         new_state.data.post_done = 0;
196                         if (old_state.data.post_done)
197                                 post_done = TRUE;
198                 }
199         } while (!set_state (old_state, new_state));
200
201         if (post_done)
202                 MONO_SEM_POST (&workers_done_sem);
203
204         MONO_SEM_WAIT (&workers_waiting_sem);
205
206         do {
207                 new_state = old_state = workers_state;
208
209                 SGEN_ASSERT (0, old_state.data.num_posted > 0, "How can we be awake without the semaphore having been posted?");
210                 SGEN_ASSERT (0, old_state.data.num_awake < workers_num, "There are too many worker threads awake");
211
212                 --new_state.data.num_posted;
213                 ++new_state.data.num_awake;
214
215                 assert_working_or_nursery_collection (new_state);
216         } while (!set_state (old_state, new_state));
217 }
218
219 static gboolean
220 collection_needs_workers (void)
221 {
222         return sgen_collection_is_concurrent ();
223 }
224
225 void
226 sgen_workers_enqueue_job (JobFunc func, void *data)
227 {
228         int num_entries;
229         JobQueueEntry *entry;
230
231         if (!collection_needs_workers ()) {
232                 func (NULL, data);
233                 return;
234         }
235
236         entry = sgen_alloc_internal (INTERNAL_MEM_JOB_QUEUE_ENTRY);
237         entry->func = func;
238         entry->data = data;
239
240         mono_mutex_lock (&workers_job_queue_mutex);
241         entry->next = workers_job_queue;
242         workers_job_queue = entry;
243         num_entries = ++workers_job_queue_num_entries;
244         ++workers_num_jobs_enqueued;
245         mono_mutex_unlock (&workers_job_queue_mutex);
246
247         if (workers_state.data.state != STATE_NURSERY_COLLECTION)
248                 workers_signal_enqueue_work_if_necessary (num_entries < workers_num ? num_entries : workers_num);
249 }
250
251 void
252 sgen_workers_wait_for_jobs_finished (void)
253 {
254         // FIXME: implement this properly
255         while (workers_num_jobs_finished < workers_num_jobs_enqueued) {
256                 workers_signal_enqueue_work_if_necessary (workers_num);
257                 /* FIXME: sleep less? */
258                 g_usleep (1000);
259         }
260 }
261
262 void
263 sgen_workers_signal_start_nursery_collection_and_wait (void)
264 {
265         State old_state, new_state;
266
267         do {
268                 new_state = old_state = workers_state;
269
270                 new_state.data.state = STATE_NURSERY_COLLECTION;
271
272                 if (old_state.data.state == STATE_NOT_WORKING) {
273                         assert_not_working (old_state);
274                 } else {
275                         assert_working (old_state, FALSE);
276                         SGEN_ASSERT (0, !old_state.data.post_done, "We are not waiting for the workers");
277
278                         new_state.data.post_done = 1;
279                 }
280         } while (!set_state (old_state, new_state));
281
282         if (new_state.data.post_done)
283                 MONO_SEM_WAIT (&workers_done_sem);
284
285         old_state = workers_state;
286         assert_nursery_collection (old_state, FALSE);
287         SGEN_ASSERT (0, !old_state.data.post_done, "We got the semaphore, so it must have been posted");
288 }
289
290 void
291 sgen_workers_signal_finish_nursery_collection (void)
292 {
293         State old_state = workers_state;
294
295         assert_nursery_collection (old_state, FALSE);
296         SGEN_ASSERT (0, !old_state.data.post_done, "We are finishing the nursery collection, so we should have waited for the semaphore earlier");
297
298         workers_signal_enqueue_work (workers_num, TRUE);
299 }
300
301 static gboolean
302 workers_dequeue_and_do_job (WorkerData *data)
303 {
304         JobQueueEntry *entry;
305
306         /*
307          * At this point the GC might not be running anymore.  We
308          * could have been woken up by a job that was then taken by
309          * another thread, after which the collection finished, so we
310          * first have to successfully dequeue a job before doing
311          * anything assuming that the collection is still ongoing.
312          */
313
314         if (!workers_job_queue_num_entries)
315                 return FALSE;
316
317         mono_mutex_lock (&workers_job_queue_mutex);
318         entry = (JobQueueEntry*)workers_job_queue;
319         if (entry) {
320                 workers_job_queue = entry->next;
321                 --workers_job_queue_num_entries;
322         }
323         mono_mutex_unlock (&workers_job_queue_mutex);
324
325         if (!entry)
326                 return FALSE;
327
328         g_assert (collection_needs_workers ());
329
330         entry->func (data, entry->data);
331         sgen_free_internal (entry, INTERNAL_MEM_JOB_QUEUE_ENTRY);
332
333         SGEN_ATOMIC_ADD (workers_num_jobs_finished, 1);
334
335         return TRUE;
336 }
337
338 static gboolean
339 workers_steal (WorkerData *data, WorkerData *victim_data, gboolean lock)
340 {
341         SgenGrayQueue *queue = &data->private_gray_queue;
342         int num, n;
343
344         g_assert (!queue->first);
345
346         if (!victim_data->stealable_stack_fill)
347                 return FALSE;
348
349         if (lock && mono_mutex_trylock (&victim_data->stealable_stack_mutex))
350                 return FALSE;
351
352         n = num = (victim_data->stealable_stack_fill + 1) / 2;
353         /* We're stealing num entries. */
354
355         while (n > 0) {
356                 int m = MIN (SGEN_GRAY_QUEUE_SECTION_SIZE, n);
357                 n -= m;
358
359                 sgen_gray_object_alloc_queue_section (queue);
360                 memcpy (queue->first->entries,
361                                 victim_data->stealable_stack + victim_data->stealable_stack_fill - num + n,
362                                 sizeof (GrayQueueEntry) * m);
363                 queue->first->size = m;
364
365                 /*
366                  * DO NOT move outside this loop
367                  * Doing so trigger "assert not reached" in sgen-scan-object.h : we use the queue->cursor
368                  * to compute the size of the first section during section allocation (via alloc_prepare_func
369                  * -> workers_gray_queue_share_redirect -> sgen_gray_object_dequeue_section) which will be then
370                  * set to 0, because queue->cursor is still pointing to queue->first->entries [-1], thus
371                  * losing objects in the gray queue.
372                  */
373                 queue->cursor = queue->first->entries + queue->first->size - 1;
374         }
375
376         victim_data->stealable_stack_fill -= num;
377
378         if (lock)
379                 mono_mutex_unlock (&victim_data->stealable_stack_mutex);
380
381         if (data == victim_data) {
382                 if (lock)
383                         stat_workers_stolen_from_self_lock += num;
384                 else
385                         stat_workers_stolen_from_self_no_lock += num;
386         } else {
387                 stat_workers_stolen_from_others += num;
388         }
389
390         return num != 0;
391 }
392
393 static gboolean
394 workers_get_work (WorkerData *data)
395 {
396         SgenMajorCollector *major;
397         int i;
398
399         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
400
401         /* Try to steal from our own stack. */
402         if (workers_steal (data, data, TRUE))
403                 return TRUE;
404
405         /* From another worker. */
406         for (i = data->index + 1; i < workers_num + data->index; ++i) {
407                 WorkerData *victim_data = &workers_data [i % workers_num];
408                 g_assert (data != victim_data);
409                 if (workers_steal (data, victim_data, TRUE))
410                         return TRUE;
411         }
412
413         /*
414          * If we're concurrent or parallel, from the workers
415          * distribute gray queue.
416          */
417         major = sgen_get_major_collector ();
418         if (major->is_concurrent) {
419                 GrayQueueSection *section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
420                 if (section) {
421                         sgen_gray_object_enqueue_section (&data->private_gray_queue, section);
422                         return TRUE;
423                 }
424         }
425
426         /* Nobody to steal from */
427         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
428         return FALSE;
429 }
430
431 static void
432 workers_gray_queue_share_redirect (SgenGrayQueue *queue)
433 {
434         GrayQueueSection *section;
435         WorkerData *data = queue->alloc_prepare_data;
436
437         if (data->stealable_stack_fill) {
438                 /*
439                  * There are still objects in the stealable stack, so
440                  * wake up any workers that might be sleeping
441                  */
442                 workers_signal_enqueue_work_if_necessary (workers_num);
443                 return;
444         }
445
446         /* The stealable stack is empty, so fill it. */
447         mono_mutex_lock (&data->stealable_stack_mutex);
448
449         while (data->stealable_stack_fill < STEALABLE_STACK_SIZE &&
450                         (section = sgen_gray_object_dequeue_section (queue))) {
451                 int num = MIN (section->size, STEALABLE_STACK_SIZE - data->stealable_stack_fill);
452
453                 memcpy (data->stealable_stack + data->stealable_stack_fill,
454                                 section->entries + section->size - num,
455                                 sizeof (GrayQueueEntry) * num);
456
457                 section->size -= num;
458                 data->stealable_stack_fill += num;
459
460                 if (section->size)
461                         sgen_gray_object_enqueue_section (queue, section);
462                 else
463                         sgen_gray_object_free_queue_section (section);
464         }
465
466         if (sgen_gray_object_queue_is_empty (queue))
467                 workers_steal (data, data, FALSE);
468
469         mono_mutex_unlock (&data->stealable_stack_mutex);
470
471         workers_signal_enqueue_work_if_necessary (workers_num);
472 }
473
474 static void
475 concurrent_enqueue_check (char *obj)
476 {
477         g_assert (sgen_concurrent_collection_in_progress ());
478         g_assert (!sgen_ptr_in_nursery (obj));
479         g_assert (SGEN_LOAD_VTABLE (obj));
480 }
481
482 static void
483 init_private_gray_queue (WorkerData *data)
484 {
485         sgen_gray_object_queue_init_with_alloc_prepare (&data->private_gray_queue,
486                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
487                         workers_gray_queue_share_redirect, data);
488 }
489
490 static mono_native_thread_return_t
491 workers_thread_func (void *data_untyped)
492 {
493         WorkerData *data = data_untyped;
494         SgenMajorCollector *major = sgen_get_major_collector ();
495
496         mono_thread_info_register_small_id ();
497
498         if (major->init_worker_thread)
499                 major->init_worker_thread (data->major_collector_data);
500
501         init_private_gray_queue (data);
502
503         for (;;) {
504                 gboolean did_work = FALSE;
505
506                 SGEN_ASSERT (0, sgen_get_current_collection_generation () != GENERATION_NURSERY, "Why are we doing work while there's a nursery collection happening?");
507
508                 while (workers_state.data.state == STATE_WORKING && workers_dequeue_and_do_job (data)) {
509                         did_work = TRUE;
510                         /* FIXME: maybe distribute the gray queue here? */
511                 }
512
513                 if (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data)) {
514                         SgenObjectOperations *ops = sgen_concurrent_collection_in_progress ()
515                                 ? &major->major_concurrent_ops
516                                 : &major->major_ops;
517                         ScanCopyContext ctx = { ops->scan_object, NULL, &data->private_gray_queue };
518
519                         g_assert (!sgen_gray_object_queue_is_empty (&data->private_gray_queue));
520
521                         while (!sgen_drain_gray_stack (32, ctx)) {
522                                 if (workers_state.data.state == STATE_NURSERY_COLLECTION)
523                                         workers_wait ();
524
525                                 workers_gray_queue_share_redirect (&data->private_gray_queue);
526                         }
527                         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
528
529                         init_private_gray_queue (data);
530
531                         did_work = TRUE;
532                 }
533
534                 if (!did_work)
535                         workers_wait ();
536         }
537
538         /* dummy return to make compilers happy */
539         return NULL;
540 }
541
542 static void
543 init_distribute_gray_queue (gboolean locked)
544 {
545         if (workers_distribute_gray_queue_inited) {
546                 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
547                 g_assert (!workers_distribute_gray_queue.locked == !locked);
548                 return;
549         }
550
551         sgen_section_gray_queue_init (&workers_distribute_gray_queue, locked,
552                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
553         workers_distribute_gray_queue_inited = TRUE;
554 }
555
556 void
557 sgen_workers_init_distribute_gray_queue (void)
558 {
559         if (!collection_needs_workers ())
560                 return;
561
562         init_distribute_gray_queue (sgen_get_major_collector ()->is_concurrent);
563 }
564
565 void
566 sgen_workers_init (int num_workers)
567 {
568         int i;
569
570         if (!sgen_get_major_collector ()->is_concurrent)
571                 return;
572
573         //g_print ("initing %d workers\n", num_workers);
574
575         workers_num = num_workers;
576
577         workers_data = sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
578         memset (workers_data, 0, sizeof (WorkerData) * num_workers);
579
580         MONO_SEM_INIT (&workers_waiting_sem, 0);
581         MONO_SEM_INIT (&workers_done_sem, 0);
582
583         init_distribute_gray_queue (sgen_get_major_collector ()->is_concurrent);
584
585         if (sgen_get_major_collector ()->alloc_worker_data)
586                 workers_gc_thread_major_collector_data = sgen_get_major_collector ()->alloc_worker_data ();
587
588         for (i = 0; i < workers_num; ++i) {
589                 workers_data [i].index = i;
590
591                 /* private gray queue is inited by the thread itself */
592                 mono_mutex_init (&workers_data [i].stealable_stack_mutex);
593                 workers_data [i].stealable_stack_fill = 0;
594
595                 if (sgen_get_major_collector ()->alloc_worker_data)
596                         workers_data [i].major_collector_data = sgen_get_major_collector ()->alloc_worker_data ();
597         }
598
599         LOCK_INIT (workers_job_queue_mutex);
600
601         sgen_register_fixed_internal_mem_type (INTERNAL_MEM_JOB_QUEUE_ENTRY, sizeof (JobQueueEntry));
602
603         mono_counters_register ("Stolen from self lock", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_stolen_from_self_lock);
604         mono_counters_register ("Stolen from self no lock", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_stolen_from_self_no_lock);
605         mono_counters_register ("Stolen from others", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_stolen_from_others);
606         mono_counters_register ("# workers waited", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_waited);
607 }
608
609 /* only the GC thread is allowed to start and join workers */
610
611 static void
612 workers_start_worker (int index)
613 {
614         g_assert (index >= 0 && index < workers_num);
615
616         g_assert (!workers_data [index].thread);
617         mono_native_thread_create (&workers_data [index].thread, workers_thread_func, &workers_data [index]);
618 }
619
620 void
621 sgen_workers_start_all_workers (void)
622 {
623         State old_state, new_state;
624         int i;
625         gboolean result;
626
627         if (!collection_needs_workers ())
628                 return;
629
630         if (sgen_get_major_collector ()->init_worker_thread)
631                 sgen_get_major_collector ()->init_worker_thread (workers_gc_thread_major_collector_data);
632
633         old_state = new_state = workers_state;
634         assert_not_working (old_state);
635
636         g_assert (workers_job_queue_num_entries == 0);
637         workers_num_jobs_enqueued = 0;
638         workers_num_jobs_finished = 0;
639
640         if (workers_started) {
641                 workers_signal_enqueue_work (workers_num, FALSE);
642                 return;
643         }
644
645         new_state.data.state = STATE_WORKING;
646         new_state.data.num_awake = workers_num;
647         result = set_state (old_state, new_state);
648         SGEN_ASSERT (0, result, "Nobody else should have modified the state - workers have not been started yet");
649
650         for (i = 0; i < workers_num; ++i)
651                 workers_start_worker (i);
652
653         workers_started = TRUE;
654 }
655
656 gboolean
657 sgen_workers_have_started (void)
658 {
659         return workers_started;
660 }
661
662 void
663 sgen_workers_join (void)
664 {
665         State old_state;
666         int i;
667
668         if (!collection_needs_workers ())
669                 return;
670
671         for (;;) {
672                 old_state = workers_state;
673                 SGEN_ASSERT (0, old_state.data.state != STATE_NURSERY_COLLECTION, "Can't be in nursery collection when joining");
674
675                 if (old_state.data.state == STATE_WORKING) {
676                         State new_state = old_state;
677
678                         SGEN_ASSERT (0, !old_state.data.post_done, "Why is post_done already set?");
679                         new_state.data.post_done = 1;
680                         if (!set_state (old_state, new_state))
681                                 continue;
682
683                         MONO_SEM_WAIT (&workers_done_sem);
684
685                         old_state = workers_state;
686                 }
687
688                 assert_not_working (old_state);
689
690                 /*
691                  * Checking whether there is still work left and, if not, going to sleep,
692                  * are two separate actions that are not performed atomically by the
693                  * workers.  Therefore there's a race condition where work can be added
694                  * after they've checked for work, and before they've gone to sleep.
695                  */
696                 if (!workers_job_queue_num_entries && sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
697                         break;
698
699                 workers_signal_enqueue_work (workers_num, FALSE);
700         }
701
702         /* At this point all the workers have stopped. */
703
704         if (sgen_get_major_collector ()->reset_worker_data) {
705                 for (i = 0; i < workers_num; ++i)
706                         sgen_get_major_collector ()->reset_worker_data (workers_data [i].major_collector_data);
707         }
708
709         g_assert (workers_job_queue_num_entries == 0);
710         g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
711         for (i = 0; i < workers_num; ++i) {
712                 g_assert (!workers_data [i].stealable_stack_fill);
713                 g_assert (sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue));
714         }
715 }
716
717 gboolean
718 sgen_workers_all_done (void)
719 {
720         return workers_state.data.state == STATE_NOT_WORKING;
721 }
722
723 gboolean
724 sgen_workers_are_working (void)
725 {
726         State state = workers_state;
727         return state.data.num_awake > 0 || state.data.num_posted > 0;
728 }
729
730 gboolean
731 sgen_is_worker_thread (MonoNativeThreadId thread)
732 {
733         int i;
734
735         if (sgen_get_major_collector ()->is_worker_thread && sgen_get_major_collector ()->is_worker_thread (thread))
736                 return TRUE;
737
738         for (i = 0; i < workers_num; ++i) {
739                 if (workers_data [i].thread == thread)
740                         return TRUE;
741         }
742         return FALSE;
743 }
744
745 SgenSectionGrayQueue*
746 sgen_workers_get_distribute_section_gray_queue (void)
747 {
748         return &workers_distribute_gray_queue;
749 }
750
751 void
752 sgen_workers_reset_data (void)
753 {
754         if (sgen_get_major_collector ()->reset_worker_data)
755                 sgen_get_major_collector ()->reset_worker_data (workers_gc_thread_major_collector_data);
756 }
757 #endif