2 * sgen-thread-pool.c: Threadpool for all concurrent GC work.
4 * Copyright (C) 2015 Xamarin Inc
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
12 #include "mono/sgen/sgen-gc.h"
13 #include "mono/sgen/sgen-thread-pool.h"
14 #include "mono/sgen/sgen-pointer-queue.h"
15 #include "mono/utils/mono-os-mutex.h"
16 #ifndef SGEN_WITHOUT_MONO
17 #include "mono/utils/mono-threads.h"
20 static mono_mutex_t lock;
21 static mono_cond_t work_cond;
22 static mono_cond_t done_cond;
24 static MonoNativeThreadId thread;
26 /* Only accessed with the lock held. */
27 static SgenPointerQueue job_queue;
29 static SgenThreadPoolThreadInitFunc thread_init_func;
30 static SgenThreadPoolIdleJobFunc idle_job_func;
31 static SgenThreadPoolContinueIdleJobFunc continue_idle_job_func;
39 /* Assumes that the lock is held. */
40 static SgenThreadPoolJob*
41 get_job_and_set_in_progress (void)
43 for (size_t i = 0; i < job_queue.next_slot; ++i) {
44 SgenThreadPoolJob *job = (SgenThreadPoolJob *)job_queue.data [i];
45 if (job->state == STATE_WAITING) {
46 job->state = STATE_IN_PROGRESS;
53 /* Assumes that the lock is held. */
55 find_job_in_queue (SgenThreadPoolJob *job)
57 for (ssize_t i = 0; i < job_queue.next_slot; ++i) {
58 if (job_queue.data [i] == job)
64 /* Assumes that the lock is held. */
66 remove_job (SgenThreadPoolJob *job)
69 SGEN_ASSERT (0, job->state == STATE_DONE, "Why are we removing a job that's not done?");
70 index = find_job_in_queue (job);
71 SGEN_ASSERT (0, index >= 0, "Why is the job we're trying to remove not in the queue?");
72 job_queue.data [index] = NULL;
73 sgen_pointer_queue_remove_nulls (&job_queue);
74 sgen_thread_pool_job_free (job);
78 continue_idle_job (void)
80 if (!continue_idle_job_func)
82 return continue_idle_job_func ();
85 static mono_native_thread_return_t
86 thread_func (void *thread_data)
88 thread_init_func (thread_data);
90 mono_os_mutex_lock (&lock);
93 * It's important that we check the continue idle flag with the lock held.
94 * Suppose we didn't check with the lock held, and the result is FALSE. The
95 * main thread might then set continue idle and signal us before we can take
96 * the lock, and we'd lose the signal.
98 gboolean do_idle = continue_idle_job ();
99 SgenThreadPoolJob *job = get_job_and_set_in_progress ();
101 if (!job && !do_idle) {
103 * pthread_cond_wait() can return successfully despite the condition
104 * not being signalled, so we have to run this in a loop until we
105 * really have work to do.
107 mono_os_cond_wait (&work_cond, &lock);
111 mono_os_mutex_unlock (&lock);
114 job->func (thread_data, job);
116 mono_os_mutex_lock (&lock);
118 SGEN_ASSERT (0, job->state == STATE_IN_PROGRESS, "The job should still be in progress.");
119 job->state = STATE_DONE;
122 * Only the main GC thread will ever wait on the done condition, so we don't
125 mono_os_cond_signal (&done_cond);
127 SGEN_ASSERT (0, do_idle, "Why did we unlock if we still have to wait for idle?");
128 SGEN_ASSERT (0, idle_job_func, "Why do we have idle work when there's no idle job function?");
130 idle_job_func (thread_data);
131 do_idle = continue_idle_job ();
132 } while (do_idle && !job_queue.next_slot);
134 mono_os_mutex_lock (&lock);
137 mono_os_cond_signal (&done_cond);
141 return (mono_native_thread_return_t)0;
145 sgen_thread_pool_init (int num_threads, SgenThreadPoolThreadInitFunc init_func, SgenThreadPoolIdleJobFunc idle_func, SgenThreadPoolContinueIdleJobFunc continue_idle_func, void **thread_datas)
147 SGEN_ASSERT (0, num_threads == 1, "We only support 1 thread pool thread for now.");
149 mono_os_mutex_init (&lock);
150 mono_os_cond_init (&work_cond);
151 mono_os_cond_init (&done_cond);
153 thread_init_func = init_func;
154 idle_job_func = idle_func;
155 continue_idle_job_func = continue_idle_func;
157 mono_native_thread_create (&thread, thread_func, thread_datas ? thread_datas [0] : NULL);
161 sgen_thread_pool_job_alloc (const char *name, SgenThreadPoolJobFunc func, size_t size)
163 SgenThreadPoolJob *job = (SgenThreadPoolJob *)sgen_alloc_internal_dynamic (size, INTERNAL_MEM_THREAD_POOL_JOB, TRUE);
166 job->state = STATE_WAITING;
172 sgen_thread_pool_job_free (SgenThreadPoolJob *job)
174 sgen_free_internal_dynamic (job, job->size, INTERNAL_MEM_THREAD_POOL_JOB);
178 sgen_thread_pool_job_enqueue (SgenThreadPoolJob *job)
180 mono_os_mutex_lock (&lock);
182 sgen_pointer_queue_add (&job_queue, job);
184 * FIXME: We could check whether there is a job in progress. If there is, there's
185 * no need to signal the condition, at least as long as we have only one thread.
187 mono_os_cond_signal (&work_cond);
189 mono_os_mutex_unlock (&lock);
193 sgen_thread_pool_job_wait (SgenThreadPoolJob *job)
195 SGEN_ASSERT (0, job, "Where's the job?");
197 mono_os_mutex_lock (&lock);
199 while (find_job_in_queue (job) >= 0)
200 mono_os_cond_wait (&done_cond, &lock);
202 mono_os_mutex_unlock (&lock);
206 sgen_thread_pool_idle_signal (void)
208 SGEN_ASSERT (0, idle_job_func, "Why are we signaling idle without an idle function?");
210 mono_os_mutex_lock (&lock);
212 if (continue_idle_job_func ())
213 mono_os_cond_signal (&work_cond);
215 mono_os_mutex_unlock (&lock);
219 sgen_thread_pool_idle_wait (void)
221 SGEN_ASSERT (0, idle_job_func, "Why are we waiting for idle without an idle function?");
223 mono_os_mutex_lock (&lock);
225 while (continue_idle_job_func ())
226 mono_os_cond_wait (&done_cond, &lock);
228 mono_os_mutex_unlock (&lock);
232 sgen_thread_pool_wait_for_all_jobs (void)
234 mono_os_mutex_lock (&lock);
236 while (!sgen_pointer_queue_is_empty (&job_queue))
237 mono_os_cond_wait (&done_cond, &lock);
239 mono_os_mutex_unlock (&lock);
243 sgen_thread_pool_is_thread_pool_thread (MonoNativeThreadId some_thread)
245 return some_thread == thread;