Exemplo n.º 1
0
/*
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:
-Ok: Done with blocking, just move on;
-Wait: This thread was suspended while in blocking, wait for resume.
*/
MonoDoneBlockingResult
mono_threads_transition_done_blocking (MonoThreadInfo* info, const char *func)
{
	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_BLOCKING:
		if (!(suspend_count == 0))
			mono_fatal_with_history ("%s suspend_count = %d, but should be == 0", func, suspend_count);
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change_sigsafe ("DONE_BLOCKING", info, raw_state, STATE_RUNNING, 0, func);
		return DoneBlockingOk;
	case STATE_BLOCKING_SUSPEND_REQUESTED:
		if (!(suspend_count > 0))
			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING_SELF_SUSPENDED, suspend_count), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change_with_func ("DONE_BLOCKING", info, raw_state, STATE_BLOCKING_SELF_SUSPENDED, 0, func);
		return DoneBlockingWait;
/*
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_BLOCKING_SELF_SUSPENDED: This an exit state of done blocking
STATE_BLOCKING_ASYNC_SUSPENDED: This is an exit state of done blocking
*/
	default:
		mono_fatal_with_history ("Cannot transition thread %p from %s with DONE_BLOCKING", mono_thread_info_get_tid (info), state_name (cur_state));
	}
}
/*
This transition initiates the suspension of another thread.

Returns one of the following values:

- AsyncSuspendInitSuspend: Thread suspend requested, async suspend needs to be done.
- AsyncSuspendAlreadySuspended: Thread already suspended, nothing to do.
- AsyncSuspendWait: Self suspend in progress, asked it to notify us. Caller must add target to the notification set.
*/
MonoRequestAsyncSuspendResult
mono_threads_transition_request_async_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 an async suspend request
		g_assert (suspend_count == 0);
		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);
		return AsyncSuspendInitSuspend; //This is the first async suspend request against the target

	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;
		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 (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 (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.
The expected behavior is that the target should poll its state very soon so the the suspend latency should be minimal.

STATE_ASYNC_SUSPEND_REQUESTED: Since there can only be one async suspend in progress and it must finish, it should not be possible to witness this.
*/
	default:
		g_error ("Cannot transition thread %p from %s with ASYNC_SUSPEND_REQUESTED", info, state_name (cur_state));
	}
	return FALSE;
}
Exemplo n.º 3
0
/*
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, const char *func)
{
	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
		if (!(suspend_count == 0))
			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
		if (mono_atomic_cas_i32 (&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:
		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;
/*
STATE_ASYNC_SUSPENDED
STATE_SELF_SUSPENDED: Code should not be running while suspended.
STATE_BLOCKING:
STATE_BLOCKING_SUSPEND_REQUESTED:
STATE_BLOCKING_SELF_SUSPENDED: Blocking is not nestabled
STATE_BLOCKING_ASYNC_SUSPENDED: Blocking is not nestable _and_ code should not be running while suspended
*/
	default:
		mono_fatal_with_history ("%s Cannot transition thread %p from %s with DO_BLOCKING", func, 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.

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 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:
		g_error ("Cannot transition thread %p from %s with COMPENSATE_FINISH_ASYNC_SUSPEND", info, state_name (cur_state));

	}
}
/*
This performs the last step of async suspend.

Returns TRUE if the caller should wait for resume.
*/
gboolean
mono_threads_transition_finish_async_suspend (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_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

	case STATE_ASYNC_SUSPEND_REQUESTED:
		if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPENDED, suspend_count), raw_state) != raw_state)
			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

/*
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_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));
	}
}
/*
Check the current state of the thread and try to init a self suspend.
This must be called with self state saved.

Returns one of the following values:

- Resumed: Async resume happened and current thread should keep running
- Suspend: Caller should wait for a resume signal
- SelfSuspendNotifyAndWait: Notify the suspend initiator and wait for a resume signals
 suspend should start.

*/
MonoSelfSupendResult
mono_threads_transition_state_poll (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:
		g_assert (suspend_count == 0);
		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 (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);
		if (cur_state == STATE_SELF_SUSPEND_REQUESTED)
			return SelfSuspendWait; //Caller should wait for resume
		else
			return SelfSuspendNotifyAndWait; //Caller should notify suspend initiator and wait for resume

/*
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));
	}
}
Exemplo n.º 8
0
/*
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;
-IgnoreAndPoll: Thread was not blocking and there's a pending suspend that needs to be processed;
-Ok: Blocking state successfully aborted;
-Wait: Blocking state successfully aborted, there's a pending suspend to be processed though, wait for resume.
*/
MonoAbortBlockingResult
mono_threads_transition_abort_blocking (THREAD_INFO_TYPE* info, const char *func)
{
	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_with_func ("ABORT_BLOCKING", info, raw_state, cur_state, 0, func);
		return AbortBlockingIgnore;

	case STATE_ASYNC_SUSPEND_REQUESTED: //thread is runnable and have a pending suspend
		trace_state_change_with_func ("ABORT_BLOCKING", info, raw_state, cur_state, 0, func);
		return AbortBlockingIgnoreAndPoll;

	case STATE_BLOCKING:
		if (!(suspend_count == 0))
			mono_fatal_with_history ("suspend_count = %d,  but should be == 0", suspend_count);
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change_sigsafe ("ABORT_BLOCKING", info, raw_state, STATE_RUNNING, 0, func);
		return AbortBlockingOk;
	case STATE_BLOCKING_SUSPEND_REQUESTED:
		if (!(suspend_count > 0))
			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING_SELF_SUSPENDED, suspend_count), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change_with_func ("ABORT_BLOCKING", info, raw_state, STATE_BLOCKING_SELF_SUSPENDED, 0, func);
		return AbortBlockingWait;
/*
STATE_ASYNC_SUSPENDED:
STATE_SELF_SUSPENDED: Code should not be running while suspended.
STATE_BLOCKING_SELF_SUSPENDED: This is an exit state of done blocking, can't happen here.
STATE_BLOCKING_ASYNC_SUSPENDED: This is an exit state of abort blocking, can't happen here.
*/
	default:
		mono_fatal_with_history ("Cannot transition thread %p from %s with DONE_BLOCKING", mono_thread_info_get_tid (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));
	}
}
/*
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:
		g_error ("Cannot transition thread %p from %s with SUSPEND_REQUEST", 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));
	}
}
Exemplo n.º 12
0
/*
This performs the last step of preemptive suspend.

Returns TRUE if the caller should wait for resume.
*/
gboolean
mono_threads_transition_finish_async_suspend (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_SELF_SUSPENDED: //async suspend raced with self suspend and lost
	case STATE_BLOCKING_SELF_SUSPENDED: //async suspend raced with blocking and lost
		trace_state_change_sigsafe ("FINISH_ASYNC_SUSPEND", info, raw_state, cur_state, 0, "");
		return FALSE; //let self suspend wait

	case STATE_ASYNC_SUSPEND_REQUESTED:
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPENDED, suspend_count), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change_sigsafe ("FINISH_ASYNC_SUSPEND", info, raw_state, STATE_ASYNC_SUSPENDED, 0, "");
		return TRUE; //Async suspend worked, now wait for resume
	case STATE_BLOCKING_SUSPEND_REQUESTED:
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING_ASYNC_SUSPENDED, suspend_count), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change_sigsafe ("FINISH_ASYNC_SUSPEND", info, raw_state, STATE_BLOCKING_ASYNC_SUSPENDED, 0, "");
		return TRUE; //Async suspend of blocking thread worked, now wait for resume

/*
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_BLOCKING: If a thread is subject to preemptive suspend, there is no race as the resume initiator should have suspended the thread to STATE_BLOCKING_ASYNC_SUSPENDED or STATE_BLOCKING_SELF_SUSPENDED before resuming.
                With cooperative suspend, there are no finish_async_suspend transitions since there's no path back from asyns_suspend requested to running.
STATE_BLOCKING_ASYNC_SUSPENDED: There can only be one suspend initiator at a given time, meaning this state should have ben visible on the first stage of suspend.
*/
	default:
		mono_fatal_with_history ("Cannot transition thread %p from %s with FINISH_ASYNC_SUSPEND", mono_thread_info_get_tid (info), state_name (cur_state));
	}
}
Exemplo n.º 13
0
/*
Check the current state of the thread and try to init a self suspend.
This must be called with self state saved.

Returns one of the following values:

- Resumed: Async resume happened and current thread should keep running
- Suspend: Caller should wait for a resume signal
- SelfSuspendNotifyAndWait: Notify the suspend initiator and wait for a resume signals
 suspend should start.

*/
MonoSelfSupendResult
mono_threads_transition_state_poll (MonoThreadInfo *info)
{
	int raw_state, cur_state, suspend_count;
	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:
		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
		if (!(suspend_count > 0))
			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
		if (mono_atomic_cas_i32 (&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);
		return SelfSuspendNotifyAndWait; //Caller should notify suspend initiator and wait for resume

/*
STATE_ASYNC_SUSPENDED: Code should not be running while suspended.
STATE_SELF_SUSPENDED: Code should not be running while suspended.
STATE_BLOCKING:
STATE_BLOCKING_SUSPEND_REQUESTED:
STATE_BLOCKING_ASYNC_SUSPENDED:
STATE_BLOCKING_SELF_SUSPENDED: Poll is a local state transition. No VM activities are allowed while in blocking mode.
      (In all the blocking states - the local thread has no checkpoints, hence
      no polling, it can only do abort blocking or done blocking on itself).
*/
	default:
		mono_fatal_with_history ("Cannot transition thread %p from %s with STATE_POLL", mono_thread_info_get_tid (info), state_name (cur_state));
	}
}
Exemplo n.º 14
0
/*
Try to resume a suspended thread.

Returns one of the following values:
- Sucess: The thread was resumed.
- Error: The thread was not suspended in the first place. [2]
- InitSelfResume: The thread is blocked on self suspend and should be resumed 
- InitAsyncResume: 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
      FIXME: ResumeInitBlockingResume is just InitSelfResume by a different name.

[2] This threading system uses an unsigned suspend count. Which means a resume cannot be
used as a suspend permit and cancel each other.

Suspend permits are really useful to implement managed synchronization structures that
don't consume native resources. The downside is that they further complicate the design of this
system as the RUNNING state now has a non zero suspend counter.

It can be implemented in the future if we find resume/suspend races that cannot be (efficiently) fixed by other means.

One major issue with suspend permits is runtime facilities (GC, debugger) that must have the target suspended when requested.
This would make permits really harder to add.
*/
MonoResumeResult
mono_threads_transition_request_resume (MonoThreadInfo* info)
{
	int raw_state, cur_state, suspend_count;
	g_assert (info != mono_thread_info_current ()); //One can't self resume [3]

retry_state_change:
	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
	switch (cur_state) {
	case STATE_RUNNING: //Thread already running.
		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

	case STATE_BLOCKING: //Blocking, might have a suspend count, we decrease if it's > 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;
	case STATE_BLOCKING_SUSPEND_REQUESTED:
		if (!(suspend_count > 0))
			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
		if (suspend_count > 1) {
			if (mono_atomic_cas_i32 (&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.
		} else {
			if (mono_atomic_cas_i32 (&info->thread_state, STATE_BLOCKING, raw_state) != raw_state)
				goto retry_state_change;
			trace_state_change ("RESUME", info, raw_state, STATE_BLOCKING, -1);
			return ResumeOk; // Resume worked, back in blocking, nothing for the caller to do.
		}
	case STATE_BLOCKING_ASYNC_SUSPENDED:
		if (!(suspend_count > 0))
			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
		if (suspend_count > 1) {
			if (mono_atomic_cas_i32 (&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, there's nothing else for the caller to do.
		} else {
			if (mono_atomic_cas_i32 (&info->thread_state, STATE_BLOCKING, raw_state) != raw_state)
				goto retry_state_change;
			trace_state_change ("RESUME", info, raw_state, STATE_BLOCKING, -1);
			return ResumeInitAsyncResume; // Resume worked and caller must do async resume, thread resumes in BLOCKING
		}
	case STATE_ASYNC_SUSPENDED:
	case STATE_SELF_SUSPENDED:
	case STATE_BLOCKING_SELF_SUSPENDED: //Decrease the suspend_count and maybe resume
		if (!(suspend_count > 0))
			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
		if (suspend_count > 1) {
			if (mono_atomic_cas_i32 (&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.
		} else {
			if (mono_atomic_cas_i32 (&info->thread_state, STATE_RUNNING, raw_state) != raw_state)
				goto retry_state_change;
			trace_state_change ("RESUME", info, raw_state, STATE_RUNNING, -1);

			if (cur_state == STATE_ASYNC_SUSPENDED)
				return ResumeInitAsyncResume; //Resume worked and caller must do async resume
			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
		}

/*

STATE_ASYNC_SUSPEND_REQUESTED: 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.

[4] It's questionable on whether a resume (an async operation) should be able to cancel a self suspend. The scenario where this would happen
is similar to the one described in [2] when this is used for as a synchronization primitive.

If this turns to be a problem we should either implement [2] or make this an invalid transition.

*/
	default:
		mono_fatal_with_history ("Cannot transition thread %p from %s with REQUEST_RESUME", mono_thread_info_get_tid (info), state_name (cur_state));
	}
}
Exemplo n.º 15
0
/*
This transition initiates the suspension of another thread.

Returns one of the following values:

- ReqSuspendInitSuspendRunning: Thread suspend requested, caller must initiate suspend.
- ReqSuspendInitSuspendBlocking: Thread in blocking state, caller may initiate suspend.
- ReqSuspendAlreadySuspended: Thread was already suspended and not executing, nothing to do.
- ReqSuspendAlreadySuspendedBlocking: Thread was already in blocking and a suspend was requested
                                      and the thread is still executing (perhaps in a syscall),
                                      nothing to do.
*/
MonoRequestSuspendResult
mono_threads_transition_request_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 an async suspend request
		if (!(suspend_count == 0))
			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPEND_REQUESTED, 1), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change ("SUSPEND_INIT_REQUESTED", info, raw_state, STATE_ASYNC_SUSPEND_REQUESTED, 1);
		return ReqSuspendInitSuspendRunning; //This is the first async suspend request against the target

	case STATE_ASYNC_SUSPENDED:
	case STATE_SELF_SUSPENDED:
	case STATE_BLOCKING_SELF_SUSPENDED:
	case STATE_BLOCKING_ASYNC_SUSPENDED:
		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 (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change ("SUSPEND_INIT_REQUESTED", info, raw_state, cur_state, 1);
		return ReqSuspendAlreadySuspended; //Thread is already suspended so we don't need to wait it to suspend

	case STATE_BLOCKING:
		if (!(suspend_count == 0))
			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING_SUSPEND_REQUESTED, 1), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change ("SUSPEND_INIT_REQUESTED", info, raw_state, STATE_BLOCKING_SUSPEND_REQUESTED, 1);
		return ReqSuspendInitSuspendBlocking; //A thread in the blocking state has its state saved so we can treat it as suspended.
	case STATE_BLOCKING_SUSPEND_REQUESTED:
		/* This should only be happening if we're doing a cooperative suspend of a blocking thread.
		 * In which case we could be in BLOCKING_SUSPEND_REQUESTED until we execute a done or abort blocking.
		 * In preemptive suspend of a blocking thread since there's a single suspend initiator active at a time,
		 * we would expect a finish_async_suspension or a done/abort blocking before the next suspension request
		 */
		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 (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
			goto retry_state_change;
		trace_state_change ("SUSPEND_INIT_REQUESTED", info, raw_state, cur_state, 1);
		return ReqSuspendAlreadySuspendedBlocking;
		
/*

[1] It's questionable on what to do if we hit the beginning of a self suspend.
The expected behavior is that the target should poll its state very soon so the the suspend latency should be minimal.

STATE_ASYNC_SUSPEND_REQUESTED: Since there can only be one async suspend in progress and it must finish, it should not be possible to witness this.
*/
	default:
		mono_fatal_with_history ("Cannot transition thread %p from %s with SUSPEND_INIT_REQUESTED", mono_thread_info_get_tid (info), state_name (cur_state));
	}
	return (MonoRequestSuspendResult) FALSE;
}