Tue Jan 11 15:26:00 CET 2005 Paolo Molaro <lupus@ximian.comt>
[mono.git] / mono / metadata / monitor.c
1 /*
2  * monitor.c:  Monitor locking functions
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2003 Ximian, Inc.
8  */
9
10 #include <config.h>
11 #include <glib.h>
12
13 #include <mono/metadata/monitor.h>
14 #include <mono/metadata/threads-types.h>
15 #include <mono/metadata/exception.h>
16 #include <mono/metadata/threads.h>
17 #include <mono/io-layer/io-layer.h>
18 #include <mono/metadata/object-internals.h>
19
20 #include <mono/os/gc_wrapper.h>
21
22 #undef THREAD_LOCK_DEBUG
23
24 /*
25  * The monitor implementation here is based on
26  * http://www.usenix.org/events/jvm01/full_papers/dice/dice.pdf and
27  * http://www.research.ibm.com/people/d/dfb/papers/Bacon98Thin.ps
28  *
29  * The Dice paper describes a technique for saving lock record space
30  * by returning records to a free list when they become unused.  That
31  * sounds like unnecessary complexity to me, though if it becomes
32  * clear that unused lock records are taking up lots of space or we
33  * need to shave more time off by avoiding a malloc then we can always
34  * implement the free list idea later.  The timeout parameter to
35  * try_enter voids some of the assumptions about the reference count
36  * field in Dice's implementation too.  In his version, the thread
37  * attempting to lock a contended object will block until it succeeds,
38  * so the reference count will never be decremented while an object is
39  * locked.
40  *
41  * Bacon's thin locks have a fast path that doesn't need a lock record
42  * for the common case of locking an unlocked or shallow-nested
43  * object, but the technique relies on encoding the thread ID in 15
44  * bits (to avoid too much per-object space overhead.)  Unfortunately
45  * I don't think it's possible to reliably encode a pthread_t into 15
46  * bits. (The JVM implementation used seems to have a 15-bit
47  * per-thread identifier available.)
48  *
49  * This implementation then combines Dice's basic lock model with
50  * Bacon's simplification of keeping a lock record for the lifetime of
51  * an object.
52  */
53
54
55 static void mon_finalize (void *o, void *unused)
56 {
57         MonoThreadsSync *mon=(MonoThreadsSync *)o;
58         
59 #ifdef THREAD_LOCK_DEBUG
60         g_message (G_GNUC_PRETTY_FUNCTION ": Finalizing sync %p", mon);
61 #endif
62
63         if(mon->entry_sem!=NULL) {
64                 CloseHandle (mon->entry_sem);
65         }
66         /* If this isn't empty then something is seriously broken - it
67          * means a thread is still waiting on the object that owned
68          * this lock, but the object has been finalized.
69          */
70         g_assert (mon->wait_list==NULL);
71 }
72
73 static MonoThreadsSync *mon_new(guint32 id)
74 {
75         MonoThreadsSync *new;
76         
77 #if HAVE_BOEHM_GC
78         new=(MonoThreadsSync *)GC_MALLOC (sizeof(MonoThreadsSync));
79         GC_REGISTER_FINALIZER (new, mon_finalize, NULL, NULL, NULL);
80 #else
81         /* This should be freed when the object that owns it is
82          * deleted
83          */
84         new=(MonoThreadsSync *)g_new0 (MonoThreadsSync, 1);
85 #endif
86         new->owner=id;
87         new->nest=1;
88         
89         return(new);
90 }
91
92 /* If allow_interruption==TRUE, the method will be interrumped if abort or suspend
93  * is requested. In this case it returns -1.
94  */ 
95 static gint32 mono_monitor_try_enter_internal (MonoObject *obj, guint32 ms, gboolean allow_interruption)
96 {
97         MonoThreadsSync *mon;
98         guint32 id=GetCurrentThreadId ();
99         HANDLE sem;
100         guint32 then=0, now, delta;
101         guint32 waitms;
102         guint32 ret;
103         
104 #ifdef THREAD_LOCK_DEBUG
105         g_message(G_GNUC_PRETTY_FUNCTION
106                   ": (%d) Trying to lock object %p (%d ms)", id, obj, ms);
107 #endif
108
109 retry:
110         mon=obj->synchronisation;
111
112         /* If the object has never been locked... */
113         if(mon==NULL) {
114                 mon=mon_new(id);
115                 if(InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, mon, NULL)==NULL) {
116                         /* Successfully locked */
117                         return(1);
118                 } else {
119                         /* Another thread got in first, so try again.
120                          * GC will take care of the monitor record
121                          */
122 #ifndef HAVE_BOEHM_GC
123                         mon_finalize (mon, NULL);
124 #endif
125                         goto retry;
126                 }
127         }
128
129         /* If the object is currently locked by this thread... */
130         if(mon->owner==id) {
131                 mon->nest++;
132                 return(1);
133         }
134
135         /* If the object has previously been locked but isn't now... */
136
137         /* This case differs from Dice's case 3 because we don't
138          * deflate locks or cache unused lock records
139          */
140         if(mon->owner==0) {
141                 /* Try to install our ID in the owner field, nest
142                  * should have been left at 1 by the previous unlock
143                  * operation
144                  */
145                 if(InterlockedCompareExchange (&mon->owner, id, 0)==0) {
146                         /* Success */
147                         g_assert (mon->nest==1);
148                         return(1);
149                 } else {
150                         /* Trumped again! */
151                         goto retry;
152                 }
153         }
154
155         /* The object must be locked by someone else... */
156
157         /* If ms is 0 we don't block, but just fail straight away */
158         if(ms==0) {
159 #ifdef THREAD_LOCK_DEBUG
160                 g_message (G_GNUC_PRETTY_FUNCTION
161                            ": (%d) timed out, returning FALSE", id);
162 #endif
163
164                 return(0);
165         }
166
167         /* The slow path begins here.  We need to make sure theres a
168          * semaphore handle (creating it if necessary), and block on
169          * it
170          */
171         if(mon->entry_sem==NULL) {
172                 /* Create the semaphore */
173                 sem=CreateSemaphore (NULL, 0, 0x7fffffff, NULL);
174                 if(InterlockedCompareExchangePointer ((gpointer*)&mon->entry_sem, sem, NULL)!=NULL) {
175                         /* Someone else just put a handle here */
176                         CloseHandle (sem);
177                 }
178         }
179
180         /* If we need to time out, record a timestamp and adjust ms,
181          * because WaitForSingleObject doesn't tell us how long it
182          * waited for.
183          *
184          * Don't block forever here, because theres a chance the owner
185          * thread released the lock while we were creating the
186          * semaphore: we would not get the wakeup.  Using the event
187          * handle technique from pulse/wait would involve locking the
188          * lock struct and therefore slowing down the fast path.
189          */
190         if(ms!=INFINITE) {
191                 then=GetTickCount ();
192                 if(ms<100) {
193                         waitms=ms;
194                 } else {
195                         waitms=100;
196                 }
197         } else {
198                 waitms=100;
199         }
200         
201         InterlockedIncrement (&mon->entry_count);
202         ret=WaitForSingleObjectEx (mon->entry_sem, waitms, allow_interruption);
203         InterlockedDecrement (&mon->entry_count);
204
205         if(ms!=INFINITE) {
206                 now=GetTickCount ();
207                 
208                 if(now<then) {
209                         /* The counter must have wrapped around */
210 #ifdef THREAD_LOCK_DEBUG
211                         g_message (G_GNUC_PRETTY_FUNCTION
212                                    ": wrapped around! now=0x%x then=0x%x",
213                                    now, then);
214 #endif
215                         
216                         now+=(0xffffffff - then);
217                         then=0;
218
219 #ifdef THREAD_LOCK_DEBUG
220                         g_message (G_GNUC_PRETTY_FUNCTION ": wrap rejig: now=0x%x then=0x%x delta=0x%x", now, then, now-then);
221 #endif
222                 }
223                 
224                 delta=now-then;
225                 if(delta >= ms) {
226                         ms=0;
227                 } else {
228                         ms-=delta;
229                 }
230
231                 if((ret==WAIT_TIMEOUT || (ret==WAIT_IO_COMPLETION && !allow_interruption)) && ms>0) {
232                         /* More time left */
233                         goto retry;
234                 }
235         } else {
236                 if(ret==WAIT_TIMEOUT || (ret==WAIT_IO_COMPLETION && !allow_interruption)) {
237                         /* Infinite wait, so just try again */
238                         goto retry;
239                 }
240         }
241         
242         if(ret==WAIT_OBJECT_0) {
243                 /* retry from the top */
244                 goto retry;
245         }
246         
247         /* We must have timed out */
248 #ifdef THREAD_LOCK_DEBUG
249         g_message (G_GNUC_PRETTY_FUNCTION
250                    ": (%d) timed out waiting, returning FALSE", id);
251 #endif
252
253         if (ret==WAIT_IO_COMPLETION) return(-1);
254         else return(0);
255 }
256
257 gboolean mono_monitor_enter (MonoObject *obj)
258 {
259         return mono_monitor_try_enter_internal (obj, INFINITE, FALSE) == 1;
260 }
261
262 gboolean mono_monitor_try_enter (MonoObject *obj, guint32 ms)
263 {
264         return mono_monitor_try_enter_internal (obj, ms, FALSE) == 1;
265 }
266
267 void mono_monitor_exit (MonoObject *obj)
268 {
269         MonoThreadsSync *mon;
270         guint32 nest;
271         
272 #ifdef THREAD_LOCK_DEBUG
273         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Unlocking %p",
274                   GetCurrentThreadId (), obj);
275 #endif
276
277         mon=obj->synchronisation;
278
279         if(mon==NULL) {
280                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
281                 return;
282         }
283         if(mon->owner!=GetCurrentThreadId ()) {
284                 return;
285         }
286         
287         nest=mon->nest-1;
288         if(nest==0) {
289 #ifdef THREAD_LOCK_DEBUG
290                 g_message(G_GNUC_PRETTY_FUNCTION
291                           ": (%d) Object %p is now unlocked",
292                           GetCurrentThreadId (), obj);
293 #endif
294         
295                 /* object is now unlocked, leave nest==1 so we don't
296                  * need to set it when the lock is reacquired
297                  */
298                 mon->owner=0;
299
300                 /* Do the wakeup stuff.  It's possible that the last
301                  * blocking thread gave up waiting just before we
302                  * release the semaphore resulting in a futile wakeup
303                  * next time there's contention for this object, but
304                  * it means we don't have to waste time locking the
305                  * struct.
306                  */
307                 if(mon->entry_count>0) {
308                         ReleaseSemaphore (mon->entry_sem, 1, NULL);
309                 }
310         } else {
311 #ifdef THREAD_LOCK_DEBUG
312                 g_message(G_GNUC_PRETTY_FUNCTION
313                           ": (%d) Object %p is now locked %d times",
314                           GetCurrentThreadId (), obj,
315                           nest);
316 #endif
317                 mon->nest=nest;
318         }
319 }
320
321 gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj,
322                                                               guint32 ms)
323 {
324         gint32 res;
325         MONO_ARCH_SAVE_REGS;
326
327         do {
328                 res = mono_monitor_try_enter_internal (obj, ms, TRUE);
329                 if (res == -1)
330                         mono_thread_interruption_checkpoint ();
331         }
332         while (res == -1);
333         
334         return(res == 1);
335 }
336
337 void ves_icall_System_Threading_Monitor_Monitor_exit(MonoObject *obj)
338 {
339         MONO_ARCH_SAVE_REGS;
340
341         mono_monitor_exit (obj);
342 }
343
344 gboolean ves_icall_System_Threading_Monitor_Monitor_test_owner(MonoObject *obj)
345 {
346         MonoThreadsSync *mon;
347         
348         MONO_ARCH_SAVE_REGS;
349
350 #ifdef THREAD_LOCK_DEBUG
351         g_message(G_GNUC_PRETTY_FUNCTION
352                   ": Testing if %p is owned by thread %d", obj,
353                   GetCurrentThreadId());
354 #endif
355         
356         mon=obj->synchronisation;
357         if(mon==NULL) {
358                 return(FALSE);
359         }
360         
361         if(mon->owner==GetCurrentThreadId ()) {
362                 return(TRUE);
363         }
364         
365         return(FALSE);
366 }
367
368 gboolean ves_icall_System_Threading_Monitor_Monitor_test_synchronised(MonoObject *obj)
369 {
370         MonoThreadsSync *mon;
371         
372         MONO_ARCH_SAVE_REGS;
373
374 #ifdef THREAD_LOCK_DEBUG
375         g_message(G_GNUC_PRETTY_FUNCTION
376                   ": (%d) Testing if %p is owned by any thread",
377                   GetCurrentThreadId (), obj);
378 #endif
379         
380         mon=obj->synchronisation;
381         if(mon==NULL) {
382                 return(FALSE);
383         }
384         
385         if(mon->owner!=0) {
386                 return(TRUE);
387         }
388         
389         return(FALSE);
390 }
391
392 /* All wait list manipulation in the pulse, pulseall and wait
393  * functions happens while the monitor lock is held, so we don't need
394  * any extra struct locking
395  */
396
397 void ves_icall_System_Threading_Monitor_Monitor_pulse(MonoObject *obj)
398 {
399         MonoThreadsSync *mon;
400         
401         MONO_ARCH_SAVE_REGS;
402
403 #ifdef THREAD_LOCK_DEBUG
404         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Pulsing %p",
405                   GetCurrentThreadId (), obj);
406 #endif
407         
408         mon=obj->synchronisation;
409         if(mon==NULL) {
410                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
411                 return;
412         }
413         if(mon->owner!=GetCurrentThreadId ()) {
414                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked by this thread"));
415                 return;
416         }
417
418 #ifdef THREAD_LOCK_DEBUG
419         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) %d threads waiting",
420                   GetCurrentThreadId (), g_slist_length (mon->wait_list));
421 #endif
422         
423         if(mon->wait_list!=NULL) {
424 #ifdef THREAD_LOCK_DEBUG
425                 g_message(G_GNUC_PRETTY_FUNCTION
426                           ": (%d) signalling and dequeuing handle %p",
427                           GetCurrentThreadId (), mon->wait_list->data);
428 #endif
429         
430                 SetEvent (mon->wait_list->data);
431                 mon->wait_list=g_slist_remove (mon->wait_list,
432                                                mon->wait_list->data);
433         }
434 }
435
436 void ves_icall_System_Threading_Monitor_Monitor_pulse_all(MonoObject *obj)
437 {
438         MonoThreadsSync *mon;
439         
440         MONO_ARCH_SAVE_REGS;
441
442 #ifdef THREAD_LOCK_DEBUG
443         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Pulsing all %p",
444                   GetCurrentThreadId (), obj);
445 #endif
446         
447         mon=obj->synchronisation;
448         if(mon==NULL) {
449                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
450                 return;
451         }
452         if(mon->owner!=GetCurrentThreadId ()) {
453                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked by this thread"));
454                 return;
455         }
456
457 #ifdef THREAD_LOCK_DEBUG
458         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) %d threads waiting",
459                   GetCurrentThreadId (), g_slist_length (mon->wait_list));
460 #endif
461         
462         while(mon->wait_list!=NULL) {
463 #ifdef THREAD_LOCK_DEBUG
464                 g_message(G_GNUC_PRETTY_FUNCTION
465                           ": (%d) signalling and dequeuing handle %p",
466                           GetCurrentThreadId (), mon->wait_list->data);
467 #endif
468         
469                 SetEvent (mon->wait_list->data);
470                 mon->wait_list=g_slist_remove (mon->wait_list,
471                                                mon->wait_list->data);
472         }
473 }
474
475 gboolean ves_icall_System_Threading_Monitor_Monitor_wait(MonoObject *obj,
476                                                          guint32 ms)
477 {
478         MonoThreadsSync *mon;
479         HANDLE event;
480         guint32 nest;
481         guint32 ret;
482         gboolean success=FALSE;
483         gint32 regain;
484         MonoThread *thread = mono_thread_current ();
485         
486         MONO_ARCH_SAVE_REGS;
487
488 #ifdef THREAD_LOCK_DEBUG
489         g_message(G_GNUC_PRETTY_FUNCTION
490                   ": (%d) Trying to wait for %p with timeout %dms",
491                   GetCurrentThreadId (), obj, ms);
492 #endif
493         
494         mon=obj->synchronisation;
495         if(mon==NULL) {
496                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
497                 return(FALSE);
498         }
499         if(mon->owner!=GetCurrentThreadId ()) {
500                 mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked by this thread"));
501                 return(FALSE);
502         }
503         
504         event=CreateEvent (NULL, FALSE, FALSE, NULL);
505         if(event==NULL) {
506                 mono_raise_exception (mono_get_exception_synchronization_lock ("Failed to set up wait event"));
507                 return(FALSE);
508         }
509         
510 #ifdef THREAD_LOCK_DEBUG
511         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) queuing handle %p",
512                   GetCurrentThreadId (), event);
513 #endif
514
515         mono_monitor_enter (thread->synch_lock);
516         thread->state |= ThreadState_WaitSleepJoin;
517         mono_monitor_exit (thread->synch_lock);
518
519         mon->wait_list=g_slist_append (mon->wait_list, event);
520         
521         /* Save the nest count, and release the lock */
522         nest=mon->nest;
523         mon->nest=1;
524         mono_monitor_exit (obj);
525
526 #ifdef THREAD_LOCK_DEBUG
527         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Unlocked %p lock %p",
528                   GetCurrentThreadId (), obj, mon);
529 #endif
530
531         /* There's no race between unlocking mon and waiting for the
532          * event, because auto reset events are sticky, and this event
533          * is private to this thread.  Therefore even if the event was
534          * signalled before we wait, we still succeed.
535          */
536         ret=WaitForSingleObjectEx (event, ms, TRUE);
537
538         /* Reset the thread state fairly early, so we don't have to worry
539          * about the monitor error checking
540          */
541         mono_monitor_enter (thread->synch_lock);
542         thread->state &= ~ThreadState_WaitSleepJoin;
543         mono_monitor_exit (thread->synch_lock);
544         
545         if (mono_thread_interruption_requested ()) {
546                 CloseHandle (event);
547                 return(FALSE);
548         }
549
550         /* Regain the lock with the previous nest count */
551         do {
552                 regain=mono_monitor_try_enter_internal (obj, INFINITE, TRUE);
553                 if (regain == -1) 
554                         mono_thread_interruption_checkpoint ();
555         }
556         while (regain == -1);
557
558         if(regain==0) {
559                 /* Something went wrong, so throw a
560                  * SynchronizationLockException
561                  */
562                 CloseHandle (event);
563                 mono_raise_exception (mono_get_exception_synchronization_lock ("Failed to regain lock"));
564                 return(FALSE);
565         }
566
567         mon->nest=nest;
568
569 #ifdef THREAD_LOCK_DEBUG
570         g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Regained %p lock %p",
571                   GetCurrentThreadId (), obj, mon);
572 #endif
573
574         if(ret==WAIT_TIMEOUT) {
575                 /* Poll the event again, just in case it was signalled
576                  * while we were trying to regain the monitor lock
577                  */
578                 ret=WaitForSingleObjectEx (event, 0, FALSE);
579         }
580
581         /* Pulse will have popped our event from the queue if it signalled
582          * us, so we only do it here if the wait timed out.
583          *
584          * This avoids a race condition where the thread holding the
585          * lock can Pulse several times before the WaitForSingleObject
586          * returns.  If we popped the queue here then this event might
587          * be signalled more than once, thereby starving another
588          * thread.
589          */
590         
591         if(ret==WAIT_OBJECT_0) {
592 #ifdef THREAD_LOCK_DEBUG
593                 g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Success",
594                           GetCurrentThreadId ());
595 #endif
596                 success=TRUE;
597         } else {
598 #ifdef THREAD_LOCK_DEBUG
599                 g_message(G_GNUC_PRETTY_FUNCTION ": (%d) Wait failed",
600                           GetCurrentThreadId ());
601                 g_message(G_GNUC_PRETTY_FUNCTION ": (%d) dequeuing handle %p",
602                           GetCurrentThreadId (), event);
603 #endif
604                 /* No pulse, so we have to remove ourself from the
605                  * wait queue
606                  */
607                 mon->wait_list=g_slist_remove (mon->wait_list, event);
608         }
609         CloseHandle (event);
610         
611         return(success);
612 }
613