Merge pull request #5459 from HinTak/patch-5
[mono.git] / mono / utils / mono-threads-state-machine.c
index 32491a4ca225229e622799504f8d7cc6012c031c..1518438babbb9a99d2f1844bf65b3a35888d8bd0 100644 (file)
@@ -1,3 +1,7 @@
+/**
+ * \file
+ */
+
 #include <config.h>
 
 #include <mono/utils/mono-compiler.h>
@@ -6,6 +10,7 @@
 #include <mono/utils/mono-memory-model.h>
 #include <mono/utils/atomic.h>
 #include <mono/utils/checked-build.h>
+#include <mono/utils/mono-threads-debug.h>
 
 #include <errno.h>
 
@@ -107,7 +112,8 @@ retry_state_change:
        UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
        switch (cur_state) {
        case STATE_STARTING:
-               g_assert (suspend_count == 0);
+               if (!(suspend_count == 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, STATE_RUNNING, raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("ATTACH", info, raw_state, STATE_RUNNING, 0);
@@ -133,18 +139,20 @@ retry_state_change:
        UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
        switch (cur_state) {
        case STATE_RUNNING:
-               g_assert (suspend_count == 0);
+       case STATE_BLOCKING: /* An OS thread on coop goes STARTING->BLOCKING->RUNNING->BLOCKING->DETACHED */
+               if (!(suspend_count == 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, STATE_DETACHED, raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("DETACH", info, raw_state, STATE_DETACHED, 0);
                return TRUE;
        case STATE_ASYNC_SUSPEND_REQUESTED: //Can't detach until whoever asked us to suspend to be happy with us
                return FALSE;
+
 /*
 STATE_ASYNC_SUSPENDED: Code should not be running while suspended.
 STATE_SELF_SUSPENDED: Code should not be running while suspended.
 STATE_SELF_SUSPEND_REQUESTED: This is a bug in the self suspend code that didn't execute the second part of it
-STATE_BLOCKING: This is a bug in the coop code that forgot to do a finish blocking before exiting.
 STATE_BLOCKING_AND_SUSPENDED: This is a bug in coop x suspend that resulted the thread in an undetachable state.
 */
        default:
@@ -152,48 +160,6 @@ STATE_BLOCKING_AND_SUSPENDED: This is a bug in coop x suspend that resulted the
        }
 }
 
-/*
-This transition initiates the suspension of the current thread.
-*/
-void
-mono_threads_transition_request_self_suspension (MonoThreadInfo *info)
-{
-       int raw_state, cur_state, suspend_count;
-       g_assert (info ==  mono_thread_info_current ());
-
-retry_state_change:
-       UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
-
-       switch (cur_state) {
-       case STATE_RUNNING: //Post a self suspend request
-               g_assert (suspend_count == 0);
-               if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_SELF_SUSPEND_REQUESTED, 1), raw_state) != raw_state)
-                       goto retry_state_change;
-               trace_state_change ("SELF_SUSPEND_REQUEST", info, raw_state, STATE_SELF_SUSPEND_REQUESTED, 1);
-               break;
-
-       case STATE_ASYNC_SUSPEND_REQUESTED: //Bump the suspend count but don't change the request type as async takes preference
-               g_assert (suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX);
-               if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
-                       goto retry_state_change;
-               trace_state_change ("SUSPEND_REQUEST", info, raw_state, cur_state, 1);
-               break;
-/*
-Other states:
-STATE_ASYNC_SUSPENDED: Code should not be running while suspended.
-STATE_SELF_SUSPENDED: Code should not be running while suspended.
-STATE_SELF_SUSPEND_REQUESTED: Self suspends should not nest as begin/end should be paired. [1]
-STATE_BLOCKING:
-STATE_BLOCKING_AND_SUSPENDED: Self suspension cannot be started when the thread is in blocking state as it must finish first
-
-[1] This won't trap this sequence of requests: self suspend, async suspend and self suspend. 
-If this turns to be an issue we can introduce a new suspend request state for when both have been requested.
-*/
-       default:
-               mono_fatal_with_history ("Cannot transition thread %p from %s with SUSPEND_REQUEST", mono_thread_info_get_tid (info), state_name (cur_state));
-       }
-}
-
 /*
 This transition initiates the suspension of another thread.
 
@@ -215,7 +181,8 @@ retry_state_change:
 
        switch (cur_state) {
        case STATE_RUNNING: //Post an async suspend request
-               g_assert (suspend_count == 0);
+               if (!(suspend_count == 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPEND_REQUESTED, 1), raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, STATE_ASYNC_SUSPEND_REQUESTED, 1);
@@ -224,21 +191,24 @@ retry_state_change:
        case STATE_ASYNC_SUSPENDED:
        case STATE_SELF_SUSPENDED: //Async suspend can suspend the same thread multiple times as it starts from the outside
        case STATE_BLOCKING_AND_SUSPENDED:
-               g_assert (suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX);
+               if (!(suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX))
+                       mono_fatal_with_history ("suspend_count = %d, but should be > 0 and < THREAD_SUSPEND_COUNT_MAX", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, cur_state, 1);
                return AsyncSuspendAlreadySuspended; //Thread is already suspended so we don't need to wait it to suspend
 
        case STATE_SELF_SUSPEND_REQUESTED: //This suspend needs to notify the initiator, so we need to promote the suspend to async
-               g_assert (suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX);
+               if (!(suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX))
+                       mono_fatal_with_history ("suspend_count = %d, but should be > 0 and < THREAD_SUSPEND_COUNT_MAX", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPEND_REQUESTED, suspend_count + 1), raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, STATE_ASYNC_SUSPEND_REQUESTED, 1);
                return AsyncSuspendWait; //This is the first async suspend request, change the thread and let it notify us [1]
 
        case STATE_BLOCKING:
-               g_assert (suspend_count < THREAD_SUSPEND_COUNT_MAX);
+               if (!(suspend_count < THREAD_SUSPEND_COUNT_MAX))
+                       mono_fatal_with_history ("suspend_count = %d, but should be < THREAD_SUSPEND_COUNT_MAX", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, cur_state, 1);
@@ -273,19 +243,21 @@ MonoSelfSupendResult
 mono_threads_transition_state_poll (MonoThreadInfo *info)
 {
        int raw_state, cur_state, suspend_count;
-       g_assert (info == mono_thread_info_current ());
+       g_assert (mono_thread_info_is_current (info));
 
 retry_state_change:
        UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
        switch (cur_state) {
        case STATE_RUNNING:
-               g_assert (suspend_count == 0);
+               if (!(suspend_count == 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
                trace_state_change ("STATE_POLL", info, raw_state, cur_state, 0);
                return SelfSuspendResumed; //We're fine, don't suspend
 
        case STATE_ASYNC_SUSPEND_REQUESTED: //Async suspend requested, service it with a self suspend
        case STATE_SELF_SUSPEND_REQUESTED: //Start the self suspend process
-               g_assert (suspend_count > 0);
+               if (!(suspend_count > 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_SELF_SUSPENDED, suspend_count), raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("STATE_POLL", info, raw_state, STATE_SELF_SUSPENDED, 0);
@@ -337,7 +309,8 @@ retry_state_change:
        UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
        switch (cur_state) {
        case STATE_RUNNING: //Thread already running.
-               g_assert (suspend_count == 0);
+               if (!(suspend_count == 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
                trace_state_change ("RESUME", info, raw_state, cur_state, 0);
                return ResumeError; //Resume failed because thread was not blocked
 
@@ -355,7 +328,8 @@ retry_state_change:
        case STATE_ASYNC_SUSPENDED:
        case STATE_SELF_SUSPENDED:
        case STATE_BLOCKING_AND_SUSPENDED: //Decrease the suspend_count and maybe resume
-               g_assert (suspend_count > 0);
+               if (!(suspend_count > 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
                if (suspend_count > 1) {
                        if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
                                        goto retry_state_change;
@@ -376,7 +350,8 @@ retry_state_change:
                }
 
        case STATE_SELF_SUSPEND_REQUESTED: //Self suspend was requested but another thread decided to resume it.
-               g_assert (suspend_count > 0);
+               if (!(suspend_count > 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
                if (suspend_count > 1) {
                        if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
                                        goto retry_state_change;
@@ -441,51 +416,6 @@ STATE_BLOCKING: Async suspend only begins if a transition to async suspend reque
        }
 }
 
-/*
-This the compensatory transition for failed async suspend.
-
-Async suspend can land on a thread as it began cleaning up and is no longer
-functional. This happens as cleanup is a racy process from the async suspend
-perspective. The thread could have cleaned up its domain or jit_tls, for example.
-
-It can only transition the state as left by a sucessfull finish async suspend transition.
-
-*/
-void
-mono_threads_transition_async_suspend_compensation (MonoThreadInfo* info)
-{
-       int raw_state, cur_state, suspend_count;
-
-retry_state_change:
-       UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
-       switch (cur_state) {
-
-       case STATE_ASYNC_SUSPENDED:
-               /*
-               Must be one since if a self suspend is in progress the thread should still be async suspendable.
-               If count > 1 and no self suspend is in progress then it means one of the following two.
-               - the thread was previously suspended, which means we should never reach end suspend in the first place.
-               - another suspend happened concurrently, which means the global suspend lock didn't happen.
-               */
-               g_assert (suspend_count == 1);
-               if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count - 1), raw_state) != raw_state)
-                       goto retry_state_change;
-               trace_state_change ("COMPENSATE_FINISH_ASYNC_SUSPEND", info, raw_state, STATE_RUNNING, -1);
-               break;
-/*
-STATE_RUNNING
-STATE_SELF_SUSPENDED
-STATE_ASYNC_SUSPEND_REQUESTED
-STATE_BLOCKING
-STATE_BLOCKING_AND_SUSPENDED
-STATE_SELF_SUSPEND_REQUESTED: All those are invalid end states of a sucessfull finish async suspend
-*/
-       default:
-               mono_fatal_with_history ("Cannot transition thread %p from %s with COMPENSATE_FINISH_ASYNC_SUSPEND", mono_thread_info_get_tid (info), state_name (cur_state));
-
-       }
-}
-
 /*
 This transitions the thread into a cooperative state where it's assumed to be suspended but can continue.
 
@@ -508,14 +438,16 @@ retry_state_change:
        switch (cur_state) {
 
        case STATE_RUNNING: //transition to blocked
-               g_assert (suspend_count == 0);
+               if (!(suspend_count == 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
                if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_BLOCKING, suspend_count), raw_state) != raw_state)
                        goto retry_state_change;
                trace_state_change ("DO_BLOCKING", info, raw_state, STATE_BLOCKING, 0);
                return DoBlockingContinue;
 
        case STATE_ASYNC_SUSPEND_REQUESTED:
-               g_assert (suspend_count > 0);
+               if (!(suspend_count > 0))
+                       mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
                trace_state_change ("DO_BLOCKING", info, raw_state, cur_state, 0);
                return DoBlockingPollAndRetry;
 /*
@@ -548,11 +480,6 @@ mono_threads_transition_done_blocking (MonoThreadInfo* info)
 retry_state_change:
        UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
        switch (cur_state) {
-       case STATE_RUNNING: //Blocking was aborted and not properly restored
-       case STATE_ASYNC_SUSPEND_REQUESTED: //Blocking was aborted, not properly restored and now there's a pending suspend
-               trace_state_change ("DONE_BLOCKING", info, raw_state, cur_state, 0);
-               return DoneBlockingAborted;
-
        case STATE_BLOCKING:
                if (suspend_count == 0) {
                        if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count), raw_state) != raw_state)
@@ -560,7 +487,8 @@ retry_state_change:
                        trace_state_change ("DONE_BLOCKING", info, raw_state, STATE_RUNNING, 0);
                        return DoneBlockingOk;
                } else {
-                       g_assert (suspend_count >= 0);
+                       if (!(suspend_count >= 0))
+                               mono_fatal_with_history ("suspend_count = %d, but should be >= 0", suspend_count);
                        if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_BLOCKING_AND_SUSPENDED, suspend_count), raw_state) != raw_state)
                                goto retry_state_change;
                        trace_state_change ("DONE_BLOCKING", info, raw_state, STATE_BLOCKING_AND_SUSPENDED, 0);
@@ -568,6 +496,8 @@ retry_state_change:
                }
 
 /*
+STATE_RUNNING: //Blocking was aborted and not properly restored
+STATE_ASYNC_SUSPEND_REQUESTED: //Blocking was aborted, not properly restored and now there's a pending suspend
 STATE_ASYNC_SUSPENDED
 STATE_SELF_SUSPENDED: Code should not be running while suspended.
 STATE_SELF_SUSPEND_REQUESTED: A blocking operation must not be done while trying to self suspend
@@ -587,7 +517,7 @@ It returns one of:
 -Ignore: Thread was not in blocking, nothing to do;
 -IgnoreAndPool: Thread was not blocking and there's a pending suspend that needs to be processed;
 -Ok: Blocking state successfully aborted;
--OkAndPool: Blocking state successfully aborted, there's a pending suspend to be processed though
+-Wait: Blocking state successfully aborted, there's a pending suspend to be processed though
 */
 MonoAbortBlockingResult
 mono_threads_transition_abort_blocking (THREAD_INFO_TYPE* info)
@@ -612,10 +542,12 @@ retry_state_change:
                        trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_RUNNING, 0);
                        return AbortBlockingOk;
                } else {
-                       if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_SELF_SUSPEND_REQUESTED, suspend_count), raw_state) != raw_state)
+                       if (!(suspend_count > 0))
+                               mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
+                       if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_BLOCKING_AND_SUSPENDED, suspend_count), raw_state) != raw_state)
                                goto retry_state_change;
-                       trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_SELF_SUSPEND_REQUESTED, 0);
-                       return AbortBlockingOkAndPool;
+                       trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_BLOCKING_AND_SUSPENDED, 0);
+                       return AbortBlockingWait;
                }
 /*
 STATE_ASYNC_SUSPENDED:
@@ -701,3 +633,21 @@ mono_thread_state_name (int state)
 {
        return state_name (state);
 }
+
+gboolean
+mono_thread_is_gc_unsafe_mode (void)
+{
+       MonoThreadInfo *cur = mono_thread_info_current ();
+
+       if (!cur)
+               return FALSE;
+
+       switch (mono_thread_info_current_state (cur)) {
+       case STATE_RUNNING:
+       case STATE_ASYNC_SUSPEND_REQUESTED:
+       case STATE_SELF_SUSPEND_REQUESTED:
+               return TRUE;
+       default:
+               return FALSE;
+       }
+}