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