2004-11-30 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mono / io-layer / timed-thread.c
1 /*
2  * timed-thread.c:  Implementation of timed thread joining
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2002 Ximian, Inc.
8  */
9
10 #include <config.h>
11 #include <glib.h>
12 #include <mono/os/gc_wrapper.h>
13 #include <pthread.h>
14 #ifdef HAVE_SEMAPHORE_H
15 #include <semaphore.h>
16 #endif
17 #include <errno.h>
18
19 #include <mono/io-layer/processes.h>
20
21 #include "timed-thread.h"
22
23 #include "mono-mutex.h"
24
25 #undef DEBUG
26
27 /*
28  * Implementation of timed thread joining from the P1003.1d/D14 (July 1999)
29  * draft spec, figure B-6.
30  */
31
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;
35
36
37 static void timed_thread_init(void)
38 {
39         int thr_ret;
40         
41         thr_ret = pthread_key_create(&timed_thread_key, NULL);
42         g_assert (thr_ret == 0);
43         
44         thr_ret = mono_mutex_init(&apc_mutex, NULL);
45         g_assert (thr_ret == 0);
46 }
47
48 void _wapi_timed_thread_exit(guint32 exitstatus)
49 {
50         TimedThread *thread;
51         void *specific;
52         int thr_ret;
53         
54         if((specific = pthread_getspecific(timed_thread_key)) == NULL) {
55                 /* Handle cases which won't happen with correct usage.
56                  */
57                 pthread_exit(NULL);
58         }
59         
60         thread=(TimedThread *)specific;
61
62         if(thread->exit_routine!=NULL) {
63                 thread->exit_routine(exitstatus, thread->exit_userdata);
64         }
65         
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);
70         
71         /* Tell a joiner that we're exiting.
72          */
73 #ifdef DEBUG
74         g_message(G_GNUC_PRETTY_FUNCTION
75                   ": Setting thread %p id %ld exit status to %d",
76                   thread, thread->id, exitstatus);
77 #endif
78
79         thread->exitstatus=exitstatus;
80         thread->exiting=TRUE;
81         
82         thr_ret = pthread_cond_signal(&thread->exit_cond);
83         g_assert (thr_ret == 0);
84         
85         thr_ret = mono_mutex_unlock(&thread->join_mutex);
86         g_assert (thr_ret == 0);
87         pthread_cleanup_pop (0);
88         
89         /* Call pthread_exit() to call destructors and really exit the
90          * thread.
91          */
92         pthread_exit(NULL);
93 }
94
95 /* Routine to establish thread specific data value and run the actual
96  * thread start routine which was supplied to timed_thread_create()
97  */
98 static void *timed_thread_start_routine(gpointer args) G_GNUC_NORETURN;
99 static void *timed_thread_start_routine(gpointer args)
100 {
101         TimedThread *thread = (TimedThread *)args;
102         int thr_ret;
103         
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);
107
108         /* This used to be pthread_detach(thread->id);
109          *
110          * thread->id is set in _wapi_timed_thread_create:
111          *
112          * if((result = pthread_create(&thread->id, attr,
113          *                          timed_thread_start_routine,
114          *                          (void *)thread)) != 0) {
115          *
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
122          * detached.
123          *
124          * This was 100% reproducible on Debian Woody with gcc 2.95.4,
125          * and on Red Hat 9 with gcc 3.2.2.
126          */
127         thr_ret = pthread_detach(pthread_self ());
128         g_assert (thr_ret == 0);
129
130         if(thread->create_flags & CREATE_SUSPENDED) {
131                 thread->suspend_count = 1;
132                 _wapi_timed_thread_suspend (thread);
133         }
134         
135         _wapi_timed_thread_exit(thread->start_routine(thread->arg));
136 }
137
138 /* Allocate a thread which can be used with timed_thread_join().
139  */
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)
146 {
147         TimedThread *thread;
148         int result;
149         int thr_ret;
150         
151         thread=(TimedThread *)g_new0(TimedThread, 1);
152         
153         thr_ret = mono_mutex_init(&thread->join_mutex, NULL);
154         g_assert (thr_ret == 0);
155         
156         thr_ret = pthread_cond_init(&thread->exit_cond, NULL);
157         g_assert (thr_ret == 0);
158         
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;
164         thread->arg = arg;
165         thread->exit_userdata = exit_userdata;
166         thread->exitstatus = 0;
167         thread->exiting = FALSE;
168         thread->apc_queue = NULL;
169         
170         *threadp = thread;
171
172         if((result = pthread_create(&thread->id, attr,
173                                     timed_thread_start_routine,
174                                     (void *)thread)) != 0) {
175                 g_free(thread);
176                 return(result);
177         }
178         
179         return(0);
180 }
181
182 int _wapi_timed_thread_attach(TimedThread **threadp,
183                               void (*exit_routine)(guint32, gpointer),
184                               gpointer exit_userdata)
185 {
186         TimedThread *thread;
187         int thr_ret;
188         
189         thread=(TimedThread *)g_new0(TimedThread, 1);
190
191         thr_ret = mono_mutex_init(&thread->join_mutex, NULL);
192         g_assert (thr_ret == 0);
193         
194         thr_ret = pthread_cond_init(&thread->exit_cond, NULL);
195         g_assert (thr_ret == 0);
196         
197         thr_ret = MONO_SEM_INIT (&thread->suspend_sem, 0);
198         g_assert (thr_ret != -1);
199         
200         thr_ret = MONO_SEM_INIT (&thread->suspended_sem, 0);
201         g_assert (thr_ret != -1);
202         
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();
208
209         /* Make sure the timed-thread initialisation that the start
210          * routing does happens here too (we might be first to be
211          * called)
212          */
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);
216
217         *threadp = thread;
218
219         return(0);
220 }
221
222 int _wapi_timed_thread_join(TimedThread *thread, struct timespec *timeout,
223                             guint32 *exitstatus)
224 {
225         int result;
226         int thr_ret;
227         
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);
232         
233         result=0;
234         
235         /* Wait until the thread announces that it's exiting, or until
236          * timeout.
237          */
238         while(result == 0 && !thread->exiting) {
239                 if(timeout == NULL) {
240                         result = pthread_cond_wait(&thread->exit_cond,
241                                                    &thread->join_mutex);
242                 } else {
243                         result = pthread_cond_timedwait(&thread->exit_cond,
244                                                         &thread->join_mutex,
245                                                         timeout);
246                 }
247         }
248         
249         thr_ret = mono_mutex_unlock(&thread->join_mutex);
250         g_assert (thr_ret == 0);
251         pthread_cleanup_pop (0);
252         
253         if(result == 0 && thread->exiting) {
254                 if(exitstatus!=NULL) {
255                         *exitstatus = thread->exitstatus;
256                 }
257
258                 _wapi_timed_thread_destroy (thread);
259         }
260         return(result);
261 }
262
263 void _wapi_timed_thread_destroy (TimedThread *thread)
264 {
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);
269         
270         g_free(thread);
271 }
272
273 /* I was going to base thread suspending on the algorithm presented at
274  * http://home.earthlink.net/~anneart/family/Threads/code/susp.c
275  *
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.
279  *
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.
283  */
284 void _wapi_timed_thread_suspend (TimedThread *thread)
285 {
286         TimedThread *self;
287         void *specific;
288         
289         if((specific = pthread_getspecific (timed_thread_key))==NULL) {
290                 g_warning (G_GNUC_PRETTY_FUNCTION ": thread lookup failed");
291                 return;
292         }
293         self=(TimedThread *)specific;
294         
295         if(thread != self) {
296                 g_error (G_GNUC_PRETTY_FUNCTION
297                          ": attempt to suspend a different thread!");
298                 exit (-1);
299         }
300
301         while (MONO_SEM_WAIT (&thread->suspend_sem) != 0 && errno == EINTR);
302 }
303
304 void _wapi_timed_thread_resume (TimedThread *thread)
305 {
306         MONO_SEM_POST (&thread->suspend_sem);
307 }
308
309 void _wapi_timed_thread_queue_apc (TimedThread *thread, 
310         guint32 (*apc_callback)(gpointer), gpointer param)
311 {
312         ApcInfo *apc;
313         int thr_ret;
314         
315         pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
316                               (void *)&apc_mutex);
317         thr_ret = mono_mutex_lock(&apc_mutex);
318         g_assert (thr_ret == 0);
319         
320         apc = (ApcInfo *)g_new(ApcInfo, 1);
321         apc->callback = apc_callback;
322         apc->param = param;
323         thread->apc_queue = g_slist_append (thread->apc_queue, apc);
324
325         thr_ret = mono_mutex_unlock(&apc_mutex);
326         g_assert (thr_ret == 0);
327         pthread_cleanup_pop (0);
328 }
329
330 gboolean _wapi_timed_thread_apc_pending (TimedThread *thread)
331 {
332         return thread->apc_queue != NULL;
333 }
334
335 void _wapi_timed_thread_dispatch_apc_queue (TimedThread *thread)
336 {
337         ApcInfo* apc;
338         GSList *list;
339         int thr_ret;
340
341         pthread_cleanup_push ((void(*)(void *))mono_mutex_unlock_in_cleanup,
342                               (void *)&apc_mutex);
343         thr_ret = mono_mutex_lock(&apc_mutex);
344         g_assert (thr_ret == 0);
345         
346         list = thread->apc_queue;
347         thread->apc_queue = NULL;
348
349         thr_ret = mono_mutex_unlock(&apc_mutex);
350         g_assert (thr_ret == 0);
351         pthread_cleanup_pop (0);
352         
353         while (list != NULL) {
354                 apc = (ApcInfo*)list->data;
355                 apc->callback (apc->param);
356                 g_free (apc);
357                 list = g_slist_next (list);
358         }
359         g_slist_free (list);
360 }
361