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