3 * Worker threads for parallel and concurrent GC.
5 * Copyright 2001-2003 Ximian, Inc
6 * Copyright 2003-2010 Novell, Inc.
7 * Copyright (C) 2012 Xamarin Inc
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
17 #include "mono/sgen/sgen-gc.h"
18 #include "mono/sgen/sgen-workers.h"
19 #include "mono/sgen/sgen-thread-pool.h"
20 #include "mono/utils/mono-membar.h"
21 #include "mono/sgen/sgen-client.h"
23 static int workers_num;
24 static int active_workers_num;
25 static volatile gboolean forced_stop;
26 static WorkerData *workers_data;
27 static SgenWorkerCallback worker_init_cb;
30 * When using multiple workers, we need to have the last worker
31 * enqueue the preclean jobs (if there are any). This lock ensures
32 * that when the last worker takes it, all the other workers have
33 * gracefully finished, so it can restart them.
35 static mono_mutex_t finished_lock;
36 static volatile gboolean workers_finished;
37 static int worker_awakenings;
39 static SgenSectionGrayQueue workers_distribute_gray_queue;
40 static gboolean workers_distribute_gray_queue_inited;
43 * Allowed transitions:
45 * | from \ to | NOT WORKING | WORKING | WORK ENQUEUED |
46 * |--------------------+-------------+---------+---------------+
47 * | NOT WORKING | - | - | main / worker |
48 * | WORKING | worker | - | main / worker |
49 * | WORK ENQUEUED | - | worker | - |
51 * The WORK ENQUEUED state guarantees that the worker thread will inspect the queue again at
52 * least once. Only after looking at the queue will it go back to WORKING, and then,
53 * eventually, to NOT WORKING. After enqueuing work the main thread transitions the state
54 * to WORK ENQUEUED. Signalling the worker thread to wake up is only necessary if the old
55 * state was NOT WORKING.
66 static SgenObjectOperations * volatile idle_func_object_ops;
67 static SgenObjectOperations *idle_func_object_ops_par, *idle_func_object_ops_nopar;
69 * finished_callback is called only when the workers finish work normally (when they
70 * are not forced to finish). The callback is used to enqueue preclean jobs.
72 static volatile SgenWorkersFinishCallback finish_callback;
74 static guint64 stat_workers_num_finished;
77 set_state (WorkerData *data, State old_state, State new_state)
79 SGEN_ASSERT (0, old_state != new_state, "Why are we transitioning to the same state?");
80 if (new_state == STATE_NOT_WORKING)
81 SGEN_ASSERT (0, old_state == STATE_WORKING, "We can only transition to NOT WORKING from WORKING");
82 else if (new_state == STATE_WORKING)
83 SGEN_ASSERT (0, old_state == STATE_WORK_ENQUEUED, "We can only transition to WORKING from WORK ENQUEUED");
84 if (new_state == STATE_NOT_WORKING || new_state == STATE_WORKING)
85 SGEN_ASSERT (6, sgen_thread_pool_is_thread_pool_thread (mono_native_thread_id_get ()), "Only the worker thread is allowed to transition to NOT_WORKING or WORKING");
87 return InterlockedCompareExchange (&data->state, new_state, old_state) == old_state;
91 state_is_working_or_enqueued (State state)
93 return state == STATE_WORKING || state == STATE_WORK_ENQUEUED;
97 sgen_workers_ensure_awake (void)
100 gboolean need_signal = FALSE;
103 * All workers are awaken, make sure we reset the parallel context.
104 * We call this function only when starting the workers so nobody is running,
105 * or when the last worker is enqueuing preclean work. In both cases we can't
106 * have a worker working using a nopar context, which means it is safe.
108 idle_func_object_ops = (active_workers_num > 1) ? idle_func_object_ops_par : idle_func_object_ops_nopar;
109 workers_finished = FALSE;
111 for (i = 0; i < active_workers_num; i++) {
113 gboolean did_set_state;
116 old_state = workers_data [i].state;
118 if (old_state == STATE_WORK_ENQUEUED)
121 did_set_state = set_state (&workers_data [i], old_state, STATE_WORK_ENQUEUED);
122 } while (!did_set_state);
124 if (!state_is_working_or_enqueued (old_state))
129 sgen_thread_pool_idle_signal ();
133 worker_try_finish (WorkerData *data)
138 ++stat_workers_num_finished;
140 mono_os_mutex_lock (&finished_lock);
142 for (i = 0; i < active_workers_num; i++) {
143 if (state_is_working_or_enqueued (workers_data [i].state))
148 SgenWorkersFinishCallback callback = finish_callback;
149 SGEN_ASSERT (0, idle_func_object_ops == idle_func_object_ops_nopar, "Why are we finishing with parallel context");
150 /* We are the last one left. Enqueue preclean job if we have one and awake everybody */
151 SGEN_ASSERT (0, data->state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
153 finish_callback = NULL;
155 worker_awakenings = 0;
156 /* Make sure each worker has a chance of seeing the enqueued jobs */
157 sgen_workers_ensure_awake ();
158 SGEN_ASSERT (0, data->state == STATE_WORK_ENQUEUED, "Why did we fail to set our own state to ENQUEUED");
164 old_state = data->state;
166 SGEN_ASSERT (0, old_state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
167 if (old_state == STATE_WORK_ENQUEUED)
169 SGEN_ASSERT (0, old_state == STATE_WORKING, "What other possibility is there?");
170 } while (!set_state (data, old_state, STATE_NOT_WORKING));
173 * If we are second to last to finish, we set the scan context to the non-parallel
174 * version so we can speed up the last worker. This helps us maintain same level
175 * of performance as non-parallel mode even if we fail to distribute work properly.
178 idle_func_object_ops = idle_func_object_ops_nopar;
180 workers_finished = TRUE;
181 mono_os_mutex_unlock (&finished_lock);
183 binary_protocol_worker_finish (sgen_timestamp (), forced_stop);
185 sgen_gray_object_queue_trim_free_list (&data->private_gray_queue);
189 mono_os_mutex_unlock (&finished_lock);
193 sgen_workers_enqueue_job (SgenThreadPoolJob *job, gboolean enqueue)
196 job->func (NULL, job);
197 sgen_thread_pool_job_free (job);
201 sgen_thread_pool_job_enqueue (job);
205 workers_get_work (WorkerData *data)
207 SgenMajorCollector *major;
209 g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
211 /* If we're concurrent, steal from the workers distribute gray queue. */
212 major = sgen_get_major_collector ();
213 if (major->is_concurrent) {
214 GrayQueueSection *section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
216 sgen_gray_object_enqueue_section (&data->private_gray_queue, section, major->is_parallel);
221 /* Nobody to steal from */
222 g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
227 workers_steal_work (WorkerData *data)
229 SgenMajorCollector *major = sgen_get_major_collector ();
230 GrayQueueSection *section = NULL;
231 int i, current_worker;
233 if (!major->is_parallel)
236 /* If we're parallel, steal from other workers' private gray queues */
237 g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
239 current_worker = (int) (data - workers_data);
241 for (i = 1; i < active_workers_num && !section; i++) {
242 int steal_worker = (current_worker + i) % active_workers_num;
243 if (state_is_working_or_enqueued (workers_data [steal_worker].state))
244 section = sgen_gray_object_steal_section (&workers_data [steal_worker].private_gray_queue);
248 sgen_gray_object_enqueue_section (&data->private_gray_queue, section, TRUE);
252 /* Nobody to steal from */
253 g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
258 concurrent_enqueue_check (GCObject *obj)
260 g_assert (sgen_concurrent_collection_in_progress ());
261 g_assert (!sgen_ptr_in_nursery (obj));
262 g_assert (SGEN_LOAD_VTABLE (obj));
266 init_private_gray_queue (WorkerData *data)
268 sgen_gray_object_queue_init (&data->private_gray_queue,
269 sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
274 thread_pool_init_func (void *data_untyped)
276 WorkerData *data = (WorkerData *)data_untyped;
277 SgenMajorCollector *major = sgen_get_major_collector ();
279 sgen_client_thread_register_worker ();
281 if (!major->is_concurrent)
284 init_private_gray_queue (data);
287 worker_init_cb (data);
291 continue_idle_func (void *data_untyped)
294 WorkerData *data = (WorkerData *)data_untyped;
295 return state_is_working_or_enqueued (data->state);
297 /* Return if any of the threads is working */
298 return !sgen_workers_all_done ();
303 should_work_func (void *data_untyped)
305 WorkerData *data = (WorkerData*)data_untyped;
306 int current_worker = (int) (data - workers_data);
308 return current_worker < active_workers_num;
312 marker_idle_func (void *data_untyped)
314 WorkerData *data = (WorkerData *)data_untyped;
316 SGEN_ASSERT (0, continue_idle_func (data_untyped), "Why are we called when we're not supposed to work?");
317 SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "The worker should only mark in concurrent collections.");
319 if (data->state == STATE_WORK_ENQUEUED) {
320 set_state (data, STATE_WORK_ENQUEUED, STATE_WORKING);
321 SGEN_ASSERT (0, data->state != STATE_NOT_WORKING, "How did we get from WORK ENQUEUED to NOT WORKING?");
324 if (!forced_stop && (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data) || workers_steal_work (data))) {
325 ScanCopyContext ctx = CONTEXT_FROM_OBJECT_OPERATIONS (idle_func_object_ops, &data->private_gray_queue);
327 SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data->private_gray_queue), "How is our gray queue empty if we just got work?");
329 sgen_drain_gray_stack (ctx);
331 if (data->private_gray_queue.num_sections > 16 && workers_finished && worker_awakenings < active_workers_num) {
332 /* We bound the number of worker awakenings just to be sure */
334 mono_os_mutex_lock (&finished_lock);
335 sgen_workers_ensure_awake ();
336 mono_os_mutex_unlock (&finished_lock);
339 worker_try_finish (data);
344 init_distribute_gray_queue (void)
346 if (workers_distribute_gray_queue_inited) {
347 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
348 g_assert (workers_distribute_gray_queue.locked);
352 sgen_section_gray_queue_init (&workers_distribute_gray_queue, TRUE,
353 sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
354 workers_distribute_gray_queue_inited = TRUE;
358 sgen_workers_init_distribute_gray_queue (void)
360 SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent,
361 "Why should we init the distribute gray queue if we don't need it?");
362 init_distribute_gray_queue ();
366 sgen_workers_init (int num_workers, SgenWorkerCallback callback)
369 void **workers_data_ptrs = (void **)alloca(num_workers * sizeof(void *));
371 if (!sgen_get_major_collector ()->is_concurrent) {
372 sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL, NULL, NULL);
376 mono_os_mutex_init (&finished_lock);
377 //g_print ("initing %d workers\n", num_workers);
379 workers_num = num_workers;
380 active_workers_num = num_workers;
382 workers_data = (WorkerData *)sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
383 memset (workers_data, 0, sizeof (WorkerData) * num_workers);
385 init_distribute_gray_queue ();
387 for (i = 0; i < num_workers; ++i)
388 workers_data_ptrs [i] = (void *) &workers_data [i];
390 worker_init_cb = callback;
392 sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, continue_idle_func, should_work_func, workers_data_ptrs);
394 mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
398 sgen_workers_stop_all_workers (void)
400 finish_callback = NULL;
401 mono_memory_write_barrier ();
404 sgen_thread_pool_wait_for_all_jobs ();
405 sgen_thread_pool_idle_wait ();
406 SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
410 sgen_workers_set_num_active_workers (int num_workers)
413 SGEN_ASSERT (0, active_workers_num <= workers_num, "We can't start more workers than we initialized");
414 active_workers_num = num_workers;
416 active_workers_num = workers_num;
421 sgen_workers_start_all_workers (SgenObjectOperations *object_ops_nopar, SgenObjectOperations *object_ops_par, SgenWorkersFinishCallback callback)
423 idle_func_object_ops_par = object_ops_par;
424 idle_func_object_ops_nopar = object_ops_nopar;
426 finish_callback = callback;
427 worker_awakenings = 0;
428 mono_memory_write_barrier ();
431 * We expect workers to start finishing only after all of them were awaken.
432 * Otherwise we might think that we have fewer workers and use wrong context.
434 mono_os_mutex_lock (&finished_lock);
435 sgen_workers_ensure_awake ();
436 mono_os_mutex_unlock (&finished_lock);
440 sgen_workers_join (void)
444 sgen_thread_pool_wait_for_all_jobs ();
445 sgen_thread_pool_idle_wait ();
446 SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
448 /* At this point all the workers have stopped. */
450 SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is there still work left to do?");
451 for (i = 0; i < active_workers_num; ++i)
452 SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue), "Why is there still work left to do?");
456 * Can only be called if the workers are stopped.
457 * If we're stopped, there are also no pending jobs.
460 sgen_workers_have_idle_work (void)
464 SGEN_ASSERT (0, forced_stop && sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
466 if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
469 for (i = 0; i < active_workers_num; ++i) {
470 if (!sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue))
478 sgen_workers_all_done (void)
482 for (i = 0; i < active_workers_num; i++) {
483 if (state_is_working_or_enqueued (workers_data [i].state))
489 /* Must only be used for debugging */
491 sgen_workers_are_working (void)
493 return !sgen_workers_all_done ();
497 sgen_workers_assert_gray_queue_is_empty (void)
499 SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is the workers gray queue not empty?");
503 sgen_workers_take_from_queue (SgenGrayQueue *queue)
505 sgen_gray_object_spread (queue, sgen_workers_get_job_split_count ());
508 GrayQueueSection *section = sgen_gray_object_dequeue_section (queue);
511 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue, section);
514 SGEN_ASSERT (0, !sgen_workers_are_working (), "We should fully populate the distribute gray queue before we start the workers");
517 SgenObjectOperations*
518 sgen_workers_get_idle_func_object_ops (void)
520 return (idle_func_object_ops_par) ? idle_func_object_ops_par : idle_func_object_ops_nopar;
524 * If we have a single worker, splitting into multiple jobs makes no sense. With
525 * more than one worker, we split into a larger number of jobs so that, in case
526 * the work load is uneven, a worker that finished quickly can take up more jobs
530 sgen_workers_get_job_split_count (void)
532 return (active_workers_num > 1) ? active_workers_num * 4 : 1;
536 sgen_workers_foreach (SgenWorkerCallback callback)
540 for (i = 0; i < workers_num; i++)
541 callback (&workers_data [i]);