[threads] Fix async call in coop (#4423)
[mono.git] / mono / utils / mono-threads.c
index e344ec1ed1f11728d88fe90db446891adaaae43a..76821d0d359980fb78176606d20a210f61b48b0f 100644 (file)
@@ -31,8 +31,7 @@
 #include <mono/utils/mono-threads-coop.h>
 #include <mono/utils/mono-threads-debug.h>
 #include <mono/utils/os-event.h>
-
-#include <mono/io-layer/io-layer.h>
+#include <mono/utils/w32api.h>
 
 #include <errno.h>
 
@@ -66,7 +65,7 @@ static MonoThreadInfoCallbacks threads_callbacks;
 static MonoThreadInfoRuntimeCallbacks runtime_callbacks;
 static MonoNativeTlsKey thread_info_key, thread_exited_key;
 #ifdef HAVE_KW_THREAD
-static __thread guint32 tls_small_id MONO_TLS_FAST;
+static __thread guint32 tls_small_id;
 #else
 static MonoNativeTlsKey small_id_key;
 #endif
@@ -76,6 +75,8 @@ static gboolean mono_threads_inited = FALSE;
 static MonoSemType suspend_semaphore;
 static size_t pending_suspends;
 
+static mono_mutex_t join_mutex;
+
 #define mono_thread_info_run_state(info) (((MonoThreadInfo*)info)->thread_state & THREAD_STATE_MASK)
 
 /*warn at 50 ms*/
@@ -426,6 +427,9 @@ unregister_thread (void *arg)
        g_assert (mono_thread_info_is_current (info));
        g_assert (mono_thread_info_is_live (info));
 
+       /* Pump the HP queue while the thread is alive.*/
+       mono_thread_hazardous_try_free_some ();
+
        small_id = info->small_id;
 
        /* We only enter the GC unsafe region, as when exiting this function, the thread
@@ -481,8 +485,6 @@ unregister_thread (void *arg)
 
        /*now it's safe to free the thread info.*/
        mono_thread_hazardous_try_free (info, free_thread_info);
-       /* Pump the HP queue */
-       mono_thread_hazardous_try_free_some ();
 
        mono_thread_small_id_free (small_id);
 
@@ -706,11 +708,13 @@ mono_threads_init (MonoThreadInfoCallbacks *callbacks, size_t info_size)
 
        mono_os_sem_init (&global_suspend_semaphore, 1);
        mono_os_sem_init (&suspend_semaphore, 0);
+       mono_os_mutex_init (&join_mutex);
 
        mono_lls_init (&thread_list, NULL);
        mono_thread_smr_init ();
        mono_threads_suspend_init ();
        mono_threads_coop_init ();
+       mono_threads_platform_init ();
 
 #if defined(__MACH__)
        mono_mach_init (thread_info_key);
@@ -831,6 +835,9 @@ is_thread_in_critical_region (MonoThreadInfo *info)
        gpointer stack_start;
        MonoThreadUnwindState *state;
 
+       if (mono_threads_platform_in_critical_region (mono_thread_info_get_tid (info)))
+               return TRUE;
+
        /* Are we inside a system critical region? */
        if (info->inside_critical_region)
                return TRUE;
@@ -998,8 +1005,12 @@ currently used only to deliver exceptions.
 void
 mono_thread_info_setup_async_call (MonoThreadInfo *info, void (*target_func)(void*), void *user_data)
 {
-       /* An async call can only be setup on an async suspended thread */
-       g_assert (mono_thread_info_run_state (info) == STATE_ASYNC_SUSPENDED);
+       if (!mono_threads_is_coop_enabled ()) {
+               /* In non-coop mode, an async call can only be setup on an async suspended thread, but in coop mode, a thread
+                * may be in blocking state, and will execute the async call when leaving the safepoint, leaving a gc safe
+                * region or entering a gc unsafe region */
+               g_assert (mono_thread_info_run_state (info) == STATE_ASYNC_SUSPENDED);
+       }
        /*FIXME this is a bad assert, we probably should do proper locking and fail if one is already set*/
        g_assert (!info->async_target);
        info->async_target = target_func;
@@ -1227,6 +1238,7 @@ mono_thread_info_yield (void)
 {
        return mono_threads_platform_yield ();
 }
+
 static mono_lazy_init_t sleep_init = MONO_LAZY_INIT_STATUS_NOT_INITIALIZED;
 static MonoCoopMutex sleep_mutex;
 static MonoCoopCond sleep_cond;
@@ -1647,7 +1659,7 @@ mono_thread_info_wait_one_handle (MonoThreadHandle *thread_handle, guint32 timeo
 {
        MonoOSEventWaitRet res;
 
-       res = mono_os_event_wait_one (&thread_handle->event, timeout);
+       res = mono_os_event_wait_one (&thread_handle->event, timeout, alertable);
        if (res == MONO_OS_EVENT_WAIT_RET_SUCCESS_0)
                return MONO_THREAD_INFO_WAIT_RET_SUCCESS_0;
        else if (res == MONO_OS_EVENT_WAIT_RET_ALERTED)
@@ -1675,7 +1687,7 @@ mono_thread_info_wait_multiple_handle (MonoThreadHandle **thread_handles, gsize
        if (background_change_event)
                thread_events [nhandles ++] = background_change_event;
 
-       res = mono_os_event_wait_multiple (thread_events, nhandles, waitall, timeout);
+       res = mono_os_event_wait_multiple (thread_events, nhandles, waitall, timeout, alertable);
        if (res >= MONO_OS_EVENT_WAIT_RET_SUCCESS_0 && res <= MONO_OS_EVENT_WAIT_RET_SUCCESS_0 + nhandles - 1)
                return MONO_THREAD_INFO_WAIT_RET_SUCCESS_0 + (res - MONO_OS_EVENT_WAIT_RET_SUCCESS_0);
        else if (res == MONO_OS_EVENT_WAIT_RET_ALERTED)
@@ -1685,3 +1697,26 @@ mono_thread_info_wait_multiple_handle (MonoThreadHandle **thread_handles, gsize
        else
                g_error ("%s: unknown res value %d", __func__, res);
 }
+
+/*
+ * mono_threads_join_mutex:
+ *
+ *   This mutex is used to avoid races between pthread_create () and pthread_join () on osx, see
+ * https://bugzilla.xamarin.com/show_bug.cgi?id=50529
+ * The code inside the lock should not block.
+ */
+void
+mono_threads_join_lock (void)
+{
+#ifdef TARGET_OSX
+       mono_os_mutex_lock (&join_mutex);
+#endif
+}
+
+void
+mono_threads_join_unlock (void)
+{
+#ifdef TARGET_OSX
+       mono_os_mutex_unlock (&join_mutex);
+#endif
+}