1 /**
2  * \file
3  */
4 
5 #include <config.h>
6 
7 #include <mono/utils/mono-compiler.h>
8 #include <mono/utils/mono-threads.h>
9 #include <mono/utils/mono-tls.h>
10 #include <mono/utils/mono-memory-model.h>
11 #include <mono/utils/atomic.h>
12 #include <mono/utils/checked-build.h>
13 #include <mono/utils/mono-threads-debug.h>
14 
15 #include <errno.h>
16 
17 /*thread state helpers*/
18 static inline int
get_thread_state(int thread_state)19 get_thread_state (int thread_state)
20 {
21 	return thread_state & THREAD_STATE_MASK;
22 }
23 
24 static inline int
get_thread_suspend_count(int thread_state)25 get_thread_suspend_count (int thread_state)
26 {
27 	return (thread_state & THREAD_SUSPEND_COUNT_MASK) >> THREAD_SUSPEND_COUNT_SHIFT;
28 }
29 
30 static inline int
build_thread_state(int thread_state,int suspend_count)31 build_thread_state (int thread_state, int suspend_count)
32 {
33 	g_assert (suspend_count >= 0 && suspend_count <= THREAD_SUSPEND_COUNT_MAX);
34 	g_assert (thread_state >= 0 && thread_state <= STATE_MAX);
35 
36 	return thread_state | (suspend_count << THREAD_SUSPEND_COUNT_SHIFT);
37 }
38 
39 static const char*
state_name(int state)40 state_name (int state)
41 {
42 	static const char *state_names [] = {
43 		"STARTING",
44 		"RUNNING",
45 		"DETACHED",
46 		"ASYNC_SUSPENDED",
47 		"SELF_SUSPENDED",
48 		"ASYNC_SUSPEND_REQUESTED",
49 		"SELF_SUSPEND_REQUESTED",
50 		"STATE_BLOCKING",
51 		"STATE_BLOCKING_AND_SUSPENDED",
52 	};
53 	return state_names [get_thread_state (state)];
54 }
55 
56 #define UNWRAP_THREAD_STATE(RAW,CUR,COUNT,INFO) do {	\
57 	RAW = (INFO)->thread_state;	\
58 	CUR = get_thread_state (RAW);	\
59 	COUNT = get_thread_suspend_count (RAW);	\
60 } while (0)
61 
62 static void
check_thread_state(MonoThreadInfo * info)63 check_thread_state (MonoThreadInfo* info)
64 {
65 	int raw_state, cur_state, suspend_count;
66 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
67 	switch (cur_state) {
68 	case STATE_STARTING:
69 	case STATE_RUNNING:
70 	case STATE_DETACHED:
71 		g_assert (suspend_count == 0);
72 		break;
73 	case STATE_ASYNC_SUSPENDED:
74 	case STATE_SELF_SUSPENDED:
75 	case STATE_ASYNC_SUSPEND_REQUESTED:
76 	case STATE_SELF_SUSPEND_REQUESTED:
77 	case STATE_BLOCKING_AND_SUSPENDED:
78 		g_assert (suspend_count > 0);
79 		break;
80 	case STATE_BLOCKING: //this is a special state that can have zero or positive suspend count.
81 		break;
82 	default:
83 		g_error ("Invalid state %d", cur_state);
84 	}
85 }
86 
87 static inline void
trace_state_change(const char * transition,MonoThreadInfo * info,int cur_raw_state,int next_state,int suspend_count_delta)88 trace_state_change (const char *transition, MonoThreadInfo *info, int cur_raw_state, int next_state, int suspend_count_delta)
89 {
90 	check_thread_state (info);
91 	THREADS_STATE_MACHINE_DEBUG ("[%s][%p] %s -> %s (%d -> %d)\n",
92 		transition,
93 		mono_thread_info_get_tid (info),
94 		state_name (get_thread_state (cur_raw_state)),
95 		state_name (next_state),
96 		get_thread_suspend_count (cur_raw_state),
97 		get_thread_suspend_count (cur_raw_state) + suspend_count_delta);
98 
99 	CHECKED_BUILD_THREAD_TRANSITION (transition, info, get_thread_state (cur_raw_state), get_thread_suspend_count (cur_raw_state), next_state, suspend_count_delta);
100 }
101 
102 /*
103 This is the transition that signals that a thread is functioning.
104 Its main goal is to catch threads been witnessed before been fully registered.
105 */
106 void
mono_threads_transition_attach(MonoThreadInfo * info)107 mono_threads_transition_attach (MonoThreadInfo* info)
108 {
109 	int raw_state, cur_state, suspend_count;
110 
111 retry_state_change:
112 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
113 	switch (cur_state) {
114 	case STATE_STARTING:
115 		if (!(suspend_count == 0))
116 			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
117 		if (mono_atomic_cas_i32 (&info->thread_state, STATE_RUNNING, raw_state) != raw_state)
118 			goto retry_state_change;
119 		trace_state_change ("ATTACH", info, raw_state, STATE_RUNNING, 0);
120 		break;
121 	default:
122 		mono_fatal_with_history ("Cannot transition current thread from %s with ATTACH", state_name (cur_state));
123 	}
124 }
125 
126 /*
127 This is the transition that signals that a thread is no longer registered with the runtime.
128 Its main goal is to catch threads been witnessed after they detach.
129 
130 This returns TRUE is the transition succeeded.
131 If it returns false it means that there's a pending suspend that should be acted upon.
132 */
133 gboolean
mono_threads_transition_detach(MonoThreadInfo * info)134 mono_threads_transition_detach (MonoThreadInfo *info)
135 {
136 	int raw_state, cur_state, suspend_count;
137 
138 retry_state_change:
139 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
140 	switch (cur_state) {
141 	case STATE_RUNNING:
142 	case STATE_BLOCKING: /* An OS thread on coop goes STARTING->BLOCKING->RUNNING->BLOCKING->DETACHED */
143 		if (!(suspend_count == 0))
144 			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
145 		if (mono_atomic_cas_i32 (&info->thread_state, STATE_DETACHED, raw_state) != raw_state)
146 			goto retry_state_change;
147 		trace_state_change ("DETACH", info, raw_state, STATE_DETACHED, 0);
148 		return TRUE;
149 	case STATE_ASYNC_SUSPEND_REQUESTED: //Can't detach until whoever asked us to suspend to be happy with us
150 		return FALSE;
151 
152 /*
153 STATE_ASYNC_SUSPENDED: Code should not be running while suspended.
154 STATE_SELF_SUSPENDED: Code should not be running while suspended.
155 STATE_SELF_SUSPEND_REQUESTED: This is a bug in the self suspend code that didn't execute the second part of it
156 STATE_BLOCKING_AND_SUSPENDED: This is a bug in coop x suspend that resulted the thread in an undetachable state.
157 */
158 	default:
159 		mono_fatal_with_history ("Cannot transition current thread %p from %s with DETACH", info, state_name (cur_state));
160 	}
161 }
162 
163 /*
164 This transition initiates the suspension of another thread.
165 
166 Returns one of the following values:
167 
168 - AsyncSuspendInitSuspend: Thread suspend requested, async suspend needs to be done.
169 - AsyncSuspendAlreadySuspended: Thread already suspended, nothing to do.
170 - AsyncSuspendWait: Self suspend in progress, asked it to notify us. Caller must add target to the notification set.
171 - AsyncSuspendBlocking: Thread in blocking state
172 */
173 MonoRequestAsyncSuspendResult
mono_threads_transition_request_async_suspension(MonoThreadInfo * info)174 mono_threads_transition_request_async_suspension (MonoThreadInfo *info)
175 {
176 	int raw_state, cur_state, suspend_count;
177 	g_assert (info != mono_thread_info_current ());
178 
179 retry_state_change:
180 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
181 
182 	switch (cur_state) {
183 	case STATE_RUNNING: //Post an async suspend request
184 		if (!(suspend_count == 0))
185 			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
186 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPEND_REQUESTED, 1), raw_state) != raw_state)
187 			goto retry_state_change;
188 		trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, STATE_ASYNC_SUSPEND_REQUESTED, 1);
189 		return AsyncSuspendInitSuspend; //This is the first async suspend request against the target
190 
191 	case STATE_ASYNC_SUSPENDED:
192 	case STATE_SELF_SUSPENDED: //Async suspend can suspend the same thread multiple times as it starts from the outside
193 	case STATE_BLOCKING_AND_SUSPENDED:
194 		if (!(suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX))
195 			mono_fatal_with_history ("suspend_count = %d, but should be > 0 and < THREAD_SUSPEND_COUNT_MAX", suspend_count);
196 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
197 			goto retry_state_change;
198 		trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, cur_state, 1);
199 		return AsyncSuspendAlreadySuspended; //Thread is already suspended so we don't need to wait it to suspend
200 
201 	case STATE_SELF_SUSPEND_REQUESTED: //This suspend needs to notify the initiator, so we need to promote the suspend to async
202 		if (!(suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX))
203 			mono_fatal_with_history ("suspend_count = %d, but should be > 0 and < THREAD_SUSPEND_COUNT_MAX", suspend_count);
204 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPEND_REQUESTED, suspend_count + 1), raw_state) != raw_state)
205 			goto retry_state_change;
206 		trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, STATE_ASYNC_SUSPEND_REQUESTED, 1);
207 		return AsyncSuspendWait; //This is the first async suspend request, change the thread and let it notify us [1]
208 
209 	case STATE_BLOCKING:
210 		if (!(suspend_count < THREAD_SUSPEND_COUNT_MAX))
211 			mono_fatal_with_history ("suspend_count = %d, but should be < THREAD_SUSPEND_COUNT_MAX", suspend_count);
212 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count + 1), raw_state) != raw_state)
213 			goto retry_state_change;
214 		trace_state_change ("ASYNC_SUSPEND_REQUESTED", info, raw_state, cur_state, 1);
215 		return AsyncSuspendBlocking; //A thread in the blocking state has its state saved so we can treat it as suspended.
216 
217 /*
218 
219 [1] It's questionable on what to do if we hit the beginning of a self suspend.
220 The expected behavior is that the target should poll its state very soon so the the suspend latency should be minimal.
221 
222 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.
223 */
224 	default:
225 		mono_fatal_with_history ("Cannot transition thread %p from %s with ASYNC_SUSPEND_REQUESTED", mono_thread_info_get_tid (info), state_name (cur_state));
226 	}
227 	return (MonoRequestAsyncSuspendResult) FALSE;
228 }
229 
230 /*
231 Check the current state of the thread and try to init a self suspend.
232 This must be called with self state saved.
233 
234 Returns one of the following values:
235 
236 - Resumed: Async resume happened and current thread should keep running
237 - Suspend: Caller should wait for a resume signal
238 - SelfSuspendNotifyAndWait: Notify the suspend initiator and wait for a resume signals
239  suspend should start.
240 
241 */
242 MonoSelfSupendResult
mono_threads_transition_state_poll(MonoThreadInfo * info)243 mono_threads_transition_state_poll (MonoThreadInfo *info)
244 {
245 	int raw_state, cur_state, suspend_count;
246 	g_assert (mono_thread_info_is_current (info));
247 
248 retry_state_change:
249 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
250 	switch (cur_state) {
251 	case STATE_RUNNING:
252 		if (!(suspend_count == 0))
253 			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
254 		trace_state_change ("STATE_POLL", info, raw_state, cur_state, 0);
255 		return SelfSuspendResumed; //We're fine, don't suspend
256 
257 	case STATE_ASYNC_SUSPEND_REQUESTED: //Async suspend requested, service it with a self suspend
258 	case STATE_SELF_SUSPEND_REQUESTED: //Start the self suspend process
259 		if (!(suspend_count > 0))
260 			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
261 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_SELF_SUSPENDED, suspend_count), raw_state) != raw_state)
262 			goto retry_state_change;
263 		trace_state_change ("STATE_POLL", info, raw_state, STATE_SELF_SUSPENDED, 0);
264 		if (cur_state == STATE_SELF_SUSPEND_REQUESTED)
265 			return SelfSuspendWait; //Caller should wait for resume
266 		else
267 			return SelfSuspendNotifyAndWait; //Caller should notify suspend initiator and wait for resume
268 
269 /*
270 STATE_ASYNC_SUSPENDED: Code should not be running while suspended.
271 STATE_SELF_SUSPENDED: Code should not be running while suspended.
272 STATE_BLOCKING:
273 STATE_BLOCKING_AND_SUSPENDED: Pool is a local state transition. No VM activities are allowed while in blocking mode.
274 */
275 	default:
276 		mono_fatal_with_history ("Cannot transition thread %p from %s with STATE_POLL", mono_thread_info_get_tid (info), state_name (cur_state));
277 	}
278 }
279 
280 /*
281 Try to resume a suspended thread.
282 
283 Returns one of the following values:
284 - Sucess: The thread was resumed.
285 - Error: The thread was not suspended in the first place. [2]
286 - InitSelfResume: The thread is blocked on self suspend and should be resumed
287 - InitAsycResume: The thread is blocked on async suspend and should be resumed
288 - ResumeInitBlockingResume: The thread was suspended on the exit path of blocking state and should be resumed
289 
290 [2] This threading system uses an unsigned suspend count. Which means a resume cannot be
291 used as a suspend permit and cancel each other.
292 
293 Suspend permits are really useful to implement managed synchronization structures that
294 don't consume native resources. The downside is that they further complicate the design of this
295 system as the RUNNING state now has a non zero suspend counter.
296 
297 It can be implemented in the future if we find resume/suspend races that cannot be (efficiently) fixed by other means.
298 
299 One major issue with suspend permits is runtime facilities (GC, debugger) that must have the target suspended when requested.
300 This would make permits really harder to add.
301 */
302 MonoResumeResult
mono_threads_transition_request_resume(MonoThreadInfo * info)303 mono_threads_transition_request_resume (MonoThreadInfo* info)
304 {
305 	int raw_state, cur_state, suspend_count;
306 	g_assert (info != mono_thread_info_current ()); //One can't self resume [3]
307 
308 retry_state_change:
309 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
310 	switch (cur_state) {
311 	case STATE_RUNNING: //Thread already running.
312 		if (!(suspend_count == 0))
313 			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
314 		trace_state_change ("RESUME", info, raw_state, cur_state, 0);
315 		return ResumeError; //Resume failed because thread was not blocked
316 
317 	case STATE_BLOCKING: //Blocking, might have a suspend count, we decrease if it's > 0
318 		if (suspend_count == 0) {
319 			trace_state_change ("RESUME", info, raw_state, cur_state, 0);
320 			return ResumeError;
321 		} else {
322 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
323 					goto retry_state_change;
324 			trace_state_change ("RESUME", info, raw_state, cur_state, -1);
325 			return ResumeOk; //Resume worked and there's nothing for the caller to do.
326 		}
327 		break;
328 	case STATE_ASYNC_SUSPENDED:
329 	case STATE_SELF_SUSPENDED:
330 	case STATE_BLOCKING_AND_SUSPENDED: //Decrease the suspend_count and maybe resume
331 		if (!(suspend_count > 0))
332 			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
333 		if (suspend_count > 1) {
334 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
335 					goto retry_state_change;
336 			trace_state_change ("RESUME", info, raw_state, cur_state, -1);
337 
338 			return ResumeOk; //Resume worked and there's nothing for the caller to do.
339 		} else {
340 			if (mono_atomic_cas_i32 (&info->thread_state, STATE_RUNNING, raw_state) != raw_state)
341 				goto retry_state_change;
342 			trace_state_change ("RESUME", info, raw_state, STATE_RUNNING, -1);
343 
344 			if (cur_state == STATE_ASYNC_SUSPENDED)
345 				return ResumeInitAsyncResume; //Resume worked and caller must do async resume
346 			else if (cur_state == STATE_SELF_SUSPENDED)
347 				return ResumeInitSelfResume; //Resume worked and caller must do self resume
348 			else
349 				return ResumeInitBlockingResume; //Resume worked and caller must do blocking resume
350 		}
351 
352 	case STATE_SELF_SUSPEND_REQUESTED: //Self suspend was requested but another thread decided to resume it.
353 		if (!(suspend_count > 0))
354 			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
355 		if (suspend_count > 1) {
356 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (cur_state, suspend_count - 1), raw_state) != raw_state)
357 					goto retry_state_change;
358 			trace_state_change ("RESUME", info, raw_state, cur_state, -1);
359 		} else {
360 			if (mono_atomic_cas_i32 (&info->thread_state, STATE_RUNNING, raw_state) != raw_state)
361 				goto retry_state_change;
362 			trace_state_change ("RESUME", info, raw_state, STATE_RUNNING, -1);
363 		}
364 		return ResumeOk; //Resume worked and there's nothing for the caller to do (the target never actually suspend).
365 /*
366 
367 STATE_ASYNC_SUSPEND_REQUESTED: Only one async suspend/resume operation can be in flight, so a resume cannot witness an internal state of suspend
368 
369 [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
370 sense as a suspend permit, but as explained in [2] we don't support it so this is a bug.
371 
372 [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
373 is similar to the one described in [2] when this is used for as a synchronization primitive.
374 
375 If this turns to be a problem we should either implement [2] or make this an invalid transition.
376 
377 */
378 	default:
379 		mono_fatal_with_history ("Cannot transition thread %p from %s with REQUEST_RESUME", mono_thread_info_get_tid (info), state_name (cur_state));
380 	}
381 }
382 
383 /*
384 This performs the last step of async suspend.
385 
386 Returns TRUE if the caller should wait for resume.
387 */
388 gboolean
mono_threads_transition_finish_async_suspend(MonoThreadInfo * info)389 mono_threads_transition_finish_async_suspend (MonoThreadInfo* info)
390 {
391 	int raw_state, cur_state, suspend_count;
392 
393 retry_state_change:
394 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
395 	switch (cur_state) {
396 
397 	case STATE_SELF_SUSPENDED: //async suspend raced with self suspend and lost
398 	case STATE_BLOCKING_AND_SUSPENDED: //async suspend raced with blocking and lost
399 		trace_state_change ("FINISH_ASYNC_SUSPEND", info, raw_state, cur_state, 0);
400 		return FALSE; //let self suspend wait
401 
402 	case STATE_ASYNC_SUSPEND_REQUESTED:
403 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_ASYNC_SUSPENDED, suspend_count), raw_state) != raw_state)
404 			goto retry_state_change;
405 		trace_state_change ("FINISH_ASYNC_SUSPEND", info, raw_state, STATE_ASYNC_SUSPENDED, 0);
406 		return TRUE; //Async suspend worked, now wait for resume
407 
408 /*
409 STATE_RUNNING: A thread cannot escape suspension once requested.
410 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.
411 STATE_SELF_SUSPEND_REQUESTED: When self suspend and async suspend happen together, they converge to async suspend so this state should not be visible.
412 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.
413 */
414 	default:
415 		mono_fatal_with_history ("Cannot transition thread %p from %s with FINISH_ASYNC_SUSPEND", mono_thread_info_get_tid (info), state_name (cur_state));
416 	}
417 }
418 
419 /*
420 This transitions the thread into a cooperative state where it's assumed to be suspended but can continue.
421 
422 Native runtime code might want to put itself into a state where the thread is considered suspended but can keep running.
423 That state only works as long as the only managed state touched is blitable and was pinned before the transition.
424 
425 It returns the action the caller must perform:
426 
427 - Continue: Entered blocking state sucessfully;
428 - PollAndRetry: Async suspend raced and won, try to suspend and then retry;
429 
430 */
431 MonoDoBlockingResult
mono_threads_transition_do_blocking(MonoThreadInfo * info)432 mono_threads_transition_do_blocking (MonoThreadInfo* info)
433 {
434 	int raw_state, cur_state, suspend_count;
435 
436 retry_state_change:
437 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
438 	switch (cur_state) {
439 
440 	case STATE_RUNNING: //transition to blocked
441 		if (!(suspend_count == 0))
442 			mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
443 		if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING, suspend_count), raw_state) != raw_state)
444 			goto retry_state_change;
445 		trace_state_change ("DO_BLOCKING", info, raw_state, STATE_BLOCKING, 0);
446 		return DoBlockingContinue;
447 
448 	case STATE_ASYNC_SUSPEND_REQUESTED:
449 		if (!(suspend_count > 0))
450 			mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
451 		trace_state_change ("DO_BLOCKING", info, raw_state, cur_state, 0);
452 		return DoBlockingPollAndRetry;
453 /*
454 STATE_ASYNC_SUSPENDED
455 STATE_SELF_SUSPENDED: Code should not be running while suspended.
456 STATE_SELF_SUSPEND_REQUESTED: A blocking operation must not be done while trying to self suspend
457 STATE_BLOCKING:
458 STATE_BLOCKING_AND_SUSPENDED: Blocking is not nestabled
459 */
460 	default:
461 		mono_fatal_with_history ("Cannot transition thread %p from %s with DO_BLOCKING", mono_thread_info_get_tid (info), state_name (cur_state));
462 	}
463 }
464 
465 /*
466 This is the exit transition from the blocking state. If this thread is logically async suspended it will have to wait
467 until its resumed before continuing.
468 
469 It returns one of:
470 -Aborted: The blocking operation was aborted and not properly restored. Aborts can happen due to lazy loading and some n2m transitions;
471 -Ok: Done with blocking, just move on;
472 -Wait: This thread was async suspended, wait for resume
473 
474 */
475 MonoDoneBlockingResult
mono_threads_transition_done_blocking(MonoThreadInfo * info)476 mono_threads_transition_done_blocking (MonoThreadInfo* info)
477 {
478 	int raw_state, cur_state, suspend_count;
479 
480 retry_state_change:
481 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
482 	switch (cur_state) {
483 	case STATE_BLOCKING:
484 		if (suspend_count == 0) {
485 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count), raw_state) != raw_state)
486 				goto retry_state_change;
487 			trace_state_change ("DONE_BLOCKING", info, raw_state, STATE_RUNNING, 0);
488 			return DoneBlockingOk;
489 		} else {
490 			if (!(suspend_count >= 0))
491 				mono_fatal_with_history ("suspend_count = %d, but should be >= 0", suspend_count);
492 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING_AND_SUSPENDED, suspend_count), raw_state) != raw_state)
493 				goto retry_state_change;
494 			trace_state_change ("DONE_BLOCKING", info, raw_state, STATE_BLOCKING_AND_SUSPENDED, 0);
495 			return DoneBlockingWait;
496 		}
497 
498 /*
499 STATE_RUNNING: //Blocking was aborted and not properly restored
500 STATE_ASYNC_SUSPEND_REQUESTED: //Blocking was aborted, not properly restored and now there's a pending suspend
501 STATE_ASYNC_SUSPENDED
502 STATE_SELF_SUSPENDED: Code should not be running while suspended.
503 STATE_SELF_SUSPEND_REQUESTED: A blocking operation must not be done while trying to self suspend
504 STATE_BLOCKING_AND_SUSPENDED: This an exit state of done blocking
505 */
506 	default:
507 		mono_fatal_with_history ("Cannot transition thread %p from %s with DONE_BLOCKING", mono_thread_info_get_tid (info), state_name (cur_state));
508 	}
509 }
510 
511 /*
512 Transition a thread in what should be a blocking state back to running state.
513 This is different that done blocking because the goal is to get back to blocking once we're done.
514 This is required to be able to bail out of blocking in case we're back to inside the runtime.
515 
516 It returns one of:
517 -Ignore: Thread was not in blocking, nothing to do;
518 -IgnoreAndPool: Thread was not blocking and there's a pending suspend that needs to be processed;
519 -Ok: Blocking state successfully aborted;
520 -Wait: Blocking state successfully aborted, there's a pending suspend to be processed though
521 */
522 MonoAbortBlockingResult
mono_threads_transition_abort_blocking(THREAD_INFO_TYPE * info)523 mono_threads_transition_abort_blocking (THREAD_INFO_TYPE* info)
524 {
525 	int raw_state, cur_state, suspend_count;
526 
527 retry_state_change:
528 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
529 	switch (cur_state) {
530 	case STATE_RUNNING: //thread already in runnable state
531 		trace_state_change ("ABORT_BLOCKING", info, raw_state, cur_state, 0);
532 		return AbortBlockingIgnore;
533 
534 	case STATE_ASYNC_SUSPEND_REQUESTED: //thread is runnable and have a pending suspend
535 		trace_state_change ("ABORT_BLOCKING", info, raw_state, cur_state, 0);
536 		return AbortBlockingIgnoreAndPoll;
537 
538 	case STATE_BLOCKING:
539 		if (suspend_count == 0) {
540 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_RUNNING, suspend_count), raw_state) != raw_state)
541 				goto retry_state_change;
542 			trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_RUNNING, 0);
543 			return AbortBlockingOk;
544 		} else {
545 			if (!(suspend_count > 0))
546 				mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
547 			if (mono_atomic_cas_i32 (&info->thread_state, build_thread_state (STATE_BLOCKING_AND_SUSPENDED, suspend_count), raw_state) != raw_state)
548 				goto retry_state_change;
549 			trace_state_change ("ABORT_BLOCKING", info, raw_state, STATE_BLOCKING_AND_SUSPENDED, 0);
550 			return AbortBlockingWait;
551 		}
552 /*
553 STATE_ASYNC_SUSPENDED:
554 STATE_SELF_SUSPENDED: Code should not be running while suspended.
555 STATE_SELF_SUSPEND_REQUESTED: A blocking operation must not be done while trying to self suspend.
556 STATE_BLOCKING_AND_SUSPENDED: This is an exit state of done blocking, can't happen here.
557 */
558 	default:
559 		mono_fatal_with_history ("Cannot transition thread %p from %s with DONE_BLOCKING", mono_thread_info_get_tid (info), state_name (cur_state));
560 	}
561 }
562 
563 MonoThreadUnwindState*
mono_thread_info_get_suspend_state(MonoThreadInfo * info)564 mono_thread_info_get_suspend_state (MonoThreadInfo *info)
565 {
566 	int raw_state, cur_state, suspend_count;
567 	UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, info);
568 	switch (cur_state) {
569 	case STATE_ASYNC_SUSPENDED:
570 		return &info->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX];
571 	case STATE_SELF_SUSPENDED:
572 	case STATE_BLOCKING_AND_SUSPENDED:
573 		return &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
574 	case STATE_BLOCKING:
575 		if (suspend_count > 0)
576 			return &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
577 	default:
578 /*
579 STATE_RUNNING
580 STATE_SELF_SUSPENDED
581 STATE_ASYNC_SUSPEND_REQUESTED
582 STATE_BLOCKING: All those are invalid suspend states.
583 */
584 		g_error ("Cannot read suspend state when target %p is in the %s state", mono_thread_info_get_tid (info), state_name (cur_state));
585 	}
586 }
587 
588 // State checking code
589 /**
590  * Return TRUE is the thread is in a runnable state.
591 */
592 gboolean
mono_thread_info_is_running(MonoThreadInfo * info)593 mono_thread_info_is_running (MonoThreadInfo *info)
594 {
595 	switch (get_thread_state (info->thread_state)) {
596 	case STATE_RUNNING:
597 	case STATE_ASYNC_SUSPEND_REQUESTED:
598 	case STATE_SELF_SUSPEND_REQUESTED:
599 	case STATE_BLOCKING:
600 		return TRUE;
601 	}
602 	return FALSE;
603 }
604 
605 /**
606  * Return TRUE is the thread is in an usable (suspendable) state
607  */
608 gboolean
mono_thread_info_is_live(MonoThreadInfo * info)609 mono_thread_info_is_live (MonoThreadInfo *info)
610 {
611 	switch (get_thread_state (info->thread_state)) {
612 	case STATE_STARTING:
613 	case STATE_DETACHED:
614 		return FALSE;
615 	}
616 	return TRUE;
617 }
618 
619 int
mono_thread_info_suspend_count(MonoThreadInfo * info)620 mono_thread_info_suspend_count (MonoThreadInfo *info)
621 {
622 	return get_thread_suspend_count (info->thread_state);
623 }
624 
625 int
mono_thread_info_current_state(MonoThreadInfo * info)626 mono_thread_info_current_state (MonoThreadInfo *info)
627 {
628 	return get_thread_state (info->thread_state);
629 }
630 
631 const char*
mono_thread_state_name(int state)632 mono_thread_state_name (int state)
633 {
634 	return state_name (state);
635 }
636 
637 gboolean
mono_thread_is_gc_unsafe_mode(void)638 mono_thread_is_gc_unsafe_mode (void)
639 {
640 	MonoThreadInfo *cur = mono_thread_info_current ();
641 
642 	if (!cur)
643 		return FALSE;
644 
645 	switch (mono_thread_info_current_state (cur)) {
646 	case STATE_RUNNING:
647 	case STATE_ASYNC_SUSPEND_REQUESTED:
648 	case STATE_SELF_SUSPEND_REQUESTED:
649 		return TRUE;
650 	default:
651 		return FALSE;
652 	}
653 }
654