2002-04-30 Jeffrey Stedfast <fejj@ximian.com>
[mono.git] / mono / metadata / threads.c
index 22d288756bd16928bbd8f8945ea7e025b73e92c9..d8d99f9d215756064879c6f8611305b5bbc49236 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Author:
  *     Dick Porter (dick@ximian.com)
+ *     Patrik Torstensson (patrik.torstensson@labs2.com)
  *
  * (C) 2001 Ximian, Inc.
  */
 #include <glib.h>
 
 #include <mono/metadata/object.h>
+#include <mono/metadata/appdomain.h>
+#include <mono/metadata/profiler-private.h>
 #include <mono/metadata/threads.h>
 #include <mono/metadata/threads-types.h>
 #include <mono/io-layer/io-layer.h>
 
+#if HAVE_BOEHM_GC
+#include <gc/gc.h>
+#endif
+
 #undef THREAD_DEBUG
 #undef THREAD_LOCK_DEBUG
 #undef THREAD_WAIT_DEBUG
@@ -23,8 +30,15 @@ struct StartInfo
 {
        guint32 (*func)(void *);
        MonoObject *obj;
+       void *this;
+       MonoDomain *domain;
 };
 
+typedef union {
+       gint32 ival;
+       gfloat fval;
+} IntFloatUnion;
 /* Controls access to the 'threads' array */
 static CRITICAL_SECTION threads_mutex;
 
@@ -45,10 +59,57 @@ static guint32 current_object_key;
 /* The TLS key that holds the LocalDataStoreSlot hash in each thread */
 static guint32 slothash_key;
 
+/* Spin lock for InterlockedXXX 64 bit functions */
+static CRITICAL_SECTION interlocked_mutex;
+               
+/* handle_store() and handle_remove() manage the array of threads that
+ * still need to be waited for when the main thread exits.
+ */
+static void handle_store(HANDLE thread)
+{
+#ifdef THREAD_DEBUG
+       g_message(G_GNUC_PRETTY_FUNCTION ": thread %p", thread);
+#endif
+
+       EnterCriticalSection(&threads_mutex);
+       if(threads==NULL) {
+               threads=g_ptr_array_new();
+       }
+       g_ptr_array_add(threads, thread);
+       LeaveCriticalSection(&threads_mutex);
+}
+
+static void handle_remove(HANDLE thread)
+{
+#ifdef THREAD_DEBUG
+       g_message(G_GNUC_PRETTY_FUNCTION ": thread %p", thread);
+#endif
+
+       EnterCriticalSection(&threads_mutex);
+       g_ptr_array_remove_fast(threads, thread);
+       LeaveCriticalSection(&threads_mutex);
+
+       /* Don't close the handle here, wait for the object finalizer
+        * to do it. Otherwise, the following race condition applies:
+        *
+        * 1) Thread exits (and handle_remove() closes the handle)
+        *
+        * 2) Some other handle is reassigned the same slot
+        *
+        * 3) Another thread tries to join the first thread, and
+        * blocks waiting for the reassigned handle to be signalled
+        * (which might never happen).  This is possible, because the
+        * thread calling Join() still has a reference to the first
+        * thread's object.
+        */
+}
+
 static guint32 start_wrapper(void *data)
 {
        struct StartInfo *start_info=(struct StartInfo *)data;
        guint32 (*start_func)(void *);
+       void *this;
+       HANDLE thread;
        
 #ifdef THREAD_DEBUG
        g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper");
@@ -60,51 +121,61 @@ static guint32 start_wrapper(void *data)
         * This is recorded so CurrentThread can return the
         * Thread object.
         */
-       TlsSetValue(current_object_key, start_info->obj);
-       start_func=start_info->func;
-       
-       g_free(start_info);
+       TlsSetValue (current_object_key, start_info->obj);
+       start_func = start_info->func;
+       mono_domain_set (start_info->domain);
+       this = start_info->this;
+       g_free (start_info);
+
+       thread=GetCurrentThread ();
        
-       start_func(NULL);
+       handle_store(thread);
+       mono_profiler_thread_start (thread);
+
+       start_func (this);
 
 #ifdef THREAD_DEBUG
        g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper terminating");
 #endif
+       
+       mono_profiler_thread_end (thread);
+       handle_remove (thread);
 
        return(0);
 }
-               
-static void handle_store(HANDLE thread)
+
+MonoObject *
+mono_thread_create (MonoDomain *domain, gpointer func)
 {
-#ifdef THREAD_DEBUG
-       g_message(G_GNUC_PRETTY_FUNCTION ": thread %p", thread);
-#endif
+       MonoClassField *field;
+       MonoObject *thread;
+       HANDLE thread_handle;
+       struct StartInfo *start_info;
+       guint32 tid;
+       
+       thread = mono_object_new (domain, mono_defaults.thread_class);
 
-       EnterCriticalSection(&threads_mutex);
-       if(threads==NULL) {
-               threads=g_ptr_array_new();
-       }
-       g_ptr_array_add(threads, thread);
-       LeaveCriticalSection(&threads_mutex);
-}
+       field=mono_class_get_field_from_name(mono_defaults.thread_class, "system_thread_handle");
+       g_assert (field);
 
-static void handle_remove(HANDLE thread)
-{
-#ifdef THREAD_DEBUG
-       g_message(G_GNUC_PRETTY_FUNCTION ": thread %p", thread);
-#endif
+       start_info=g_new0 (struct StartInfo, 1);
+       start_info->func = func;
+       start_info->obj = thread;
+       start_info->domain = domain;
+       /* start_info->this needs to be set? */
+               
+       thread_handle = CreateThread(NULL, 0, start_wrapper, start_info, 0, &tid);
+       g_assert (thread_handle);
 
-       EnterCriticalSection(&threads_mutex);
-       g_ptr_array_remove_fast(threads, thread);
-       LeaveCriticalSection(&threads_mutex);
-       CloseHandle(thread);
-}
+       *(gpointer *)(((char *)thread) + field->offset) = thread_handle; 
 
+       return thread;
+}
 
 HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
                                                         MonoObject *start)
 {
-       MonoClassField *field;
+       MonoMulticastDelegate *delegate = (MonoMulticastDelegate*)start;
        guint32 (*start_func)(void *);
        struct StartInfo *start_info;
        HANDLE thread;
@@ -116,8 +187,7 @@ HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
                  this, start);
 #endif
        
-       field=mono_class_get_field_from_name(mono_defaults.delegate_class, "method_ptr");
-       start_func= *(gpointer *)(((char *)start) + field->offset);
+       start_func = delegate->delegate.method_ptr;
        
        if(start_func==NULL) {
                g_warning(G_GNUC_PRETTY_FUNCTION
@@ -125,9 +195,11 @@ HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
                return(NULL);
        } else {
                /* This is freed in start_wrapper */
-               start_info=g_new0(struct StartInfo, 1);
-               start_info->func=start_func;
-               start_info->obj=this;
+               start_info = g_new0 (struct StartInfo, 1);
+               start_info->func = start_func;
+               start_info->this = delegate->delegate.target;
+               start_info->obj = this;
+               start_info->domain = mono_domain_get ();
                
                thread=CreateThread(NULL, 0, start_wrapper, start_info,
                                    CREATE_SUSPENDED, &tid);
@@ -138,17 +210,25 @@ HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
                }
                
 #ifdef THREAD_DEBUG
-               g_message(G_GNUC_PRETTY_FUNCTION ": Started thread ID %d",
-                         tid);
+               g_message(G_GNUC_PRETTY_FUNCTION
+                         ": Started thread ID %d (handle %p)", tid, thread);
 #endif
-
-               /* Store handle for cleanup later */
-               handle_store(thread);
                
                return(thread);
        }
 }
 
+void ves_icall_System_Threading_Thread_Thread_free_internal (MonoObject *this,
+                                                            HANDLE thread)
+{
+#ifdef THREAD_DEBUG
+       g_message (G_GNUC_PRETTY_FUNCTION ": Closing thread %p, handle %p",
+                  this, thread);
+#endif
+
+       CloseHandle (thread);
+}
+
 void ves_icall_System_Threading_Thread_Start_internal(MonoObject *this,
                                                      HANDLE thread)
 {
@@ -168,12 +248,23 @@ void ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms)
        Sleep(ms);
 }
 
+MonoAppDomain *ves_icall_System_Threading_Thread_CurrentThreadDomain_internal(void) 
+{
+       /* return the current app */
+       return mono_domain_get()->domain;
+}
+
 MonoObject *ves_icall_System_Threading_Thread_CurrentThread_internal(void)
 {
        MonoObject *thread;
        
        /* Find the current thread object */
        thread=TlsGetValue(current_object_key);
+
+#ifdef THREAD_DEBUG
+       g_message (G_GNUC_PRETTY_FUNCTION ": returning %p", thread);
+#endif
+
        return(thread);
 }
 
@@ -185,14 +276,24 @@ gboolean ves_icall_System_Threading_Thread_Join_internal(MonoObject *this,
        if(ms== -1) {
                ms=INFINITE;
        }
+#ifdef THREAD_DEBUG
+       g_message (G_GNUC_PRETTY_FUNCTION ": joining thread handle %p, %d ms",
+                  thread, ms);
+#endif
        
        ret=WaitForSingleObject(thread, ms);
        if(ret==WAIT_OBJECT_0) {
-               /* Clean up the handle */
-               handle_remove(thread);
+#ifdef THREAD_DEBUG
+               g_message (G_GNUC_PRETTY_FUNCTION ": join successful");
+#endif
+
                return(TRUE);
        }
        
+#ifdef THREAD_DEBUG
+               g_message (G_GNUC_PRETTY_FUNCTION ": join failed");
+#endif
+
        return(FALSE);
 }
 
@@ -219,20 +320,39 @@ MonoObject *ves_icall_System_Threading_Thread_SlotHash_lookup(void)
        return(data);
 }
 
+static void mon_finalize (void *o, void *unused)
+{
+       MonoThreadsSync *mon=(MonoThreadsSync *)o;
+       
+#ifdef THREAD_LOCK_DEBUG
+       g_message (G_GNUC_PRETTY_FUNCTION ": Finalizing sync");
+#endif
+       
+       CloseHandle (mon->monitor);
+       CloseHandle (mon->sema);
+       CloseHandle (mon->waiters_done);
+}
+
 static MonoThreadsSync *mon_new(void)
 {
        MonoThreadsSync *new;
        
+#if HAVE_BOEHM_GC
+       new=(MonoThreadsSync *)GC_debug_malloc (sizeof(MonoThreadsSync), "sync", 1);
+       GC_register_finalizer (new, mon_finalize, NULL, NULL, NULL);
+#else
        /* This should be freed when the object that owns it is
         * deleted
         */
-
-       new=(MonoThreadsSync *)g_new0(MonoThreadsSync, 1);
-       new->monitor=CreateMutex(NULL, FALSE, NULL);
-#ifdef THREAD_LOCK_DEBUG
-       g_message(G_GNUC_PRETTY_FUNCTION ": ThreadsSync mutex created: %p",
-                 new->monitor);
+       new=(MonoThreadsSync *)g_new0 (MonoThreadsSync, 1);
 #endif
+       
+       new->monitor=CreateMutex(NULL, FALSE, "sync");
+       if(new->monitor==NULL) {
+               /* Throw some sort of system exception? (ditto for the
+                * sem and event handles below)
+                */
+       }
 
        new->waiters_count=0;
        new->was_broadcast=FALSE;
@@ -240,6 +360,12 @@ static MonoThreadsSync *mon_new(void)
        new->sema=CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
        new->waiters_done=CreateEvent(NULL, FALSE, FALSE, NULL);
        
+#ifdef THREAD_LOCK_DEBUG
+       g_message(G_GNUC_PRETTY_FUNCTION
+                 ": ThreadsSync %p mutex created: %p, sem: %p, event: %p",
+                 new, new->monitor, new->sema, new->waiters_done);
+#endif
+       
        return(new);
 }
 
@@ -251,7 +377,7 @@ gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj,
        
 #ifdef THREAD_LOCK_DEBUG
        g_message(G_GNUC_PRETTY_FUNCTION
-                 ": Trying to lock %p in thread %d", obj,
+                 ": Trying to lock object %p in thread %d", obj,
                  GetCurrentThreadId());
 #endif
 
@@ -279,8 +405,8 @@ gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj,
                mon->tid=GetCurrentThreadId();
        
 #ifdef THREAD_LOCK_DEBUG
-       g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj,
-                 mon->count);
+               g_message(G_GNUC_PRETTY_FUNCTION
+                         ": object %p now locked %d times", obj, mon->count);
 #endif
 
                return(TRUE);
@@ -348,6 +474,11 @@ gboolean ves_icall_System_Threading_Monitor_Monitor_test_owner(MonoObject *obj)
        }
 
        if(mon->tid!=GetCurrentThreadId()) {
+#ifdef THREAD_LOCK_DEBUG
+               g_message (G_GNUC_PRETTY_FUNCTION
+                          ": object %p is owned by thread %d", obj, mon->tid);
+#endif
+
                goto finished;
        }
        
@@ -665,20 +796,20 @@ HANDLE ves_icall_System_Threading_Mutex_CreateMutex_internal (MonoBoolean owned,
 void ves_icall_System_Threading_Mutex_ReleaseMutex_internal (HANDLE handle ) { 
        ReleaseMutex(handle);
 }
-\r
-HANDLE ves_icall_System_Threading_Events_CreateEvent_internal (MonoBoolean manual,\r
-                                                                                                                         MonoBoolean initial,\r
-                                                                                                                         char *name) {\r
-       return (CreateEvent(NULL,manual,initial,name));\r
-}\r
-\r
-gboolean ves_icall_System_Threading_Events_SetEvent_internal (HANDLE handle) {\r
-       return (SetEvent(handle));\r
-}\r
-
-gboolean ves_icall_System_Threading_Events_ResetEvent_internal (HANDLE handle) {\r
-       return (ResetEvent(handle));\r
-}\r
+
+HANDLE ves_icall_System_Threading_Events_CreateEvent_internal (MonoBoolean manual,
+                                                                                                                         MonoBoolean initial,
+                                                                                                                         char *name) {
+       return (CreateEvent(NULL,manual,initial,name));
+}
+
+gboolean ves_icall_System_Threading_Events_SetEvent_internal (HANDLE handle) {
+       return (SetEvent(handle));
+}
+
+gboolean ves_icall_System_Threading_Events_ResetEvent_internal (HANDLE handle) {
+       return (ResetEvent(handle));
+}
 
 void mono_thread_init(MonoDomain *domain)
 {
@@ -693,12 +824,26 @@ void mono_thread_init(MonoDomain *domain)
         * object? In theory, I guess the whole program should act as
         * though exit() were called :-)
         */
+#ifdef THREAD_DEBUG
+       g_message(G_GNUC_PRETTY_FUNCTION
+                 ": Starting to build main Thread object");
+#endif
        main_thread = mono_object_new (domain, thread_class);
+#ifdef THREAD_DEBUG
+       g_message(G_GNUC_PRETTY_FUNCTION
+                 ": Finished building main Thread object: %p", main_thread);
+#endif
 
        InitializeCriticalSection(&threads_mutex);
        InitializeCriticalSection(&monitor_mutex);
+       InitializeCriticalSection(&interlocked_mutex);
        
        current_object_key=TlsAlloc();
+#ifdef THREAD_DEBUG
+       g_message (G_GNUC_PRETTY_FUNCTION ": Allocated current_object_key %d",
+                  current_object_key);
+#endif
+
        TlsSetValue(current_object_key, main_thread);
 
        slothash_key=TlsAlloc();
@@ -725,10 +870,15 @@ void mono_thread_cleanup(void)
         *
         * The first method call should be started in its own thread,
         * and then the main thread should poll an event and wait for
-        * any terminated threads, until there are none left.
+        * any terminated threads, until there are none left. (This
+        * loop will break if a subthread creates new threads after
+        * the main thread ends.)
         */
 #ifdef THREAD_DEBUG
        g_message("There are %d threads to join", threads->len);
+       for(i=0; i<threads->len; i++) {
+               g_message("Waiting for: %p", g_ptr_array_index(threads, i));
+       }
 #endif
 
        for(i=0; i<threads->len; i+=MAXIMUM_WAIT_OBJECTS) {
@@ -748,3 +898,90 @@ void mono_thread_cleanup(void)
        g_ptr_array_free(threads, FALSE);
        threads=NULL;
 }
+
+gint32 ves_icall_System_Threading_Interlocked_Increment_Int (gint32 *location)
+{
+       return InterlockedIncrement (location);
+}
+
+gint64 ves_icall_System_Threading_Interlocked_Increment_Long (gint64 *location)
+{
+       gint32 lowret;
+       gint32 highret;
+
+       EnterCriticalSection(&interlocked_mutex);
+
+       lowret = InterlockedIncrement((gint32 *) location);
+       if (0 == lowret)
+               highret = InterlockedIncrement((gint32 *) location + 1);
+       else
+               highret = *((gint32 *) location + 1);
+
+       LeaveCriticalSection(&interlocked_mutex);
+
+       return (gint64) highret << 32 | (gint64) lowret;
+}
+
+gint32 ves_icall_System_Threading_Interlocked_Decrement_Int (gint32 *location)
+{
+       return InterlockedDecrement(location);
+}
+
+gint64 ves_icall_System_Threading_Interlocked_Decrement_Long (gint64 * location)
+{
+       gint32 lowret;
+       gint32 highret;
+
+       EnterCriticalSection(&interlocked_mutex);
+
+       lowret = InterlockedDecrement((gint32 *) location);
+       if (-1 == lowret)
+               highret = InterlockedDecrement((gint32 *) location + 1);
+       else
+               highret = *((gint32 *) location + 1);
+
+       LeaveCriticalSection(&interlocked_mutex);
+
+       return (gint64) highret << 32 | (gint64) lowret;
+}
+
+gint32 ves_icall_System_Threading_Interlocked_Exchange_Int (gint32 *location1, gint32 value)
+{
+       return InterlockedExchange(location1, value);
+}
+
+MonoObject * ves_icall_System_Threading_Interlocked_Exchange_Object (MonoObject **location1, MonoObject *value)
+{
+       return (MonoObject *) InterlockedExchangePointer((gpointer *) location1, value);
+}
+
+gfloat ves_icall_System_Threading_Interlocked_Exchange_Single (gfloat *location1, gfloat value)
+{
+       IntFloatUnion val, ret;
+
+       val.fval = value;
+       ret.ival = InterlockedExchange((gint32 *) location1, val.ival);
+
+       return ret.fval;
+}
+
+gint32 ves_icall_System_Threading_Interlocked_CompareExchange_Int(gint32 *location1, gint32 value, gint32 comparand)
+{
+       return InterlockedCompareExchange(location1, value, comparand);
+}
+
+MonoObject * ves_icall_System_Threading_Interlocked_CompareExchange_Object (MonoObject **location1, MonoObject *value, MonoObject *comparand)
+{
+       return (MonoObject *) InterlockedCompareExchangePointer((gpointer *) location1, value, comparand);
+}
+
+gfloat ves_icall_System_Threading_Interlocked_CompareExchange_Single (gfloat *location1, gfloat value, gfloat comparand)
+{
+       IntFloatUnion val, ret, cmp;
+
+       val.fval = value;
+       cmp.fval = comparand;
+       ret.ival = InterlockedCompareExchange((gint32 *) location1, val.ival, cmp.ival);
+
+       return ret.fval;
+}