2 * sgen-workers.c: Worker threads for parallel and concurrent GC.
4 * Copyright 2001-2003 Ximian, Inc
5 * Copyright 2003-2010 Novell, Inc.
6 * Copyright (C) 2012 Xamarin Inc
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
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"
22 static int workers_num
;
23 static volatile gboolean forced_stop
;
24 static WorkerData
*workers_data
;
26 static SgenSectionGrayQueue workers_distribute_gray_queue
;
27 static gboolean workers_distribute_gray_queue_inited
;
30 * Allowed transitions:
32 * | from \ to | NOT WORKING | WORKING | WORK ENQUEUED |
33 * |--------------------+-------------+---------+---------------+
34 * | NOT WORKING | - | - | main |
35 * | WORKING | worker | - | main |
36 * | WORK ENQUEUED | - | worker | - |
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.
53 static volatile State workers_state
;
55 static SgenObjectOperations
* volatile idle_func_object_ops
;
56 static SgenThreadPoolJob
* volatile preclean_job
;
58 static guint64 stat_workers_num_finished
;
61 set_state (State old_state
, State new_state
)
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");
71 return InterlockedCompareExchange (&workers_state
, new_state
, old_state
) == old_state
;
75 state_is_working_or_enqueued (State state
)
77 return state
== STATE_WORKING
|| state
== STATE_WORK_ENQUEUED
;
81 sgen_workers_ensure_awake (void)
84 gboolean did_set_state
;
87 old_state
= workers_state
;
89 if (old_state
== STATE_WORK_ENQUEUED
)
92 did_set_state
= set_state (old_state
, STATE_WORK_ENQUEUED
);
93 } while (!did_set_state
);
95 if (!state_is_working_or_enqueued (old_state
))
96 sgen_thread_pool_idle_signal ();
100 worker_try_finish (WorkerData
*data
)
104 ++stat_workers_num_finished
;
107 old_state
= workers_state
;
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
)
112 SGEN_ASSERT (0, old_state
== STATE_WORKING
, "What other possibility is there?");
114 /* We are the last thread to go to sleep. */
115 } while (!set_state (old_state
, STATE_NOT_WORKING
));
117 binary_protocol_worker_finish (sgen_timestamp (), forced_stop
);
119 sgen_gray_object_queue_trim_free_list (&data
->private_gray_queue
);
123 sgen_workers_enqueue_job (SgenThreadPoolJob
*job
, gboolean enqueue
)
126 job
->func (NULL
, job
);
127 sgen_thread_pool_job_free (job
);
131 sgen_thread_pool_job_enqueue (job
);
135 sgen_workers_wait_for_jobs_finished (void)
137 sgen_thread_pool_wait_for_all_jobs ();
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
145 sgen_workers_ensure_awake ();
149 workers_get_work (WorkerData
*data
)
151 SgenMajorCollector
*major
;
153 g_assert (sgen_gray_object_queue_is_empty (&data
->private_gray_queue
));
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
);
160 sgen_gray_object_enqueue_section (&data
->private_gray_queue
, section
);
165 /* Nobody to steal from */
166 g_assert (sgen_gray_object_queue_is_empty (&data
->private_gray_queue
));
171 concurrent_enqueue_check (GCObject
*obj
)
173 g_assert (sgen_concurrent_collection_in_progress ());
174 g_assert (!sgen_ptr_in_nursery (obj
));
175 g_assert (SGEN_LOAD_VTABLE (obj
));
179 init_private_gray_queue (WorkerData
*data
)
181 sgen_gray_object_queue_init (&data
->private_gray_queue
,
182 sgen_get_major_collector ()->is_concurrent
? concurrent_enqueue_check
: NULL
,
187 thread_pool_init_func (void *data_untyped
)
189 WorkerData
*data
= (WorkerData
*)data_untyped
;
190 SgenMajorCollector
*major
= sgen_get_major_collector ();
192 sgen_client_thread_register_worker ();
194 if (!major
->is_concurrent
)
197 init_private_gray_queue (data
);
201 continue_idle_func (void)
203 return state_is_working_or_enqueued (workers_state
);
207 marker_idle_func (void *data_untyped
)
209 WorkerData
*data
= (WorkerData
*)data_untyped
;
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.");
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?");
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
);
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?");
224 sgen_drain_gray_stack (ctx
);
226 SgenThreadPoolJob
*job
= preclean_job
;
228 sgen_thread_pool_job_enqueue (job
);
231 worker_try_finish (data
);
237 init_distribute_gray_queue (void)
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
);
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
;
251 sgen_workers_init_distribute_gray_queue (void)
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 ();
259 sgen_workers_init (int num_workers
)
262 void **workers_data_ptrs
= (void **)alloca(num_workers
* sizeof(void *));
264 if (!sgen_get_major_collector ()->is_concurrent
) {
265 sgen_thread_pool_init (num_workers
, thread_pool_init_func
, NULL
, NULL
, NULL
);
269 //g_print ("initing %d workers\n", num_workers);
271 workers_num
= num_workers
;
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
);
276 init_distribute_gray_queue ();
278 for (i
= 0; i
< workers_num
; ++i
)
279 workers_data_ptrs
[i
] = (void *) &workers_data
[i
];
281 sgen_thread_pool_init (num_workers
, thread_pool_init_func
, marker_idle_func
, continue_idle_func
, workers_data_ptrs
);
283 mono_counters_register ("# workers finished", MONO_COUNTER_GC
| MONO_COUNTER_ULONG
, &stat_workers_num_finished
);
287 sgen_workers_stop_all_workers (void)
290 mono_memory_write_barrier ();
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");
299 sgen_workers_start_all_workers (SgenObjectOperations
*object_ops
, SgenThreadPoolJob
*job
)
302 idle_func_object_ops
= object_ops
;
304 mono_memory_write_barrier ();
306 sgen_workers_ensure_awake ();
310 sgen_workers_join (void)
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");
318 /* At this point all the workers have stopped. */
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?");
326 * Can only be called if the workers are stopped.
327 * If we're stopped, there are also no pending jobs.
330 sgen_workers_have_idle_work (void)
334 SGEN_ASSERT (0, forced_stop
&& sgen_workers_all_done (), "Checking for idle work should only happen if the workers are stopped.");
336 if (!sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue
))
339 for (i
= 0; i
< workers_num
; ++i
) {
340 if (!sgen_gray_object_queue_is_empty (&workers_data
[i
].private_gray_queue
))
348 sgen_workers_all_done (void)
350 return workers_state
== STATE_NOT_WORKING
;
353 /* Must only be used for debugging */
355 sgen_workers_are_working (void)
357 return state_is_working_or_enqueued (workers_state
);
361 sgen_workers_assert_gray_queue_is_empty (void)
363 SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue
), "Why is the workers gray queue not empty?");
367 sgen_workers_take_from_queue_and_awake (SgenGrayQueue
*queue
)
369 gboolean wake
= FALSE
;
372 GrayQueueSection
*section
= sgen_gray_object_dequeue_section (queue
);
375 sgen_section_gray_queue_enqueue (&workers_distribute_gray_queue
, section
);
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 ();