f5169f45ab2e711c87ac1a59d41007ef8715b236
[mono.git] / mono / sgen / sgen-workers.c
1 /**
2  * \file
3  * Worker threads for parallel and concurrent GC.
4  *
5  * Copyright 2001-2003 Ximian, Inc
6  * Copyright 2003-2010 Novell, Inc.
7  * Copyright (C) 2012 Xamarin Inc
8  *
9  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
10  */
11
12 #include "config.h"
13 #ifdef HAVE_SGEN_GC
14
15 #include <string.h>
16
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"
22
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;
28
29 /*
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.
34  */
35 static mono_mutex_t finished_lock;
36 static volatile gboolean workers_finished;
37 static int worker_awakenings;
38
39 static SgenSectionGrayQueue workers_distribute_gray_queue;
40 static gboolean workers_distribute_gray_queue_inited;
41
42 /*
43  * Allowed transitions:
44  *
45  * | from \ to          | NOT WORKING | WORKING | WORK ENQUEUED |
46  * |--------------------+-------------+---------+---------------+
47  * | NOT WORKING        | -           | -       | main / worker |
48  * | WORKING            | worker      | -       | main / worker |
49  * | WORK ENQUEUED      | -           | worker  | -             |
50  *
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.
56  */
57
58 enum {
59         STATE_NOT_WORKING,
60         STATE_WORKING,
61         STATE_WORK_ENQUEUED
62 };
63
64 typedef gint32 State;
65
66 static SgenObjectOperations * volatile idle_func_object_ops;
67 static SgenObjectOperations *idle_func_object_ops_par, *idle_func_object_ops_nopar;
68 /*
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.
71  */
72 static volatile SgenWorkersFinishCallback finish_callback;
73
74 static guint64 stat_workers_num_finished;
75
76 static gboolean
77 set_state (WorkerData *data, State old_state, State new_state)
78 {
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");
86
87         return InterlockedCompareExchange (&data->state, new_state, old_state) == old_state;
88 }
89
90 static gboolean
91 state_is_working_or_enqueued (State state)
92 {
93         return state == STATE_WORKING || state == STATE_WORK_ENQUEUED;
94 }
95
96 static void
97 sgen_workers_ensure_awake (void)
98 {
99         int i;
100         gboolean need_signal = FALSE;
101
102         /*
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.
107          */
108         idle_func_object_ops = (active_workers_num > 1) ? idle_func_object_ops_par : idle_func_object_ops_nopar;
109         workers_finished = FALSE;
110
111         for (i = 0; i < active_workers_num; i++) {
112                 State old_state;
113                 gboolean did_set_state;
114
115                 do {
116                         old_state = workers_data [i].state;
117
118                         if (old_state == STATE_WORK_ENQUEUED)
119                                 break;
120
121                         did_set_state = set_state (&workers_data [i], old_state, STATE_WORK_ENQUEUED);
122                 } while (!did_set_state);
123
124                 if (!state_is_working_or_enqueued (old_state))
125                         need_signal = TRUE;
126         }
127
128         if (need_signal)
129                 sgen_thread_pool_idle_signal ();
130 }
131
132 static void
133 worker_try_finish (WorkerData *data)
134 {
135         State old_state;
136         int i, working = 0;
137
138         ++stat_workers_num_finished;
139
140         mono_os_mutex_lock (&finished_lock);
141
142         for (i = 0; i < active_workers_num; i++) {
143                 if (state_is_working_or_enqueued (workers_data [i].state))
144                         working++;
145         }
146
147         if (working == 1) {
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?");
152                 if (callback) {
153                         finish_callback = NULL;
154                         callback ();
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");
159                         goto work_available;
160                 }
161         }
162
163         do {
164                 old_state = data->state;
165
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)
168                         goto work_available;
169                 SGEN_ASSERT (0, old_state == STATE_WORKING, "What other possibility is there?");
170         } while (!set_state (data, old_state, STATE_NOT_WORKING));
171
172         /*
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.
176          */
177         if (working == 2)
178                 idle_func_object_ops = idle_func_object_ops_nopar;
179
180         workers_finished = TRUE;
181         mono_os_mutex_unlock (&finished_lock);
182
183         binary_protocol_worker_finish (sgen_timestamp (), forced_stop);
184
185         sgen_gray_object_queue_trim_free_list (&data->private_gray_queue);
186         return;
187
188 work_available:
189         mono_os_mutex_unlock (&finished_lock);
190 }
191
192 void
193 sgen_workers_enqueue_job (SgenThreadPoolJob *job, gboolean enqueue)
194 {
195         if (!enqueue) {
196                 job->func (NULL, job);
197                 sgen_thread_pool_job_free (job);
198                 return;
199         }
200
201         sgen_thread_pool_job_enqueue (job);
202 }
203
204 static gboolean
205 workers_get_work (WorkerData *data)
206 {
207         SgenMajorCollector *major;
208
209         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
210
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);
215                 if (section) {
216                         sgen_gray_object_enqueue_section (&data->private_gray_queue, section, major->is_parallel);
217                         return TRUE;
218                 }
219         }
220
221         /* Nobody to steal from */
222         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
223         return FALSE;
224 }
225
226 static gboolean
227 workers_steal_work (WorkerData *data)
228 {
229         SgenMajorCollector *major = sgen_get_major_collector ();
230         GrayQueueSection *section = NULL;
231         int i, current_worker;
232
233         if (!major->is_parallel)
234                 return FALSE;
235
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));
238
239         current_worker = (int) (data - workers_data);
240
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);
245         }
246
247         if (section) {
248                 sgen_gray_object_enqueue_section (&data->private_gray_queue, section, TRUE);
249                 return TRUE;
250         }
251
252         /* Nobody to steal from */
253         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
254         return FALSE;
255 }
256
257 static void
258 concurrent_enqueue_check (GCObject *obj)
259 {
260         g_assert (sgen_concurrent_collection_in_progress ());
261         g_assert (!sgen_ptr_in_nursery (obj));
262         g_assert (SGEN_LOAD_VTABLE (obj));
263 }
264
265 static void
266 init_private_gray_queue (WorkerData *data)
267 {
268         sgen_gray_object_queue_init (&data->private_gray_queue,
269                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
270                         FALSE);
271 }
272
273 static void
274 thread_pool_init_func (void *data_untyped)
275 {
276         WorkerData *data = (WorkerData *)data_untyped;
277         SgenMajorCollector *major = sgen_get_major_collector ();
278
279         sgen_client_thread_register_worker ();
280
281         if (!major->is_concurrent)
282                 return;
283
284         init_private_gray_queue (data);
285
286         if (worker_init_cb)
287                 worker_init_cb (data);
288 }
289
290 static gboolean
291 continue_idle_func (void *data_untyped)
292 {
293         if (data_untyped) {
294                 WorkerData *data = (WorkerData *)data_untyped;
295                 return state_is_working_or_enqueued (data->state);
296         } else {
297                 /* Return if any of the threads is working */
298                 return !sgen_workers_all_done ();
299         }
300 }
301
302 static gboolean
303 should_work_func (void *data_untyped)
304 {
305         WorkerData *data = (WorkerData*)data_untyped;
306         int current_worker = (int) (data - workers_data);
307
308         return current_worker < active_workers_num;
309 }
310
311 static void
312 marker_idle_func (void *data_untyped)
313 {
314         WorkerData *data = (WorkerData *)data_untyped;
315
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.");
318
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?");
322         }
323
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);
326
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?");
328
329                 sgen_drain_gray_stack (ctx);
330
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 */
333                         worker_awakenings++;
334                         mono_os_mutex_lock (&finished_lock);
335                         sgen_workers_ensure_awake ();
336                         mono_os_mutex_unlock (&finished_lock);
337                 }
338         } else {
339                 worker_try_finish (data);
340         }
341 }
342
343 static void
344 init_distribute_gray_queue (void)
345 {
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);
349                 return;
350         }
351
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;
355 }
356
357 void
358 sgen_workers_init_distribute_gray_queue (void)
359 {
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 ();
363 }
364
365 void
366 sgen_workers_init (int num_workers, SgenWorkerCallback callback)
367 {
368         int i;
369         void **workers_data_ptrs = (void **)alloca(num_workers * sizeof(void *));
370
371         if (!sgen_get_major_collector ()->is_concurrent) {
372                 sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL, NULL, NULL);
373                 return;
374         }
375
376         mono_os_mutex_init (&finished_lock);
377         //g_print ("initing %d workers\n", num_workers);
378
379         workers_num = num_workers;
380         active_workers_num = num_workers;
381
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);
384
385         init_distribute_gray_queue ();
386
387         for (i = 0; i < num_workers; ++i)
388                 workers_data_ptrs [i] = (void *) &workers_data [i];
389
390         worker_init_cb = callback;
391
392         sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, continue_idle_func, should_work_func, workers_data_ptrs);
393
394         mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
395 }
396
397 void
398 sgen_workers_stop_all_workers (void)
399 {
400         finish_callback = NULL;
401         mono_memory_write_barrier ();
402         forced_stop = TRUE;
403
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");
407 }
408
409 void
410 sgen_workers_set_num_active_workers (int num_workers)
411 {
412         if (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;
415         } else {
416                 active_workers_num = workers_num;
417         }
418 }
419
420 void
421 sgen_workers_start_all_workers (SgenObjectOperations *object_ops_nopar, SgenObjectOperations *object_ops_par, SgenWorkersFinishCallback callback)
422 {
423         idle_func_object_ops_par = object_ops_par;
424         idle_func_object_ops_nopar = object_ops_nopar;
425         forced_stop = FALSE;
426         finish_callback = callback;
427         worker_awakenings = 0;
428         mono_memory_write_barrier ();
429
430         /*
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.
433          */
434         mono_os_mutex_lock (&finished_lock);
435         sgen_workers_ensure_awake ();
436         mono_os_mutex_unlock (&finished_lock);
437 }
438
439 void
440 sgen_workers_join (void)
441 {
442         int i;
443
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");
447
448         /* At this point all the workers have stopped. */
449
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?");
453 }
454
455 /*
456  * Can only be called if the workers are stopped.
457  * If we're stopped, there are also no pending jobs.
458  */
459 gboolean
460 sgen_workers_have_idle_work (void)
461 {
462         int i;
463
464         SGEN_ASSERT (0, forced_stop && sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
465
466         if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
467                 return TRUE;
468
469         for (i = 0; i < active_workers_num; ++i) {
470                 if (!sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue))
471                         return TRUE;
472         }
473
474         return FALSE;
475 }
476
477 gboolean
478 sgen_workers_all_done (void)
479 {
480         int i;
481
482         for (i = 0; i < active_workers_num; i++) {
483                 if (state_is_working_or_enqueued (workers_data [i].state))
484                         return FALSE;
485         }
486         return TRUE;
487 }
488
489 /* Must only be used for debugging */
490 gboolean
491 sgen_workers_are_working (void)
492 {
493         return !sgen_workers_all_done ();
494 }
495
496 void
497 sgen_workers_assert_gray_queue_is_empty (void)
498 {
499         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is the workers gray queue not empty?");
500 }
501
502 void
503 sgen_workers_take_from_queue (SgenGrayQueue *queue)
504 {
505         sgen_gray_object_spread (queue, sgen_workers_get_job_split_count ());
506
507         for (;;) {
508                 GrayQueueSection *section = sgen_gray_object_dequeue_section (queue);
509                 if (!section)
510                         break;
511                 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue, section);
512         }
513
514         SGEN_ASSERT (0, !sgen_workers_are_working (), "We should fully populate the distribute gray queue before we start the workers");
515 }
516
517 SgenObjectOperations*
518 sgen_workers_get_idle_func_object_ops (void)
519 {
520         return (idle_func_object_ops_par) ? idle_func_object_ops_par : idle_func_object_ops_nopar;
521 }
522
523 /*
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
527  * than another one.
528  */
529 int
530 sgen_workers_get_job_split_count (void)
531 {
532         return (active_workers_num > 1) ? active_workers_num * 4 : 1;
533 }
534
535 void
536 sgen_workers_foreach (SgenWorkerCallback callback)
537 {
538         int i;
539
540         for (i = 0; i < workers_num; i++)
541                 callback (&workers_data [i]);
542 }
543
544 #endif