[threads] Enable blocking transition with MONO_ENABLE_BLOCKING_TRANSITION env variabl...
[mono.git] / mono / utils / mono-threads-coop.c
index 31604eab9d7f0358a0f390a185a92f176b843441..440eff85125121729d9c330de899bf5d74e43d0f 100644 (file)
@@ -1,5 +1,6 @@
- /*
- * mono-threads.c: Coop threading
+/**
+ * \file
+ * Coop threading
  *
  * Author:
  *     Rodrigo Kumpera (kumpera@gmail.com)
@@ -27,6 +28,7 @@
 #include <mono/utils/mono-threads-coop.h>
 #include <mono/utils/mono-threads-api.h>
 #include <mono/utils/checked-build.h>
+#include <mono/utils/mono-threads-debug.h>
 
 #ifdef TARGET_OSX
 #include <mono/utils/mach-support.h>
@@ -70,7 +72,7 @@ coop_tls_pop (gpointer received_cookie)
 
        stack = mono_native_tls_get_value (coop_reset_count_stack_key);
        if (!stack || 0 == stack->len)
-               mono_fatal_with_history ("Received cookie %p but found no stack at all, %x\n", received_cookie);
+               mono_fatal_with_history ("Received cookie %p but found no stack at all\n", received_cookie);
 
        expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
        stack->len --;
@@ -86,24 +88,42 @@ coop_tls_pop (gpointer received_cookie)
 
 #endif
 
+static void
+check_info (MonoThreadInfo *info, const gchar *action, const gchar *state)
+{
+       if (!info)
+               g_error ("Cannot %s GC %s region if the thread is not attached", action, state);
+       if (!mono_thread_info_is_current (info))
+               g_error ("[%p] Cannot %s GC %s region on a different thread", mono_thread_info_get_tid (info), action, state);
+       if (!mono_thread_info_is_live (info))
+               g_error ("[%p] Cannot %s GC %s region if the thread is not live", mono_thread_info_get_tid (info), action, state);
+}
+
 static int coop_reset_blocking_count;
 static int coop_try_blocking_count;
 static int coop_do_blocking_count;
 static int coop_do_polling_count;
 static int coop_save_count;
 
+static void
+mono_threads_state_poll_with_info (MonoThreadInfo *info);
+
 void
 mono_threads_state_poll (void)
 {
-       MonoThreadInfo *info;
+       mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
+}
 
-       g_assert (mono_threads_is_coop_enabled ());
+static void
+mono_threads_state_poll_with_info (MonoThreadInfo *info)
+{
+       g_assert (mono_threads_is_blocking_transition_enabled ());
 
        ++coop_do_polling_count;
 
-       info = mono_thread_info_current_unchecked ();
        if (!info)
                return;
+
        THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
 
        /* Fast check for pending suspend requests */
@@ -116,7 +136,7 @@ mono_threads_state_poll (void)
        /* commit the saved state and notify others if needed */
        switch (mono_threads_transition_state_poll (info)) {
        case SelfSuspendResumed:
-               return;
+               break;
        case SelfSuspendWait:
                mono_thread_info_wait_for_resume (info);
                break;
@@ -125,13 +145,22 @@ mono_threads_state_poll (void)
                mono_thread_info_wait_for_resume (info);
                break;
        }
+
+       if (info->async_target) {
+               info->async_target (info->user_data);
+               info->async_target = NULL;
+               info->user_data = NULL;
+       }
 }
 
-static void *
-return_stack_ptr ()
+static volatile gpointer* dummy_global;
+
+static MONO_NEVER_INLINE
+void*
+return_stack_ptr (gpointer *i)
 {
-       gpointer i;
-       return &i;
+       dummy_global = i;
+       return i;
 }
 
 static void
@@ -139,7 +168,8 @@ copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
 {
        MonoThreadUnwindState *state;
        int stackdata_size;
-       void* stackdata_end = return_stack_ptr ();
+       gpointer dummy;
+       void* stackdata_end = return_stack_ptr (&dummy);
 
        SAVE_REGS_ON_STACK;
 
@@ -162,15 +192,24 @@ copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
        state->gc_stackdata_size = stackdata_size;
 }
 
+static gpointer
+mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
+
+gpointer
+mono_threads_enter_gc_safe_region (gpointer *stackdata)
+{
+       return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
+}
+
 gpointer
-mono_threads_prepare_blocking (gpointer *stackdata)
+mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
 {
        gpointer cookie;
 
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_blocking_transition_enabled ())
                return NULL;
 
-       cookie = mono_threads_prepare_blocking_unbalanced (stackdata);
+       cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
 
 #ifdef ENABLE_CHECKED_BUILD_GC
        if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
@@ -181,21 +220,20 @@ mono_threads_prepare_blocking (gpointer *stackdata)
 }
 
 gpointer
-mono_threads_prepare_blocking_unbalanced (gpointer *stackdata)
+mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
 {
-       MonoThreadInfo *info;
+       return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
+}
 
-       if (!mono_threads_is_coop_enabled ())
+static gpointer
+mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
+{
+       if (!mono_threads_is_blocking_transition_enabled ())
                return NULL;
 
        ++coop_do_blocking_count;
 
-       info = mono_thread_info_current_unchecked ();
-       /* If the thread is not attached, it doesn't make sense prepare for suspend. */
-       if (!info || !mono_thread_info_is_live (info)) {
-               THREADS_SUSPEND_DEBUG ("PREPARE-BLOCKING failed %p\n", info ? mono_thread_info_get_tid (info) : NULL);
-               return NULL;
-       }
+       check_info (info, "enter", "safe");
 
        copy_stack_data (info, stackdata);
 
@@ -207,7 +245,7 @@ retry:
        case DoBlockingContinue:
                break;
        case DoBlockingPollAndRetry:
-               mono_threads_state_poll ();
+               mono_threads_state_poll_with_info (info);
                goto retry;
        }
 
@@ -215,9 +253,9 @@ retry:
 }
 
 void
-mono_threads_finish_blocking (gpointer cookie, gpointer *stackdata)
+mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
 {
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_blocking_transition_enabled ())
                return;
 
 #ifdef ENABLE_CHECKED_BUILD_GC
@@ -225,32 +263,22 @@ mono_threads_finish_blocking (gpointer cookie, gpointer *stackdata)
                coop_tls_pop (cookie);
 #endif
 
-       mono_threads_finish_blocking_unbalanced (cookie, stackdata);
+       mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
 }
 
 void
-mono_threads_finish_blocking_unbalanced (gpointer cookie, gpointer *stackdata)
+mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
 {
-       static gboolean warned_about_bad_transition;
        MonoThreadInfo *info;
 
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_blocking_transition_enabled ())
                return;
 
        info = (MonoThreadInfo *)cookie;
-       if (!info)
-               return;
 
-       g_assert (info == mono_thread_info_current_unchecked ());
+       check_info (info, "exit", "safe");
 
        switch (mono_threads_transition_done_blocking (info)) {
-       case DoneBlockingAborted:
-               if (!warned_about_bad_transition) {
-                       warned_about_bad_transition = TRUE;
-                       g_warning ("[%p] Blocking call ended in running state for, this might lead to unbound GC pauses.", mono_thread_info_get_tid (info));
-               }
-               mono_threads_state_poll ();
-               break;
        case DoneBlockingOk:
                info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
                break;
@@ -261,30 +289,35 @@ mono_threads_finish_blocking_unbalanced (gpointer cookie, gpointer *stackdata)
        default:
                g_error ("Unknown thread state");
        }
+
+       if (info->async_target) {
+               info->async_target (info->user_data);
+               info->async_target = NULL;
+               info->user_data = NULL;
+       }
 }
 
-gpointer
-mono_threads_cookie_for_reset_blocking_start (MonoThreadInfo *info)
+void
+mono_threads_assert_gc_safe_region (void)
 {
-       g_assert (mono_threads_is_coop_enabled ());
-
-#ifdef ENABLE_CHECKED_BUILD_GC
-       if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
-               coop_tls_push (info);
-#endif
+       MONO_REQ_GC_SAFE_MODE;
+}
 
-       return info;
+gpointer
+mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
+{
+       return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
 }
 
 gpointer
-mono_threads_reset_blocking_start (gpointer *stackdata)
+mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
 {
        gpointer cookie;
 
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_blocking_transition_enabled ())
                return NULL;
 
-       cookie = mono_threads_reset_blocking_start_unbalanced (stackdata);
+       cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
 
 #ifdef ENABLE_CHECKED_BUILD_GC
        if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
@@ -295,20 +328,20 @@ mono_threads_reset_blocking_start (gpointer *stackdata)
 }
 
 gpointer
-mono_threads_reset_blocking_start_unbalanced (gpointer *stackdata)
+mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
 {
-       MonoThreadInfo *info;
+       return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
+}
 
-       if (!mono_threads_is_coop_enabled ())
+gpointer
+mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
+{
+       if (!mono_threads_is_blocking_transition_enabled ())
                return NULL;
 
        ++coop_reset_blocking_count;
 
-       info = mono_thread_info_current_unchecked ();
-
-       /* If the thread is not attached, it doesn't make sense prepare for suspend. */
-       if (!info || !mono_thread_info_is_live (info))
-               return NULL;
+       check_info (info, "enter", "unsafe");
 
        copy_stack_data (info, stackdata);
 
@@ -317,7 +350,7 @@ mono_threads_reset_blocking_start_unbalanced (gpointer *stackdata)
                info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
                return NULL;
        case AbortBlockingIgnoreAndPoll:
-               mono_threads_state_poll ();
+               mono_threads_state_poll_with_info (info);
                return NULL;
        case AbortBlockingOk:
                info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
@@ -329,13 +362,38 @@ mono_threads_reset_blocking_start_unbalanced (gpointer *stackdata)
                g_error ("Unknown thread state");
        }
 
+       if (info->async_target) {
+               info->async_target (info->user_data);
+               info->async_target = NULL;
+               info->user_data = NULL;
+       }
+
+       return info;
+}
+
+gpointer
+mono_threads_enter_gc_unsafe_region_cookie (void)
+{
+       MonoThreadInfo *info;
+
+       g_assert (mono_threads_is_blocking_transition_enabled ());
+
+       info = mono_thread_info_current_unchecked ();
+
+       check_info (info, "enter (cookie)", "unsafe");
+
+#ifdef ENABLE_CHECKED_BUILD_GC
+       if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
+               coop_tls_push (info);
+#endif
+
        return info;
 }
 
 void
-mono_threads_reset_blocking_end (gpointer cookie, gpointer *stackdata)
+mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
 {
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_blocking_transition_enabled ())
                return;
 
 #ifdef ENABLE_CHECKED_BUILD_GC
@@ -343,32 +401,58 @@ mono_threads_reset_blocking_end (gpointer cookie, gpointer *stackdata)
                coop_tls_pop (cookie);
 #endif
 
-       mono_threads_reset_blocking_end_unbalanced (cookie, stackdata);
+       mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
 }
 
 void
-mono_threads_reset_blocking_end_unbalanced (gpointer cookie, gpointer *stackdata)
+mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
 {
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_blocking_transition_enabled ())
                return;
 
        if (!cookie)
                return;
 
-#ifdef ENABLE_CHECKED_BUILD_GC
-       if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
+       mono_threads_enter_gc_safe_region_unbalanced (stackdata);
+}
+
+void
+mono_threads_assert_gc_unsafe_region (void)
+{
+       MONO_REQ_GC_UNSAFE_MODE;
+}
+
+gboolean
+mono_threads_is_coop_enabled (void)
+{
+#if defined(USE_COOP_GC)
+       return TRUE;
+#else
+       static int is_coop_enabled = -1;
+       if (G_UNLIKELY (is_coop_enabled == -1))
+               is_coop_enabled = g_hasenv ("MONO_ENABLE_COOP") ? 1 : 0;
+       return is_coop_enabled == 1;
 #endif
-       {
-               g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
-       }
+}
 
-       mono_threads_prepare_blocking_unbalanced (stackdata);
+gboolean
+mono_threads_is_blocking_transition_enabled (void)
+{
+#if defined(USE_COOP_GC)
+       return TRUE;
+#else
+       static int is_blocking_transition_enabled = -1;
+       if (G_UNLIKELY (is_blocking_transition_enabled == -1))
+               is_blocking_transition_enabled = (g_hasenv ("MONO_ENABLE_COOP") || g_hasenv ("MONO_ENABLE_BLOCKING_TRANSITION")) ? 1 : 0;
+       return is_blocking_transition_enabled == 1;
+#endif
 }
 
+
 void
-mono_threads_init_coop (void)
+mono_threads_coop_init (void)
 {
-       if (!mono_threads_is_coop_enabled ())
+       if (!mono_threads_is_coop_enabled () && !mono_threads_is_blocking_transition_enabled ())
                return;
 
        mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
@@ -396,51 +480,3 @@ mono_threads_coop_end_global_suspend (void)
        if (mono_threads_is_coop_enabled ())
                mono_polling_required = 0;
 }
-
-gpointer
-mono_threads_enter_gc_unsafe_region (gpointer* stackdata)
-{
-       if (!mono_threads_is_coop_enabled ())
-               return NULL;
-
-       return mono_threads_reset_blocking_start (stackdata);
-}
-
-void
-mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer* stackdata)
-{
-       if (!mono_threads_is_coop_enabled ())
-               return;
-
-       mono_threads_reset_blocking_end (cookie, stackdata);
-}
-
-void
-mono_threads_assert_gc_unsafe_region (void)
-{
-       MONO_REQ_GC_UNSAFE_MODE;
-}
-
-gpointer
-mono_threads_enter_gc_safe_region (gpointer *stackdata)
-{
-       if (!mono_threads_is_coop_enabled ())
-               return NULL;
-
-       return mono_threads_prepare_blocking (stackdata);
-}
-
-void
-mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
-{
-       if (!mono_threads_is_coop_enabled ())
-               return;
-
-       mono_threads_finish_blocking (cookie, stackdata);
-}
-
-void
-mono_threads_assert_gc_safe_region (void)
-{
-       MONO_REQ_GC_SAFE_MODE;
-}