Merge pull request #2820 from kumpera/license-change-rebased
[mono.git] / mono / utils / mono-threads-coop.c
index edfb11d6c028b7aea1737d98cfa8d0c13173af19..4283537b0f9fa205fcbcd1f5752bdac1ab22afd5 100644 (file)
@@ -5,6 +5,7 @@
  *     Rodrigo Kumpera (kumpera@gmail.com)
  *
  * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
  */
 
 #include <config.h>
@@ -15,7 +16,6 @@
 #endif
 
 #include <mono/utils/mono-compiler.h>
-#include <mono/utils/mono-semaphore.h>
 #include <mono/utils/mono-threads.h>
 #include <mono/utils/mono-tls.h>
 #include <mono/utils/hazard-pointer.h>
@@ -25,6 +25,8 @@
 #include <mono/utils/mono-time.h>
 #include <mono/utils/mono-counters.h>
 #include <mono/utils/mono-threads-coop.h>
+#include <mono/utils/mono-threads-api.h>
+#include <mono/utils/checked-build.h>
 
 #ifdef TARGET_OSX
 #include <mono/utils/mach-support.h>
 
 volatile size_t mono_polling_required;
 
+// FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
+// perhaps with a per-thread cookie in the high bits.
+#ifdef ENABLE_CHECKED_BUILD_GC
+// Maintains a single per-thread stack of ints, used to ensure nesting is not violated
+MonoNativeTlsKey coop_reset_count_stack_key;
+static int coop_tls_push (int v) {
+       GArray *stack = mono_native_tls_get_value (coop_reset_count_stack_key);
+       if (!stack) {
+               stack = g_array_new (FALSE,FALSE,sizeof(int));
+               mono_native_tls_set_value (coop_reset_count_stack_key, stack);
+       }
+       g_array_append_val (stack, v);
+       return stack->len;
+}
+static int coop_tls_pop (int *v) {
+       GArray *stack = mono_native_tls_get_value (coop_reset_count_stack_key);
+       if (!stack || 0 == stack->len)
+               return -1;
+       stack->len--;
+       *v = g_array_index (stack, int, stack->len);
+       int len = stack->len;
+       if (0 == len) {
+               g_array_free (stack,TRUE);
+               mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
+       }
+       return len;
+}
+#endif
+
 static int coop_reset_blocking_count;
 static int coop_try_blocking_count;
 static int coop_do_blocking_count;
@@ -158,7 +189,7 @@ mono_threads_finish_blocking (void *cookie, void* stackdata)
        if (!mono_threads_is_coop_enabled ())
                return;
 
-       info = cookie;
+       info = (MonoThreadInfo *)cookie;
        if (!info)
                return;
 
@@ -193,9 +224,18 @@ mono_threads_reset_blocking_start (void* stackdata)
        if (!mono_threads_is_coop_enabled ())
                return NULL;
 
+       info = mono_thread_info_current_unchecked ();
+
+#ifdef ENABLE_CHECKED_BUILD_GC
+       int reset_blocking_count = InterlockedIncrement (&coop_reset_blocking_count);
+       // In this mode, the blocking count is used as the reset cookie. We would prefer
+       // (but do not require) this to be unique across invocations and threads.
+       if (reset_blocking_count == 0) // We *do* require it be nonzero
+               reset_blocking_count = coop_reset_blocking_count = 1;
+#else
        ++coop_reset_blocking_count;
+#endif
 
-       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;
@@ -211,72 +251,51 @@ mono_threads_reset_blocking_start (void* stackdata)
                return NULL;
        case AbortBlockingOk:
                info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
-               return info;
+               break;
        case AbortBlockingOkAndPool:
                mono_threads_state_poll ();
-               return info;
+               break;
        default:
                g_error ("Unknown thread state");
        }
+
+#ifdef ENABLE_CHECKED_BUILD_GC
+       if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
+               int level = coop_tls_push (reset_blocking_count);
+               //g_warning("Entering reset nest; level %d; cookie %d\n", level, reset_blocking_count);
+               return (void *)(intptr_t)reset_blocking_count;
+       }
+#endif
+
+       return info;
 }
 
 void
 mono_threads_reset_blocking_end (void *cookie, void* stackdata)
 {
-       MonoThreadInfo *info;
-
        if (!mono_threads_is_coop_enabled ())
                return;
 
-       info = cookie;
-       if (!info)
+       if (!cookie)
                return;
 
-       g_assert (info == mono_thread_info_current_unchecked ());
-       mono_threads_prepare_blocking (stackdata);
-}
-
-void*
-mono_threads_try_prepare_blocking (void* stackdata)
-{
-       MonoThreadInfo *info;
-
-       if (!mono_threads_is_coop_enabled ())
-               return NULL;
-
-       ++coop_try_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) || mono_thread_info_current_state (info) == STATE_BLOCKING) {
-               THREADS_SUSPEND_DEBUG ("PREPARE-TRY-BLOCKING failed %p\n", mono_thread_info_get_tid (info));
-               return NULL;
-       }
-
-       copy_stack_data (info, stackdata);
-
-retry:
-       ++coop_save_count;
-       mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
-
-       switch (mono_threads_transition_do_blocking (info)) {
-       case DoBlockingContinue:
-               break;
-       case DoBlockingPollAndRetry:
-               mono_threads_state_poll ();
-               goto retry;
+#ifdef ENABLE_CHECKED_BUILD_GC
+       if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
+               int received_cookie = (int)(intptr_t)cookie;
+               int desired_cookie;
+               int level = coop_tls_pop (&desired_cookie);
+               //g_warning("Leaving reset nest; back to level %d; desired cookie %d; received cookie %d\n", level, desired_cookie, received_cookie);
+               if (level < 0)
+                       mono_fatal_with_history ("Expected cookie %d but found no stack at all\n", desired_cookie);
+               if (desired_cookie != received_cookie)
+                       mono_fatal_with_history ("Expected cookie %d but received %d\n", desired_cookie, received_cookie);
+       } else // Notice this matches the line after the endif
+#endif
+       {
+               g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
        }
 
-       return info;
-}
-
-void
-mono_threads_finish_try_blocking (void* cookie, void* stackdata)
-{
-       if (!mono_threads_is_coop_enabled ())
-               return;
-
-       mono_threads_finish_blocking (cookie, stackdata);
+       mono_threads_prepare_blocking (stackdata);
 }
 
 void
@@ -291,6 +310,10 @@ mono_threads_init_coop (void)
        mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
        mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
        //See the above for what's wrong here.
+
+#ifdef ENABLE_CHECKED_BUILD_GC
+       mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
+#endif
 }
 
 void
@@ -307,8 +330,8 @@ mono_threads_coop_end_global_suspend (void)
                mono_polling_required = 0;
 }
 
-void*
-mono_threads_enter_gc_unsafe_region (void* stackdata)
+gpointer
+mono_threads_enter_gc_unsafe_region (gpointer* stackdata)
 {
        if (!mono_threads_is_coop_enabled ())
                return NULL;
@@ -317,10 +340,40 @@ mono_threads_enter_gc_unsafe_region (void* stackdata)
 }
 
 void
-mono_threads_exit_gc_unsafe_region (void *regions_cookie, void* stackdata)
+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_reset_blocking_end (regions_cookie, stackdata);
+       mono_threads_finish_blocking (cookie, stackdata);
+}
+
+void
+mono_threads_assert_gc_safe_region (void)
+{
+       MONO_REQ_GC_SAFE_MODE;
 }