2003-07-02 Dick Porter <dick@ximian.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
34 static void timed_thread_init(void)
35 {
36         pthread_key_create(&timed_thread_key, NULL);
37 }
38
39 void _wapi_timed_thread_exit(guint32 exitstatus)
40 {
41         TimedThread *thread;
42         void *specific;
43         
44         if((specific = pthread_getspecific(timed_thread_key)) == NULL) {
45                 /* Handle cases which won't happen with correct usage.
46                  */
47                 pthread_exit(NULL);
48         }
49         
50         thread=(TimedThread *)specific;
51         
52         mono_mutex_lock(&thread->join_mutex);
53         
54         /* Tell a joiner that we're exiting.
55          */
56 #ifdef DEBUG
57         g_message(G_GNUC_PRETTY_FUNCTION
58                   ": Setting thread %p id %ld exit status to %d",
59                   thread, thread->id, exitstatus);
60 #endif
61
62         thread->exitstatus=exitstatus;
63         thread->exiting=TRUE;
64
65         if(thread->exit_routine!=NULL) {
66                 thread->exit_routine(exitstatus, thread->exit_userdata);
67         }
68         
69         pthread_cond_signal(&thread->exit_cond);
70         mono_mutex_unlock(&thread->join_mutex);
71
72         _wapi_timed_thread_destroy (thread);
73         
74         /* Call pthread_exit() to call destructors and really exit the
75          * thread.
76          */
77         pthread_exit(NULL);
78 }
79
80 /* Routine to establish thread specific data value and run the actual
81  * thread start routine which was supplied to timed_thread_create()
82  */
83 static void *timed_thread_start_routine(gpointer args) G_GNUC_NORETURN;
84 static void *timed_thread_start_routine(gpointer args)
85 {
86         TimedThread *thread = (TimedThread *)args;
87         
88         mono_once(&timed_thread_once, timed_thread_init);
89         pthread_setspecific(timed_thread_key, (void *)thread);
90         pthread_detach(thread->id);
91
92         if(thread->create_flags & CREATE_SUSPENDED) {
93                 thread->suspend_count = 1;
94                 _wapi_timed_thread_suspend (thread);
95         }
96         
97         _wapi_timed_thread_exit(thread->start_routine(thread->arg));
98 }
99
100 /* Allocate a thread which can be used with timed_thread_join().
101  */
102 int _wapi_timed_thread_create(TimedThread **threadp,
103                               const pthread_attr_t *attr,
104                               guint32 create_flags,
105                               guint32 (*start_routine)(gpointer),
106                               void (*exit_routine)(guint32, gpointer),
107                               gpointer arg, gpointer exit_userdata)
108 {
109         TimedThread *thread;
110         int result;
111         
112         thread=(TimedThread *)g_new0(TimedThread, 1);
113         
114         mono_mutex_init(&thread->join_mutex, NULL);
115         pthread_cond_init(&thread->exit_cond, NULL);
116         thread->create_flags = create_flags;
117         sem_init (&thread->suspend_sem, 0, 0);
118         sem_init (&thread->suspended_sem, 0, 0);
119         thread->start_routine = start_routine;
120         thread->exit_routine = exit_routine;
121         thread->arg = arg;
122         thread->exit_userdata = exit_userdata;
123         thread->exitstatus = 0;
124         thread->exiting = FALSE;
125         
126         *threadp = thread;
127
128         if((result = pthread_create(&thread->id, attr,
129                                     timed_thread_start_routine,
130                                     (void *)thread)) != 0) {
131                 g_free(thread);
132                 return(result);
133         }
134         
135         return(0);
136 }
137
138 int _wapi_timed_thread_attach(TimedThread **threadp,
139                               void (*exit_routine)(guint32, gpointer),
140                               gpointer exit_userdata)
141 {
142         TimedThread *thread;
143
144         thread=(TimedThread *)g_new0(TimedThread, 1);
145
146         mono_mutex_init(&thread->join_mutex, NULL);
147         pthread_cond_init(&thread->exit_cond, NULL);
148         sem_init (&thread->suspend_sem, 0, 0);
149         sem_init (&thread->suspended_sem, 0, 0);
150         thread->exit_routine = exit_routine;
151         thread->exit_userdata = exit_userdata;
152         thread->exitstatus = 0;
153         thread->exiting = FALSE;
154         thread->id = pthread_self();
155
156         /* Make sure the timed-thread initialisation that the start
157          * routing does happens here too (we might be first to be
158          * called)
159          */
160         mono_once(&timed_thread_once, timed_thread_init);
161         pthread_setspecific(timed_thread_key, (void *)thread);
162
163         *threadp = thread;
164
165         return(0);
166 }
167
168 int _wapi_timed_thread_join(TimedThread *thread, struct timespec *timeout,
169                             guint32 *exitstatus)
170 {
171         int result;
172         
173         mono_mutex_lock(&thread->join_mutex);
174         result=0;
175         
176         /* Wait until the thread announces that it's exiting, or until
177          * timeout.
178          */
179         while(result == 0 && !thread->exiting) {
180                 if(timeout == NULL) {
181                         result = pthread_cond_wait(&thread->exit_cond,
182                                                    &thread->join_mutex);
183                 } else {
184                         result = pthread_cond_timedwait(&thread->exit_cond,
185                                                         &thread->join_mutex,
186                                                         timeout);
187                 }
188         }
189         
190         mono_mutex_unlock(&thread->join_mutex);
191         if(result == 0 && thread->exiting) {
192                 if(exitstatus!=NULL) {
193                         *exitstatus = thread->exitstatus;
194                 }
195         }
196         return(result);
197 }
198
199 void _wapi_timed_thread_destroy (TimedThread *thread)
200 {
201         mono_mutex_destroy (&thread->join_mutex);
202         pthread_cond_destroy (&thread->exit_cond);
203         sem_destroy (&thread->suspend_sem);
204         sem_destroy (&thread->suspended_sem);
205         
206         g_free(thread);
207 }
208
209 /* I was going to base thread suspending on the algorithm presented at
210  * http://home.earthlink.net/~anneart/family/Threads/code/susp.c
211  *
212  * Unfortunately the Boehm GC library also wants to use this technique
213  * to stop the world, and will deadlock if a thread has already been
214  * suspended when it tries.
215  *
216  * While Mono is still using libgc this will just have to be a kludge
217  * to implement suspended creation of threads, rather than the general
218  * purpose thread suspension.
219  */
220 void _wapi_timed_thread_suspend (TimedThread *thread)
221 {
222         TimedThread *self;
223         void *specific;
224         
225         if((specific = pthread_getspecific (timed_thread_key))==NULL) {
226                 g_warning (G_GNUC_PRETTY_FUNCTION ": thread lookup failed");
227                 return;
228         }
229         self=(TimedThread *)specific;
230         
231         if(thread != self) {
232                 g_error (G_GNUC_PRETTY_FUNCTION
233                          ": attempt to suspend a different thread!");
234                 exit (-1);
235         }
236
237         sem_wait (&thread->suspend_sem);
238 }
239
240 void _wapi_timed_thread_resume (TimedThread *thread)
241 {
242         sem_post (&thread->suspend_sem);
243 }