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
);