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