/* * threads.c: Thread support internal calls * * Author: * Dick Porter (dick@ximian.com) * Patrik Torstensson (patrik.torstensson@labs2.com) * * (C) 2001 Ximian, Inc. */ #include #include #include #include #include #include #include #include #if HAVE_BOEHM_GC #include #endif #undef THREAD_DEBUG #undef THREAD_LOCK_DEBUG #undef THREAD_WAIT_DEBUG 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; /* Controls access to the sync field in MonoObjects, to avoid race * conditions when adding sync data to an object for the first time. */ static CRITICAL_SECTION monitor_mutex; /* The array of existing threads that need joining before exit */ static GArray *threads=NULL; /* The MonoObject associated with the main thread */ static MonoObject *main_thread; /* The TLS key that holds the MonoObject assigned to each thread */ static guint32 current_object_key; /* function called at thread start */ static MonoThreadStartCB mono_thread_start_cb = NULL; /* 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(guint32 tid) { guint32 idx; #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": thread ID %d", tid); #endif EnterCriticalSection(&threads_mutex); if(threads==NULL) { threads=g_array_new(FALSE, FALSE, sizeof(guint32)); } /* Make sure there is only one instance in the array. We * store the thread both in the start_wrapper (in the * subthread), and as soon as possible in the parent thread. * This is to minimise the window in which the thread exists * but we haven't recorded it. */ for(idx=0; idxlen; idx++) { if(g_array_index (threads, guint32, idx)==tid) { g_array_remove_index_fast (threads, idx); } } g_array_append_val(threads, tid); LeaveCriticalSection(&threads_mutex); } static void handle_remove(guint32 tid) { guint32 idx; #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": thread ID %d", tid); #endif EnterCriticalSection(&threads_mutex); for(idx=0; idxlen; idx++) { if(g_array_index (threads, guint32, idx)==tid) { g_array_remove_index_fast(threads, idx); } } 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; guint32 tid; #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper"); #endif /* FIXME: GC problem here with recorded object * pointer! * * This is recorded so CurrentThread can return the * Thread object. */ 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); tid=GetCurrentThreadId (); handle_store(tid); mono_profiler_thread_start (tid); if (mono_thread_start_cb) mono_thread_start_cb (&tid); start_func (this); #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper terminating"); #endif mono_profiler_thread_end (tid); handle_remove (tid); return(0); } MonoObject * mono_thread_create (MonoDomain *domain, gpointer func) { MonoClassField *field; MonoObject *thread; HANDLE thread_handle; struct StartInfo *start_info; guint32 tid; thread = mono_object_new (domain, mono_defaults.thread_class); field=mono_class_get_field_from_name(mono_defaults.thread_class, "system_thread_handle"); g_assert (field); 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); #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Started thread ID %d (handle %p)", tid, thread_handle); #endif g_assert (thread_handle); handle_store(tid); *(gpointer *)(((char *)thread) + field->offset) = thread_handle; return thread; } HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this, MonoObject *start) { MonoMulticastDelegate *delegate = (MonoMulticastDelegate*)start; guint32 (*start_func)(void *); struct StartInfo *start_info; HANDLE thread; guint32 tid; #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Trying to start a new thread: this (%p) start (%p)", this, start); #endif start_func = delegate->delegate.method_ptr; if(start_func==NULL) { g_warning(G_GNUC_PRETTY_FUNCTION ": Can't locate start method!"); return(NULL); } else { /* This is freed in start_wrapper */ 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); if(thread==NULL) { g_warning(G_GNUC_PRETTY_FUNCTION ": CreateThread error 0x%x", GetLastError()); return(NULL); } handle_store(tid); #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Started thread ID %d (handle %p)", tid, thread); #endif 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) { #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Launching thread %p", this); #endif ResumeThread(thread); } void ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms) { #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Sleeping for %d ms", ms); #endif 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); } gboolean ves_icall_System_Threading_Thread_Join_internal(MonoObject *this, int ms, HANDLE thread) { gboolean ret; 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) { #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); } void ves_icall_System_Threading_Thread_SlotHash_store(MonoObject *data) { #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Storing key %p", data); #endif /* Object location stored here */ TlsSetValue(slothash_key, data); } MonoObject *ves_icall_System_Threading_Thread_SlotHash_lookup(void) { MonoObject *data; data=TlsGetValue(slothash_key); #ifdef THREAD_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Retrieved key %p", data); #endif 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_debug_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); #endif new->monitor=CreateMutex(NULL, FALSE, NULL); 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; InitializeCriticalSection(&new->waiters_count_lock); 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); } gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj, int ms) { MonoThreadsSync *mon; guint32 ret; #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Trying to lock object %p in thread %d", obj, GetCurrentThreadId()); #endif EnterCriticalSection(&monitor_mutex); mon=obj->synchronisation; if(mon==NULL) { mon=mon_new(); obj->synchronisation=mon; } /* Don't hold the monitor lock while waiting to acquire the * object lock */ LeaveCriticalSection(&monitor_mutex); /* Acquire the mutex */ #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Acquiring monitor mutex %p", mon->monitor); #endif ret=WaitForSingleObject(mon->monitor, ms); if(ret==WAIT_OBJECT_0) { mon->count++; mon->tid=GetCurrentThreadId(); #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": object %p now locked %d times", obj, mon->count); #endif return(TRUE); } return(FALSE); } void ves_icall_System_Threading_Monitor_Monitor_exit(MonoObject *obj) { MonoThreadsSync *mon; #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Unlocking %p in thread %d", obj, GetCurrentThreadId()); #endif /* No need to lock monitor_mutex here because we only adjust * the monitor state if this thread already owns the lock */ mon=obj->synchronisation; if(mon==NULL) { return; } if(mon->tid!=GetCurrentThreadId()) { return; } mon->count--; #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj, mon->count); #endif if(mon->count==0) { mon->tid=0; /* FIXME: check that 0 isnt a valid id */ } #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Releasing mutex %p", mon->monitor); #endif ReleaseMutex(mon->monitor); } gboolean ves_icall_System_Threading_Monitor_Monitor_test_owner(MonoObject *obj) { MonoThreadsSync *mon; gboolean ret=FALSE; #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Testing if %p is owned by thread %d", obj, GetCurrentThreadId()); #endif EnterCriticalSection(&monitor_mutex); mon=obj->synchronisation; if(mon==NULL) { goto finished; } 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; } ret=TRUE; finished: LeaveCriticalSection(&monitor_mutex); return(ret); } gboolean ves_icall_System_Threading_Monitor_Monitor_test_synchronised(MonoObject *obj) { MonoThreadsSync *mon; gboolean ret=FALSE; #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Testing if %p is owned by any thread", obj); #endif EnterCriticalSection(&monitor_mutex); mon=obj->synchronisation; if(mon==NULL) { goto finished; } if(mon->tid==0) { goto finished; } g_assert(mon->count); ret=TRUE; finished: LeaveCriticalSection(&monitor_mutex); return(ret); } void ves_icall_System_Threading_Monitor_Monitor_pulse(MonoObject *obj) { gboolean have_waiters; MonoThreadsSync *mon; #ifdef THREAD_LOCK_DEBUG g_message("Pulsing %p in thread %d", obj, GetCurrentThreadId()); #endif EnterCriticalSection(&monitor_mutex); mon=obj->synchronisation; if(mon==NULL) { LeaveCriticalSection(&monitor_mutex); return; } if(mon->tid!=GetCurrentThreadId()) { LeaveCriticalSection(&monitor_mutex); return; } LeaveCriticalSection(&monitor_mutex); EnterCriticalSection(&mon->waiters_count_lock); have_waiters=(mon->waiters_count>0); LeaveCriticalSection(&mon->waiters_count_lock); if(have_waiters==TRUE) { ReleaseSemaphore(mon->sema, 1, 0); } } void ves_icall_System_Threading_Monitor_Monitor_pulse_all(MonoObject *obj) { gboolean have_waiters=FALSE; MonoThreadsSync *mon; #ifdef THREAD_LOCK_DEBUG g_message("Pulsing all %p", obj); #endif EnterCriticalSection(&monitor_mutex); mon=obj->synchronisation; if(mon==NULL) { LeaveCriticalSection(&monitor_mutex); return; } if(mon->tid!=GetCurrentThreadId()) { LeaveCriticalSection(&monitor_mutex); return; } LeaveCriticalSection(&monitor_mutex); EnterCriticalSection(&mon->waiters_count_lock); if(mon->waiters_count>0) { mon->was_broadcast=TRUE; have_waiters=TRUE; } if(have_waiters==TRUE) { ReleaseSemaphore(mon->sema, mon->waiters_count, 0); LeaveCriticalSection(&mon->waiters_count_lock); WaitForSingleObject(mon->waiters_done, INFINITE); mon->was_broadcast=FALSE; } else { LeaveCriticalSection(&mon->waiters_count_lock); } } gboolean ves_icall_System_Threading_Monitor_Monitor_wait(MonoObject *obj, int ms) { gboolean last_waiter; MonoThreadsSync *mon; guint32 save_count; #ifdef THREAD_LOCK_DEBUG g_message("Trying to wait for %p in thread %d with timeout %dms", obj, GetCurrentThreadId(), ms); #endif EnterCriticalSection(&monitor_mutex); mon=obj->synchronisation; if(mon==NULL) { LeaveCriticalSection(&monitor_mutex); return(FALSE); } if(mon->tid!=GetCurrentThreadId()) { LeaveCriticalSection(&monitor_mutex); return(FALSE); } LeaveCriticalSection(&monitor_mutex); EnterCriticalSection(&mon->waiters_count_lock); mon->waiters_count++; LeaveCriticalSection(&mon->waiters_count_lock); #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": %p locked %d times", obj, mon->count); #endif /* We need to put the lock count back afterwards */ save_count=mon->count; while(mon->count>1) { #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Releasing mutex %p", mon->monitor); #endif ReleaseMutex(mon->monitor); mon->count--; } /* We're releasing this mutex */ mon->count=0; mon->tid=0; #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Signalling monitor mutex %p", mon->monitor); #endif SignalObjectAndWait(mon->monitor, mon->sema, INFINITE, FALSE); EnterCriticalSection(&mon->waiters_count_lock); mon->waiters_count++; last_waiter=mon->was_broadcast && mon->waiters_count==0; LeaveCriticalSection(&mon->waiters_count_lock); if(last_waiter) { #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p", mon->monitor); #endif SignalObjectAndWait(mon->waiters_done, mon->monitor, INFINITE, FALSE); } else { #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p", mon->monitor); #endif WaitForSingleObject(mon->monitor, INFINITE); } /* We've reclaimed this mutex */ mon->count=save_count; mon->tid=GetCurrentThreadId(); /* Lock the mutex the required number of times */ while(save_count>1) { #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p", mon->monitor); #endif WaitForSingleObject(mon->monitor, INFINITE); save_count--; } #ifdef THREAD_LOCK_DEBUG g_message(G_GNUC_PRETTY_FUNCTION ": %p still locked %d times", obj, mon->count); #endif return(TRUE); } /* FIXME: exitContext isnt documented */ gboolean ves_icall_System_Threading_WaitHandle_WaitAll_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext) { HANDLE *handles; guint32 numhandles; guint32 ret; guint32 i; numhandles=mono_array_length(mono_handles); handles=g_new0(HANDLE, numhandles); for(i=0; ilen); for(i=0; ilen; i++) { g_message("Waiting for: %d", g_array_index(threads, guint32, i)); } #endif for(i=0; ilen; i++) { wait[i]=OpenThread (THREAD_ALL_ACCESS, FALSE, g_array_index(threads, guint32, i)); } #ifdef THREAD_DEBUG g_message("%d threads to wait for in this batch", i); #endif LeaveCriticalSection (&threads_mutex); WaitForMultipleObjects(i, wait, TRUE, INFINITE); for(j=0; j0); g_array_free(threads, FALSE); threads=NULL; #endif /* __MINGW32__ */ }