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