[threads] Fix async call in coop (#4423)
[mono.git] / mono / utils / mono-threads-coop.c
1  /*
2  * mono-threads.c: Coop threading
3  *
4  * Author:
5  *      Rodrigo Kumpera (kumpera@gmail.com)
6  *
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.
9  */
10
11 #include <config.h>
12
13 /* enable pthread extensions */
14 #ifdef TARGET_MACH
15 #define _DARWIN_C_SOURCE
16 #endif
17
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>
31
32 #ifdef TARGET_OSX
33 #include <mono/utils/mach-support.h>
34 #endif
35
36 #ifdef _MSC_VER
37 // TODO: Find MSVC replacement for __builtin_unwind_init
38 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
39 #else 
40 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
41 #endif
42
43 volatile size_t mono_polling_required;
44
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
48
49 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
50 static MonoNativeTlsKey coop_reset_count_stack_key;
51
52 static void
53 coop_tls_push (gpointer cookie)
54 {
55         GArray *stack;
56
57         stack = mono_native_tls_get_value (coop_reset_count_stack_key);
58         if (!stack) {
59                 stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
60                 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
61         }
62
63         g_array_append_val (stack, cookie);
64 }
65
66 static void
67 coop_tls_pop (gpointer received_cookie)
68 {
69         GArray *stack;
70         gpointer expected_cookie;
71
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);
75
76         expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
77         stack->len --;
78
79         if (0 == stack->len) {
80                 g_array_free (stack,TRUE);
81                 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
82         }
83
84         if (expected_cookie != received_cookie)
85                 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
86 }
87
88 #endif
89
90 static void
91 check_info (MonoThreadInfo *info, const gchar *action, const gchar *state)
92 {
93         if (!info)
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);
99 }
100
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;
106
107 static void
108 mono_threads_state_poll_with_info (MonoThreadInfo *info);
109
110 void
111 mono_threads_state_poll (void)
112 {
113         mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
114 }
115
116 static void
117 mono_threads_state_poll_with_info (MonoThreadInfo *info)
118 {
119         g_assert (mono_threads_is_coop_enabled ());
120
121         ++coop_do_polling_count;
122
123         if (!info)
124                 return;
125
126         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
127
128         /* Fast check for pending suspend requests */
129         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
130                 return;
131
132         ++coop_save_count;
133         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
134
135         /* commit the saved state and notify others if needed */
136         switch (mono_threads_transition_state_poll (info)) {
137         case SelfSuspendResumed:
138                 break;
139         case SelfSuspendWait:
140                 mono_thread_info_wait_for_resume (info);
141                 break;
142         case SelfSuspendNotifyAndWait:
143                 mono_threads_notify_initiator_of_suspend (info);
144                 mono_thread_info_wait_for_resume (info);
145                 break;
146         }
147
148         if (info->async_target) {
149                 info->async_target (info->user_data);
150                 info->async_target = NULL;
151                 info->user_data = NULL;
152         }
153 }
154
155 static volatile gpointer* dummy_global;
156
157 static MONO_NEVER_INLINE
158 void*
159 return_stack_ptr (gpointer *i)
160 {
161         dummy_global = i;
162         return i;
163 }
164
165 static void
166 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
167 {
168         MonoThreadUnwindState *state;
169         int stackdata_size;
170         gpointer dummy;
171         void* stackdata_end = return_stack_ptr (&dummy);
172
173         SAVE_REGS_ON_STACK;
174
175         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
176
177         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
178
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);
183
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);
186
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);
190
191         state->gc_stackdata_size = stackdata_size;
192 }
193
194 static gpointer
195 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
196
197 gpointer
198 mono_threads_enter_gc_safe_region (gpointer *stackdata)
199 {
200         return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
201 }
202
203 gpointer
204 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
205 {
206         gpointer cookie;
207
208         if (!mono_threads_is_coop_enabled ())
209                 return NULL;
210
211         cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
212
213 #ifdef ENABLE_CHECKED_BUILD_GC
214         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
215                 coop_tls_push (cookie);
216 #endif
217
218         return cookie;
219 }
220
221 gpointer
222 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
223 {
224         return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
225 }
226
227 static gpointer
228 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
229 {
230         if (!mono_threads_is_coop_enabled ())
231                 return NULL;
232
233         ++coop_do_blocking_count;
234
235         check_info (info, "enter", "safe");
236
237         copy_stack_data (info, stackdata);
238
239 retry:
240         ++coop_save_count;
241         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
242
243         switch (mono_threads_transition_do_blocking (info)) {
244         case DoBlockingContinue:
245                 break;
246         case DoBlockingPollAndRetry:
247                 mono_threads_state_poll_with_info (info);
248                 goto retry;
249         }
250
251         return info;
252 }
253
254 void
255 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
256 {
257         if (!mono_threads_is_coop_enabled ())
258                 return;
259
260 #ifdef ENABLE_CHECKED_BUILD_GC
261         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
262                 coop_tls_pop (cookie);
263 #endif
264
265         mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
266 }
267
268 void
269 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
270 {
271         MonoThreadInfo *info;
272
273         if (!mono_threads_is_coop_enabled ())
274                 return;
275
276         info = (MonoThreadInfo *)cookie;
277
278         check_info (info, "exit", "safe");
279
280         switch (mono_threads_transition_done_blocking (info)) {
281         case DoneBlockingOk:
282                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
283                 break;
284         case DoneBlockingWait:
285                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
286                 mono_thread_info_wait_for_resume (info);
287                 break;
288         default:
289                 g_error ("Unknown thread state");
290         }
291
292         if (info->async_target) {
293                 info->async_target (info->user_data);
294                 info->async_target = NULL;
295                 info->user_data = NULL;
296         }
297 }
298
299 void
300 mono_threads_assert_gc_safe_region (void)
301 {
302         MONO_REQ_GC_SAFE_MODE;
303 }
304
305 gpointer
306 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
307 {
308         return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
309 }
310
311 gpointer
312 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
313 {
314         gpointer cookie;
315
316         if (!mono_threads_is_coop_enabled ())
317                 return NULL;
318
319         cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
320
321 #ifdef ENABLE_CHECKED_BUILD_GC
322         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
323                 coop_tls_push (cookie);
324 #endif
325
326         return cookie;
327 }
328
329 gpointer
330 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
331 {
332         return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
333 }
334
335 gpointer
336 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
337 {
338         if (!mono_threads_is_coop_enabled ())
339                 return NULL;
340
341         ++coop_reset_blocking_count;
342
343         check_info (info, "enter", "unsafe");
344
345         copy_stack_data (info, stackdata);
346
347         switch (mono_threads_transition_abort_blocking (info)) {
348         case AbortBlockingIgnore:
349                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
350                 return NULL;
351         case AbortBlockingIgnoreAndPoll:
352                 mono_threads_state_poll_with_info (info);
353                 return NULL;
354         case AbortBlockingOk:
355                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
356                 break;
357         case AbortBlockingWait:
358                 mono_thread_info_wait_for_resume (info);
359                 break;
360         default:
361                 g_error ("Unknown thread state");
362         }
363
364         if (info->async_target) {
365                 info->async_target (info->user_data);
366                 info->async_target = NULL;
367                 info->user_data = NULL;
368         }
369
370         return info;
371 }
372
373 gpointer
374 mono_threads_enter_gc_unsafe_region_cookie (void)
375 {
376         MonoThreadInfo *info;
377
378         g_assert (mono_threads_is_coop_enabled ());
379
380         info = mono_thread_info_current_unchecked ();
381
382         check_info (info, "enter (cookie)", "unsafe");
383
384 #ifdef ENABLE_CHECKED_BUILD_GC
385         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
386                 coop_tls_push (info);
387 #endif
388
389         return info;
390 }
391
392 void
393 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
394 {
395         if (!mono_threads_is_coop_enabled ())
396                 return;
397
398 #ifdef ENABLE_CHECKED_BUILD_GC
399         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
400                 coop_tls_pop (cookie);
401 #endif
402
403         mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
404 }
405
406 void
407 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
408 {
409         if (!mono_threads_is_coop_enabled ())
410                 return;
411
412         if (!cookie)
413                 return;
414
415         mono_threads_enter_gc_safe_region_unbalanced (stackdata);
416 }
417
418 void
419 mono_threads_assert_gc_unsafe_region (void)
420 {
421         MONO_REQ_GC_UNSAFE_MODE;
422 }
423
424 gboolean
425 mono_threads_is_coop_enabled (void)
426 {
427 #if defined(USE_COOP_GC)
428         return TRUE;
429 #else
430         static int is_coop_enabled = -1;
431         if (G_UNLIKELY (is_coop_enabled == -1))
432                 is_coop_enabled = g_getenv ("MONO_ENABLE_COOP") != NULL ? 1 : 0;
433         return is_coop_enabled == 1;
434 #endif
435 }
436
437
438 void
439 mono_threads_coop_init (void)
440 {
441         if (!mono_threads_is_coop_enabled ())
442                 return;
443
444         mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
445         mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
446         mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
447         mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
448         mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
449         //See the above for what's wrong here.
450
451 #ifdef ENABLE_CHECKED_BUILD_GC
452         mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
453 #endif
454 }
455
456 void
457 mono_threads_coop_begin_global_suspend (void)
458 {
459         if (mono_threads_is_coop_enabled ())
460                 mono_polling_required = 1;
461 }
462
463 void
464 mono_threads_coop_end_global_suspend (void)
465 {
466         if (mono_threads_is_coop_enabled ())
467                 mono_polling_required = 0;
468 }