[sgen] Increase gray stack section size
[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 #define SGEN_WORKER_MIN_SECTIONS_SIGNAL 4
65
66 typedef gint32 State;
67
68 static SgenObjectOperations * volatile idle_func_object_ops;
69 static SgenObjectOperations *idle_func_object_ops_par, *idle_func_object_ops_nopar;
70 /*
71  * finished_callback is called only when the workers finish work normally (when they
72  * are not forced to finish). The callback is used to enqueue preclean jobs.
73  */
74 static volatile SgenWorkersFinishCallback finish_callback;
75
76 static guint64 stat_workers_num_finished;
77
78 static gboolean
79 set_state (WorkerData *data, State old_state, State new_state)
80 {
81         SGEN_ASSERT (0, old_state != new_state, "Why are we transitioning to the same state?");
82         if (new_state == STATE_NOT_WORKING)
83                 SGEN_ASSERT (0, old_state == STATE_WORKING, "We can only transition to NOT WORKING from WORKING");
84         else if (new_state == STATE_WORKING)
85                 SGEN_ASSERT (0, old_state == STATE_WORK_ENQUEUED, "We can only transition to WORKING from WORK ENQUEUED");
86         if (new_state == STATE_NOT_WORKING || new_state == STATE_WORKING)
87                 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");
88
89         return InterlockedCompareExchange (&data->state, new_state, old_state) == old_state;
90 }
91
92 static gboolean
93 state_is_working_or_enqueued (State state)
94 {
95         return state == STATE_WORKING || state == STATE_WORK_ENQUEUED;
96 }
97
98 static void
99 sgen_workers_ensure_awake (void)
100 {
101         int i;
102         gboolean need_signal = FALSE;
103
104         /*
105          * All workers are awaken, make sure we reset the parallel context.
106          * We call this function only when starting the workers so nobody is running,
107          * or when the last worker is enqueuing preclean work. In both cases we can't
108          * have a worker working using a nopar context, which means it is safe.
109          */
110         idle_func_object_ops = (active_workers_num > 1) ? idle_func_object_ops_par : idle_func_object_ops_nopar;
111         workers_finished = FALSE;
112
113         for (i = 0; i < active_workers_num; i++) {
114                 State old_state;
115                 gboolean did_set_state;
116
117                 do {
118                         old_state = workers_data [i].state;
119
120                         if (old_state == STATE_WORK_ENQUEUED)
121                                 break;
122
123                         did_set_state = set_state (&workers_data [i], old_state, STATE_WORK_ENQUEUED);
124                 } while (!did_set_state);
125
126                 if (!state_is_working_or_enqueued (old_state))
127                         need_signal = TRUE;
128         }
129
130         if (need_signal)
131                 sgen_thread_pool_idle_signal ();
132 }
133
134 static void
135 worker_try_finish (WorkerData *data)
136 {
137         State old_state;
138         int i, working = 0;
139
140         ++stat_workers_num_finished;
141
142         mono_os_mutex_lock (&finished_lock);
143
144         for (i = 0; i < active_workers_num; i++) {
145                 if (state_is_working_or_enqueued (workers_data [i].state))
146                         working++;
147         }
148
149         if (working == 1) {
150                 SgenWorkersFinishCallback callback = finish_callback;
151                 SGEN_ASSERT (0, idle_func_object_ops == idle_func_object_ops_nopar, "Why are we finishing with parallel context");
152                 /* We are the last one left. Enqueue preclean job if we have one and awake everybody */
153                 SGEN_ASSERT (0, data->state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
154                 if (callback) {
155                         finish_callback = NULL;
156                         callback ();
157                         worker_awakenings = 0;
158                         /* Make sure each worker has a chance of seeing the enqueued jobs */
159                         sgen_workers_ensure_awake ();
160                         SGEN_ASSERT (0, data->state == STATE_WORK_ENQUEUED, "Why did we fail to set our own state to ENQUEUED");
161                         goto work_available;
162                 }
163         }
164
165         do {
166                 old_state = data->state;
167
168                 SGEN_ASSERT (0, old_state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
169                 if (old_state == STATE_WORK_ENQUEUED)
170                         goto work_available;
171                 SGEN_ASSERT (0, old_state == STATE_WORKING, "What other possibility is there?");
172         } while (!set_state (data, old_state, STATE_NOT_WORKING));
173
174         /*
175          * If we are second to last to finish, we set the scan context to the non-parallel
176          * version so we can speed up the last worker. This helps us maintain same level
177          * of performance as non-parallel mode even if we fail to distribute work properly.
178          */
179         if (working == 2)
180                 idle_func_object_ops = idle_func_object_ops_nopar;
181
182         workers_finished = TRUE;
183         mono_os_mutex_unlock (&finished_lock);
184
185         binary_protocol_worker_finish (sgen_timestamp (), forced_stop);
186
187         sgen_gray_object_queue_trim_free_list (&data->private_gray_queue);
188         return;
189
190 work_available:
191         mono_os_mutex_unlock (&finished_lock);
192 }
193
194 void
195 sgen_workers_enqueue_job (SgenThreadPoolJob *job, gboolean enqueue)
196 {
197         if (!enqueue) {
198                 job->func (NULL, job);
199                 sgen_thread_pool_job_free (job);
200                 return;
201         }
202
203         sgen_thread_pool_job_enqueue (job);
204 }
205
206 static gboolean
207 workers_get_work (WorkerData *data)
208 {
209         SgenMajorCollector *major = sgen_get_major_collector ();
210         SgenMinorCollector *minor = sgen_get_minor_collector ();
211         GrayQueueSection *section;
212
213         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
214         g_assert (major->is_concurrent || minor->is_parallel);
215
216         section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
217         if (section) {
218                 sgen_gray_object_enqueue_section (&data->private_gray_queue, section, major->is_parallel);
219                 return TRUE;
220         }
221
222         /* Nobody to steal from */
223         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
224         return FALSE;
225 }
226
227 static gboolean
228 workers_steal_work (WorkerData *data)
229 {
230         SgenMajorCollector *major = sgen_get_major_collector ();
231         SgenMinorCollector *minor = sgen_get_minor_collector ();
232         int generation = sgen_get_current_collection_generation ();
233         GrayQueueSection *section = NULL;
234         int i, current_worker;
235
236         if ((generation == GENERATION_OLD && !major->is_parallel) ||
237                         (generation == GENERATION_NURSERY && !minor->is_parallel))
238                 return FALSE;
239
240         /* If we're parallel, steal from other workers' private gray queues  */
241         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
242
243         current_worker = (int) (data - workers_data);
244
245         for (i = 1; i < active_workers_num && !section; i++) {
246                 int steal_worker = (current_worker + i) % active_workers_num;
247                 if (state_is_working_or_enqueued (workers_data [steal_worker].state))
248                         section = sgen_gray_object_steal_section (&workers_data [steal_worker].private_gray_queue);
249         }
250
251         if (section) {
252                 sgen_gray_object_enqueue_section (&data->private_gray_queue, section, TRUE);
253                 return TRUE;
254         }
255
256         /* Nobody to steal from */
257         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
258         return FALSE;
259 }
260
261 static void
262 concurrent_enqueue_check (GCObject *obj)
263 {
264         g_assert (sgen_concurrent_collection_in_progress ());
265         g_assert (!sgen_ptr_in_nursery (obj));
266         g_assert (SGEN_LOAD_VTABLE (obj));
267 }
268
269 static void
270 init_private_gray_queue (WorkerData *data)
271 {
272         sgen_gray_object_queue_init (&data->private_gray_queue,
273                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
274                         FALSE);
275 }
276
277 static void
278 thread_pool_init_func (void *data_untyped)
279 {
280         WorkerData *data = (WorkerData *)data_untyped;
281         SgenMajorCollector *major = sgen_get_major_collector ();
282         SgenMinorCollector *minor = sgen_get_minor_collector ();
283
284         sgen_client_thread_register_worker ();
285
286         if (!major->is_concurrent && !minor->is_parallel)
287                 return;
288
289         init_private_gray_queue (data);
290
291         if (worker_init_cb)
292                 worker_init_cb (data);
293 }
294
295 static gboolean
296 continue_idle_func (void *data_untyped)
297 {
298         if (data_untyped) {
299                 WorkerData *data = (WorkerData *)data_untyped;
300                 return state_is_working_or_enqueued (data->state);
301         } else {
302                 /* Return if any of the threads is working */
303                 return !sgen_workers_all_done ();
304         }
305 }
306
307 static gboolean
308 should_work_func (void *data_untyped)
309 {
310         WorkerData *data = (WorkerData*)data_untyped;
311         int current_worker = (int) (data - workers_data);
312
313         return current_worker < active_workers_num;
314 }
315
316 static void
317 marker_idle_func (void *data_untyped)
318 {
319         WorkerData *data = (WorkerData *)data_untyped;
320
321         SGEN_ASSERT (0, continue_idle_func (data_untyped), "Why are we called when we're not supposed to work?");
322
323         if (data->state == STATE_WORK_ENQUEUED) {
324                 set_state (data, STATE_WORK_ENQUEUED, STATE_WORKING);
325                 SGEN_ASSERT (0, data->state != STATE_NOT_WORKING, "How did we get from WORK ENQUEUED to NOT WORKING?");
326         }
327
328         if (!forced_stop && (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data) || workers_steal_work (data))) {
329                 ScanCopyContext ctx = CONTEXT_FROM_OBJECT_OPERATIONS (idle_func_object_ops, &data->private_gray_queue);
330
331                 SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data->private_gray_queue), "How is our gray queue empty if we just got work?");
332
333                 sgen_drain_gray_stack (ctx);
334
335                 if (data->private_gray_queue.num_sections >= SGEN_WORKER_MIN_SECTIONS_SIGNAL
336                                 && workers_finished && worker_awakenings < active_workers_num) {
337                         /* We bound the number of worker awakenings just to be sure */
338                         worker_awakenings++;
339                         mono_os_mutex_lock (&finished_lock);
340                         sgen_workers_ensure_awake ();
341                         mono_os_mutex_unlock (&finished_lock);
342                 }
343         } else {
344                 worker_try_finish (data);
345         }
346 }
347
348 static void
349 init_distribute_gray_queue (void)
350 {
351         if (workers_distribute_gray_queue_inited) {
352                 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
353                 g_assert (workers_distribute_gray_queue.locked);
354                 return;
355         }
356
357         sgen_section_gray_queue_init (&workers_distribute_gray_queue, TRUE,
358                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
359         workers_distribute_gray_queue_inited = TRUE;
360 }
361
362 void
363 sgen_workers_init_distribute_gray_queue (void)
364 {
365         SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent || sgen_get_minor_collector ()->is_parallel,
366                         "Why should we init the distribute gray queue if we don't need it?");
367         init_distribute_gray_queue ();
368 }
369
370 void
371 sgen_workers_init (int num_workers, SgenWorkerCallback callback)
372 {
373         int i;
374         void **workers_data_ptrs = (void **)alloca(num_workers * sizeof(void *));
375
376         if (!sgen_get_major_collector ()->is_concurrent && !sgen_get_minor_collector ()->is_parallel) {
377                 sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL, NULL, NULL);
378                 return;
379         }
380
381         mono_os_mutex_init (&finished_lock);
382         //g_print ("initing %d workers\n", num_workers);
383
384         workers_num = num_workers;
385         active_workers_num = num_workers;
386
387         workers_data = (WorkerData *)sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
388         memset (workers_data, 0, sizeof (WorkerData) * num_workers);
389
390         init_distribute_gray_queue ();
391
392         for (i = 0; i < num_workers; ++i)
393                 workers_data_ptrs [i] = (void *) &workers_data [i];
394
395         worker_init_cb = callback;
396
397         sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, continue_idle_func, should_work_func, workers_data_ptrs);
398
399         mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
400 }
401
402 void
403 sgen_workers_stop_all_workers (void)
404 {
405         finish_callback = NULL;
406         mono_memory_write_barrier ();
407         forced_stop = TRUE;
408
409         sgen_thread_pool_wait_for_all_jobs ();
410         sgen_thread_pool_idle_wait ();
411         SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
412 }
413
414 void
415 sgen_workers_set_num_active_workers (int num_workers)
416 {
417         if (num_workers) {
418                 SGEN_ASSERT (0, active_workers_num <= workers_num, "We can't start more workers than we initialized");
419                 active_workers_num = num_workers;
420         } else {
421                 active_workers_num = workers_num;
422         }
423 }
424
425 void
426 sgen_workers_start_all_workers (SgenObjectOperations *object_ops_nopar, SgenObjectOperations *object_ops_par, SgenWorkersFinishCallback callback)
427 {
428         idle_func_object_ops_par = object_ops_par;
429         idle_func_object_ops_nopar = object_ops_nopar;
430         forced_stop = FALSE;
431         finish_callback = callback;
432         worker_awakenings = 0;
433         mono_memory_write_barrier ();
434
435         /*
436          * We expect workers to start finishing only after all of them were awaken.
437          * Otherwise we might think that we have fewer workers and use wrong context.
438          */
439         mono_os_mutex_lock (&finished_lock);
440         sgen_workers_ensure_awake ();
441         mono_os_mutex_unlock (&finished_lock);
442 }
443
444 void
445 sgen_workers_join (void)
446 {
447         int i;
448
449         sgen_thread_pool_wait_for_all_jobs ();
450         sgen_thread_pool_idle_wait ();
451         SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
452
453         /* At this point all the workers have stopped. */
454
455         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is there still work left to do?");
456         for (i = 0; i < active_workers_num; ++i)
457                 SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue), "Why is there still work left to do?");
458 }
459
460 /*
461  * Can only be called if the workers are stopped.
462  * If we're stopped, there are also no pending jobs.
463  */
464 gboolean
465 sgen_workers_have_idle_work (void)
466 {
467         int i;
468
469         SGEN_ASSERT (0, forced_stop && sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
470
471         if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
472                 return TRUE;
473
474         for (i = 0; i < active_workers_num; ++i) {
475                 if (!sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue))
476                         return TRUE;
477         }
478
479         return FALSE;
480 }
481
482 gboolean
483 sgen_workers_all_done (void)
484 {
485         int i;
486
487         for (i = 0; i < active_workers_num; i++) {
488                 if (state_is_working_or_enqueued (workers_data [i].state))
489                         return FALSE;
490         }
491         return TRUE;
492 }
493
494 /* Must only be used for debugging */
495 gboolean
496 sgen_workers_are_working (void)
497 {
498         return !sgen_workers_all_done ();
499 }
500
501 void
502 sgen_workers_assert_gray_queue_is_empty (void)
503 {
504         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is the workers gray queue not empty?");
505 }
506
507 void
508 sgen_workers_take_from_queue (SgenGrayQueue *queue)
509 {
510         sgen_gray_object_spread (queue, sgen_workers_get_job_split_count ());
511
512         for (;;) {
513                 GrayQueueSection *section = sgen_gray_object_dequeue_section (queue);
514                 if (!section)
515                         break;
516                 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue, section);
517         }
518
519         SGEN_ASSERT (0, !sgen_workers_are_working (), "We should fully populate the distribute gray queue before we start the workers");
520 }
521
522 SgenObjectOperations*
523 sgen_workers_get_idle_func_object_ops (void)
524 {
525         return (idle_func_object_ops_par) ? idle_func_object_ops_par : idle_func_object_ops_nopar;
526 }
527
528 /*
529  * If we have a single worker, splitting into multiple jobs makes no sense. With
530  * more than one worker, we split into a larger number of jobs so that, in case
531  * the work load is uneven, a worker that finished quickly can take up more jobs
532  * than another one.
533  */
534 int
535 sgen_workers_get_job_split_count (void)
536 {
537         return (active_workers_num > 1) ? active_workers_num * 4 : 1;
538 }
539
540 void
541 sgen_workers_foreach (SgenWorkerCallback callback)
542 {
543         int i;
544
545         for (i = 0; i < workers_num; i++)
546                 callback (&workers_data [i]);
547 }
548
549 #endif