2006-07-20 Dick Porter <dick@ximian.com>
[mono.git] / mono / metadata / monitor.c
index d3e3cc758509f23976d3e4ece3729a1ef8138f1c..55dddd39c616f893ead5bfff7bd066eb48cdf842 100644 (file)
@@ -16,8 +16,7 @@
 #include <mono/metadata/threads.h>
 #include <mono/io-layer/io-layer.h>
 #include <mono/metadata/object-internals.h>
-
-#include <mono/os/gc_wrapper.h>
+#include <mono/metadata/gc-internal.h>
 
 /*#define LOCK_DEBUG(a) do { a; } while (0)*/
 #define LOCK_DEBUG(a)
  * an object.
  */
 
+struct _MonoThreadsSync
+{
+       gsize owner;                    /* thread ID */
+       guint32 nest;
+#ifdef HAVE_MOVING_COLLECTOR
+       gint32 hash_code;
+#endif
+       volatile guint32 entry_count;
+       HANDLE entry_sem;
+       GSList *wait_list;
+       void *data;
+};
+
+typedef struct _MonitorArray MonitorArray;
+
+struct _MonitorArray {
+       MonitorArray *next;
+       int num_monitors;
+       MonoThreadsSync monitors [MONO_ZERO_LEN_ARRAY];
+};
+
+#define mono_monitor_allocator_lock() EnterCriticalSection (&monitor_mutex)
+#define mono_monitor_allocator_unlock() LeaveCriticalSection (&monitor_mutex)
+static CRITICAL_SECTION monitor_mutex;
+static MonoThreadsSync *monitor_freelist;
+static MonitorArray *monitor_allocated;
+static int array_size = 16;
+
+void
+mono_monitor_init (void)
+{
+       InitializeCriticalSection (&monitor_mutex);
+}
+void
+mono_monitor_cleanup (void)
+{
+       DeleteCriticalSection (&monitor_mutex);
+}
 
+/* LOCKING: this is called with monitor_mutex held */
 static void 
-mon_finalize (void *o, void *unused)
+mon_finalize (MonoThreadsSync *mon)
 {
-       MonoThreadsSync *mon = o;
-       
        LOCK_DEBUG (g_message (G_GNUC_PRETTY_FUNCTION ": Finalizing sync %p", mon));
 
        if (mon->entry_sem != NULL) {
                CloseHandle (mon->entry_sem);
+               mon->entry_sem = NULL;
        }
        /* If this isn't empty then something is seriously broken - it
         * means a thread is still waiting on the object that owned
         * this lock, but the object has been finalized.
         */
        g_assert (mon->wait_list == NULL);
+
+       mon->entry_count = 0;
+       /* owner and nest are set in mon_new, no need to zero them out */
+
+       mon->data = monitor_freelist;
+       monitor_freelist = mon;
 }
 
+/* LOCKING: this is called with monitor_mutex held */
 static MonoThreadsSync *
-mon_new (guint32 id)
+mon_new (gsize id)
 {
        MonoThreadsSync *new;
-       
-#if HAVE_BOEHM_GC
-       new = GC_MALLOC (sizeof (MonoThreadsSync));
-       GC_REGISTER_FINALIZER (new, mon_finalize, NULL, NULL, NULL);
-#else
-       /* This should be freed when the object that owns it is
-        * deleted
-        */
-       new = g_new0 (MonoThreadsSync, 1);
-#endif
+
+       if (!monitor_freelist) {
+               MonitorArray *marray;
+               int i;
+               /* see if any sync block has been collected */
+               new = NULL;
+               for (marray = monitor_allocated; marray; marray = marray->next) {
+                       for (i = 0; i < marray->num_monitors; ++i) {
+                               if (marray->monitors [i].data == NULL) {
+                                       new = &marray->monitors [i];
+                                       new->data = monitor_freelist;
+                                       monitor_freelist = new;
+                               }
+                       }
+                       /* small perf tweak to avoid scanning all the blocks */
+                       if (new)
+                               break;
+               }
+               /* need to allocate a new array of monitors */
+               if (!monitor_freelist) {
+                       MonitorArray *last;
+                       LOCK_DEBUG (g_message (G_GNUC_PRETTY_FUNCTION ": allocating more monitors: %d", array_size));
+                       marray = g_malloc0 (sizeof (MonoArray) + array_size * sizeof (MonoThreadsSync));
+                       marray->num_monitors = array_size;
+                       array_size *= 2;
+                       /* link into the freelist */
+                       for (i = 0; i < marray->num_monitors - 1; ++i) {
+                               marray->monitors [i].data = &marray->monitors [i + 1];
+                       }
+                       marray->monitors [i].data = NULL; /* the last one */
+                       monitor_freelist = &marray->monitors [0];
+                       /* we happend the marray instead of prepending so that
+                        * the collecting loop above will need to scan smaller arrays first
+                        */
+                       if (!monitor_allocated) {
+                               monitor_allocated = marray;
+                       } else {
+                               last = monitor_allocated;
+                               while (last->next)
+                                       last = last->next;
+                               last->next = marray;
+                       }
+               }
+       }
+
+       new = monitor_freelist;
+       monitor_freelist = new->data;
+
        new->owner = id;
        new->nest = 1;
        
        return new;
 }
 
+/*
+ * Format of the lock word:
+ * thinhash | fathash | data
+ *
+ * thinhash is the lower bit: if set data is the shifted hashcode of the object.
+ * fathash is another bit: if set the hash code is stored in the MonoThreadsSync
+ *   struct pointed to by data
+ * if neither bit is set and data is non-NULL, data is a MonoThreadsSync
+ */
+typedef union {
+       gsize lock_word;
+       MonoThreadsSync *sync;
+} LockWord;
+
+enum {
+       LOCK_WORD_THIN_HASH = 1,
+       LOCK_WORD_FAT_HASH = 1 << 1,
+       LOCK_WORD_BITS_MASK = 0x3,
+       LOCK_WORD_HASH_SHIFT = 2
+};
+
+#define MONO_OBJECT_ALIGNMENT_SHIFT    3
+
+/*
+ * mono_object_hash:
+ * @obj: an object
+ *
+ * Calculate a hash code for @obj that is constant while @obj is alive.
+ */
+int
+mono_object_hash (MonoObject* obj)
+{
+#ifdef HAVE_MOVING_COLLECTOR
+       LockWord lw;
+       unsigned int hash;
+       if (!obj)
+               return 0;
+       lw.sync = obj->synchronisation;
+       if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+               /*g_print ("fast thin hash %d for obj %p store\n", (unsigned int)lw.lock_word >> LOCK_WORD_HASH_SHIFT, obj);*/
+               return (unsigned int)lw.lock_word >> LOCK_WORD_HASH_SHIFT;
+       }
+       if (lw.lock_word & LOCK_WORD_FAT_HASH) {
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               /*g_print ("fast fat hash %d for obj %p store\n", lw.sync->hash_code, obj);*/
+               return lw.sync->hash_code;
+       }
+       /*
+        * while we are inside this function, the GC will keep this object pinned,
+        * since we are in the unmanaged stack. Thanks to this and to the hash
+        * function that depends only on the address, we can ignore the races if
+        * another thread computes the hash at the same time, because it'll end up
+        * with the same value.
+        */
+       hash = (GPOINTER_TO_UINT (obj) >> MONO_OBJECT_ALIGNMENT_SHIFT) * 2654435761u;
+       /* clear the top bits as they can be discarded */
+       hash &= ~(LOCK_WORD_BITS_MASK << 30);
+       /* no hash flags were set, so it must be a MonoThreadsSync pointer if not NULL */
+       if (lw.sync) {
+               lw.sync->hash_code = hash;
+               /*g_print ("storing hash code %d for obj %p in sync %p\n", hash, obj, lw.sync);*/
+               lw.lock_word |= LOCK_WORD_FAT_HASH;
+               /* this is safe since we don't deflate locks */
+               obj->synchronisation = lw.sync;
+       } else {
+               /*g_print ("storing thin hash code %d for obj %p\n", hash, obj);*/
+               lw.lock_word = LOCK_WORD_THIN_HASH | (hash << LOCK_WORD_HASH_SHIFT);
+               if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, lw.sync, NULL) == NULL)
+                       return hash;
+               /*g_print ("failed store\n");*/
+               /* someone set the hash flag or someone inflated the object */
+               lw.sync = obj->synchronisation;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH)
+                       return hash;
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               lw.sync->hash_code = hash;
+               lw.lock_word |= LOCK_WORD_FAT_HASH;
+               /* this is safe since we don't deflate locks */
+               obj->synchronisation = lw.sync;
+       }
+       return hash;
+#else
+/*
+ * Wang's address-based hash function:
+ *   http://www.concentric.net/~Ttwang/tech/addrhash.htm
+ */
+       return (GPOINTER_TO_UINT (obj) >> MONO_OBJECT_ALIGNMENT_SHIFT) * 2654435761u;
+#endif
+}
+
 /* If allow_interruption==TRUE, the method will be interrumped if abort or suspend
  * is requested. In this case it returns -1.
  */ 
@@ -97,7 +270,7 @@ static gint32
 mono_monitor_try_enter_internal (MonoObject *obj, guint32 ms, gboolean allow_interruption)
 {
        MonoThreadsSync *mon;
-       guint32 id = GetCurrentThreadId ();
+       gsize id = GetCurrentThreadId ();
        HANDLE sem;
        guint32 then = 0, now, delta;
        guint32 waitms;
@@ -111,20 +284,84 @@ retry:
 
        /* If the object has never been locked... */
        if (mon == NULL) {
+               mono_monitor_allocator_lock ();
                mon = mon_new (id);
                if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, mon, NULL) == NULL) {
+                       mono_gc_weak_link_add (&mon->data, obj);
+                       mono_monitor_allocator_unlock ();
                        /* Successfully locked */
                        return 1;
                } else {
-                       /* Another thread got in first, so try again.
-                        * GC will take care of the monitor record
-                        */
-#ifndef HAVE_BOEHM_GC
-                       mon_finalize (mon, NULL);
+#ifdef HAVE_MOVING_COLLECTOR
+                       LockWord lw;
+                       lw.sync = obj->synchronisation;
+                       if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+                               MonoThreadsSync *oldlw = lw.sync;
+                               /* move the already calculated hash */
+                               mon->hash_code = lw.lock_word >> LOCK_WORD_HASH_SHIFT;
+                               lw.sync = mon;
+                               lw.lock_word |= LOCK_WORD_FAT_HASH;
+                               if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, lw.sync, oldlw) == oldlw) {
+                                       mono_gc_weak_link_add (&mon->data, obj);
+                                       mono_monitor_allocator_unlock ();
+                                       /* Successfully locked */
+                                       return 1;
+                               } else {
+                                       mon_finalize (mon);
+                                       mono_monitor_allocator_unlock ();
+                                       goto retry;
+                               }
+                       } else if (lw.lock_word & LOCK_WORD_FAT_HASH) {
+                               mon_finalize (mon);
+                               mono_monitor_allocator_unlock ();
+                               /* get the old lock without the fat hash bit */
+                               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+                               mon = lw.sync;
+                       } else {
+                               mon_finalize (mon);
+                               mono_monitor_allocator_unlock ();
+                               mon = obj->synchronisation;
+                       }
+#else
+                       mon_finalize (mon);
+                       mono_monitor_allocator_unlock ();
+                       mon = obj->synchronisation;
 #endif
-                       goto retry;
                }
+       } else {
+#ifdef HAVE_MOVING_COLLECTOR
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+                       MonoThreadsSync *oldlw = lw.sync;
+                       mono_monitor_allocator_lock ();
+                       mon = mon_new (id);
+                       /* move the already calculated hash */
+                       mon->hash_code = lw.lock_word >> LOCK_WORD_HASH_SHIFT;
+                       lw.sync = mon;
+                       lw.lock_word |= LOCK_WORD_FAT_HASH;
+                       if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, lw.sync, oldlw) == oldlw) {
+                               mono_gc_weak_link_add (&mon->data, obj);
+                               mono_monitor_allocator_unlock ();
+                               /* Successfully locked */
+                               return 1;
+                       } else {
+                               mon_finalize (mon);
+                               mono_monitor_allocator_unlock ();
+                               goto retry;
+                       }
+               }
+#endif
+       }
+
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
        }
+#endif
 
        /* If the object is currently locked by this thread... */
        if (mon->owner == id) {
@@ -142,7 +379,7 @@ retry:
                 * should have been left at 1 by the previous unlock
                 * operation
                 */
-               if (InterlockedCompareExchange (&mon->owner, id, 0) == 0) {
+               if (InterlockedCompareExchangePointer ((gpointer *)&mon->owner, (gpointer)id, 0) == 0) {
                        /* Success */
                        g_assert (mon->nest == 1);
                        return 1;
@@ -266,8 +503,18 @@ mono_monitor_exit (MonoObject *obj)
 
        mon = obj->synchronisation;
 
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH)
+                       return;
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
+       }
+#endif
        if (mon == NULL) {
-               mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+               /* No one ever used Enter. Just ignore the Exit request as MS does */
                return;
        }
        if (mon->owner != GetCurrentThreadId ()) {
@@ -330,6 +577,16 @@ ves_icall_System_Threading_Monitor_Monitor_test_owner (MonoObject *obj)
                  ": Testing if %p is owned by thread %d", obj, GetCurrentThreadId()));
 
        mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH)
+                       return FALSE;
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
+       }
+#endif
        if (mon == NULL) {
                return FALSE;
        }
@@ -350,6 +607,16 @@ ves_icall_System_Threading_Monitor_Monitor_test_synchronised (MonoObject *obj)
                  ": (%d) Testing if %p is owned by any thread", GetCurrentThreadId (), obj));
        
        mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH)
+                       return FALSE;
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
+       }
+#endif
        if (mon == NULL) {
                return FALSE;
        }
@@ -375,6 +642,18 @@ ves_icall_System_Threading_Monitor_Monitor_pulse (MonoObject *obj)
                GetCurrentThreadId (), obj));
        
        mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+                       mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+                       return;
+               }
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
+       }
+#endif
        if (mon == NULL) {
                mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
                return;
@@ -406,6 +685,18 @@ ves_icall_System_Threading_Monitor_Monitor_pulse_all (MonoObject *obj)
                  GetCurrentThreadId (), obj));
 
        mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+                       mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+                       return;
+               }
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
+       }
+#endif
        if (mon == NULL) {
                mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
                return;
@@ -444,6 +735,18 @@ ves_icall_System_Threading_Monitor_Monitor_wait (MonoObject *obj, guint32 ms)
                  GetCurrentThreadId (), obj, ms));
        
        mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+       {
+               LockWord lw;
+               lw.sync = mon;
+               if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+                       mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+                       return FALSE;
+               }
+               lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+               mon = lw.sync;
+       }
+#endif
        if (mon == NULL) {
                mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
                return FALSE;