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>
30 #include <mono/utils/mono-threads-debug.h>
33 #include <mono/utils/mach-support.h>
37 // TODO: Find MSVC replacement for __builtin_unwind_init
38 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
40 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
43 volatile size_t mono_polling_required
;
45 // FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
46 // perhaps with a per-thread cookie in the high bits.
47 #ifdef ENABLE_CHECKED_BUILD_GC
49 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
50 static MonoNativeTlsKey coop_reset_count_stack_key
;
53 coop_tls_push (gpointer cookie
)
57 stack
= mono_native_tls_get_value (coop_reset_count_stack_key
);
59 stack
= g_array_new (FALSE
, FALSE
, sizeof(gpointer
));
60 mono_native_tls_set_value (coop_reset_count_stack_key
, stack
);
63 g_array_append_val (stack
, cookie
);
67 coop_tls_pop (gpointer received_cookie
)
70 gpointer expected_cookie
;
72 stack
= mono_native_tls_get_value (coop_reset_count_stack_key
);
73 if (!stack
|| 0 == stack
->len
)
74 mono_fatal_with_history ("Received cookie %p but found no stack at all\n", received_cookie
);
76 expected_cookie
= g_array_index (stack
, gpointer
, stack
->len
- 1);
79 if (0 == stack
->len
) {
80 g_array_free (stack
,TRUE
);
81 mono_native_tls_set_value (coop_reset_count_stack_key
, NULL
);
84 if (expected_cookie
!= received_cookie
)
85 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie
, expected_cookie
);
91 check_info (MonoThreadInfo
*info
, const gchar
*action
, const gchar
*state
)
94 g_error ("Cannot %s GC %s region if the thread is not attached", action
, state
);
95 if (!mono_thread_info_is_current (info
))
96 g_error ("[%p] Cannot %s GC %s region on a different thread", mono_thread_info_get_tid (info
), action
, state
);
97 if (!mono_thread_info_is_live (info
))
98 g_error ("[%p] Cannot %s GC %s region if the thread is not live", mono_thread_info_get_tid (info
), action
, state
);
101 static int coop_reset_blocking_count
;
102 static int coop_try_blocking_count
;
103 static int coop_do_blocking_count
;
104 static int coop_do_polling_count
;
105 static int coop_save_count
;
108 mono_threads_state_poll_with_info (MonoThreadInfo
*info
);
111 mono_threads_state_poll (void)
113 mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
117 mono_threads_state_poll_with_info (MonoThreadInfo
*info
)
119 g_assert (mono_threads_is_coop_enabled ());
121 ++coop_do_polling_count
;
126 THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info
));
128 /* Fast check for pending suspend requests */
129 if (!(info
->thread_state
& (STATE_ASYNC_SUSPEND_REQUESTED
| STATE_SELF_SUSPEND_REQUESTED
)))
133 mono_threads_get_runtime_callbacks ()->thread_state_init (&info
->thread_saved_state
[SELF_SUSPEND_STATE_INDEX
]);
135 /* commit the saved state and notify others if needed */
136 switch (mono_threads_transition_state_poll (info
)) {
137 case SelfSuspendResumed
:
139 case SelfSuspendWait
:
140 mono_thread_info_wait_for_resume (info
);
142 case SelfSuspendNotifyAndWait
:
143 mono_threads_notify_initiator_of_suspend (info
);
144 mono_thread_info_wait_for_resume (info
);
148 if (info
->async_target
) {
149 info
->async_target (info
->user_data
);
150 info
->async_target
= NULL
;
151 info
->user_data
= NULL
;
155 static volatile gpointer
* dummy_global
;
157 static MONO_NEVER_INLINE
159 return_stack_ptr (gpointer
*i
)
166 copy_stack_data (MonoThreadInfo
*info
, gpointer
*stackdata_begin
)
168 MonoThreadUnwindState
*state
;
171 void* stackdata_end
= return_stack_ptr (&dummy
);
175 state
= &info
->thread_saved_state
[SELF_SUSPEND_STATE_INDEX
];
177 stackdata_size
= (char*)stackdata_begin
- (char*)stackdata_end
;
179 if (((gsize
) stackdata_begin
& (SIZEOF_VOID_P
- 1)) != 0)
180 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin
, SIZEOF_VOID_P
);
181 if (((gsize
) stackdata_end
& (SIZEOF_VOID_P
- 1)) != 0)
182 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end
, SIZEOF_VOID_P
);
184 if (stackdata_size
<= 0)
185 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size
, stackdata_begin
, stackdata_end
);
187 g_byte_array_set_size (info
->stackdata
, stackdata_size
);
188 state
->gc_stackdata
= info
->stackdata
->data
;
189 memcpy (state
->gc_stackdata
, stackdata_end
, stackdata_size
);
191 state
->gc_stackdata_size
= stackdata_size
;
195 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo
*info
, gpointer
*stackdata
);
198 mono_threads_enter_gc_safe_region (gpointer
*stackdata
)
200 return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata
);
204 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo
*info
, gpointer
*stackdata
)
208 if (!mono_threads_is_coop_enabled ())
211 cookie
= mono_threads_enter_gc_safe_region_unbalanced_with_info (info
, stackdata
);
213 #ifdef ENABLE_CHECKED_BUILD_GC
214 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
215 coop_tls_push (cookie
);
222 mono_threads_enter_gc_safe_region_unbalanced (gpointer
*stackdata
)
224 return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata
);
228 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo
*info
, gpointer
*stackdata
)
230 if (!mono_threads_is_coop_enabled ())
233 ++coop_do_blocking_count
;
235 check_info (info
, "enter", "safe");
237 copy_stack_data (info
, stackdata
);
241 mono_threads_get_runtime_callbacks ()->thread_state_init (&info
->thread_saved_state
[SELF_SUSPEND_STATE_INDEX
]);
243 switch (mono_threads_transition_do_blocking (info
)) {
244 case DoBlockingContinue
:
246 case DoBlockingPollAndRetry
:
247 mono_threads_state_poll_with_info (info
);
255 mono_threads_exit_gc_safe_region (gpointer cookie
, gpointer
*stackdata
)
257 if (!mono_threads_is_coop_enabled ())
260 #ifdef ENABLE_CHECKED_BUILD_GC
261 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
262 coop_tls_pop (cookie
);
265 mono_threads_exit_gc_safe_region_unbalanced (cookie
, stackdata
);
269 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie
, gpointer
*stackdata
)
271 MonoThreadInfo
*info
;
273 if (!mono_threads_is_coop_enabled ())
276 info
= (MonoThreadInfo
*)cookie
;
278 check_info (info
, "exit", "safe");
280 switch (mono_threads_transition_done_blocking (info
)) {
282 info
->thread_saved_state
[SELF_SUSPEND_STATE_INDEX
].valid
= FALSE
;
284 case DoneBlockingWait
:
285 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
286 mono_thread_info_wait_for_resume (info
);
289 g_error ("Unknown thread state");
294 mono_threads_assert_gc_safe_region (void)
296 MONO_REQ_GC_SAFE_MODE
;
300 mono_threads_enter_gc_unsafe_region (gpointer
*stackdata
)
302 return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata
);
306 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE
*info
, gpointer
*stackdata
)
310 if (!mono_threads_is_coop_enabled ())
313 cookie
= mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info
, stackdata
);
315 #ifdef ENABLE_CHECKED_BUILD_GC
316 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
317 coop_tls_push (cookie
);
324 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer
*stackdata
)
326 return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata
);
330 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo
*info
, gpointer
*stackdata
)
332 if (!mono_threads_is_coop_enabled ())
335 ++coop_reset_blocking_count
;
337 check_info (info
, "enter", "unsafe");
339 copy_stack_data (info
, stackdata
);
341 switch (mono_threads_transition_abort_blocking (info
)) {
342 case AbortBlockingIgnore
:
343 info
->thread_saved_state
[SELF_SUSPEND_STATE_INDEX
].valid
= FALSE
;
345 case AbortBlockingIgnoreAndPoll
:
346 mono_threads_state_poll_with_info (info
);
348 case AbortBlockingOk
:
349 info
->thread_saved_state
[SELF_SUSPEND_STATE_INDEX
].valid
= FALSE
;
351 case AbortBlockingWait
:
352 mono_thread_info_wait_for_resume (info
);
355 g_error ("Unknown thread state");
362 mono_threads_enter_gc_unsafe_region_cookie (void)
364 MonoThreadInfo
*info
;
366 g_assert (mono_threads_is_coop_enabled ());
368 info
= mono_thread_info_current_unchecked ();
370 check_info (info
, "enter (cookie)", "unsafe");
372 #ifdef ENABLE_CHECKED_BUILD_GC
373 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
374 coop_tls_push (info
);
381 mono_threads_exit_gc_unsafe_region (gpointer cookie
, gpointer
*stackdata
)
383 if (!mono_threads_is_coop_enabled ())
386 #ifdef ENABLE_CHECKED_BUILD_GC
387 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC
))
388 coop_tls_pop (cookie
);
391 mono_threads_exit_gc_unsafe_region_unbalanced (cookie
, stackdata
);
395 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie
, gpointer
*stackdata
)
397 if (!mono_threads_is_coop_enabled ())
403 mono_threads_enter_gc_safe_region_unbalanced (stackdata
);
407 mono_threads_assert_gc_unsafe_region (void)
409 MONO_REQ_GC_UNSAFE_MODE
;
413 mono_threads_is_coop_enabled (void)
415 #if defined(USE_COOP_GC)
418 static int is_coop_enabled
= -1;
419 if (G_UNLIKELY (is_coop_enabled
== -1))
420 is_coop_enabled
= g_getenv ("MONO_ENABLE_COOP") != NULL
? 1 : 0;
421 return is_coop_enabled
== 1;
427 mono_threads_coop_init (void)
429 if (!mono_threads_is_coop_enabled ())
432 mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC
| MONO_COUNTER_INT
, &coop_reset_blocking_count
);
433 mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC
| MONO_COUNTER_INT
, &coop_try_blocking_count
);
434 mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC
| MONO_COUNTER_INT
, &coop_do_blocking_count
);
435 mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC
| MONO_COUNTER_INT
, &coop_do_polling_count
);
436 mono_counters_register ("Coop Save Count", MONO_COUNTER_GC
| MONO_COUNTER_INT
, &coop_save_count
);
437 //See the above for what's wrong here.
439 #ifdef ENABLE_CHECKED_BUILD_GC
440 mono_native_tls_alloc (&coop_reset_count_stack_key
, NULL
);
445 mono_threads_coop_begin_global_suspend (void)
447 if (mono_threads_is_coop_enabled ())
448 mono_polling_required
= 1;
452 mono_threads_coop_end_global_suspend (void)
454 if (mono_threads_is_coop_enabled ())
455 mono_polling_required
= 0;