Merge pull request #2983 from akoeplinger/fix-bug40462
[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 // 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);
51         if (!stack) {
52                 stack = g_array_new (FALSE,FALSE,sizeof(int));
53                 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
54         }
55         g_array_append_val (stack, v);
56         return stack->len;
57 }
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)
61                 return -1;
62         stack->len--;
63         *v = g_array_index (stack, int, stack->len);
64         int len = stack->len;
65         if (0 == len) {
66                 g_array_free (stack,TRUE);
67                 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
68         }
69         return len;
70 }
71 #endif
72
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;
78
79 void
80 mono_threads_state_poll (void)
81 {
82         MonoThreadInfo *info;
83
84         g_assert (mono_threads_is_coop_enabled ());
85
86         ++coop_do_polling_count;
87
88         info = mono_thread_info_current_unchecked ();
89         if (!info)
90                 return;
91         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
92
93         /* Fast check for pending suspend requests */
94         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
95                 return;
96
97         ++coop_save_count;
98         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
99
100         /* commit the saved state and notify others if needed */
101         switch (mono_threads_transition_state_poll (info)) {
102         case SelfSuspendResumed:
103                 return;
104         case SelfSuspendWait:
105                 mono_thread_info_wait_for_resume (info);
106                 break;
107         case SelfSuspendNotifyAndWait:
108                 mono_threads_notify_initiator_of_suspend (info);
109                 mono_thread_info_wait_for_resume (info);
110                 break;
111         }
112 }
113
114 static void *
115 return_stack_ptr ()
116 {
117         gpointer i;
118         return &i;
119 }
120
121 static void
122 copy_stack_data (MonoThreadInfo *info, void* stackdata_begin)
123 {
124         MonoThreadUnwindState *state;
125         int stackdata_size;
126         void* stackdata_end = return_stack_ptr ();
127
128         SAVE_REGS_ON_STACK;
129
130         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
131
132         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
133
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);
138
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);
141
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);
145
146         state->gc_stackdata_size = stackdata_size;
147 }
148
149 void*
150 mono_threads_prepare_blocking (void* stackdata)
151 {
152         MonoThreadInfo *info;
153
154         if (!mono_threads_is_coop_enabled ())
155                 return NULL;
156
157         ++coop_do_blocking_count;
158
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);
163                 return NULL;
164         }
165
166         copy_stack_data (info, stackdata);
167
168 retry:
169         ++coop_save_count;
170         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
171
172         switch (mono_threads_transition_do_blocking (info)) {
173         case DoBlockingContinue:
174                 break;
175         case DoBlockingPollAndRetry:
176                 mono_threads_state_poll ();
177                 goto retry;
178         }
179
180         return info;
181 }
182
183 void
184 mono_threads_finish_blocking (void *cookie, void* stackdata)
185 {
186         static gboolean warned_about_bad_transition;
187         MonoThreadInfo *info;
188
189         if (!mono_threads_is_coop_enabled ())
190                 return;
191
192         info = (MonoThreadInfo *)cookie;
193         if (!info)
194                 return;
195
196         g_assert (info == mono_thread_info_current_unchecked ());
197
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));
203                 }
204                 mono_threads_state_poll ();
205                 break;
206         case DoneBlockingOk:
207                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
208                 break;
209         case DoneBlockingWait:
210                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
211                 mono_thread_info_wait_for_resume (info);
212                 break;
213         default:
214                 g_error ("Unknown thread state");
215         }
216 }
217
218
219 gpointer
220 mono_threads_cookie_for_reset_blocking_start (MonoThreadInfo *info, int reset_blocking_count)
221 {
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;
228         }
229 #endif
230
231         return info;
232 }
233
234 void*
235 mono_threads_reset_blocking_start (void* stackdata)
236 {
237         MonoThreadInfo *info;
238
239         if (!mono_threads_is_coop_enabled ())
240                 return NULL;
241
242         info = mono_thread_info_current_unchecked ();
243
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;
250 #else
251         int reset_blocking_count = ++coop_reset_blocking_count;
252 #endif
253
254         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
255         if (!info || !mono_thread_info_is_live (info))
256                 return NULL;
257
258         copy_stack_data (info, stackdata);
259
260         switch (mono_threads_transition_abort_blocking (info)) {
261         case AbortBlockingIgnore:
262                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
263                 return NULL;
264         case AbortBlockingIgnoreAndPoll:
265                 mono_threads_state_poll ();
266                 return NULL;
267         case AbortBlockingOk:
268                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
269                 break;
270         case AbortBlockingWait:
271                 mono_thread_info_wait_for_resume (info);
272                 break;
273         default:
274                 g_error ("Unknown thread state");
275         }
276
277         return mono_threads_cookie_for_reset_blocking_start (info, reset_blocking_count);
278 }
279
280 void
281 mono_threads_reset_blocking_end (void *cookie, void* stackdata)
282 {
283         if (!mono_threads_is_coop_enabled ())
284                 return;
285
286         if (!cookie)
287                 return;
288
289 #ifdef ENABLE_CHECKED_BUILD_GC
290         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
291                 int received_cookie = (int)(intptr_t)cookie;
292                 int desired_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);
295                 if (level < 0)
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
300 #endif
301         {
302                 g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
303         }
304
305         mono_threads_prepare_blocking (stackdata);
306 }
307
308 void
309 mono_threads_init_coop (void)
310 {
311         if (!mono_threads_is_coop_enabled ())
312                 return;
313
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.
320
321 #ifdef ENABLE_CHECKED_BUILD_GC
322         mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
323 #endif
324 }
325
326 void
327 mono_threads_coop_begin_global_suspend (void)
328 {
329         if (mono_threads_is_coop_enabled ())
330                 mono_polling_required = 1;
331 }
332
333 void
334 mono_threads_coop_end_global_suspend (void)
335 {
336         if (mono_threads_is_coop_enabled ())
337                 mono_polling_required = 0;
338 }
339
340 gpointer
341 mono_threads_enter_gc_unsafe_region (gpointer* stackdata)
342 {
343         if (!mono_threads_is_coop_enabled ())
344                 return NULL;
345
346         return mono_threads_reset_blocking_start (stackdata);
347 }
348
349 void
350 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer* stackdata)
351 {
352         if (!mono_threads_is_coop_enabled ())
353                 return;
354
355         mono_threads_reset_blocking_end (cookie, stackdata);
356 }
357
358 void
359 mono_threads_assert_gc_unsafe_region (void)
360 {
361         MONO_REQ_GC_UNSAFE_MODE;
362 }
363
364 gpointer
365 mono_threads_enter_gc_safe_region (gpointer *stackdata)
366 {
367         if (!mono_threads_is_coop_enabled ())
368                 return NULL;
369
370         return mono_threads_prepare_blocking (stackdata);
371 }
372
373 void
374 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
375 {
376         if (!mono_threads_is_coop_enabled ())
377                 return;
378
379         mono_threads_finish_blocking (cookie, stackdata);
380 }
381
382 void
383 mono_threads_assert_gc_safe_region (void)
384 {
385         MONO_REQ_GC_SAFE_MODE;
386 }