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>
19 #include <mono/io-layer/processes.h>
21 #include "timed-thread.h"
23 #include "mono-mutex.h"
28 * Implementation of timed thread joining from the P1003.1d/D14 (July 1999)
29 * draft spec, figure B-6.
32 static pthread_key_t timed_thread_key;
33 static mono_once_t timed_thread_once = MONO_ONCE_INIT;
34 static mono_mutex_t apc_mutex;
37 static void timed_thread_init(void)
41 thr_ret = pthread_key_create(&timed_thread_key, NULL);
42 g_assert (thr_ret == 0);
44 thr_ret = mono_mutex_init(&apc_mutex, NULL);
45 g_assert (thr_ret == 0);
48 void _wapi_timed_thread_exit(guint32 exitstatus)
54 if((specific = pthread_getspecific(timed_thread_key)) == NULL) {
55 /* Handle cases which won't happen with correct usage.
60 thread=(TimedThread *)specific;
62 if(thread->exit_routine!=NULL) {
63 thread->exit_routine(exitstatus, thread->exit_userdata);
66 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
67 (void *)&thread->join_mutex);
68 thr_ret = mono_mutex_lock(&thread->join_mutex);
69 g_assert (thr_ret == 0);
71 /* Tell a joiner that we're exiting.
74 g_message(G_GNUC_PRETTY_FUNCTION
75 ": Setting thread %p id %ld exit status to %d",
76 thread, thread->id, exitstatus);
79 thread->exitstatus=exitstatus;
82 thr_ret = pthread_cond_signal(&thread->exit_cond);
83 g_assert (thr_ret == 0);
85 thr_ret = mono_mutex_unlock(&thread->join_mutex);
86 g_assert (thr_ret == 0);
87 pthread_cleanup_pop (0);
89 /* Call pthread_exit() to call destructors and really exit the
95 /* Routine to establish thread specific data value and run the actual
96 * thread start routine which was supplied to timed_thread_create()
98 static void *timed_thread_start_routine(gpointer args) G_GNUC_NORETURN;
99 static void *timed_thread_start_routine(gpointer args)
101 TimedThread *thread = (TimedThread *)args;
104 mono_once(&timed_thread_once, timed_thread_init);
105 thr_ret = pthread_setspecific(timed_thread_key, (void *)thread);
106 g_assert (thr_ret == 0);
108 /* This used to be pthread_detach(thread->id);
110 * thread->id is set in _wapi_timed_thread_create:
112 * if((result = pthread_create(&thread->id, attr,
113 * timed_thread_start_routine,
114 * (void *)thread)) != 0) {
116 * Strangeness happened: if _wapi_timed_thread_create was
117 * called directly, then thread->id was always set here.
118 * However, if _wapi_timed_thread_create was called via
119 * another function that did nothing but call
120 * _wapi_timed_thread_create, thread->id was not ever set,
121 * leading to the thread's 2M stack being wasted as it was not
124 * This was 100% reproducible on Debian Woody with gcc 2.95.4,
125 * and on Red Hat 9 with gcc 3.2.2.
127 thr_ret = pthread_detach(pthread_self ());
128 g_assert (thr_ret == 0);
130 if(thread->create_flags & CREATE_SUSPENDED) {
131 thread->suspend_count = 1;
132 _wapi_timed_thread_suspend (thread);
135 _wapi_timed_thread_exit(thread->start_routine(thread->arg));
138 /* Allocate a thread which can be used with timed_thread_join().
140 int _wapi_timed_thread_create(TimedThread **threadp,
141 const pthread_attr_t *attr,
142 guint32 create_flags,
143 guint32 (*start_routine)(gpointer),
144 void (*exit_routine)(guint32, gpointer),
145 gpointer arg, gpointer exit_userdata)
151 thread=(TimedThread *)g_new0(TimedThread, 1);
153 thr_ret = mono_mutex_init(&thread->join_mutex, NULL);
154 g_assert (thr_ret == 0);
156 thr_ret = pthread_cond_init(&thread->exit_cond, NULL);
157 g_assert (thr_ret == 0);
159 thread->create_flags = create_flags;
160 MONO_SEM_INIT (&thread->suspend_sem, 0);
161 MONO_SEM_INIT (&thread->suspended_sem, 0);
162 thread->start_routine = start_routine;
163 thread->exit_routine = exit_routine;
165 thread->exit_userdata = exit_userdata;
166 thread->exitstatus = 0;
167 thread->exiting = FALSE;
168 thread->apc_queue = NULL;
172 if((result = pthread_create(&thread->id, attr,
173 timed_thread_start_routine,
174 (void *)thread)) != 0) {
182 int _wapi_timed_thread_attach(TimedThread **threadp,
183 void (*exit_routine)(guint32, gpointer),
184 gpointer exit_userdata)
189 thread=(TimedThread *)g_new0(TimedThread, 1);
191 thr_ret = mono_mutex_init(&thread->join_mutex, NULL);
192 g_assert (thr_ret == 0);
194 thr_ret = pthread_cond_init(&thread->exit_cond, NULL);
195 g_assert (thr_ret == 0);
197 thr_ret = MONO_SEM_INIT (&thread->suspend_sem, 0);
198 g_assert (thr_ret != -1);
200 thr_ret = MONO_SEM_INIT (&thread->suspended_sem, 0);
201 g_assert (thr_ret != -1);
203 thread->exit_routine = exit_routine;
204 thread->exit_userdata = exit_userdata;
205 thread->exitstatus = 0;
206 thread->exiting = FALSE;
207 thread->id = pthread_self();
209 /* Make sure the timed-thread initialisation that the start
210 * routing does happens here too (we might be first to be
213 mono_once(&timed_thread_once, timed_thread_init);
214 thr_ret = pthread_setspecific(timed_thread_key, (void *)thread);
215 g_assert (thr_ret == 0);
222 int _wapi_timed_thread_join(TimedThread *thread, struct timespec *timeout,
228 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
229 (void *)&thread->join_mutex);
230 thr_ret = mono_mutex_lock(&thread->join_mutex);
231 g_assert (thr_ret == 0);
235 /* Wait until the thread announces that it's exiting, or until
238 while(result == 0 && !thread->exiting) {
239 if(timeout == NULL) {
240 result = pthread_cond_wait(&thread->exit_cond,
241 &thread->join_mutex);
243 result = pthread_cond_timedwait(&thread->exit_cond,
249 thr_ret = mono_mutex_unlock(&thread->join_mutex);
250 g_assert (thr_ret == 0);
251 pthread_cleanup_pop (0);
253 if(result == 0 && thread->exiting) {
254 if(exitstatus!=NULL) {
255 *exitstatus = thread->exitstatus;
258 _wapi_timed_thread_destroy (thread);
263 void _wapi_timed_thread_destroy (TimedThread *thread)
265 mono_mutex_destroy (&thread->join_mutex);
266 pthread_cond_destroy (&thread->exit_cond);
267 MONO_SEM_DESTROY (&thread->suspend_sem);
268 MONO_SEM_DESTROY (&thread->suspended_sem);
273 /* I was going to base thread suspending on the algorithm presented at
274 * http://home.earthlink.net/~anneart/family/Threads/code/susp.c
276 * Unfortunately the Boehm GC library also wants to use this technique
277 * to stop the world, and will deadlock if a thread has already been
278 * suspended when it tries.
280 * While Mono is still using libgc this will just have to be a kludge
281 * to implement suspended creation of threads, rather than the general
282 * purpose thread suspension.
284 void _wapi_timed_thread_suspend (TimedThread *thread)
289 if((specific = pthread_getspecific (timed_thread_key))==NULL) {
290 g_warning (G_GNUC_PRETTY_FUNCTION ": thread lookup failed");
293 self=(TimedThread *)specific;
296 g_error (G_GNUC_PRETTY_FUNCTION
297 ": attempt to suspend a different thread!");
301 while (MONO_SEM_WAIT (&thread->suspend_sem) != 0 && errno == EINTR);
304 void _wapi_timed_thread_resume (TimedThread *thread)
306 MONO_SEM_POST (&thread->suspend_sem);
309 void _wapi_timed_thread_queue_apc (TimedThread *thread,
310 guint32 (*apc_callback)(gpointer), gpointer param)
315 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
317 thr_ret = mono_mutex_lock(&apc_mutex);
318 g_assert (thr_ret == 0);
320 apc = (ApcInfo *)g_new(ApcInfo, 1);
321 apc->callback = apc_callback;
323 thread->apc_queue = g_slist_append (thread->apc_queue, apc);
325 thr_ret = mono_mutex_unlock(&apc_mutex);
326 g_assert (thr_ret == 0);
327 pthread_cleanup_pop (0);
330 gboolean _wapi_timed_thread_apc_pending (TimedThread *thread)
332 return thread->apc_queue != NULL;
335 void _wapi_timed_thread_dispatch_apc_queue (TimedThread *thread)
341 pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
343 thr_ret = mono_mutex_lock(&apc_mutex);
344 g_assert (thr_ret == 0);
346 list = thread->apc_queue;
347 thread->apc_queue = NULL;
349 thr_ret = mono_mutex_unlock(&apc_mutex);
350 g_assert (thr_ret == 0);
351 pthread_cleanup_pop (0);
353 while (list != NULL) {
354 apc = (ApcInfo*)list->data;
355 apc->callback (apc->param);
357 list = g_slist_next (list);