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
47 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
48 MonoNativeTlsKey coop_reset_count_stack_key;
49 static int coop_tls_push (int v) {
50 GArray *stack = mono_native_tls_get_value (coop_reset_count_stack_key);
52 stack = g_array_new (FALSE,FALSE,sizeof(int));
53 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
55 g_array_append_val (stack, v);
58 static int coop_tls_pop (int *v) {
59 GArray *stack = mono_native_tls_get_value (coop_reset_count_stack_key);
60 if (!stack || 0 == stack->len)
63 *v = g_array_index (stack, int, stack->len);
66 g_array_free (stack,TRUE);
67 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
73 static int coop_reset_blocking_count;
74 static int coop_try_blocking_count;
75 static int coop_do_blocking_count;
76 static int coop_do_polling_count;
77 static int coop_save_count;
80 mono_threads_state_poll (void)
84 g_assert (mono_threads_is_coop_enabled ());
86 ++coop_do_polling_count;
88 info = mono_thread_info_current_unchecked ();
91 THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
93 /* Fast check for pending suspend requests */
94 if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
98 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
100 /* commit the saved state and notify others if needed */
101 switch (mono_threads_transition_state_poll (info)) {
102 case SelfSuspendResumed:
104 case SelfSuspendWait:
105 mono_thread_info_wait_for_resume (info);
107 case SelfSuspendNotifyAndWait:
108 mono_threads_notify_initiator_of_suspend (info);
109 mono_thread_info_wait_for_resume (info);
122 copy_stack_data (MonoThreadInfo *info, void* stackdata_begin)
124 MonoThreadUnwindState *state;
126 void* stackdata_end = return_stack_ptr ();
130 state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
132 stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
134 if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
135 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
136 if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
137 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
139 if (stackdata_size <= 0)
140 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
142 g_byte_array_set_size (info->stackdata, stackdata_size);
143 state->gc_stackdata = info->stackdata->data;
144 memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
146 state->gc_stackdata_size = stackdata_size;
150 mono_threads_prepare_blocking (void* stackdata)
152 MonoThreadInfo *info;
154 if (!mono_threads_is_coop_enabled ())
157 ++coop_do_blocking_count;
159 info = mono_thread_info_current_unchecked ();
160 /* If the thread is not attached, it doesn't make sense prepare for suspend. */
161 if (!info || !mono_thread_info_is_live (info)) {
162 THREADS_SUSPEND_DEBUG ("PREPARE-BLOCKING failed %p\n", info ? mono_thread_info_get_tid (info) : NULL);
166 copy_stack_data (info, stackdata);
170 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
172 switch (mono_threads_transition_do_blocking (info)) {
173 case DoBlockingContinue:
175 case DoBlockingPollAndRetry:
176 mono_threads_state_poll ();
184 mono_threads_finish_blocking (void *cookie, void* stackdata)
186 static gboolean warned_about_bad_transition;
187 MonoThreadInfo *info;
189 if (!mono_threads_is_coop_enabled ())
192 info = (MonoThreadInfo *)cookie;
196 g_assert (info == mono_thread_info_current_unchecked ());
198 switch (mono_threads_transition_done_blocking (info)) {
199 case DoneBlockingAborted:
200 if (!warned_about_bad_transition) {
201 warned_about_bad_transition = TRUE;
202 g_warning ("[%p] Blocking call ended in running state for, this might lead to unbound GC pauses.", mono_thread_info_get_tid (info));
204 mono_threads_state_poll ();
207 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
209 case DoneBlockingWait:
210 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
211 mono_thread_info_wait_for_resume (info);
214 g_error ("Unknown thread state");
220 mono_threads_cookie_for_reset_blocking_start (MonoThreadInfo *info, int reset_blocking_count)
222 #ifdef ENABLE_CHECKED_BUILD_GC
223 g_assert (reset_blocking_count != 0);
224 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
225 int level = coop_tls_push (reset_blocking_count);
226 //g_warning("Entering reset nest; level %d; cookie %d\n", level, reset_blocking_count);
227 return (void *)(intptr_t)reset_blocking_count;
235 mono_threads_reset_blocking_start (void* stackdata)
237 MonoThreadInfo *info;
239 if (!mono_threads_is_coop_enabled ())
242 info = mono_thread_info_current_unchecked ();
244 #ifdef ENABLE_CHECKED_BUILD_GC
245 int reset_blocking_count = InterlockedIncrement (&coop_reset_blocking_count);
246 // In this mode, the blocking count is used as the reset cookie. We would prefer
247 // (but do not require) this to be unique across invocations and threads.
248 if (reset_blocking_count == 0) // We *do* require it be nonzero
249 reset_blocking_count = coop_reset_blocking_count = 1;
251 int reset_blocking_count = ++coop_reset_blocking_count;
254 /* If the thread is not attached, it doesn't make sense prepare for suspend. */
255 if (!info || !mono_thread_info_is_live (info))
258 copy_stack_data (info, stackdata);
260 switch (mono_threads_transition_abort_blocking (info)) {
261 case AbortBlockingIgnore:
262 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
264 case AbortBlockingIgnoreAndPoll:
265 mono_threads_state_poll ();
267 case AbortBlockingOk:
268 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
270 case AbortBlockingWait:
271 mono_thread_info_wait_for_resume (info);
274 g_error ("Unknown thread state");
277 return mono_threads_cookie_for_reset_blocking_start (info, reset_blocking_count);
281 mono_threads_reset_blocking_end (void *cookie, void* stackdata)
283 if (!mono_threads_is_coop_enabled ())
289 #ifdef ENABLE_CHECKED_BUILD_GC
290 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
291 int received_cookie = (int)(intptr_t)cookie;
293 int level = coop_tls_pop (&desired_cookie);
294 //g_warning("Leaving reset nest; back to level %d; desired cookie %d; received cookie %d\n", level, desired_cookie, received_cookie);
296 mono_fatal_with_history ("Expected cookie %d but found no stack at all, %x\n", desired_cookie, level);
297 if (desired_cookie != received_cookie)
298 mono_fatal_with_history ("Expected cookie %d but received %d\n", desired_cookie, received_cookie);
299 } else // Notice this matches the line after the endif
302 g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
305 mono_threads_prepare_blocking (stackdata);
309 mono_threads_init_coop (void)
311 if (!mono_threads_is_coop_enabled ())
314 mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
315 mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
316 mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
317 mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
318 mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
319 //See the above for what's wrong here.
321 #ifdef ENABLE_CHECKED_BUILD_GC
322 mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
327 mono_threads_coop_begin_global_suspend (void)
329 if (mono_threads_is_coop_enabled ())
330 mono_polling_required = 1;
334 mono_threads_coop_end_global_suspend (void)
336 if (mono_threads_is_coop_enabled ())
337 mono_polling_required = 0;
341 mono_threads_enter_gc_unsafe_region (gpointer* stackdata)
343 if (!mono_threads_is_coop_enabled ())
346 return mono_threads_reset_blocking_start (stackdata);
350 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer* stackdata)
352 if (!mono_threads_is_coop_enabled ())
355 mono_threads_reset_blocking_end (cookie, stackdata);
359 mono_threads_assert_gc_unsafe_region (void)
361 MONO_REQ_GC_UNSAFE_MODE;
365 mono_threads_enter_gc_safe_region (gpointer *stackdata)
367 if (!mono_threads_is_coop_enabled ())
370 return mono_threads_prepare_blocking (stackdata);
374 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
376 if (!mono_threads_is_coop_enabled ())
379 mono_threads_finish_blocking (cookie, stackdata);
383 mono_threads_assert_gc_safe_region (void)
385 MONO_REQ_GC_SAFE_MODE;