2001-09-25 Dick Porter <dick@ximian.com>
[mono.git] / mono / metadata / threads-pthread.c
1 /*
2  * threads-pthread.c: System-specific thread support
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2001 Ximian, Inc.
8  */
9
10 #include <config.h>
11 #include <glib.h>
12 #include <string.h>
13 #include <pthread.h>
14 #include <stdlib.h>
15 #include <time.h>
16 #include <errno.h>
17
18 #include <mono/metadata/object.h>
19 #include <mono/metadata/threads.h>
20
21 /*
22  * Implementation of timed thread joining from the P1003.1d/D14 (July 1999)
23  * draft spec, figure B-6.
24  */
25 typedef struct {
26         pthread_t id;
27         MonoObject *object;
28         pthread_mutex_t join_mutex;
29         pthread_cond_t exit_cond;
30         void *(*start_routine)(void *arg);
31         void *arg;
32         void *status;
33         gboolean exiting;
34 } ThreadInfo;
35
36 static pthread_key_t timed_thread_key;
37 static pthread_once_t timed_thread_once = PTHREAD_ONCE_INIT;
38
39 static GHashTable *threads=NULL;
40 static MonoObject *main_thread;
41
42 static void timed_thread_init()
43 {
44         pthread_key_create(&timed_thread_key, NULL);
45 }
46
47 static void timed_thread_exit(void *status)
48 {
49         ThreadInfo *thread;
50         void *specific;
51         
52         if((specific = pthread_getspecific(timed_thread_key)) == NULL) {
53                 /* Handle cases which won't happen with correct usage.
54                  */
55                 pthread_exit(NULL);
56         }
57         
58         thread=(ThreadInfo *)specific;
59         
60         pthread_mutex_lock(&thread->join_mutex);
61         
62         /* Tell a joiner that we're exiting.
63          */
64         thread->status = status;
65         thread->exiting = TRUE;
66
67         pthread_cond_signal(&thread->exit_cond);
68         pthread_mutex_unlock(&thread->join_mutex);
69         
70         /* Call pthread_exit() to call destructors and really exit the
71          * thread.
72          */
73         pthread_exit(NULL);
74 }
75
76 /* Routine to establish thread specific data value and run the actual
77  * thread start routine which was supplied to timed_thread_create()
78  */
79 static void *timed_thread_start_routine(void *args)
80 {
81         ThreadInfo *thread = (ThreadInfo *)args;
82         
83         pthread_once(&timed_thread_once, timed_thread_init);
84         pthread_setspecific(timed_thread_key, (void *)thread);
85         timed_thread_exit((thread->start_routine)(thread->arg));
86
87         /* pthread_create routine has to return something to keep gcc
88          * quiet
89          */
90         return(NULL);
91 }
92
93 /* Allocate a thread which can be used with timed_thread_join().
94  */
95 static int timed_thread_create(ThreadInfo **threadp,
96                                const pthread_attr_t *attr,
97                                void *(*start_routine)(void *), void *arg)
98 {
99         ThreadInfo *thread;
100         int result;
101         
102         thread=(ThreadInfo *)g_new0(ThreadInfo, 1);
103         pthread_mutex_init(&thread->join_mutex, NULL);
104         pthread_cond_init(&thread->exit_cond, NULL);
105         thread->start_routine = start_routine;
106         thread->arg = arg;
107         thread->status = NULL;
108         thread->exiting = FALSE;
109         
110         if((result = pthread_create(&thread->id, attr,
111                                     timed_thread_start_routine,
112                                     (void *)thread)) != 0) {
113                 g_free(thread);
114                 return(result);
115         }
116         
117         pthread_detach(thread->id);
118         *threadp = thread;
119         return(0);
120 }
121
122 static int timed_thread_join(ThreadInfo *thread, struct timespec *timeout,
123                              void **status)
124 {
125         int result;
126         
127         pthread_mutex_lock(&thread->join_mutex);
128         result=0;
129         
130         /* Wait until the thread announces that it's exiting, or until
131          * timeout.
132          */
133         while(result == 0 && !thread->exiting) {
134                 if(timeout == NULL) {
135                         result = pthread_cond_wait(&thread->exit_cond,
136                                                    &thread->join_mutex);
137                 } else {
138                         result = pthread_cond_timedwait(&thread->exit_cond,
139                                                         &thread->join_mutex,
140                                                         timeout);
141                 }
142         }
143         
144         pthread_mutex_unlock(&thread->join_mutex);
145         if(result == 0 && thread->exiting) {
146                 if(status!=NULL) {
147                         *status = thread->status;
148                 }
149         }
150         return(result);
151 }
152
153 pthread_t ves_icall_System_Threading_Thread_Start_internal(MonoObject *this,
154                                                            MonoObject *start)
155 {
156         MonoClassField *field;
157         void *(*start_func)(void *);
158         ThreadInfo *thread;
159         int ret;
160         
161 #ifdef THREAD_DEBUG
162         g_message("Trying to start a new thread: this (%p) start (%p)",
163                   this, start);
164 #endif
165
166         field=mono_class_get_field_from_name(mono_defaults.delegate_class, "method_ptr");
167         start_func= *(gpointer *)(((char *)start) + field->offset);
168         
169         if(start_func==NULL) {
170                 g_warning("Can't locate start method!");
171                 /* Not sure if 0 can't be a valid pthread_t.  Calling
172                  * pthread_self() on the main thread seems to always
173                  * return 1024 for me
174                  */
175                 return(0);
176         } else {
177                 ret=timed_thread_create(&thread, NULL, start_func, NULL);
178                 if(ret!=0) {
179                         g_warning("pthread_create error: %s", strerror(ret));
180                         return(0);
181                 }
182         
183 #ifdef THREAD_DEBUG
184                 g_message("Started thread ID %ld", thread->id);
185 #endif
186
187                 /* Store tid for cleanup later */
188                 if(threads==NULL) {
189                         threads=g_hash_table_new(g_int_hash, g_int_equal);
190                 }
191
192                 thread->object=this;
193                 
194                 /* FIXME: need some locking around here */
195                 g_hash_table_insert(threads, &thread->id, thread);
196                 
197                 return(thread->id);
198         }
199 }
200
201 gint32 ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms)
202 {
203         struct timespec req, rem;
204         div_t divvy;
205         int ret;
206         
207 #ifdef THREAD_DEBUG
208         g_message("Sleeping for %d ms", ms);
209 #endif
210
211         divvy=div(ms, 1000);
212         
213         req.tv_sec=divvy.quot;
214         req.tv_nsec=divvy.rem*1000;
215         
216         ret=nanosleep(&req, &rem);
217         if(ret<0) {
218                 /* Sleep interrupted with rem time remaining */
219                 gint32 rems=rem.tv_sec*1000 + rem.tv_nsec/1000;
220                 
221 #ifdef THREAD_DEBUG
222                 g_message("Returning %dms early", rems);
223 #endif
224                 return(rems);
225         }
226         
227 #ifdef THREAD_DEBUG
228         g_message("Slept");
229 #endif
230         
231         return(0);
232 }
233
234 void ves_icall_System_Threading_Thread_Schedule_internal(void)
235 {
236         /* Give up the timeslice. pthread_yield() is the standard
237          * function but glibc seems to insist it's a GNU
238          * extension. However, all it does at the moment is
239          * sched_yield() anyway, and sched_yield() is a POSIX standard
240          * function.
241          */
242         
243         sched_yield();
244 }
245
246 MonoObject *ves_icall_System_Threading_Thread_CurrentThread_internal(void)
247 {
248         pthread_t tid;
249         ThreadInfo *thread_info;
250         
251         /* Find the current thread id */
252         tid=pthread_self();
253         
254         /* Look it up in the threads hash */
255         if(threads!=NULL) {
256                 thread_info=g_hash_table_lookup(threads, &tid);
257         } else {
258                 /* No threads running yet! */
259                 thread_info=NULL;
260         }
261         
262         /* Return the object associated with it */
263         if(thread_info==NULL) {
264                 /* If we can't find our own thread ID, assume it's the
265                  * main thread
266                  */
267                 return(main_thread);
268         } else {
269                 return(thread_info->object);
270         }
271 }
272
273 static void delete_thread(ThreadInfo *thread)
274 {
275         g_assert(threads!=NULL);
276         g_assert(thread!=NULL);
277         
278         g_hash_table_remove(threads, &thread->id);
279         g_free(thread);
280 }
281
282 gboolean ves_icall_System_Threading_Thread_Join_internal(MonoObject *this,
283                                                          int ms, pthread_t tid)
284 {
285         ThreadInfo *thread;
286         pthread_t myid;
287         int ret;
288         
289 #ifdef THREAD_DEBUG
290         g_message("Joining with thread %p id %ld, waiting for %dms", this,
291                   tid, ms);
292 #endif
293
294         myid=pthread_self();
295         if(myid==tid) {
296                 /* .net doesnt spot this and proceeds to
297                  * deadlock. This will have to be commented out if we
298                  * want to be bug-compatible :-(
299                  */
300                 g_warning("Can't join my own thread!");
301                 return(FALSE);
302         }
303         
304         if(threads!=NULL) {
305                 thread=g_hash_table_lookup(threads, &tid);
306         } else {
307                 /* No threads running yet! */
308                 thread=NULL;
309         }
310         
311         if(thread==NULL) {
312                 g_warning("Can't find thread id %ld", tid);
313                 return(FALSE);
314         }
315         
316         if(ms==0) {
317                 /* block until thread exits */
318                 ret=timed_thread_join(thread, NULL, NULL);
319                 
320                 if(ret==0) {
321                         delete_thread(thread);
322                         return(TRUE);
323                 } else {
324                         g_warning("Join error: %s", strerror(ret));
325                         return(FALSE);
326                 }
327         } else {
328                 /* timeout in ms milliseconds */
329                 struct timespec timeout;
330                 time_t now;
331                 div_t divvy;
332                 
333                 divvy=div(ms, 1000);
334                 now=time(NULL);
335                 
336                 timeout.tv_sec=now+divvy.quot;
337                 timeout.tv_nsec=divvy.rem*1000;
338                 
339                 ret=timed_thread_join(thread, &timeout, NULL);
340                 if(ret==0) {
341                         delete_thread(thread);
342                         return(TRUE);
343                 } else {
344                         if(ret!=ETIMEDOUT) {
345                                 g_warning("Timed join error: %s",
346                                           strerror(ret));
347                         }
348                         return(FALSE);
349                 }
350         }
351 }
352
353 static void join_all_threads(gpointer key, gpointer value, gpointer user)
354 {
355         ThreadInfo *thread_info=(ThreadInfo *)value;
356         
357 #ifdef THREAD_DEBUG
358         g_message("[%ld]", thread_info->id);
359 #endif
360         timed_thread_join(thread_info, NULL, NULL);
361 }
362
363 static gboolean free_all_threadinfo(gpointer key, gpointer value, gpointer user)
364 {
365         g_free(value);
366         
367         return(TRUE);
368 }
369
370 void mono_thread_init(void)
371 {
372         MonoClass *thread_class;
373         
374         /* Build a System.Threading.Thread object instance to return
375          * for the main line's Thread.CurrentThread property.
376          */
377         thread_class=mono_class_from_name(mono_defaults.corlib, "System.Threading", "Thread");
378         
379         /* I wonder what happens if someone tries to destroy this
380          * object? In theory, I guess the whole program should act as
381          * though exit() were called :-)
382          */
383         main_thread=mono_new_object(thread_class);
384 }
385
386
387 void mono_thread_cleanup(void)
388 {
389         /* join each thread that's still running */
390 #ifdef THREAD_DEBUG
391         g_message("Joining each running thread...");
392 #endif
393         
394         if(threads==NULL) {
395 #ifdef THREAD_DEBUG
396                 g_message("No threads");
397 #endif
398                 return;
399         }
400         
401         g_hash_table_foreach(threads, join_all_threads, NULL);
402
403         g_hash_table_foreach_remove(threads, free_all_threadinfo, NULL);
404         g_hash_table_destroy(threads);
405         threads=NULL;
406 }