2 * sgen-workers.c: Worker threads for parallel and concurrent GC.
4 * Copyright 2001-2003 Ximian, Inc
5 * Copyright 2003-2010 Novell, Inc.
6 * Copyright (C) 2012 Xamarin Inc
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
16 #include "mono/sgen/sgen-gc.h"
17 #include "mono/sgen/sgen-workers.h"
18 #include "mono/sgen/sgen-thread-pool.h"
19 #include "mono/utils/mono-membar.h"
20 #include "mono/sgen/sgen-client.h"
22 static int workers_num
;
23 static int active_workers_num
;
24 static volatile gboolean forced_stop
;
25 static WorkerData
*workers_data
;
26 static SgenWorkerCallback worker_init_cb
;
29 * When using multiple workers, we need to have the last worker
30 * enqueue the preclean jobs (if there are any). This lock ensures
31 * that when the last worker takes it, all the other workers have
32 * gracefully finished, so it can restart them.
34 static mono_mutex_t finished_lock
;
35 static volatile gboolean workers_finished
;
36 static int worker_awakenings
;
38 static SgenSectionGrayQueue workers_distribute_gray_queue
;
39 static gboolean workers_distribute_gray_queue_inited
;
42 * Allowed transitions:
44 * | from \ to | NOT WORKING | WORKING | WORK ENQUEUED |
45 * |--------------------+-------------+---------+---------------+
46 * | NOT WORKING | - | - | main / worker |
47 * | WORKING | worker | - | main / worker |
48 * | WORK ENQUEUED | - | worker | - |
50 * The WORK ENQUEUED state guarantees that the worker thread will inspect the queue again at
51 * least once. Only after looking at the queue will it go back to WORKING, and then,
52 * eventually, to NOT WORKING. After enqueuing work the main thread transitions the state
53 * to WORK ENQUEUED. Signalling the worker thread to wake up is only necessary if the old
54 * state was NOT WORKING.
65 static SgenObjectOperations
* volatile idle_func_object_ops
;
66 static SgenObjectOperations
*idle_func_object_ops_par
, *idle_func_object_ops_nopar
;
68 * finished_callback is called only when the workers finish work normally (when they
69 * are not forced to finish). The callback is used to enqueue preclean jobs.
71 static volatile SgenWorkersFinishCallback finish_callback
;
73 static guint64 stat_workers_num_finished
;
76 set_state (WorkerData
*data
, State old_state
, State new_state
)
78 SGEN_ASSERT (0, old_state
!= new_state
, "Why are we transitioning to the same state?");
79 if (new_state
== STATE_NOT_WORKING
)
80 SGEN_ASSERT (0, old_state
== STATE_WORKING
, "We can only transition to NOT WORKING from WORKING");
81 else if (new_state
== STATE_WORKING
)
82 SGEN_ASSERT (0, old_state
== STATE_WORK_ENQUEUED
, "We can only transition to WORKING from WORK ENQUEUED");
83 if (new_state
== STATE_NOT_WORKING
|| new_state
== STATE_WORKING
)
84 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");
86 return InterlockedCompareExchange (&data
->state
, new_state
, old_state
) == old_state
;
90 state_is_working_or_enqueued (State state
)
92 return state
== STATE_WORKING
|| state
== STATE_WORK_ENQUEUED
;
96 sgen_workers_ensure_awake (void)
99 gboolean need_signal
= FALSE
;
102 * All workers are awaken, make sure we reset the parallel context.
103 * We call this function only when starting the workers so nobody is running,
104 * or when the last worker is enqueuing preclean work. In both cases we can't
105 * have a worker working using a nopar context, which means it is safe.
107 idle_func_object_ops
= (active_workers_num
> 1) ? idle_func_object_ops_par
: idle_func_object_ops_nopar
;
108 workers_finished
= FALSE
;
110 for (i
= 0; i
< active_workers_num
; i
++) {
112 gboolean did_set_state
;
115 old_state
= workers_data
[i
].state
;
117 if (old_state
== STATE_WORK_ENQUEUED
)
120 did_set_state
= set_state (&workers_data
[i
], old_state
, STATE_WORK_ENQUEUED
);
121 } while (!did_set_state
);
123 if (!state_is_working_or_enqueued (old_state
))
128 sgen_thread_pool_idle_signal ();
132 worker_try_finish (WorkerData
*data
)
137 ++stat_workers_num_finished
;
139 mono_os_mutex_lock (&finished_lock
);
141 for (i
= 0; i
< active_workers_num
; i
++) {
142 if (state_is_working_or_enqueued (workers_data
[i
].state
))
147 SgenWorkersFinishCallback callback
= finish_callback
;
148 SGEN_ASSERT (0, idle_func_object_ops
== idle_func_object_ops_nopar
, "Why are we finishing with parallel context");
149 /* We are the last one left. Enqueue preclean job if we have one and awake everybody */
150 SGEN_ASSERT (0, data
->state
!= STATE_NOT_WORKING
, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
152 finish_callback
= NULL
;
154 worker_awakenings
= 0;
155 /* Make sure each worker has a chance of seeing the enqueued jobs */
156 sgen_workers_ensure_awake ();
157 SGEN_ASSERT (0, data
->state
== STATE_WORK_ENQUEUED
, "Why did we fail to set our own state to ENQUEUED");
163 old_state
= data
->state
;
165 SGEN_ASSERT (0, old_state
!= STATE_NOT_WORKING
, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
166 if (old_state
== STATE_WORK_ENQUEUED
)
168 SGEN_ASSERT (0, old_state
== STATE_WORKING
, "What other possibility is there?");
169 } while (!set_state (data
, old_state
, STATE_NOT_WORKING
));
172 * If we are second to last to finish, we set the scan context to the non-parallel
173 * version so we can speed up the last worker. This helps us maintain same level
174 * of performance as non-parallel mode even if we fail to distribute work properly.
177 idle_func_object_ops
= idle_func_object_ops_nopar
;
179 workers_finished
= TRUE
;
180 mono_os_mutex_unlock (&finished_lock
);
182 binary_protocol_worker_finish (sgen_timestamp (), forced_stop
);
184 sgen_gray_object_queue_trim_free_list (&data
->private_gray_queue
);
188 mono_os_mutex_unlock (&finished_lock
);
192 sgen_workers_enqueue_job (SgenThreadPoolJob
*job
, gboolean enqueue
)
195 job
->func (NULL
, job
);
196 sgen_thread_pool_job_free (job
);
200 sgen_thread_pool_job_enqueue (job
);
204 workers_get_work (WorkerData
*data
)
206 SgenMajorCollector
*major
;
208 g_assert (sgen_gray_object_queue_is_empty (&data
->private_gray_queue
));
210 /* If we're concurrent, steal from the workers distribute gray queue. */
211 major
= sgen_get_major_collector ();
212 if (major
->is_concurrent
) {
213 GrayQueueSection
*section
= sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue
);
215 sgen_gray_object_enqueue_section (&data
->private_gray_queue
, section
, major
->is_parallel
);
220 /* Nobody to steal from */
221 g_assert (sgen_gray_object_queue_is_empty (&data
->private_gray_queue
));
226 workers_steal_work (WorkerData
*data
)
228 SgenMajorCollector
*major
= sgen_get_major_collector ();
229 GrayQueueSection
*section
= NULL
;
230 int i
, current_worker
;
232 if (!major
->is_parallel
)
235 /* If we're parallel, steal from other workers' private gray queues */
236 g_assert (sgen_gray_object_queue_is_empty (&data
->private_gray_queue
));
238 current_worker
= (int) (data
- workers_data
);
240 for (i
= 1; i
< active_workers_num
&& !section
; i
++) {
241 int steal_worker
= (current_worker
+ i
) % active_workers_num
;
242 if (state_is_working_or_enqueued (workers_data
[steal_worker
].state
))
243 section
= sgen_gray_object_steal_section (&workers_data
[steal_worker
].private_gray_queue
);
247 sgen_gray_object_enqueue_section (&data
->private_gray_queue
, section
, TRUE
);
251 /* Nobody to steal from */
252 g_assert (sgen_gray_object_queue_is_empty (&data
->private_gray_queue
));
257 concurrent_enqueue_check (GCObject
*obj
)
259 g_assert (sgen_concurrent_collection_in_progress ());
260 g_assert (!sgen_ptr_in_nursery (obj
));
261 g_assert (SGEN_LOAD_VTABLE (obj
));
265 init_private_gray_queue (WorkerData
*data
)
267 sgen_gray_object_queue_init (&data
->private_gray_queue
,
268 sgen_get_major_collector ()->is_concurrent
? concurrent_enqueue_check
: NULL
,
273 thread_pool_init_func (void *data_untyped
)
275 WorkerData
*data
= (WorkerData
*)data_untyped
;
276 SgenMajorCollector
*major
= sgen_get_major_collector ();
278 sgen_client_thread_register_worker ();
280 if (!major
->is_concurrent
)
283 init_private_gray_queue (data
);
286 worker_init_cb (data
);
290 continue_idle_func (void *data_untyped
)
293 WorkerData
*data
= (WorkerData
*)data_untyped
;
294 return state_is_working_or_enqueued (data
->state
);
296 /* Return if any of the threads is working */
297 return !sgen_workers_all_done ();
302 should_work_func (void *data_untyped
)
304 WorkerData
*data
= (WorkerData
*)data_untyped
;
305 int current_worker
= (int) (data
- workers_data
);
307 return current_worker
< active_workers_num
;
311 marker_idle_func (void *data_untyped
)
313 WorkerData
*data
= (WorkerData
*)data_untyped
;
315 SGEN_ASSERT (0, continue_idle_func (data_untyped
), "Why are we called when we're not supposed to work?");
316 SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "The worker should only mark in concurrent collections.");
318 if (data
->state
== STATE_WORK_ENQUEUED
) {
319 set_state (data
, STATE_WORK_ENQUEUED
, STATE_WORKING
);
320 SGEN_ASSERT (0, data
->state
!= STATE_NOT_WORKING
, "How did we get from WORK ENQUEUED to NOT WORKING?");
323 if (!forced_stop
&& (!sgen_gray_object_queue_is_empty (&data
->private_gray_queue
) || workers_get_work (data
) || workers_steal_work (data
))) {
324 ScanCopyContext ctx
= CONTEXT_FROM_OBJECT_OPERATIONS (idle_func_object_ops
, &data
->private_gray_queue
);
326 SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data
->private_gray_queue
), "How is our gray queue empty if we just got work?");
328 sgen_drain_gray_stack (ctx
);
330 if (data
->private_gray_queue
.num_sections
> 16 && workers_finished
&& worker_awakenings
< active_workers_num
) {
331 /* We bound the number of worker awakenings just to be sure */
333 mono_os_mutex_lock (&finished_lock
);
334 sgen_workers_ensure_awake ();
335 mono_os_mutex_unlock (&finished_lock
);
338 worker_try_finish (data
);
343 init_distribute_gray_queue (void)
345 if (workers_distribute_gray_queue_inited
) {
346 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue
));
347 g_assert (workers_distribute_gray_queue
.locked
);
351 sgen_section_gray_queue_init (&workers_distribute_gray_queue
, TRUE
,
352 sgen_get_major_collector ()->is_concurrent
? concurrent_enqueue_check
: NULL
);
353 workers_distribute_gray_queue_inited
= TRUE
;
357 sgen_workers_init_distribute_gray_queue (void)
359 SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent
,
360 "Why should we init the distribute gray queue if we don't need it?");
361 init_distribute_gray_queue ();
365 sgen_workers_init (int num_workers
, SgenWorkerCallback callback
)
368 void **workers_data_ptrs
= (void **)alloca(num_workers
* sizeof(void *));
370 if (!sgen_get_major_collector ()->is_concurrent
) {
371 sgen_thread_pool_init (num_workers
, thread_pool_init_func
, NULL
, NULL
, NULL
, NULL
);
375 mono_os_mutex_init (&finished_lock
);
376 //g_print ("initing %d workers\n", num_workers);
378 workers_num
= num_workers
;
379 active_workers_num
= num_workers
;
381 workers_data
= (WorkerData
*)sgen_alloc_internal_dynamic (sizeof (WorkerData
) * num_workers
, INTERNAL_MEM_WORKER_DATA
, TRUE
);
382 memset (workers_data
, 0, sizeof (WorkerData
) * num_workers
);
384 init_distribute_gray_queue ();
386 for (i
= 0; i
< num_workers
; ++i
)
387 workers_data_ptrs
[i
] = (void *) &workers_data
[i
];
389 worker_init_cb
= callback
;
391 sgen_thread_pool_init (num_workers
, thread_pool_init_func
, marker_idle_func
, continue_idle_func
, should_work_func
, workers_data_ptrs
);
393 mono_counters_register ("# workers finished", MONO_COUNTER_GC
| MONO_COUNTER_ULONG
, &stat_workers_num_finished
);
397 sgen_workers_stop_all_workers (void)
399 finish_callback
= NULL
;
400 mono_memory_write_barrier ();
403 sgen_thread_pool_wait_for_all_jobs ();
404 sgen_thread_pool_idle_wait ();
405 SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
409 sgen_workers_set_num_active_workers (int num_workers
)
412 SGEN_ASSERT (0, active_workers_num
<= workers_num
, "We can't start more workers than we initialized");
413 active_workers_num
= num_workers
;
415 active_workers_num
= workers_num
;
420 sgen_workers_start_all_workers (SgenObjectOperations
*object_ops_nopar
, SgenObjectOperations
*object_ops_par
, SgenWorkersFinishCallback callback
)
422 idle_func_object_ops_par
= object_ops_par
;
423 idle_func_object_ops_nopar
= object_ops_nopar
;
425 finish_callback
= callback
;
426 worker_awakenings
= 0;
427 mono_memory_write_barrier ();
430 * We expect workers to start finishing only after all of them were awaken.
431 * Otherwise we might think that we have fewer workers and use wrong context.
433 mono_os_mutex_lock (&finished_lock
);
434 sgen_workers_ensure_awake ();
435 mono_os_mutex_unlock (&finished_lock
);
439 sgen_workers_join (void)
443 sgen_thread_pool_wait_for_all_jobs ();
444 sgen_thread_pool_idle_wait ();
445 SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
447 /* At this point all the workers have stopped. */
449 SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue
), "Why is there still work left to do?");
450 for (i
= 0; i
< active_workers_num
; ++i
)
451 SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (&workers_data
[i
].private_gray_queue
), "Why is there still work left to do?");
455 * Can only be called if the workers are stopped.
456 * If we're stopped, there are also no pending jobs.
459 sgen_workers_have_idle_work (void)
463 SGEN_ASSERT (0, forced_stop
&& sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
465 if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue
))
468 for (i
= 0; i
< active_workers_num
; ++i
) {
469 if (!sgen_gray_object_queue_is_empty (&workers_data
[i
].private_gray_queue
))
477 sgen_workers_all_done (void)
481 for (i
= 0; i
< active_workers_num
; i
++) {
482 if (state_is_working_or_enqueued (workers_data
[i
].state
))
488 /* Must only be used for debugging */
490 sgen_workers_are_working (void)
492 return !sgen_workers_all_done ();
496 sgen_workers_assert_gray_queue_is_empty (void)
498 SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue
), "Why is the workers gray queue not empty?");
502 sgen_workers_take_from_queue (SgenGrayQueue
*queue
)
504 sgen_gray_object_spread (queue
, sgen_workers_get_job_split_count ());
507 GrayQueueSection
*section
= sgen_gray_object_dequeue_section (queue
);
510 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue
, section
);
513 SGEN_ASSERT (0, !sgen_workers_are_working (), "We should fully populate the distribute gray queue before we start the workers");
516 SgenObjectOperations
*
517 sgen_workers_get_idle_func_object_ops (void)
519 return (idle_func_object_ops_par
) ? idle_func_object_ops_par
: idle_func_object_ops_nopar
;
523 * If we have a single worker, splitting into multiple jobs makes no sense. With
524 * more than one worker, we split into a larger number of jobs so that, in case
525 * the work load is uneven, a worker that finished quickly can take up more jobs
529 sgen_workers_get_job_split_count (void)
531 return (active_workers_num
> 1) ? active_workers_num
* 4 : 1;
535 sgen_workers_foreach (SgenWorkerCallback callback
)
539 for (i
= 0; i
< workers_num
; i
++)
540 callback (&workers_data
[i
]);