2 * timed-thread.c: Implementation of timed thread joining
5 * Dick Porter (dick@ximian.com)
7 * (C) 2002 Ximian, Inc.
12 #include <mono/os/gc_wrapper.h>
14 #ifdef HAVE_SEMAPHORE_H
15 #include <semaphore.h>
18 #include <mono/io-layer/processes.h>
20 #include "timed-thread.h"
22 #include "mono-mutex.h"
27 * Implementation of timed thread joining from the P1003.1d/D14 (July 1999)
28 * draft spec, figure B-6.
31 static pthread_key_t timed_thread_key;
32 static mono_once_t timed_thread_once = MONO_ONCE_INIT;
33 static mono_mutex_t apc_mutex;
36 static void timed_thread_init(void)
40 thr_ret = pthread_key_create(&timed_thread_key, NULL);
41 g_assert (thr_ret == 0);
43 thr_ret = mono_mutex_init(&apc_mutex, NULL);
44 g_assert (thr_ret == 0);
47 void _wapi_timed_thread_exit(guint32 exitstatus)
53 if((specific = pthread_getspecific(timed_thread_key)) == NULL) {
54 /* Handle cases which won't happen with correct usage.
59 thread=(TimedThread *)specific;
61 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
62 (void *)&thread->join_mutex);
63 thr_ret = mono_mutex_lock(&thread->join_mutex);
64 g_assert (thr_ret == 0);
66 /* Tell a joiner that we're exiting.
69 g_message(G_GNUC_PRETTY_FUNCTION
70 ": Setting thread %p id %ld exit status to %d",
71 thread, thread->id, exitstatus);
74 thread->exitstatus=exitstatus;
77 if(thread->exit_routine!=NULL) {
78 thread->exit_routine(exitstatus, thread->exit_userdata);
81 thr_ret = pthread_cond_signal(&thread->exit_cond);
82 g_assert (thr_ret == 0);
84 thr_ret = mono_mutex_unlock(&thread->join_mutex);
85 g_assert (thr_ret == 0);
86 pthread_cleanup_pop (0);
88 /* Call pthread_exit() to call destructors and really exit the
94 /* Routine to establish thread specific data value and run the actual
95 * thread start routine which was supplied to timed_thread_create()
97 static void *timed_thread_start_routine(gpointer args) G_GNUC_NORETURN;
98 static void *timed_thread_start_routine(gpointer args)
100 TimedThread *thread = (TimedThread *)args;
103 mono_once(&timed_thread_once, timed_thread_init);
104 thr_ret = pthread_setspecific(timed_thread_key, (void *)thread);
105 g_assert (thr_ret == 0);
107 /* This used to be pthread_detach(thread->id);
109 * thread->id is set in _wapi_timed_thread_create:
111 * if((result = pthread_create(&thread->id, attr,
112 * timed_thread_start_routine,
113 * (void *)thread)) != 0) {
115 * Strangeness happened: if _wapi_timed_thread_create was
116 * called directly, then thread->id was always set here.
117 * However, if _wapi_timed_thread_create was called via
118 * another function that did nothing but call
119 * _wapi_timed_thread_create, thread->id was not ever set,
120 * leading to the thread's 2M stack being wasted as it was not
123 * This was 100% reproducible on Debian Woody with gcc 2.95.4,
124 * and on Red Hat 9 with gcc 3.2.2.
126 thr_ret = pthread_detach(pthread_self ());
127 g_assert (thr_ret == 0);
129 if(thread->create_flags & CREATE_SUSPENDED) {
130 thread->suspend_count = 1;
131 _wapi_timed_thread_suspend (thread);
134 _wapi_timed_thread_exit(thread->start_routine(thread->arg));
137 /* Allocate a thread which can be used with timed_thread_join().
139 int _wapi_timed_thread_create(TimedThread **threadp,
140 const pthread_attr_t *attr,
141 guint32 create_flags,
142 guint32 (*start_routine)(gpointer),
143 void (*exit_routine)(guint32, gpointer),
144 gpointer arg, gpointer exit_userdata)
150 thread=(TimedThread *)g_new0(TimedThread, 1);
152 thr_ret = mono_mutex_init(&thread->join_mutex, NULL);
153 g_assert (thr_ret == 0);
155 thr_ret = pthread_cond_init(&thread->exit_cond, NULL);
156 g_assert (thr_ret == 0);
158 thread->create_flags = create_flags;
159 MONO_SEM_INIT (&thread->suspend_sem, 0);
160 MONO_SEM_INIT (&thread->suspended_sem, 0);
161 thread->start_routine = start_routine;
162 thread->exit_routine = exit_routine;
164 thread->exit_userdata = exit_userdata;
165 thread->exitstatus = 0;
166 thread->exiting = FALSE;
167 thread->apc_queue = NULL;
171 if((result = pthread_create(&thread->id, attr,
172 timed_thread_start_routine,
173 (void *)thread)) != 0) {
181 int _wapi_timed_thread_attach(TimedThread **threadp,
182 void (*exit_routine)(guint32, gpointer),
183 gpointer exit_userdata)
188 thread=(TimedThread *)g_new0(TimedThread, 1);
190 thr_ret = mono_mutex_init(&thread->join_mutex, NULL);
191 g_assert (thr_ret == 0);
193 thr_ret = pthread_cond_init(&thread->exit_cond, NULL);
194 g_assert (thr_ret == 0);
196 thr_ret = MONO_SEM_INIT (&thread->suspend_sem, 0);
197 g_assert (thr_ret != -1);
199 thr_ret = MONO_SEM_INIT (&thread->suspended_sem, 0);
200 g_assert (thr_ret != -1);
202 thread->exit_routine = exit_routine;
203 thread->exit_userdata = exit_userdata;
204 thread->exitstatus = 0;
205 thread->exiting = FALSE;
206 thread->id = pthread_self();
208 /* Make sure the timed-thread initialisation that the start
209 * routing does happens here too (we might be first to be
212 mono_once(&timed_thread_once, timed_thread_init);
213 thr_ret = pthread_setspecific(timed_thread_key, (void *)thread);
214 g_assert (thr_ret == 0);
221 int _wapi_timed_thread_join(TimedThread *thread, struct timespec *timeout,
227 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
228 (void *)&thread->join_mutex);
229 thr_ret = mono_mutex_lock(&thread->join_mutex);
230 g_assert (thr_ret == 0);
234 /* Wait until the thread announces that it's exiting, or until
237 while(result == 0 && !thread->exiting) {
238 if(timeout == NULL) {
239 result = pthread_cond_wait(&thread->exit_cond,
240 &thread->join_mutex);
242 result = pthread_cond_timedwait(&thread->exit_cond,
248 thr_ret = mono_mutex_unlock(&thread->join_mutex);
249 g_assert (thr_ret == 0);
250 pthread_cleanup_pop (0);
252 if(result == 0 && thread->exiting) {
253 if(exitstatus!=NULL) {
254 *exitstatus = thread->exitstatus;
260 void _wapi_timed_thread_destroy (TimedThread *thread)
262 mono_mutex_destroy (&thread->join_mutex);
263 pthread_cond_destroy (&thread->exit_cond);
264 MONO_SEM_DESTROY (&thread->suspend_sem);
265 MONO_SEM_DESTROY (&thread->suspended_sem);
270 /* I was going to base thread suspending on the algorithm presented at
271 * http://home.earthlink.net/~anneart/family/Threads/code/susp.c
273 * Unfortunately the Boehm GC library also wants to use this technique
274 * to stop the world, and will deadlock if a thread has already been
275 * suspended when it tries.
277 * While Mono is still using libgc this will just have to be a kludge
278 * to implement suspended creation of threads, rather than the general
279 * purpose thread suspension.
281 void _wapi_timed_thread_suspend (TimedThread *thread)
286 if((specific = pthread_getspecific (timed_thread_key))==NULL) {
287 g_warning (G_GNUC_PRETTY_FUNCTION ": thread lookup failed");
290 self=(TimedThread *)specific;
293 g_error (G_GNUC_PRETTY_FUNCTION
294 ": attempt to suspend a different thread!");
298 MONO_SEM_WAIT (&thread->suspend_sem);
301 void _wapi_timed_thread_resume (TimedThread *thread)
303 MONO_SEM_POST (&thread->suspend_sem);
306 void _wapi_timed_thread_queue_apc (TimedThread *thread,
307 guint32 (*apc_callback)(gpointer), gpointer param)
312 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
314 thr_ret = mono_mutex_lock(&apc_mutex);
315 g_assert (thr_ret == 0);
317 apc = (ApcInfo *)g_new(ApcInfo, 1);
318 apc->callback = apc_callback;
320 thread->apc_queue = g_slist_append (thread->apc_queue, apc);
322 thr_ret = mono_mutex_unlock(&apc_mutex);
323 g_assert (thr_ret == 0);
324 pthread_cleanup_pop (0);
327 gboolean _wapi_timed_thread_apc_pending (TimedThread *thread)
329 return thread->apc_queue != NULL;
332 void _wapi_timed_thread_dispatch_apc_queue (TimedThread *thread)
338 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
340 thr_ret = mono_mutex_lock(&apc_mutex);
341 g_assert (thr_ret == 0);
343 list = thread->apc_queue;
344 thread->apc_queue = NULL;
346 thr_ret = mono_mutex_unlock(&apc_mutex);
347 g_assert (thr_ret == 0);
348 pthread_cleanup_pop (0);
350 while (list != NULL) {
351 apc = (ApcInfo*)list->data;
352 apc->callback (apc->param);
354 list = g_slist_next (list);