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