[build] Skips RemoteExecuted bases tests on monodroid
[mono-project.git] / mono / utils / mono-threads-coop.c
blobab8f3e793bb8478c59453faab1313d26b9693150
1 /**
2 * \file
3 * Coop threading
5 * Author:
6 * Rodrigo Kumpera (kumpera@gmail.com)
8 * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
12 #include <config.h>
14 /* enable pthread extensions */
15 #ifdef TARGET_MACH
16 #define _DARWIN_C_SOURCE
17 #endif
19 #include <mono/utils/mono-compiler.h>
20 #include <mono/utils/mono-threads.h>
21 #include <mono/utils/mono-tls.h>
22 #include <mono/utils/hazard-pointer.h>
23 #include <mono/utils/mono-memory-model.h>
24 #include <mono/utils/mono-mmap.h>
25 #include <mono/utils/atomic.h>
26 #include <mono/utils/mono-time.h>
27 #include <mono/utils/mono-counters.h>
28 #include <mono/utils/mono-threads-coop.h>
29 #include <mono/utils/mono-threads-api.h>
30 #include <mono/utils/checked-build.h>
31 #include <mono/utils/mono-threads-debug.h>
33 #ifdef TARGET_OSX
34 #include <mono/utils/mach-support.h>
35 #endif
37 #ifdef _MSC_VER
38 // TODO: Find MSVC replacement for __builtin_unwind_init
39 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
40 #elif defined (HOST_WASM)
41 //TODO: figure out wasm stack scanning
42 #define SAVE_REGS_ON_STACK do {} while (0)
43 #else
44 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
45 #endif
47 volatile size_t mono_polling_required;
49 // FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
50 // perhaps with a per-thread cookie in the high bits.
51 #ifdef ENABLE_CHECKED_BUILD_GC
53 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
54 static MonoNativeTlsKey coop_reset_count_stack_key;
56 static void
57 coop_tls_push (gpointer cookie)
59 GArray *stack;
61 stack = mono_native_tls_get_value (coop_reset_count_stack_key);
62 if (!stack) {
63 stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
64 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
67 g_array_append_val (stack, cookie);
70 static void
71 coop_tls_pop (gpointer received_cookie)
73 GArray *stack;
74 gpointer expected_cookie;
76 stack = mono_native_tls_get_value (coop_reset_count_stack_key);
77 if (!stack || 0 == stack->len)
78 mono_fatal_with_history ("Received cookie %p but found no stack at all\n", received_cookie);
80 expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
81 stack->len --;
83 if (0 == stack->len) {
84 g_array_free (stack,TRUE);
85 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
88 if (expected_cookie != received_cookie)
89 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
92 #endif
94 static void
95 check_info (MonoThreadInfo *info, const gchar *action, const gchar *state, const char *func)
97 if (!info)
98 g_error ("%s Cannot %s GC %s region if the thread is not attached", func, action, state);
99 if (!mono_thread_info_is_current (info))
100 g_error ("%s [%p] Cannot %s GC %s region on a different thread", func, mono_thread_info_get_tid (info), action, state);
101 if (!mono_thread_info_is_live (info))
102 g_error ("%s [%p] Cannot %s GC %s region if the thread is not live", func, mono_thread_info_get_tid (info), action, state);
105 static int coop_reset_blocking_count;
106 static int coop_try_blocking_count;
107 static int coop_do_blocking_count;
108 static int coop_do_polling_count;
109 static int coop_save_count;
111 static void
112 mono_threads_state_poll_with_info (MonoThreadInfo *info);
114 void
115 mono_threads_state_poll (void)
117 mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
120 static void
121 mono_threads_state_poll_with_info (MonoThreadInfo *info)
123 g_assert (mono_threads_is_blocking_transition_enabled ());
125 ++coop_do_polling_count;
127 if (!info)
128 return;
130 THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
132 /* Fast check for pending suspend requests */
133 if (!(info->thread_state & STATE_ASYNC_SUSPEND_REQUESTED))
134 return;
136 ++coop_save_count;
137 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
139 /* commit the saved state and notify others if needed */
140 switch (mono_threads_transition_state_poll (info)) {
141 case SelfSuspendResumed:
142 break;
143 case SelfSuspendNotifyAndWait:
144 mono_threads_notify_initiator_of_suspend (info);
145 mono_thread_info_wait_for_resume (info);
146 break;
149 if (info->async_target) {
150 info->async_target (info->user_data);
151 info->async_target = NULL;
152 info->user_data = NULL;
156 static volatile gpointer* dummy_global;
158 static MONO_NEVER_INLINE
159 void*
160 return_stack_ptr (gpointer *i)
162 dummy_global = i;
163 return i;
166 static void
167 copy_stack_data (MonoThreadInfo *info, MonoStackData *stackdata_begin)
169 MonoThreadUnwindState *state;
170 int stackdata_size;
171 gpointer dummy;
172 void* stackdata_end = return_stack_ptr (&dummy);
174 SAVE_REGS_ON_STACK;
176 state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
178 stackdata_size = (char*)mono_stackdata_get_stackpointer (stackdata_begin) - (char*)stackdata_end;
180 const char *function_name = mono_stackdata_get_function_name (stackdata_begin);
182 if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
183 g_error ("%s stackdata_begin (%p) must be %d-byte aligned", function_name, stackdata_begin, SIZEOF_VOID_P);
184 if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
185 g_error ("%s stackdata_end (%p) must be %d-byte aligned", function_name, stackdata_end, SIZEOF_VOID_P);
187 if (stackdata_size <= 0)
188 g_error ("%s stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", function_name, stackdata_size, stackdata_begin, stackdata_end);
190 g_byte_array_set_size (info->stackdata, stackdata_size);
191 state->gc_stackdata = info->stackdata->data;
192 memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
194 state->gc_stackdata_size = stackdata_size;
197 static gpointer
198 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, MonoStackData *stackdata);
200 gpointer
201 mono_threads_enter_gc_safe_region_internal (MonoStackData *stackdata)
203 return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
206 gpointer
207 mono_threads_enter_gc_safe_region (gpointer *stackpointer)
209 MONO_STACKDATA (stackdata);
210 stackdata.stackpointer = stackpointer;
211 return mono_threads_enter_gc_safe_region_internal (&stackdata);
214 gpointer
215 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, MonoStackData *stackdata)
217 gpointer cookie;
219 if (!mono_threads_is_blocking_transition_enabled ())
220 return NULL;
222 cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
224 #ifdef ENABLE_CHECKED_BUILD_GC
225 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
226 coop_tls_push (cookie);
227 #endif
229 return cookie;
232 gpointer
233 mono_threads_enter_gc_safe_region_unbalanced_internal (MonoStackData *stackdata)
235 return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
238 gpointer
239 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackpointer)
241 MONO_STACKDATA (stackdata);
242 stackdata.stackpointer = stackpointer;
243 return mono_threads_enter_gc_safe_region_unbalanced_internal (&stackdata);
246 static gpointer
247 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, MonoStackData *stackdata)
249 if (!mono_threads_is_blocking_transition_enabled ())
250 return NULL;
252 ++coop_do_blocking_count;
254 const char *function_name = mono_stackdata_get_function_name (stackdata);
256 check_info (info, "enter", "safe", function_name);
258 copy_stack_data (info, stackdata);
260 retry:
261 ++coop_save_count;
262 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
264 switch (mono_threads_transition_do_blocking (info, function_name)) {
265 case DoBlockingContinue:
266 break;
267 case DoBlockingPollAndRetry:
268 mono_threads_state_poll_with_info (info);
269 goto retry;
272 return info;
275 void
276 mono_threads_exit_gc_safe_region_internal (gpointer cookie, MonoStackData *stackdata)
278 if (!mono_threads_is_blocking_transition_enabled ())
279 return;
281 #ifdef ENABLE_CHECKED_BUILD_GC
282 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
283 coop_tls_pop (cookie);
284 #endif
286 mono_threads_exit_gc_safe_region_unbalanced_internal (cookie, stackdata);
289 void
290 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackpointer)
292 MONO_STACKDATA (stackdata);
293 stackdata.stackpointer = stackpointer;
294 mono_threads_exit_gc_safe_region_internal (cookie, &stackdata);
297 void
298 mono_threads_exit_gc_safe_region_unbalanced_internal (gpointer cookie, MonoStackData *stackdata)
300 MonoThreadInfo *info;
302 if (!mono_threads_is_blocking_transition_enabled ())
303 return;
305 info = (MonoThreadInfo *)cookie;
307 const char *function_name = mono_stackdata_get_function_name (stackdata);
309 check_info (info, "exit", "safe", function_name);
311 switch (mono_threads_transition_done_blocking (info, function_name)) {
312 case DoneBlockingOk:
313 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
314 break;
315 case DoneBlockingWait:
316 /* If full coop suspend, we're just waiting for the initiator
317 * to resume us. If hybrid suspend, we were either self
318 * suspended cooperatively from async_suspend_requested (same
319 * as full coop), or we were suspended preemptively while in
320 * blocking and we're waiting for two things: the suspend
321 * signal handler to run and notify the initiator and
322 * immediately return, and then for the resume. */
323 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
324 mono_thread_info_wait_for_resume (info);
325 break;
326 default:
327 g_error ("Unknown thread state");
330 if (info->async_target) {
331 info->async_target (info->user_data);
332 info->async_target = NULL;
333 info->user_data = NULL;
337 void
338 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackpointer)
340 MONO_STACKDATA (stackdata);
341 stackdata.stackpointer = stackpointer;
342 mono_threads_exit_gc_safe_region_unbalanced_internal (cookie, &stackdata);
345 void
346 mono_threads_assert_gc_safe_region (void)
348 MONO_REQ_GC_SAFE_MODE;
351 gpointer
352 mono_threads_enter_gc_unsafe_region_internal (MonoStackData *stackdata)
354 return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
357 gpointer
358 mono_threads_enter_gc_unsafe_region (gpointer *stackpointer)
360 MONO_STACKDATA (stackdata);
361 stackdata.stackpointer = stackpointer;
362 return mono_threads_enter_gc_unsafe_region_internal (&stackdata);
365 gpointer
366 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, MonoStackData *stackdata)
368 gpointer cookie;
370 if (!mono_threads_is_blocking_transition_enabled ())
371 return NULL;
373 cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
375 #ifdef ENABLE_CHECKED_BUILD_GC
376 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
377 coop_tls_push (cookie);
378 #endif
380 return cookie;
383 gpointer
384 mono_threads_enter_gc_unsafe_region_unbalanced_internal (MonoStackData *stackdata)
386 return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
389 gpointer
390 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackpointer)
392 MONO_STACKDATA (stackdata);
393 stackdata.stackpointer = stackpointer;
394 return mono_threads_enter_gc_unsafe_region_unbalanced_internal (&stackdata);
397 gpointer
398 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, MonoStackData *stackdata)
400 if (!mono_threads_is_blocking_transition_enabled ())
401 return NULL;
403 ++coop_reset_blocking_count;
405 const char *function_name = mono_stackdata_get_function_name (stackdata);
407 check_info (info, "enter", "unsafe", function_name);
409 copy_stack_data (info, stackdata);
411 switch (mono_threads_transition_abort_blocking (info, function_name)) {
412 case AbortBlockingIgnore:
413 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
414 return NULL;
415 case AbortBlockingIgnoreAndPoll:
416 mono_threads_state_poll_with_info (info);
417 return NULL;
418 case AbortBlockingOk:
419 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
420 break;
421 case AbortBlockingWait:
422 /* If full coop suspend, we're just waiting for the initiator
423 * to resume us. If hybrid suspend, we were either self
424 * suspended cooperatively from async_suspend_requested (same
425 * as full coop), or we were suspended preemptively while in
426 * blocking and we're waiting for two things: the suspend
427 * signal handler to run and notify the initiator and
428 * immediately return, and then for the resume. */
429 mono_thread_info_wait_for_resume (info);
430 break;
431 default:
432 g_error ("Unknown thread state %s", function_name);
435 if (info->async_target) {
436 info->async_target (info->user_data);
437 info->async_target = NULL;
438 info->user_data = NULL;
441 return info;
444 gpointer
445 mono_threads_enter_gc_unsafe_region_cookie (void)
447 MonoThreadInfo *info;
449 g_assert (mono_threads_is_blocking_transition_enabled ());
451 info = mono_thread_info_current_unchecked ();
453 check_info (info, "enter (cookie)", "unsafe", "");
455 #ifdef ENABLE_CHECKED_BUILD_GC
456 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
457 coop_tls_push (info);
458 #endif
460 return info;
463 void
464 mono_threads_exit_gc_unsafe_region_internal (gpointer cookie, MonoStackData *stackdata)
466 if (!mono_threads_is_blocking_transition_enabled ())
467 return;
469 #ifdef ENABLE_CHECKED_BUILD_GC
470 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
471 coop_tls_pop (cookie);
472 #endif
474 mono_threads_exit_gc_unsafe_region_unbalanced_internal (cookie, stackdata);
477 void
478 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackpointer)
480 MONO_STACKDATA (stackdata);
481 stackdata.stackpointer = stackpointer;
482 mono_threads_exit_gc_unsafe_region_internal (cookie, &stackdata);
485 void
486 mono_threads_exit_gc_unsafe_region_unbalanced_internal (gpointer cookie, MonoStackData *stackdata)
488 if (!mono_threads_is_blocking_transition_enabled ())
489 return;
491 if (!cookie)
492 return;
494 mono_threads_enter_gc_safe_region_unbalanced_internal (stackdata);
497 void
498 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackpointer)
500 MONO_STACKDATA (stackdata);
501 stackdata.stackpointer = stackpointer;
502 mono_threads_exit_gc_unsafe_region_unbalanced_internal (cookie, &stackdata);
505 void
506 mono_threads_assert_gc_unsafe_region (void)
508 MONO_REQ_GC_UNSAFE_MODE;
511 gboolean
512 mono_threads_is_cooperative_suspension_enabled (void)
514 #if defined(ENABLE_COOP_SUSPEND)
515 return TRUE;
516 #else
517 static int is_coop_enabled = -1;
518 if (G_UNLIKELY (is_coop_enabled == -1))
519 is_coop_enabled = (g_hasenv ("MONO_ENABLE_COOP") || g_hasenv ("MONO_ENABLE_COOP_SUSPEND")) ? 1 : 0;
520 return is_coop_enabled == 1;
521 #endif
524 gboolean
525 mono_threads_is_blocking_transition_enabled (void)
527 #if defined(ENABLE_COOP_SUSPEND) || defined(ENABLE_HYBRID_SUSPEND)
528 return TRUE;
529 #else
530 static int is_blocking_transition_enabled = -1;
531 if (G_UNLIKELY (is_blocking_transition_enabled == -1))
532 is_blocking_transition_enabled = (g_hasenv ("MONO_ENABLE_COOP") || g_hasenv ("MONO_ENABLE_COOP_SUSPEND") || g_hasenv ("MONO_ENABLE_HYBRID_SUSPEND") || g_hasenv ("MONO_ENABLE_BLOCKING_TRANSITION")) ? 1 : 0;
533 return is_blocking_transition_enabled == 1;
534 #endif
537 gboolean
538 mono_threads_is_hybrid_suspension_enabled (void)
540 #if defined(ENABLE_HYBRID_SUSPEND)
541 return TRUE;
542 #else
543 static int is_hybrid_suspension_enabled = -1;
544 if (G_UNLIKELY (is_hybrid_suspension_enabled == -1))
545 is_hybrid_suspension_enabled = (g_hasenv ("MONO_ENABLE_HYBRID_SUSPEND")) ? 1 : 0;
546 return is_hybrid_suspension_enabled == 1;
547 #endif
551 void
552 mono_threads_coop_init (void)
554 if (!mono_threads_are_safepoints_enabled () && !mono_threads_is_blocking_transition_enabled ())
555 return;
557 mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
558 mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
559 mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
560 mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
561 mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
562 //See the above for what's wrong here.
564 #ifdef ENABLE_CHECKED_BUILD_GC
565 mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
566 #endif
569 void
570 mono_threads_coop_begin_global_suspend (void)
572 if (mono_threads_are_safepoints_enabled ())
573 mono_polling_required = 1;
576 void
577 mono_threads_coop_end_global_suspend (void)
579 if (mono_threads_are_safepoints_enabled ())
580 mono_polling_required = 0;