The problematic scenario is the following: [1]
thread A) [DO_BLOCKING] runnable -> blocking (0) -- i.e, do a pinvoke
thread B) [ASYNC_SUSPEND_REQUESTED] blocking -> blocking (+1) -- thread B tries to suspend thread A
thread A) [ABORT_BLOCKING] blocking -> async_suspend_requested (0) -- i.e, that pinvoke called into a reverse delegate and needs to go back to runnable state
thread A) [STATE_POLL] async_suspend_requested -> self_suspended (0) -- thread A self suspend due to the request from B
If polling witness async suspend, it must notify the initiator of a successful suspend.
If the initiator witness a thread in blocking, it bumps the suspend count, assumes it suspended and don't wait for a notification.
This means that thread A would post to the suspend semaphore but thread B (the suspend initiator) will not wait on it.
Given the initiator won't wait in this case, it makes no sense for abort blocking to put the thread in async_suspend_requested.
The solution is to put it on self_suspend_requested, which won't trigger state_poll to notify.
[1] Read it as: thread) [TRANSITION NAME] old state -> new state (suspend count delta) -- explanation
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)
+ if (InterlockedCompareExchange (&info->thread_state, build_thread_state (STATE_SELF_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);
+ trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_SELF_SUSPEND_REQUESTED, 0);
return AbortBlockingOkAndPool;
}
/*