Merge pull request #1659 from alexanderkyte/stringbuilder-referencesource
[mono.git] / mono / utils / mono-threads-state-machine.c
index 00c7293d1e9f07039c1f4e363aec174610c6305b..c82ad1f9b195aaccd80a66986edda28cc8a580da 100644 (file)
@@ -41,6 +41,8 @@ state_name (int state)
                "SELF_SUSPENDED",
                "ASYNC_SUSPEND_REQUESTED",
                "SELF_SUSPEND_REQUESTED",
+               "STATE_BLOCKING",
+               "STATE_BLOCKING_AND_SUSPENDED",
        };
        return state_names [get_thread_state (state)];
 }
@@ -66,8 +68,11 @@ check_thread_state (MonoThreadInfo* info)
        case STATE_SELF_SUSPENDED:
        case STATE_ASYNC_SUSPEND_REQUESTED:
        case STATE_SELF_SUSPEND_REQUESTED:
+       case STATE_BLOCKING_AND_SUSPENDED:
                g_assert (suspend_count > 0);
                break;
+       case STATE_BLOCKING: //this is a special state that can have zero or positive suspend count.
+               break;
        default:
                g_error ("Invalid state %d", cur_state);
        }
@@ -136,6 +141,8 @@ retry_state_change:
 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:
                g_error ("Cannot transition current thread %p from %s with DETACH", info, state_name (cur_state));
@@ -173,6 +180,8 @@ 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.
@@ -210,6 +219,7 @@ 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 (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
                        goto retry_state_change;
@@ -222,6 +232,14 @@ retry_state_change:
                        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 (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; //A thread in the blocking state has its state saved so we can treat it as suspended.
+
 /*
 
 [1] It's questionable on what to do if we hit the beginning of a self suspend.
@@ -275,6 +293,8 @@ retry_state_change:
 /*
 STATE_ASYNC_SUSPENDED: Code should not be running while suspended.
 STATE_SELF_SUSPENDED: Code should not be running while suspended.
+STATE_BLOCKING:
+STATE_BLOCKING_AND_SUSPENDED: Pool is a local state transition. No VM activities are allowed while in blocking mode.
 */
        default:
                g_error ("Cannot transition thread %p from %s with STATE_POLL", info, state_name (cur_state));
@@ -289,6 +309,7 @@ Returns one of the following values:
 - Error: The thread was not suspended in the first place. [2]
 - InitSelfResume: The thread is blocked on self suspend and should be resumed 
 - InitAsycResume: The thread is blocked on async suspend and should be resumed
+- ResumeInitBlockingResume: The thread was suspended on the exit path of blocking state and should be resumed
 
 [2] This threading system uses an unsigned suspend count. Which means a resume cannot be
 used as a suspend permit and cancel each other.
@@ -312,11 +333,24 @@ 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);
                trace_state_change ("RESUME", info, raw_state, cur_state, 0);
                return ResumeError; //Resume failed because thread was not blocked
 
+       case STATE_BLOCKING: //Blocking, might have a suspend count, we decrease if it's > 0
+               if (suspend_count == 0) {
+                       trace_state_change ("RESUME", info, raw_state, cur_state, 0);
+                       return ResumeError;
+               } else {
+                       if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
+                                       goto retry_state_change;
+                       trace_state_change ("RESUME", info, raw_state, cur_state, -1);
+                       return ResumeOk; //Resume worked and there's nothing for the caller to do.
+               }
+               break;
        case STATE_ASYNC_SUSPENDED:
-       case STATE_SELF_SUSPENDED: //Decrease the suspend_count and maybe resume
+       case STATE_SELF_SUSPENDED:
+       case STATE_BLOCKING_AND_SUSPENDED: //Decrease the suspend_count and maybe resume
                g_assert (suspend_count > 0);
                if (suspend_count > 1) {
                        if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
@@ -331,12 +365,13 @@ retry_state_change:
 
                        if (cur_state == STATE_ASYNC_SUSPENDED)
                                return ResumeInitAsyncResume; //Resume worked and caller must do async resume
-                       else
+                       else if (cur_state == STATE_SELF_SUSPENDED)
                                return ResumeInitSelfResume; //Resume worked and caller must do self resume
+                       else
+                               return ResumeInitBlockingResume; //Resume worked and caller must do blocking resume
                }
 
        case STATE_SELF_SUSPEND_REQUESTED: //Self suspend was requested but another thread decided to resume it.
-       // case STATE_SUSPEND_IN_PROGRESS: //Self suspend is in progress but another thread decided to resume it. [4]
                g_assert (suspend_count > 0);
                if (suspend_count > 1) {
                        if (InterlockedCompareExchange (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
@@ -348,11 +383,9 @@ retry_state_change:
                        trace_state_change ("RESUME", info, raw_state, STATE_RUNNING, -1);
                }
                return ResumeOk; //Resume worked and there's nothing for the caller to do (the target never actually suspend).
-
 /*
 
 STATE_ASYNC_SUSPEND_REQUESTED: Only one async suspend/resume operation can be in flight, so a resume cannot witness an internal state of suspend
-STATE_SUSPEND_PROMOTED_TO_ASYNC: Only one async suspend/resume operation can be in flight, so a resume cannot witness an internal state of suspend
 
 [3] A self-resume makes no sense given it requires the thread to be running, which means its suspend count must be zero. A self resume would make
 sense as a suspend permit, but as explained in [2] we don't support it so this is a bug.
@@ -383,6 +416,7 @@ retry_state_change:
        switch (cur_state) {
 
        case STATE_SELF_SUSPENDED: //async suspend raced with self suspend and lost
+       case STATE_BLOCKING_AND_SUSPENDED: //async suspend raced with blocking and lost
                trace_state_change ("FINISH_ASYNC_SUSPEND", info, raw_state, cur_state, 0);
                return FALSE; //let self suspend wait
 
@@ -391,21 +425,15 @@ retry_state_change:
                        goto retry_state_change;
                trace_state_change ("FINISH_ASYNC_SUSPEND", info, raw_state, STATE_ASYNC_SUSPENDED, 0);
                return TRUE; //Async suspend worked, now wait for resume
-       // 
-       // case STATE_SUSPEND_IN_PROGRESS:
-       //      if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_SUSPEND_PROMOTED_TO_ASYNC, suspend_count), raw_state) != raw_state)
-       //              goto retry_state_change;
-       //      trace_state_change ("FINISH_ASYNC_SUSPEND", info, raw_state, STATE_SUSPEND_PROMOTED_TO_ASYNC, 0);
-       //      return FALSE; //async suspend race with self suspend and lost, let the other finish it
+
 /*
 STATE_RUNNING: A thread cannot escape suspension once requested.
 STATE_ASYNC_SUSPENDED: There can be only one suspend initiator at a given time, meaning this state should have been visible on the first stage of suspend.
 STATE_SELF_SUSPEND_REQUESTED: When self suspend and async suspend happen together, they converge to async suspend so this state should not be visible.
-STATE_SUSPEND_PROMOTED_TO_ASYNC: Given there's a single initiator this cannot happen.
+STATE_BLOCKING: Async suspend only begins if a transition to async suspend requested happened. Blocking would have put us into blocking with positive suspend count if it raced with async finish.
 */
        default:
                g_error ("Cannot transition thread %p from %s with FINISH_ASYNC_SUSPEND", info, state_name (cur_state));
-
        }
 }
 
@@ -444,9 +472,9 @@ retry_state_change:
 STATE_RUNNING
 STATE_SELF_SUSPENDED
 STATE_ASYNC_SUSPEND_REQUESTED
-STATE_SELF_SUSPEND_REQUESTED
-STATE_SUSPEND_PROMOTED_TO_ASYNC:
-STATE_SUSPEND_IN_PROGRESS: All those are invalid end states of a sucessfull finish async suspend
+STATE_BLOCKING
+STATE_BLOCKING_AND_SUSPENDED
+STATE_SELF_SUSPEND_REQUESTED: All those are invalid end states of a sucessfull finish async suspend
 */
        default:
                g_error ("Cannot transition thread %p from %s with COMPENSATE_FINISH_ASYNC_SUSPEND", info, state_name (cur_state));
@@ -454,23 +482,173 @@ STATE_SUSPEND_IN_PROGRESS: All those are invalid end states of a sucessfull fini
        }
 }
 
+/*
+This transitions the thread into a cooperative state where it's assumed to be suspended but can continue.
+
+Native runtime code might want to put itself into a state where the thread is considered suspended but can keep running.
+That state only works as long as the only managed state touched is blitable and was pinned before the transition.
+
+It returns the action the caller must perform:
+
+- Continue: Entered blocking state sucessfully;
+- PollAndRetry: Async suspend raced and won, try to suspend and then retry;
+
+*/
+MonoDoBlockingResult
+mono_threads_transition_do_blocking (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_RUNNING: //transition to blocked
+               g_assert (suspend_count == 0);
+               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);
+               trace_state_change ("DO_BLOCKING", info, raw_state, cur_state, 0);
+               return DoBlockingPollAndRetry;
+/*
+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
+STATE_BLOCKING:
+STATE_BLOCKING_AND_SUSPENDED: Blocking is not nestabled
+*/
+       default:
+               g_error ("Cannot transition thread %p from %s with DO_BLOCKING", info, state_name (cur_state));
+       }
+}
+
+/*
+This is the exit transition from the blocking state. If this thread is logically async suspended it will have to wait
+until its resumed before continuing.
+
+It returns one of:
+-Aborted: The blocking operation was aborted and not properly restored. Aborts can happen due to lazy loading and some n2m transitions;
+-Ok: Done with blocking, just move on;
+-Wait: This thread was async suspended, wait for resume
+
+*/
+MonoDoneBlockingResult
+mono_threads_transition_done_blocking (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_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)
+                               goto retry_state_change;
+                       trace_state_change ("DONE_BLOCKING", info, raw_state, STATE_RUNNING, 0);
+                       return DoneBlockingOk;
+               } else {
+                       g_assert (suspend_count >= 0);
+                       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);
+                       return DoneBlockingWait;
+               }
+
+/*
+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
+STATE_BLOCKING_AND_SUSPENDED: This an exit state of done blocking
+*/
+       default:
+               g_error ("Cannot transition thread %p from %s with DONE_BLOCKING", info, state_name (cur_state));
+       }
+}
+
+/*
+Transition a thread in what should be a blocking state back to running state.
+This is different that done blocking because the goal is to get back to blocking once we're done.
+This is required to be able to bail out of blocking in case we're back to inside the runtime.
+
+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
+*/
+MonoAbortBlockingResult
+mono_threads_transition_abort_blocking (THREAD_INFO_TYPE* 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_RUNNING: //thread already in runnable state
+               trace_state_change ("ABORT_BLOCKING", info, raw_state, cur_state, 0);
+               return AbortBlockingIgnore;
+
+       case STATE_ASYNC_SUSPEND_REQUESTED: //thread is runnable and have a pending suspend
+               trace_state_change ("ABORT_BLOCKING", info, raw_state, cur_state, 0);
+               return AbortBlockingIgnoreAndPoll;
+
+       case STATE_BLOCKING:
+               if (suspend_count == 0) {
+                       if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count), raw_state) != raw_state)
+                               goto 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_ASYNC_SUSPEND_REQUESTED, suspend_count), raw_state) != raw_state)
+                               goto retry_state_change;
+                       trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_ASYNC_SUSPEND_REQUESTED, 0);
+                       return AbortBlockingOkAndPool;
+               }
+/*
+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.
+STATE_BLOCKING_AND_SUSPENDED: This is an exit state of done blocking, can't happen here.
+*/
+       default:
+               g_error ("Cannot transition thread %p from %s with DONE_BLOCKING", info, state_name (cur_state));
+       }
+}
+
 MonoThreadUnwindState*
 mono_thread_info_get_suspend_state (MonoThreadInfo *info)
 {
-       int raw_state, cur_state, suspend_count G_GNUC_UNUSED;
+       int raw_state, cur_state, suspend_count;
        UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
        switch (cur_state) {
        case STATE_ASYNC_SUSPENDED:
                return &info->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX];
        case STATE_SELF_SUSPENDED:
+       case STATE_BLOCKING_AND_SUSPENDED:
                return &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
+       case STATE_BLOCKING:
+               if (suspend_count > 0)
+                       return &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
        default:
-               g_error ("Cannot read suspend state when the target is in the %s state", state_name (cur_state));
+/*
+STATE_RUNNING
+STATE_SELF_SUSPENDED
+STATE_ASYNC_SUSPEND_REQUESTED
+STATE_BLOCKING: All those are invalid suspend states.
+*/
+               g_error ("Cannot read suspend state when target %p is in the %s state", mono_thread_info_get_tid (info), state_name (cur_state));
        }
 }
 
-
-
 // State checking code
 /**
  * Return TRUE is the thread is in a runnable state.
@@ -482,6 +660,7 @@ mono_thread_info_is_running (MonoThreadInfo *info)
        case STATE_RUNNING:
        case STATE_ASYNC_SUSPEND_REQUESTED:
        case STATE_SELF_SUSPEND_REQUESTED:
+       case STATE_BLOCKING:
                return TRUE;
        }
        return FALSE;