Merge pull request #3591 from directhex/mono_libdir_fallback
[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 static SgenSectionGrayQueue workers_distribute_gray_queue;
27 static gboolean workers_distribute_gray_queue_inited;
28
29 /*
30  * Allowed transitions:
31  *
32  * | from \ to          | NOT WORKING | WORKING | WORK ENQUEUED |
33  * |--------------------+-------------+---------+---------------+
34  * | NOT WORKING        | -           | -       | main          |
35  * | WORKING            | worker      | -       | main          |
36  * | WORK ENQUEUED      | -           | worker  | -             |
37  *
38  * The WORK ENQUEUED state guarantees that the worker thread will inspect the queue again at
39  * least once.  Only after looking at the queue will it go back to WORKING, and then,
40  * eventually, to NOT WORKING.  After enqueuing work the main thread transitions the state
41  * to WORK ENQUEUED.  Signalling the worker thread to wake up is only necessary if the old
42  * state was NOT WORKING.
43  */
44
45 enum {
46         STATE_NOT_WORKING,
47         STATE_WORKING,
48         STATE_WORK_ENQUEUED
49 };
50
51 typedef gint32 State;
52
53 static volatile State workers_state;
54
55 static SgenObjectOperations * volatile idle_func_object_ops;
56 static SgenThreadPoolJob * volatile preclean_job;
57
58 static guint64 stat_workers_num_finished;
59
60 static gboolean
61 set_state (State old_state, State new_state)
62 {
63         SGEN_ASSERT (0, old_state != new_state, "Why are we transitioning to the same state?");
64         if (new_state == STATE_NOT_WORKING)
65                 SGEN_ASSERT (0, old_state == STATE_WORKING, "We can only transition to NOT WORKING from WORKING");
66         else if (new_state == STATE_WORKING)
67                 SGEN_ASSERT (0, old_state == STATE_WORK_ENQUEUED, "We can only transition to WORKING from WORK ENQUEUED");
68         if (new_state == STATE_NOT_WORKING || new_state == STATE_WORKING)
69                 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");
70
71         return InterlockedCompareExchange (&workers_state, new_state, old_state) == old_state;
72 }
73
74 static gboolean
75 state_is_working_or_enqueued (State state)
76 {
77         return state == STATE_WORKING || state == STATE_WORK_ENQUEUED;
78 }
79
80 static void
81 sgen_workers_ensure_awake (void)
82 {
83         State old_state;
84         gboolean did_set_state;
85
86         do {
87                 old_state = workers_state;
88
89                 if (old_state == STATE_WORK_ENQUEUED)
90                         break;
91
92                 did_set_state = set_state (old_state, STATE_WORK_ENQUEUED);
93         } while (!did_set_state);
94
95         if (!state_is_working_or_enqueued (old_state))
96                 sgen_thread_pool_idle_signal ();
97 }
98
99 static void
100 worker_try_finish (WorkerData *data)
101 {
102         State old_state;
103
104         ++stat_workers_num_finished;
105
106         do {
107                 old_state = workers_state;
108
109                 SGEN_ASSERT (0, old_state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
110                 if (old_state == STATE_WORK_ENQUEUED)
111                         return;
112                 SGEN_ASSERT (0, old_state == STATE_WORKING, "What other possibility is there?");
113
114                 /* We are the last thread to go to sleep. */
115         } while (!set_state (old_state, STATE_NOT_WORKING));
116
117         binary_protocol_worker_finish (sgen_timestamp (), forced_stop);
118
119         sgen_gray_object_queue_trim_free_list (&data->private_gray_queue);
120 }
121
122 void
123 sgen_workers_enqueue_job (SgenThreadPoolJob *job, gboolean enqueue)
124 {
125         if (!enqueue) {
126                 job->func (NULL, job);
127                 sgen_thread_pool_job_free (job);
128                 return;
129         }
130
131         sgen_thread_pool_job_enqueue (job);
132 }
133
134 void
135 sgen_workers_wait_for_jobs_finished (void)
136 {
137         sgen_thread_pool_wait_for_all_jobs ();
138         /*
139          * If the idle task was never triggered or it finished before the last job did and
140          * then didn't get triggered again, we might end up in the situation of having
141          * something in the gray queue yet the idle task not working.  The easiest way to
142          * make sure this doesn't stay that way is to just trigger it again after all jobs
143          * have finished.
144          */
145         sgen_workers_ensure_awake ();
146 }
147
148 static gboolean
149 workers_get_work (WorkerData *data)
150 {
151         SgenMajorCollector *major;
152
153         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
154
155         /* If we're concurrent, steal from the workers distribute gray queue. */
156         major = sgen_get_major_collector ();
157         if (major->is_concurrent) {
158                 GrayQueueSection *section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
159                 if (section) {
160                         sgen_gray_object_enqueue_section (&data->private_gray_queue, section);
161                         return TRUE;
162                 }
163         }
164
165         /* Nobody to steal from */
166         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
167         return FALSE;
168 }
169
170 static void
171 concurrent_enqueue_check (GCObject *obj)
172 {
173         g_assert (sgen_concurrent_collection_in_progress ());
174         g_assert (!sgen_ptr_in_nursery (obj));
175         g_assert (SGEN_LOAD_VTABLE (obj));
176 }
177
178 static void
179 init_private_gray_queue (WorkerData *data)
180 {
181         sgen_gray_object_queue_init (&data->private_gray_queue,
182                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL,
183                         FALSE);
184 }
185
186 static void
187 thread_pool_init_func (void *data_untyped)
188 {
189         WorkerData *data = (WorkerData *)data_untyped;
190         SgenMajorCollector *major = sgen_get_major_collector ();
191
192         sgen_client_thread_register_worker ();
193
194         if (!major->is_concurrent)
195                 return;
196
197         init_private_gray_queue (data);
198 }
199
200 static gboolean
201 continue_idle_func (void)
202 {
203         return state_is_working_or_enqueued (workers_state);
204 }
205
206 static void
207 marker_idle_func (void *data_untyped)
208 {
209         WorkerData *data = (WorkerData *)data_untyped;
210
211         SGEN_ASSERT (0, continue_idle_func (), "Why are we called when we're not supposed to work?");
212         SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "The worker should only mark in concurrent collections.");
213
214         if (workers_state == STATE_WORK_ENQUEUED) {
215                 set_state (STATE_WORK_ENQUEUED, STATE_WORKING);
216                 SGEN_ASSERT (0, workers_state != STATE_NOT_WORKING, "How did we get from WORK ENQUEUED to NOT WORKING?");
217         }
218
219         if (!forced_stop && (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data))) {
220                 ScanCopyContext ctx = CONTEXT_FROM_OBJECT_OPERATIONS (idle_func_object_ops, &data->private_gray_queue);
221
222                 SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data->private_gray_queue), "How is our gray queue empty if we just got work?");
223
224                 sgen_drain_gray_stack (ctx);
225         } else {
226                 SgenThreadPoolJob *job = preclean_job;
227                 if (job) {
228                         sgen_thread_pool_job_enqueue (job);
229                         preclean_job = NULL;
230                 } else {
231                         worker_try_finish (data);
232                 }
233         }
234 }
235
236 static void
237 init_distribute_gray_queue (void)
238 {
239         if (workers_distribute_gray_queue_inited) {
240                 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
241                 g_assert (workers_distribute_gray_queue.locked);
242                 return;
243         }
244
245         sgen_section_gray_queue_init (&workers_distribute_gray_queue, TRUE,
246                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
247         workers_distribute_gray_queue_inited = TRUE;
248 }
249
250 void
251 sgen_workers_init_distribute_gray_queue (void)
252 {
253         SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent,
254                         "Why should we init the distribute gray queue if we don't need it?");
255         init_distribute_gray_queue ();
256 }
257
258 void
259 sgen_workers_init (int num_workers)
260 {
261         int i;
262         void **workers_data_ptrs = (void **)alloca(num_workers * sizeof(void *));
263
264         if (!sgen_get_major_collector ()->is_concurrent) {
265                 sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL, NULL);
266                 return;
267         }
268
269         //g_print ("initing %d workers\n", num_workers);
270
271         workers_num = num_workers;
272
273         workers_data = (WorkerData *)sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
274         memset (workers_data, 0, sizeof (WorkerData) * num_workers);
275
276         init_distribute_gray_queue ();
277
278         for (i = 0; i < workers_num; ++i)
279                 workers_data_ptrs [i] = (void *) &workers_data [i];
280
281         sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, continue_idle_func, workers_data_ptrs);
282
283         mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
284 }
285
286 void
287 sgen_workers_stop_all_workers (void)
288 {
289         preclean_job = NULL;
290         mono_memory_write_barrier ();
291         forced_stop = TRUE;
292
293         sgen_thread_pool_wait_for_all_jobs ();
294         sgen_thread_pool_idle_wait ();
295         SGEN_ASSERT (0, workers_state == STATE_NOT_WORKING, "Can only signal enqueue work when in no work state");
296 }
297
298 void
299 sgen_workers_start_all_workers (SgenObjectOperations *object_ops, SgenThreadPoolJob *job)
300 {
301         forced_stop = FALSE;
302         idle_func_object_ops = object_ops;
303         preclean_job = job;
304         mono_memory_write_barrier ();
305
306         sgen_workers_ensure_awake ();
307 }
308
309 void
310 sgen_workers_join (void)
311 {
312         int i;
313
314         sgen_thread_pool_wait_for_all_jobs ();
315         sgen_thread_pool_idle_wait ();
316         SGEN_ASSERT (0, workers_state == STATE_NOT_WORKING, "Can only signal enqueue work when in no work state");
317
318         /* At this point all the workers have stopped. */
319
320         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is there still work left to do?");
321         for (i = 0; i < workers_num; ++i)
322                 SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue), "Why is there still work left to do?");
323 }
324
325 /*
326  * Can only be called if the workers are stopped.
327  * If we're stopped, there are also no pending jobs.
328  */
329 gboolean
330 sgen_workers_have_idle_work (void)
331 {
332         int i;
333
334         SGEN_ASSERT (0, forced_stop && sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
335
336         if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue))
337                 return TRUE;
338
339         for (i = 0; i < workers_num; ++i) {
340                 if (!sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue))
341                         return TRUE;
342         }
343
344         return FALSE;
345 }
346
347 gboolean
348 sgen_workers_all_done (void)
349 {
350         return workers_state == STATE_NOT_WORKING;
351 }
352
353 /* Must only be used for debugging */
354 gboolean
355 sgen_workers_are_working (void)
356 {
357         return state_is_working_or_enqueued (workers_state);
358 }
359
360 void
361 sgen_workers_assert_gray_queue_is_empty (void)
362 {
363         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is the workers gray queue not empty?");
364 }
365
366 void
367 sgen_workers_take_from_queue_and_awake (SgenGrayQueue *queue)
368 {
369         gboolean wake = FALSE;
370
371         for (;;) {
372                 GrayQueueSection *section = sgen_gray_object_dequeue_section (queue);
373                 if (!section)
374                         break;
375                 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue, section);
376                 wake = TRUE;
377         }
378
379         if (wake) {
380                 SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "Why is there work to take when there's no concurrent collection in progress?");
381                 sgen_workers_ensure_awake ();
382         }
383 }
384
385 #endif