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