2 * mono-threads.c: Coop threading
5 * Rodrigo Kumpera (kumpera@gmail.com)
7 * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
13 /* enable pthread extensions */
15 #define _DARWIN_C_SOURCE
18 #include <mono/utils/mono-compiler.h>
19 #include <mono/utils/mono-threads.h>
20 #include <mono/utils/mono-tls.h>
21 #include <mono/utils/hazard-pointer.h>
22 #include <mono/utils/mono-memory-model.h>
23 #include <mono/utils/mono-mmap.h>
24 #include <mono/utils/atomic.h>
25 #include <mono/utils/mono-time.h>
26 #include <mono/utils/mono-counters.h>
27 #include <mono/utils/mono-threads-coop.h>
28 #include <mono/utils/mono-threads-api.h>
29 #include <mono/utils/checked-build.h>
32 #include <mono/utils/mach-support.h>
36 // TODO: Find MSVC replacement for __builtin_unwind_init
37 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
39 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
42 volatile size_t mono_polling_required;
44 // FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
45 // perhaps with a per-thread cookie in the high bits.
46 #ifdef ENABLE_CHECKED_BUILD_GC
48 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
49 static MonoNativeTlsKey coop_reset_count_stack_key;
52 coop_tls_push (gpointer cookie)
56 stack = mono_native_tls_get_value (coop_reset_count_stack_key);
58 stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
59 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
62 g_array_append_val (stack, cookie);
66 coop_tls_pop (gpointer received_cookie)
69 gpointer expected_cookie;
71 stack = mono_native_tls_get_value (coop_reset_count_stack_key);
72 if (!stack || 0 == stack->len)
73 mono_fatal_with_history ("Received cookie %p but found no stack at all, %x\n", received_cookie);
75 expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
78 if (0 == stack->len) {
79 g_array_free (stack,TRUE);
80 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
83 if (expected_cookie != received_cookie)
84 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
89 static int coop_reset_blocking_count;
90 static int coop_try_blocking_count;
91 static int coop_do_blocking_count;
92 static int coop_do_polling_count;
93 static int coop_save_count;
96 mono_threads_state_poll (void)
100 g_assert (mono_threads_is_coop_enabled ());
102 ++coop_do_polling_count;
104 info = mono_thread_info_current_unchecked ();
107 THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
109 /* Fast check for pending suspend requests */
110 if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
114 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
116 /* commit the saved state and notify others if needed */
117 switch (mono_threads_transition_state_poll (info)) {
118 case SelfSuspendResumed:
120 case SelfSuspendWait:
121 mono_thread_info_wait_for_resume (info);
123 case SelfSuspendNotifyAndWait:
124 mono_threads_notify_initiator_of_suspend (info);
125 mono_thread_info_wait_for_resume (info);
138 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
140 MonoThreadUnwindState *state;
142 void* stackdata_end = return_stack_ptr ();
146 state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
148 stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
150 if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
151 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
152 if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
153 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
155 if (stackdata_size <= 0)
156 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
158 g_byte_array_set_size (info->stackdata, stackdata_size);
159 state->gc_stackdata = info->stackdata->data;
160 memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
162 state->gc_stackdata_size = stackdata_size;
166 mono_threads_enter_gc_safe_region (gpointer *stackdata)
170 if (!mono_threads_is_coop_enabled ())
173 cookie = mono_threads_enter_gc_safe_region_unbalanced (stackdata);
175 #ifdef ENABLE_CHECKED_BUILD_GC
176 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
177 coop_tls_push (cookie);
184 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
186 MonoThreadInfo *info;
188 if (!mono_threads_is_coop_enabled ())
191 ++coop_do_blocking_count;
193 info = mono_thread_info_current_unchecked ();
194 /* If the thread is not attached, it doesn't make sense prepare for suspend. */
195 if (!info || !mono_thread_info_is_live (info)) {
196 THREADS_SUSPEND_DEBUG ("PREPARE-BLOCKING failed %p\n", info ? mono_thread_info_get_tid (info) : NULL);
200 copy_stack_data (info, stackdata);
204 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
206 switch (mono_threads_transition_do_blocking (info)) {
207 case DoBlockingContinue:
209 case DoBlockingPollAndRetry:
210 mono_threads_state_poll ();
218 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
220 if (!mono_threads_is_coop_enabled ())
223 #ifdef ENABLE_CHECKED_BUILD_GC
224 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
225 coop_tls_pop (cookie);
228 mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
232 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
234 MonoThreadInfo *info;
236 if (!mono_threads_is_coop_enabled ())
239 info = (MonoThreadInfo *)cookie;
243 g_assert (info == mono_thread_info_current_unchecked ());
245 switch (mono_threads_transition_done_blocking (info)) {
247 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
249 case DoneBlockingWait:
250 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
251 mono_thread_info_wait_for_resume (info);
254 g_error ("Unknown thread state");
259 mono_threads_assert_gc_safe_region (void)
261 MONO_REQ_GC_SAFE_MODE;
265 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
269 if (!mono_threads_is_coop_enabled ())
272 cookie = mono_threads_enter_gc_unsafe_region_unbalanced (stackdata);
274 #ifdef ENABLE_CHECKED_BUILD_GC
275 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
276 coop_tls_push (cookie);
283 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
285 MonoThreadInfo *info;
287 if (!mono_threads_is_coop_enabled ())
290 ++coop_reset_blocking_count;
292 info = mono_thread_info_current_unchecked ();
294 /* If the thread is not attached, it doesn't make sense prepare for suspend. */
295 if (!info || !mono_thread_info_is_live (info))
298 copy_stack_data (info, stackdata);
300 switch (mono_threads_transition_abort_blocking (info)) {
301 case AbortBlockingIgnore:
302 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
304 case AbortBlockingIgnoreAndPoll:
305 mono_threads_state_poll ();
307 case AbortBlockingOk:
308 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
310 case AbortBlockingWait:
311 mono_thread_info_wait_for_resume (info);
314 g_error ("Unknown thread state");
321 mono_threads_enter_gc_unsafe_region_cookie (MonoThreadInfo *info)
323 g_assert (mono_threads_is_coop_enabled ());
325 #ifdef ENABLE_CHECKED_BUILD_GC
326 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
327 coop_tls_push (info);
334 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
336 if (!mono_threads_is_coop_enabled ())
339 #ifdef ENABLE_CHECKED_BUILD_GC
340 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
341 coop_tls_pop (cookie);
344 mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
348 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
350 if (!mono_threads_is_coop_enabled ())
356 #ifdef ENABLE_CHECKED_BUILD_GC
357 if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
360 g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
363 mono_threads_enter_gc_safe_region_unbalanced (stackdata);
367 mono_threads_assert_gc_unsafe_region (void)
369 MONO_REQ_GC_UNSAFE_MODE;
373 mono_threads_is_coop_enabled (void)
375 #if defined(USE_COOP_GC)
378 static int is_coop_enabled = -1;
379 if (G_UNLIKELY (is_coop_enabled == -1))
380 is_coop_enabled = g_getenv ("MONO_ENABLE_COOP") != NULL ? 1 : 0;
381 return is_coop_enabled == 1;
387 mono_threads_init_coop (void)
389 if (!mono_threads_is_coop_enabled ())
392 mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
393 mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
394 mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
395 mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
396 mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
397 //See the above for what's wrong here.
399 #ifdef ENABLE_CHECKED_BUILD_GC
400 mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
405 mono_threads_coop_begin_global_suspend (void)
407 if (mono_threads_is_coop_enabled ())
408 mono_polling_required = 1;
412 mono_threads_coop_end_global_suspend (void)
414 if (mono_threads_is_coop_enabled ())
415 mono_polling_required = 0;