[sgen] Parallel major blocks precleaning
[mono.git] / mono / sgen / sgen-workers.c
1 /*
2  * sgen-workers.c: Worker threads for parallel and concurrent GC.
3  *
4  * Copyright 2001-2003 Ximian, Inc
5  * Copyright 2003-2010 Novell, Inc.
6  * Copyright (C) 2012 Xamarin Inc
7  *
8  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9  */
10
11 #include "config.h"
12 #ifdef HAVE_SGEN_GC
13
14 #include <string.h>
15
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"
21
22 static int workers_num;
23 static volatile gboolean forced_stop;
24 static WorkerData *workers_data;
25
26 /*
27  * When using multiple workers, we need to have the last worker
28  * enqueue the preclean jobs (if there are any). This lock ensures
29  * that when the last worker takes it, all the other workers have
30  * gracefully finished, so it can restart them.
31  */
32 static mono_mutex_t finished_lock;
33
34 static SgenSectionGrayQueue workers_distribute_gray_queue;
35 static gboolean workers_distribute_gray_queue_inited;
36
37 /*
38  * Allowed transitions:
39  *
40  * | from \ to          | NOT WORKING | WORKING | WORK ENQUEUED |
41  * |--------------------+-------------+---------+---------------+
42  * | NOT WORKING        | -           | -       | main / worker |
43  * | WORKING            | worker      | -       | main / worker |
44  * | WORK ENQUEUED      | -           | worker  | -             |
45  *
46  * The WORK ENQUEUED state guarantees that the worker thread will inspect the queue again at
47  * least once.  Only after looking at the queue will it go back to WORKING, and then,
48  * eventually, to NOT WORKING.  After enqueuing work the main thread transitions the state
49  * to WORK ENQUEUED.  Signalling the worker thread to wake up is only necessary if the old
50  * state was NOT WORKING.
51  */
52
53 enum {
54         STATE_NOT_WORKING,
55         STATE_WORKING,
56         STATE_WORK_ENQUEUED
57 };
58
59 typedef gint32 State;
60
61 static SgenObjectOperations * volatile idle_func_object_ops;
62 /*
63  * finished_callback is called only when the workers finish work normally (when they
64  * are not forced to finish). The callback is used to enqueue preclean jobs.
65  */
66 static volatile SgenWorkersFinishCallback finish_callback;
67
68 static guint64 stat_workers_num_finished;
69
70 static gboolean
71 set_state (WorkerData *data, State old_state, State new_state)
72 {
73         SGEN_ASSERT (0, old_state != new_state, "Why are we transitioning to the same state?");
74         if (new_state == STATE_NOT_WORKING)
75                 SGEN_ASSERT (0, old_state == STATE_WORKING, "We can only transition to NOT WORKING from WORKING");
76         else if (new_state == STATE_WORKING)
77                 SGEN_ASSERT (0, old_state == STATE_WORK_ENQUEUED, "We can only transition to WORKING from WORK ENQUEUED");
78         if (new_state == STATE_NOT_WORKING || new_state == STATE_WORKING)
79                 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");
80
81         return InterlockedCompareExchange (&data->state, new_state, old_state) == old_state;
82 }
83
84 static gboolean
85 state_is_working_or_enqueued (State state)
86 {
87         return state == STATE_WORKING || state == STATE_WORK_ENQUEUED;
88 }
89
90 static void
91 sgen_workers_ensure_awake (void)
92 {
93         int i;
94         gboolean need_signal = FALSE;
95
96         for (i = 0; i < workers_num; i++) {
97                 State old_state;
98                 gboolean did_set_state;
99
100                 do {
101                         old_state = workers_data [i].state;
102
103                         if (old_state == STATE_WORK_ENQUEUED)
104                                 break;
105
106                         did_set_state = set_state (&workers_data [i], old_state, STATE_WORK_ENQUEUED);
107                 } while (!did_set_state);
108
109                 if (!state_is_working_or_enqueued (old_state))
110                         need_signal = TRUE;
111         }
112
113         if (need_signal)
114                 sgen_thread_pool_idle_signal ();
115 }
116
117 static void
118 worker_try_finish (WorkerData *data)
119 {
120         State old_state;
121         int i, working = 0;
122
123         ++stat_workers_num_finished;
124
125         mono_os_mutex_lock (&finished_lock);
126
127         for (i = 0; i < workers_num; i++) {
128                 if (state_is_working_or_enqueued (workers_data [i].state))
129                         working++;
130         }
131
132         if (working == 1) {
133                 SgenWorkersFinishCallback callback = finish_callback;
134                 /* We are the last one left. Enqueue preclean job if we have one and awake everybody */
135                 SGEN_ASSERT (0, data->state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
136                 if (callback) {
137                         finish_callback = NULL;
138                         callback ();
139                         /* Make sure each worker has a chance of seeing the enqueued jobs */
140                         sgen_workers_ensure_awake ();
141                         SGEN_ASSERT (0, data->state == STATE_WORK_ENQUEUED, "Why did we fail to set our own state to ENQUEUED");
142                         goto work_available;
143                 }
144         }
145
146         do {
147                 old_state = data->state;
148
149                 SGEN_ASSERT (0, old_state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
150                 if (old_state == STATE_WORK_ENQUEUED)
151                         goto work_available;
152                 SGEN_ASSERT (0, old_state == STATE_WORKING, "What other possibility is there?");
153         } while (!set_state (data, old_state, STATE_NOT_WORKING));
154
155         mono_os_mutex_unlock (&finished_lock);
156
157         binary_protocol_worker_finish (sgen_timestamp (), forced_stop);
158
159         sgen_gray_object_queue_trim_free_list (&data->private_gray_queue);
160         return;
161
162 work_available:
163         mono_os_mutex_unlock (&finished_lock);
164 }
165
166 void
167 sgen_workers_enqueue_job (SgenThreadPoolJob *job, gboolean enqueue)
168 {
169         if (!enqueue) {
170                 job->func (NULL, job);
171                 sgen_thread_pool_job_free (job);
172                 return;
173         }
174
175         sgen_thread_pool_job_enqueue (job);
176 }
177
178 void
179 sgen_workers_wait_for_jobs_finished (void)
180 {
181         sgen_thread_pool_wait_for_all_jobs ();
182         /*
183          * If the idle task was never triggered or it finished before the last job did and
184          * then didn't get triggered again, we might end up in the situation of having
185          * something in the gray queue yet the idle task not working.  The easiest way to
186          * make sure this doesn't stay that way is to just trigger it again after all jobs
187          * have finished.
188          */
189         sgen_workers_ensure_awake ();
190 }
191
192 static gboolean
193 workers_get_work (WorkerData *data)
194 {
195         SgenMajorCollector *major;
196
197         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
198
199         /* If we're concurrent, steal from the workers distribute gray queue. */
200         major = sgen_get_major_collector ();
201         if (major->is_concurrent) {
202                 GrayQueueSection *section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
203                 if (section) {
204                         sgen_gray_object_enqueue_section (&data->private_gray_queue, section);
205                         return TRUE;
206                 }
207         }
208
209         /* Nobody to steal from */
210         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
211         return FALSE;
212 }
213
214 static void
215 concurrent_enqueue_check (GCObject *obj)
216 {
217         g_assert (sgen_concurrent_collection_in_progress ());
218         g_assert (!sgen_ptr_in_nursery (obj));
219         g_assert (SGEN_LOAD_VTABLE (obj));
220 }
221
222 static void
223 init_private_gray_queue (WorkerData *data)
224 {
225         sgen_gray_object_queue_init (&data->private_gray_queue,
226                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
227                         FALSE);
228 }
229
230 static void
231 thread_pool_init_func (void *data_untyped)
232 {
233         WorkerData *data = (WorkerData *)data_untyped;
234         SgenMajorCollector *major = sgen_get_major_collector ();
235
236         sgen_client_thread_register_worker ();
237
238         if (!major->is_concurrent)
239                 return;
240
241         init_private_gray_queue (data);
242 }
243
244 static gboolean
245 continue_idle_func (void *data_untyped)
246 {
247         if (data_untyped) {
248                 WorkerData *data = (WorkerData *)data_untyped;
249                 return state_is_working_or_enqueued (data->state);
250         } else {
251                 /* Return if any of the threads is working */
252                 return !sgen_workers_all_done ();
253         }
254 }
255
256 static void
257 marker_idle_func (void *data_untyped)
258 {
259         WorkerData *data = (WorkerData *)data_untyped;
260
261         SGEN_ASSERT (0, continue_idle_func (data_untyped), "Why are we called when we're not supposed to work?");
262         SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "The worker should only mark in concurrent collections.");
263
264         if (data->state == STATE_WORK_ENQUEUED) {
265                 set_state (data, STATE_WORK_ENQUEUED, STATE_WORKING);
266                 SGEN_ASSERT (0, data->state != STATE_NOT_WORKING, "How did we get from WORK ENQUEUED to NOT WORKING?");
267         }
268
269         if (!forced_stop && (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data))) {
270                 ScanCopyContext ctx = CONTEXT_FROM_OBJECT_OPERATIONS (idle_func_object_ops, &data->private_gray_queue);
271
272                 SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data->private_gray_queue), "How is our gray queue empty if we just got work?");
273
274                 sgen_drain_gray_stack (ctx);
275         } else {
276                 worker_try_finish (data);
277         }
278 }
279
280 static void
281 init_distribute_gray_queue (void)
282 {
283         if (workers_distribute_gray_queue_inited) {
284                 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
285                 g_assert (workers_distribute_gray_queue.locked);
286                 return;
287         }
288
289         sgen_section_gray_queue_init (&workers_distribute_gray_queue, TRUE,
290                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
291         workers_distribute_gray_queue_inited = TRUE;
292 }
293
294 void
295 sgen_workers_init_distribute_gray_queue (void)
296 {
297         SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent,
298                         "Why should we init the distribute gray queue if we don't need it?");
299         init_distribute_gray_queue ();
300 }
301
302 void
303 sgen_workers_init (int num_workers)
304 {
305         int i;
306         void **workers_data_ptrs = (void **)alloca(num_workers * sizeof(void *));
307
308         if (!sgen_get_major_collector ()->is_concurrent) {
309                 sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL, NULL);
310                 return;
311         }
312
313         mono_os_mutex_init (&finished_lock);
314         //g_print ("initing %d workers\n", num_workers);
315
316         workers_num = num_workers;
317
318         workers_data = (WorkerData *)sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
319         memset (workers_data, 0, sizeof (WorkerData) * num_workers);
320
321         init_distribute_gray_queue ();
322
323         for (i = 0; i < num_workers; ++i)
324                 workers_data_ptrs [i] = (void *) &workers_data [i];
325
326         sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, continue_idle_func, workers_data_ptrs);
327
328         mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
329 }
330
331 void
332 sgen_workers_stop_all_workers (void)
333 {
334         finish_callback = NULL;
335         mono_memory_write_barrier ();
336         forced_stop = TRUE;
337
338         sgen_thread_pool_wait_for_all_jobs ();
339         sgen_thread_pool_idle_wait ();
340         SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
341 }
342
343 void
344 sgen_workers_start_all_workers (SgenObjectOperations *object_ops, SgenWorkersFinishCallback callback)
345 {
346         forced_stop = FALSE;
347         idle_func_object_ops = object_ops;
348         finish_callback = callback;
349         mono_memory_write_barrier ();
350
351         sgen_workers_ensure_awake ();
352 }
353
354 void
355 sgen_workers_join (void)
356 {
357         int i;
358
359         sgen_thread_pool_wait_for_all_jobs ();
360         sgen_thread_pool_idle_wait ();
361         SGEN_ASSERT (0, sgen_workers_all_done (), "Can only signal enqueue work when in no work state");
362
363         /* At this point all the workers have stopped. */
364
365         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is there still work left to do?");
366         for (i = 0; i < workers_num; ++i)
367                 SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue), "Why is there still work left to do?");
368 }
369
370 /*
371  * Can only be called if the workers are stopped.
372  * If we're stopped, there are also no pending jobs.
373  */
374 gboolean
375 sgen_workers_have_idle_work (void)
376 {
377         int i;
378
379         SGEN_ASSERT (0, forced_stop && sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
380
381         if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
382                 return TRUE;
383
384         for (i = 0; i < workers_num; ++i) {
385                 if (!sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue))
386                         return TRUE;
387         }
388
389         return FALSE;
390 }
391
392 gboolean
393 sgen_workers_all_done (void)
394 {
395         int i;
396
397         for (i = 0; i < workers_num; i++) {
398                 if (state_is_working_or_enqueued (workers_data [i].state))
399                         return FALSE;
400         }
401         return TRUE;
402 }
403
404 /* Must only be used for debugging */
405 gboolean
406 sgen_workers_are_working (void)
407 {
408         return !sgen_workers_all_done ();
409 }
410
411 void
412 sgen_workers_assert_gray_queue_is_empty (void)
413 {
414         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is the workers gray queue not empty?");
415 }
416
417 void
418 sgen_workers_take_from_queue_and_awake (SgenGrayQueue *queue)
419 {
420         gboolean wake = FALSE;
421
422         for (;;) {
423                 GrayQueueSection *section = sgen_gray_object_dequeue_section (queue);
424                 if (!section)
425                         break;
426                 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue, section);
427                 wake = TRUE;
428         }
429
430         if (wake) {
431                 SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "Why is there work to take when there's no concurrent collection in progress?");
432                 sgen_workers_ensure_awake ();
433         }
434 }
435
436 SgenObjectOperations*
437 sgen_workers_get_idle_func_object_ops (void)
438 {
439         return idle_func_object_ops;
440 }
441
442 /*
443  * If we have a single worker, splitting into multiple jobs makes no sense. With
444  * more than one worker, we split into a larger number of jobs so that, in case
445  * the work load is uneven, a worker that finished quickly can take up more jobs
446  * than another one.
447  */
448 int
449 sgen_workers_get_job_split_count (void)
450 {
451         return (workers_num > 1) ? workers_num * 4 : 1;
452 }
453
454 #endif