Merge pull request #2969 from ludovic-henry/coop-no-try-finally
[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
31 #ifdef TARGET_OSX
32 #include <mono/utils/mach-support.h>
33 #endif
34
35 #ifdef _MSC_VER
36 // TODO: Find MSVC replacement for __builtin_unwind_init
37 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
38 #else 
39 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
40 #endif
41
42 volatile size_t mono_polling_required;
43
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
48 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
49 static MonoNativeTlsKey coop_reset_count_stack_key;
50
51 static void
52 coop_tls_push (gpointer cookie)
53 {
54         GArray *stack;
55
56         stack = mono_native_tls_get_value (coop_reset_count_stack_key);
57         if (!stack) {
58                 stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
59                 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
60         }
61
62         g_array_append_val (stack, cookie);
63 }
64
65 static void
66 coop_tls_pop (gpointer received_cookie)
67 {
68         GArray *stack;
69         gpointer expected_cookie;
70
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);
74
75         expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
76         stack->len --;
77
78         if (0 == stack->len) {
79                 g_array_free (stack,TRUE);
80                 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
81         }
82
83         if (expected_cookie != received_cookie)
84                 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
85 }
86
87 #endif
88
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;
94
95 void
96 mono_threads_state_poll (void)
97 {
98         MonoThreadInfo *info;
99
100         g_assert (mono_threads_is_coop_enabled ());
101
102         ++coop_do_polling_count;
103
104         info = mono_thread_info_current_unchecked ();
105         if (!info)
106                 return;
107         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
108
109         /* Fast check for pending suspend requests */
110         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
111                 return;
112
113         ++coop_save_count;
114         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
115
116         /* commit the saved state and notify others if needed */
117         switch (mono_threads_transition_state_poll (info)) {
118         case SelfSuspendResumed:
119                 return;
120         case SelfSuspendWait:
121                 mono_thread_info_wait_for_resume (info);
122                 break;
123         case SelfSuspendNotifyAndWait:
124                 mono_threads_notify_initiator_of_suspend (info);
125                 mono_thread_info_wait_for_resume (info);
126                 break;
127         }
128 }
129
130 static void *
131 return_stack_ptr ()
132 {
133         gpointer i;
134         return &i;
135 }
136
137 static void
138 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
139 {
140         MonoThreadUnwindState *state;
141         int stackdata_size;
142         void* stackdata_end = return_stack_ptr ();
143
144         SAVE_REGS_ON_STACK;
145
146         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
147
148         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
149
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);
154
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);
157
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);
161
162         state->gc_stackdata_size = stackdata_size;
163 }
164
165 gpointer
166 mono_threads_prepare_blocking (gpointer *stackdata)
167 {
168         gpointer cookie;
169
170         if (!mono_threads_is_coop_enabled ())
171                 return NULL;
172
173         cookie = mono_threads_prepare_blocking_unbalanced (stackdata);
174
175 #ifdef ENABLE_CHECKED_BUILD_GC
176         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
177                 coop_tls_push (cookie);
178 #endif
179
180         return cookie;
181 }
182
183 gpointer
184 mono_threads_prepare_blocking_unbalanced (gpointer *stackdata)
185 {
186         MonoThreadInfo *info;
187
188         if (!mono_threads_is_coop_enabled ())
189                 return NULL;
190
191         ++coop_do_blocking_count;
192
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);
197                 return NULL;
198         }
199
200         copy_stack_data (info, stackdata);
201
202 retry:
203         ++coop_save_count;
204         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
205
206         switch (mono_threads_transition_do_blocking (info)) {
207         case DoBlockingContinue:
208                 break;
209         case DoBlockingPollAndRetry:
210                 mono_threads_state_poll ();
211                 goto retry;
212         }
213
214         return info;
215 }
216
217 void
218 mono_threads_finish_blocking (gpointer cookie, gpointer *stackdata)
219 {
220         if (!mono_threads_is_coop_enabled ())
221                 return;
222
223 #ifdef ENABLE_CHECKED_BUILD_GC
224         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
225                 coop_tls_pop (cookie);
226 #endif
227
228         mono_threads_finish_blocking_unbalanced (cookie, stackdata);
229 }
230
231 void
232 mono_threads_finish_blocking_unbalanced (gpointer cookie, gpointer *stackdata)
233 {
234         static gboolean warned_about_bad_transition;
235         MonoThreadInfo *info;
236
237         if (!mono_threads_is_coop_enabled ())
238                 return;
239
240         info = (MonoThreadInfo *)cookie;
241         if (!info)
242                 return;
243
244         g_assert (info == mono_thread_info_current_unchecked ());
245
246         switch (mono_threads_transition_done_blocking (info)) {
247         case DoneBlockingAborted:
248                 if (!warned_about_bad_transition) {
249                         warned_about_bad_transition = TRUE;
250                         g_warning ("[%p] Blocking call ended in running state for, this might lead to unbound GC pauses.", mono_thread_info_get_tid (info));
251                 }
252                 mono_threads_state_poll ();
253                 break;
254         case DoneBlockingOk:
255                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
256                 break;
257         case DoneBlockingWait:
258                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
259                 mono_thread_info_wait_for_resume (info);
260                 break;
261         default:
262                 g_error ("Unknown thread state");
263         }
264 }
265
266 gpointer
267 mono_threads_cookie_for_reset_blocking_start (MonoThreadInfo *info)
268 {
269         g_assert (mono_threads_is_coop_enabled ());
270
271 #ifdef ENABLE_CHECKED_BUILD_GC
272         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
273                 coop_tls_push (info);
274 #endif
275
276         return info;
277 }
278
279 gpointer
280 mono_threads_reset_blocking_start (gpointer *stackdata)
281 {
282         gpointer cookie;
283
284         if (!mono_threads_is_coop_enabled ())
285                 return NULL;
286
287         cookie = mono_threads_reset_blocking_start_unbalanced (stackdata);
288
289 #ifdef ENABLE_CHECKED_BUILD_GC
290         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
291                 coop_tls_push (cookie);
292 #endif
293
294         return cookie;
295 }
296
297 gpointer
298 mono_threads_reset_blocking_start_unbalanced (gpointer *stackdata)
299 {
300         MonoThreadInfo *info;
301
302         if (!mono_threads_is_coop_enabled ())
303                 return NULL;
304
305         ++coop_reset_blocking_count;
306
307         info = mono_thread_info_current_unchecked ();
308
309         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
310         if (!info || !mono_thread_info_is_live (info))
311                 return NULL;
312
313         copy_stack_data (info, stackdata);
314
315         switch (mono_threads_transition_abort_blocking (info)) {
316         case AbortBlockingIgnore:
317                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
318                 return NULL;
319         case AbortBlockingIgnoreAndPoll:
320                 mono_threads_state_poll ();
321                 return NULL;
322         case AbortBlockingOk:
323                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
324                 break;
325         case AbortBlockingWait:
326                 mono_thread_info_wait_for_resume (info);
327                 break;
328         default:
329                 g_error ("Unknown thread state");
330         }
331
332         return info;
333 }
334
335 void
336 mono_threads_reset_blocking_end (gpointer cookie, gpointer *stackdata)
337 {
338         if (!mono_threads_is_coop_enabled ())
339                 return;
340
341 #ifdef ENABLE_CHECKED_BUILD_GC
342         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
343                 coop_tls_pop (cookie);
344 #endif
345
346         mono_threads_reset_blocking_end_unbalanced (cookie, stackdata);
347 }
348
349 void
350 mono_threads_reset_blocking_end_unbalanced (gpointer cookie, gpointer *stackdata)
351 {
352         if (!mono_threads_is_coop_enabled ())
353                 return;
354
355         if (!cookie)
356                 return;
357
358 #ifdef ENABLE_CHECKED_BUILD_GC
359         if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
360 #endif
361         {
362                 g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
363         }
364
365         mono_threads_prepare_blocking_unbalanced (stackdata);
366 }
367
368 void
369 mono_threads_init_coop (void)
370 {
371         if (!mono_threads_is_coop_enabled ())
372                 return;
373
374         mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
375         mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
376         mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
377         mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
378         mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
379         //See the above for what's wrong here.
380
381 #ifdef ENABLE_CHECKED_BUILD_GC
382         mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
383 #endif
384 }
385
386 void
387 mono_threads_coop_begin_global_suspend (void)
388 {
389         if (mono_threads_is_coop_enabled ())
390                 mono_polling_required = 1;
391 }
392
393 void
394 mono_threads_coop_end_global_suspend (void)
395 {
396         if (mono_threads_is_coop_enabled ())
397                 mono_polling_required = 0;
398 }
399
400 gpointer
401 mono_threads_enter_gc_unsafe_region (gpointer* stackdata)
402 {
403         if (!mono_threads_is_coop_enabled ())
404                 return NULL;
405
406         return mono_threads_reset_blocking_start (stackdata);
407 }
408
409 void
410 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer* stackdata)
411 {
412         if (!mono_threads_is_coop_enabled ())
413                 return;
414
415         mono_threads_reset_blocking_end (cookie, stackdata);
416 }
417
418 void
419 mono_threads_assert_gc_unsafe_region (void)
420 {
421         MONO_REQ_GC_UNSAFE_MODE;
422 }
423
424 gpointer
425 mono_threads_enter_gc_safe_region (gpointer *stackdata)
426 {
427         if (!mono_threads_is_coop_enabled ())
428                 return NULL;
429
430         return mono_threads_prepare_blocking (stackdata);
431 }
432
433 void
434 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
435 {
436         if (!mono_threads_is_coop_enabled ())
437                 return;
438
439         mono_threads_finish_blocking (cookie, stackdata);
440 }
441
442 void
443 mono_threads_assert_gc_safe_region (void)
444 {
445         MONO_REQ_GC_SAFE_MODE;
446 }