"SELF_SUSPENDED",
"ASYNC_SUSPEND_REQUESTED",
"SELF_SUSPEND_REQUESTED",
+ "STATE_BLOCKING",
+ "STATE_BLOCKING_AND_SUSPENDED",
};
return state_names [get_thread_state (state)];
}
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);
}
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));
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.
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;
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.
/*
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));
- 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.
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)
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)
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.
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
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));
-
}
}
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));
}
}
+/*
+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.
case STATE_RUNNING:
case STATE_ASYNC_SUSPEND_REQUESTED:
case STATE_SELF_SUSPEND_REQUESTED:
+ case STATE_BLOCKING:
return TRUE;
}
return FALSE;