2002-01-10 Dick Porter <dick@ximian.com>
[mono.git] / mono / metadata / threads.c
1 /*
2  * threads.c: Thread support internal calls
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
13 #include <mono/metadata/object.h>
14 #include <mono/metadata/threads.h>
15 #include <mono/metadata/threads-types.h>
16 #include <mono/io-layer/io-layer.h>
17
18 #undef THREAD_DEBUG
19 #undef THREAD_LOCK_DEBUG
20 #undef THREAD_WAIT_DEBUG
21
22 struct StartInfo 
23 {
24         guint32 (*func)(void *);
25         MonoObject *obj;
26 };
27
28 /* Controls access to the 'threads' array */
29 static CRITICAL_SECTION threads_mutex;
30
31 /* Controls access to the sync field in MonoObjects, to avoid race
32  * conditions when adding sync data to an object for the first time.
33  */
34 static CRITICAL_SECTION monitor_mutex;
35
36 /* The array of existing threads that need joining before exit */
37 static GPtrArray *threads=NULL;
38
39 /* The MonoObject associated with the main thread */
40 static MonoObject *main_thread;
41
42 /* The TLS key that holds the MonoObject assigned to each thread */
43 static guint32 current_object_key;
44
45 /* The TLS key that holds the LocalDataStoreSlot hash in each thread */
46 static guint32 slothash_key;
47
48 static guint32 start_wrapper(void *data)
49 {
50         struct StartInfo *start_info=(struct StartInfo *)data;
51         guint32 (*start_func)(void *);
52         
53 #ifdef THREAD_DEBUG
54         g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper");
55 #endif
56
57         /* FIXME: GC problem here with recorded object
58          * pointer!
59          *
60          * This is recorded so CurrentThread can return the
61          * Thread object.
62          */
63         TlsSetValue(current_object_key, start_info->obj);
64         start_func=start_info->func;
65         
66         g_free(start_info);
67         
68         start_func(NULL);
69
70 #ifdef THREAD_DEBUG
71         g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper terminating");
72 #endif
73
74         return(0);
75 }
76                 
77 static void handle_store(HANDLE thread)
78 {
79         EnterCriticalSection(&threads_mutex);
80         if(threads==NULL) {
81                 threads=g_ptr_array_new();
82         }
83         g_ptr_array_add(threads, thread);
84         LeaveCriticalSection(&threads_mutex);
85 }
86
87 static void handle_remove(HANDLE thread)
88 {
89         EnterCriticalSection(&threads_mutex);
90         g_ptr_array_remove_fast(threads, thread);
91         LeaveCriticalSection(&threads_mutex);
92         CloseHandle(thread);
93 }
94
95
96 HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
97                                                          MonoObject *start)
98 {
99         MonoClassField *field;
100         guint32 (*start_func)(void *);
101         struct StartInfo *start_info;
102         HANDLE thread;
103         guint32 tid;
104         
105 #ifdef THREAD_DEBUG
106         g_message(G_GNUC_PRETTY_FUNCTION
107                   ": Trying to start a new thread: this (%p) start (%p)",
108                   this, start);
109 #endif
110         
111         field=mono_class_get_field_from_name(mono_defaults.delegate_class, "method_ptr");
112         start_func= *(gpointer *)(((char *)start) + field->offset);
113         
114         if(start_func==NULL) {
115                 g_warning(G_GNUC_PRETTY_FUNCTION
116                           ": Can't locate start method!");
117                 return(NULL);
118         } else {
119                 /* This is freed in start_wrapper */
120                 start_info=g_new0(struct StartInfo, 1);
121                 start_info->func=start_func;
122                 start_info->obj=this;
123                 
124                 thread=CreateThread(NULL, 0, start_wrapper, start_info,
125                                     CREATE_SUSPENDED, &tid);
126                 if(thread==NULL) {
127                         g_warning(G_GNUC_PRETTY_FUNCTION
128                                   ": CreateThread error 0x%x", GetLastError());
129                         return(NULL);
130                 }
131                 
132 #ifdef THREAD_DEBUG
133                 g_message(G_GNUC_PRETTY_FUNCTION ": Started thread ID %d",
134                           tid);
135 #endif
136
137                 /* Store handle for cleanup later */
138                 handle_store(thread);
139                 
140                 return(thread);
141         }
142 }
143
144 void ves_icall_System_Threading_Thread_Start_internal(MonoObject *this,
145                                                       HANDLE thread)
146 {
147 #ifdef THREAD_DEBUG
148         g_message(G_GNUC_PRETTY_FUNCTION ": Launching thread %p", this);
149 #endif
150
151         ResumeThread(thread);
152 }
153
154 void ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms)
155 {
156 #ifdef THREAD_DEBUG
157         g_message(G_GNUC_PRETTY_FUNCTION ": Sleeping for %d ms", ms);
158 #endif
159
160         Sleep(ms);
161 }
162
163 MonoObject *ves_icall_System_Threading_Thread_CurrentThread_internal(void)
164 {
165         MonoObject *thread;
166         
167         /* Find the current thread object */
168         thread=TlsGetValue(current_object_key);
169         return(thread);
170 }
171
172 gboolean ves_icall_System_Threading_Thread_Join_internal(MonoObject *this,
173                                                          int ms, HANDLE thread)
174 {
175         gboolean ret;
176         
177         /* FIXME: make sure .net's idea of INFINITE is the same as in C */
178         ret=WaitForSingleObject(thread, ms);
179         if(ret==WAIT_OBJECT_0) {
180                 /* Clean up the handle */
181                 handle_remove(thread);
182                 return(TRUE);
183         }
184         
185         return(FALSE);
186 }
187
188 void ves_icall_System_Threading_Thread_SlotHash_store(MonoObject *data)
189 {
190 #ifdef THREAD_DEBUG
191         g_message(G_GNUC_PRETTY_FUNCTION ": Storing key %p", data);
192 #endif
193
194         /* Object location stored here */
195         TlsSetValue(slothash_key, data);
196 }
197
198 MonoObject *ves_icall_System_Threading_Thread_SlotHash_lookup(void)
199 {
200         MonoObject *data;
201
202         data=TlsGetValue(slothash_key);
203         
204 #ifdef THREAD_DEBUG
205         g_message(G_GNUC_PRETTY_FUNCTION ": Retrieved key %p", data);
206 #endif
207         
208         return(data);
209 }
210
211 static MonoThreadsSync *mon_new(void)
212 {
213         MonoThreadsSync *new;
214         
215         /* This should be freed when the object that owns it is
216          * deleted
217          */
218
219         new=(MonoThreadsSync *)g_new0(MonoThreadsSync, 1);
220         new->monitor=CreateMutex(NULL, FALSE, NULL);
221
222         new->waiters_count=0;
223         new->was_broadcast=FALSE;
224         InitializeCriticalSection(&new->waiters_count_lock);
225         new->sema=CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
226         new->waiters_done=CreateEvent(NULL, FALSE, FALSE, NULL);
227         
228         return(new);
229 }
230
231 gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj,
232                                                               int ms)
233 {
234         MonoThreadsSync *mon;
235         guint32 ret;
236         
237 #ifdef THREAD_LOCK_DEBUG
238         g_message(G_GNUC_PRETTY_FUNCTION
239                   ": Trying to lock %p in thread %d", obj,
240                   GetCurrentThreadId());
241 #endif
242
243         EnterCriticalSection(&monitor_mutex);
244
245         mon=obj->synchronisation;
246         if(mon==NULL) {
247                 mon=mon_new();
248                 obj->synchronisation=mon;
249         }
250         
251         /* Don't hold the monitor lock while waiting to acquire the
252          * object lock
253          */
254         LeaveCriticalSection(&monitor_mutex);
255         
256         /* Acquire the mutex */
257         ret=WaitForSingleObject(mon->monitor, ms);
258         if(ret==WAIT_OBJECT_0) {
259                 mon->count++;
260                 mon->tid=GetCurrentThreadId();
261         
262 #ifdef THREAD_LOCK_DEBUG
263         g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj,
264                   mon->count);
265 #endif
266
267                 return(TRUE);
268         }
269
270         return(FALSE);
271 }
272
273 void ves_icall_System_Threading_Monitor_Monitor_exit(MonoObject *obj)
274 {
275         MonoThreadsSync *mon;
276         
277 #ifdef THREAD_LOCK_DEBUG
278         g_message(G_GNUC_PRETTY_FUNCTION ": Unlocking %p in thread %d", obj,
279                   GetCurrentThreadId());
280 #endif
281
282         /* No need to lock monitor_mutex here because we only adjust
283          * the monitor state if this thread already owns the lock
284          */
285         mon=obj->synchronisation;
286
287         if(mon==NULL) {
288                 return;
289         }
290
291         if(mon->tid!=GetCurrentThreadId()) {
292                 return;
293         }
294         
295         mon->count--;
296         
297 #ifdef THREAD_LOCK_DEBUG
298         g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj,
299                   mon->count);
300 #endif
301
302         if(mon->count==0) {
303                 mon->tid=0;     /* FIXME: check that 0 isnt a valid id */
304         }
305         
306         ReleaseMutex(mon->monitor);
307 }
308
309 gboolean ves_icall_System_Threading_Monitor_Monitor_test_owner(MonoObject *obj)
310 {
311         MonoThreadsSync *mon;
312         gboolean ret=FALSE;
313         
314 #ifdef THREAD_LOCK_DEBUG
315         g_message(G_GNUC_PRETTY_FUNCTION
316                   ": Testing if %p is owned by thread %d", obj,
317                   GetCurrentThreadId());
318 #endif
319
320         EnterCriticalSection(&monitor_mutex);
321         
322         mon=obj->synchronisation;
323         if(mon==NULL) {
324                 goto finished;
325         }
326
327         if(mon->tid!=GetCurrentThreadId()) {
328                 goto finished;
329         }
330         
331         ret=TRUE;
332         
333 finished:
334         LeaveCriticalSection(&monitor_mutex);
335
336         return(ret);
337 }
338
339 gboolean ves_icall_System_Threading_Monitor_Monitor_test_synchronised(MonoObject *obj)
340 {
341         MonoThreadsSync *mon;
342         gboolean ret=FALSE;
343         
344 #ifdef THREAD_LOCK_DEBUG
345         g_message(G_GNUC_PRETTY_FUNCTION
346                   ": Testing if %p is owned by any thread", obj);
347 #endif
348
349         EnterCriticalSection(&monitor_mutex);
350         
351         mon=obj->synchronisation;
352         if(mon==NULL) {
353                 goto finished;
354         }
355
356         if(mon->tid==0) {
357                 goto finished;
358         }
359         
360         g_assert(mon->count);
361         
362         ret=TRUE;
363         
364 finished:
365         LeaveCriticalSection(&monitor_mutex);
366
367         return(ret);
368 }
369
370         
371 void ves_icall_System_Threading_Monitor_Monitor_pulse(MonoObject *obj)
372 {
373         gboolean have_waiters;
374         MonoThreadsSync *mon;
375         
376 #ifdef THREAD_LOCK_DEBUG
377         g_message("Pulsing %p in thread %d", obj, GetCurrentThreadId());
378 #endif
379
380         EnterCriticalSection(&monitor_mutex);
381         
382         mon=obj->synchronisation;
383         if(mon==NULL) {
384                 LeaveCriticalSection(&monitor_mutex);
385                 return;
386         }
387
388         if(mon->tid!=GetCurrentThreadId()) {
389                 LeaveCriticalSection(&monitor_mutex);
390                 return;
391         }
392         LeaveCriticalSection(&monitor_mutex);
393         
394         EnterCriticalSection(&mon->waiters_count_lock);
395         have_waiters=(mon->waiters_count>0);
396         LeaveCriticalSection(&mon->waiters_count_lock);
397         
398         if(have_waiters==TRUE) {
399                 ReleaseSemaphore(mon->sema, 1, 0);
400         }
401 }
402
403 void ves_icall_System_Threading_Monitor_Monitor_pulse_all(MonoObject *obj)
404 {
405         gboolean have_waiters=FALSE;
406         MonoThreadsSync *mon;
407         
408 #ifdef THREAD_LOCK_DEBUG
409         g_message("Pulsing all %p", obj);
410 #endif
411
412         EnterCriticalSection(&monitor_mutex);
413         
414         mon=obj->synchronisation;
415         if(mon==NULL) {
416                 LeaveCriticalSection(&monitor_mutex);
417                 return;
418         }
419
420         if(mon->tid!=GetCurrentThreadId()) {
421                 LeaveCriticalSection(&monitor_mutex);
422                 return;
423         }
424         LeaveCriticalSection(&monitor_mutex);
425         
426         EnterCriticalSection(&mon->waiters_count_lock);
427         if(mon->waiters_count>0) {
428                 mon->was_broadcast=TRUE;
429                 have_waiters=TRUE;
430         }
431         
432         if(have_waiters==TRUE) {
433                 ReleaseSemaphore(mon->sema, mon->waiters_count, 0);
434                 
435                 LeaveCriticalSection(&mon->waiters_count_lock);
436                 
437                 WaitForSingleObject(mon->waiters_done, INFINITE);
438                 mon->was_broadcast=FALSE;
439         } else {
440                 LeaveCriticalSection(&mon->waiters_count_lock);
441         }
442 }
443
444 gboolean ves_icall_System_Threading_Monitor_Monitor_wait(MonoObject *obj,
445                                                          int ms)
446 {
447         gboolean last_waiter;
448         MonoThreadsSync *mon;
449         guint32 save_count;
450         
451 #ifdef THREAD_LOCK_DEBUG
452         g_message("Trying to wait for %p in thread %d with timeout %dms", obj,
453                   GetCurrentThreadId(), ms);
454 #endif
455
456         EnterCriticalSection(&monitor_mutex);
457         
458         mon=obj->synchronisation;
459         if(mon==NULL) {
460                 LeaveCriticalSection(&monitor_mutex);
461                 return(FALSE);
462         }
463
464         if(mon->tid!=GetCurrentThreadId()) {
465                 LeaveCriticalSection(&monitor_mutex);
466                 return(FALSE);
467         }
468         LeaveCriticalSection(&monitor_mutex);
469         
470         EnterCriticalSection(&mon->waiters_count_lock);
471         mon->waiters_count++;
472         LeaveCriticalSection(&mon->waiters_count_lock);
473         
474 #ifdef THREAD_LOCK_DEBUG
475         g_message(G_GNUC_PRETTY_FUNCTION ": %p locked %d times", obj,
476                   mon->count);
477 #endif
478
479         /* We need to put the lock count back afterwards */
480         save_count=mon->count;
481         
482         while(mon->count>1) {
483                 ReleaseMutex(mon->monitor);
484                 mon->count--;
485         }
486         
487         /* We're releasing this mutex */
488         mon->count=0;
489         mon->tid=0;
490         SignalObjectAndWait(mon->monitor, mon->sema, INFINITE, FALSE);
491         
492         EnterCriticalSection(&mon->waiters_count_lock);
493         mon->waiters_count++;
494         last_waiter=mon->was_broadcast && mon->waiters_count==0;
495         LeaveCriticalSection(&mon->waiters_count_lock);
496         
497         if(last_waiter) {
498                 SignalObjectAndWait(mon->waiters_done, mon->monitor, INFINITE, FALSE);
499         } else {
500                 WaitForSingleObject(mon->monitor, INFINITE);
501         }
502
503         /* We've reclaimed this mutex */
504         mon->count=save_count;
505         mon->tid=GetCurrentThreadId();
506
507         /* Lock the mutex the required number of times */
508         while(save_count>1) {
509                 WaitForSingleObject(mon->monitor, INFINITE);
510                 save_count--;
511         }
512         
513 #ifdef THREAD_LOCK_DEBUG
514         g_message(G_GNUC_PRETTY_FUNCTION ": %p still locked %d times", obj,
515                   mon->count);
516 #endif
517         
518         return(TRUE);
519 }
520
521 /* FIXME: exitContext isnt documented */
522 gboolean ves_icall_System_Threading_WaitHandle_WaitAll_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext)
523 {
524         WapiHandle **handles;
525         guint32 numhandles;
526         guint32 ret;
527         guint32 i;
528         
529         numhandles=mono_array_length(mono_handles);
530         handles=g_new0(WapiHandle *, numhandles);
531         for(i=0; i<numhandles; i++) {
532                 handles[i]=mono_array_get(mono_handles, WapiHandle *, i);
533         }
534         
535         ret=WaitForMultipleObjects(numhandles, handles, TRUE, ms);
536         if(ret==WAIT_FAILED) {
537 #ifdef THREAD_WAIT_DEBUG
538                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait failed");
539 #endif
540                 return(FALSE);
541         } else if(ret==WAIT_TIMEOUT) {
542 #ifdef THREAD_WAIT_DEBUG
543                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait timed out");
544 #endif
545                 return(FALSE);
546         }
547         
548         return(TRUE);
549 }
550
551 /* FIXME: exitContext isnt documented */
552 gint32 ves_icall_System_Threading_WaitHandle_WaitAny_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext)
553 {
554         WapiHandle **handles;
555         guint32 numhandles;
556         guint32 ret;
557         guint32 i;
558         
559         numhandles=mono_array_length(mono_handles);
560         handles=g_new0(WapiHandle *, numhandles);
561         for(i=0; i<numhandles; i++) {
562                 handles[i]=mono_array_get(mono_handles, WapiHandle *, i);
563         }
564         
565         ret=WaitForMultipleObjects(numhandles, handles, FALSE, ms);
566
567 #ifdef THREAD_WAIT_DEBUG
568                 g_message(G_GNUC_PRETTY_FUNCTION ": returning %d", ret);
569 #endif
570
571         return(ret);
572 }
573
574 /* FIXME: exitContext isnt documented */
575 gboolean ves_icall_System_Threading_WaitHandle_WaitOne_internal(MonoObject *this, WapiHandle *handle, gint32 ms, gboolean exitContext)
576 {
577         guint32 ret;
578         
579         ret=WaitForSingleObject(handle, ms);
580         if(ret==WAIT_FAILED) {
581 #ifdef THREAD_WAIT_DEBUG
582                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait failed");
583 #endif
584                 return(FALSE);
585         } else if(ret==WAIT_TIMEOUT) {
586 #ifdef THREAD_WAIT_DEBUG
587                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait timed out");
588 #endif
589                 return(FALSE);
590         }
591         
592         return(TRUE);
593 }
594
595
596 void mono_thread_init(void)
597 {
598         MonoClass *thread_class;
599         
600         /* Build a System.Threading.Thread object instance to return
601          * for the main line's Thread.CurrentThread property.
602          */
603         thread_class=mono_class_from_name(mono_defaults.corlib, "System.Threading", "Thread");
604         
605         /* I wonder what happens if someone tries to destroy this
606          * object? In theory, I guess the whole program should act as
607          * though exit() were called :-)
608          */
609         main_thread = mono_object_new (thread_class);
610
611         InitializeCriticalSection(&threads_mutex);
612         InitializeCriticalSection(&monitor_mutex);
613         
614         current_object_key=TlsAlloc();
615         TlsSetValue(current_object_key, main_thread);
616
617         slothash_key=TlsAlloc();
618 }
619
620 void mono_thread_cleanup(void)
621 {
622         HANDLE wait[MAXIMUM_WAIT_OBJECTS];
623         guint32 i, j;
624         
625         /* join each thread that's still running */
626 #ifdef THREAD_DEBUG
627         g_message("Joining each running thread...");
628 #endif
629         
630         if(threads==NULL) {
631 #ifdef THREAD_DEBUG
632                 g_message("No threads");
633 #endif
634                 return;
635         }
636         
637         /* This isnt the right way to do it.
638          *
639          * The first method call should be started in its own thread,
640          * and then the main thread should poll an event and wait for
641          * any terminated threads, until there are none left.
642          */
643         for(i=0; i<threads->len; i++) {
644                 for(j=0; j<MAXIMUM_WAIT_OBJECTS && i+j<threads->len; j++) {
645                         wait[j]=g_ptr_array_index(threads, i);
646                 }
647                 WaitForMultipleObjects(j, wait, TRUE, INFINITE);
648         }
649         
650         g_ptr_array_free(threads, FALSE);
651         threads=NULL;
652 }