20740b3dcd1d330bc943bc5ca3320b9cacd39f77
[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  */
9
10 #include <config.h>
11
12 /* enable pthread extensions */
13 #ifdef TARGET_MACH
14 #define _DARWIN_C_SOURCE
15 #endif
16
17 #include <mono/utils/mono-compiler.h>
18 #include <mono/utils/mono-threads.h>
19 #include <mono/utils/mono-tls.h>
20 #include <mono/utils/hazard-pointer.h>
21 #include <mono/utils/mono-memory-model.h>
22 #include <mono/utils/mono-mmap.h>
23 #include <mono/utils/atomic.h>
24 #include <mono/utils/mono-time.h>
25 #include <mono/utils/mono-counters.h>
26 #include <mono/utils/mono-threads-coop.h>
27 #include <mono/utils/mono-threads-api.h>
28 #include <mono/utils/checked-build.h>
29
30 #ifdef TARGET_OSX
31 #include <mono/utils/mach-support.h>
32 #endif
33
34 #ifdef _MSC_VER
35 // TODO: Find MSVC replacement for __builtin_unwind_init
36 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
37 #else 
38 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
39 #endif
40
41 volatile size_t mono_polling_required;
42
43 // FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
44 // perhaps with a per-thread cookie in the high bits.
45 #ifdef ENABLE_CHECKED_BUILD_GC
46 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
47 MonoNativeTlsKey coop_reset_count_stack_key;
48 static int coop_tls_push (int v) {
49         GArray *stack = mono_native_tls_get_value (coop_reset_count_stack_key);
50         if (!stack) {
51                 stack = g_array_new (FALSE,FALSE,sizeof(int));
52                 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
53         }
54         g_array_append_val (stack, v);
55         return stack->len;
56 }
57 static int coop_tls_pop (int *v) {
58         GArray *stack = mono_native_tls_get_value (coop_reset_count_stack_key);
59         if (!stack || 0 == stack->len)
60                 return -1;
61         stack->len--;
62         *v = g_array_index (stack, int, stack->len);
63         int len = stack->len;
64         if (0 == len) {
65                 g_array_free (stack,TRUE);
66                 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
67         }
68         return len;
69 }
70 #endif
71
72 static int coop_reset_blocking_count;
73 static int coop_try_blocking_count;
74 static int coop_do_blocking_count;
75 static int coop_do_polling_count;
76 static int coop_save_count;
77
78 void
79 mono_threads_state_poll (void)
80 {
81         MonoThreadInfo *info;
82
83         g_assert (mono_threads_is_coop_enabled ());
84
85         ++coop_do_polling_count;
86
87         info = mono_thread_info_current_unchecked ();
88         if (!info)
89                 return;
90         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
91
92         /* Fast check for pending suspend requests */
93         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
94                 return;
95
96         ++coop_save_count;
97         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
98
99         /* commit the saved state and notify others if needed */
100         switch (mono_threads_transition_state_poll (info)) {
101         case SelfSuspendResumed:
102                 return;
103         case SelfSuspendWait:
104                 mono_thread_info_wait_for_resume (info);
105                 break;
106         case SelfSuspendNotifyAndWait:
107                 mono_threads_notify_initiator_of_suspend (info);
108                 mono_thread_info_wait_for_resume (info);
109                 break;
110         }
111 }
112
113 static void *
114 return_stack_ptr ()
115 {
116         gpointer i;
117         return &i;
118 }
119
120 static void
121 copy_stack_data (MonoThreadInfo *info, void* stackdata_begin)
122 {
123         MonoThreadUnwindState *state;
124         int stackdata_size;
125         void* stackdata_end = return_stack_ptr ();
126
127         SAVE_REGS_ON_STACK;
128
129         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
130
131         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
132
133         if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
134                 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
135         if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
136                 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
137
138         if (stackdata_size <= 0)
139                 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
140
141         g_byte_array_set_size (info->stackdata, stackdata_size);
142         state->gc_stackdata = info->stackdata->data;
143         memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
144
145         state->gc_stackdata_size = stackdata_size;
146 }
147
148 void*
149 mono_threads_prepare_blocking (void* stackdata)
150 {
151         MonoThreadInfo *info;
152
153         if (!mono_threads_is_coop_enabled ())
154                 return NULL;
155
156         ++coop_do_blocking_count;
157
158         info = mono_thread_info_current_unchecked ();
159         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
160         if (!info || !mono_thread_info_is_live (info)) {
161                 THREADS_SUSPEND_DEBUG ("PREPARE-BLOCKING failed %p\n", mono_thread_info_get_tid (info));
162                 return NULL;
163         }
164
165         copy_stack_data (info, stackdata);
166
167 retry:
168         ++coop_save_count;
169         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
170
171         switch (mono_threads_transition_do_blocking (info)) {
172         case DoBlockingContinue:
173                 break;
174         case DoBlockingPollAndRetry:
175                 mono_threads_state_poll ();
176                 goto retry;
177         }
178
179         return info;
180 }
181
182 void
183 mono_threads_finish_blocking (void *cookie, void* stackdata)
184 {
185         static gboolean warned_about_bad_transition;
186         MonoThreadInfo *info;
187
188         if (!mono_threads_is_coop_enabled ())
189                 return;
190
191         info = (MonoThreadInfo *)cookie;
192         if (!info)
193                 return;
194
195         g_assert (info == mono_thread_info_current_unchecked ());
196
197         switch (mono_threads_transition_done_blocking (info)) {
198         case DoneBlockingAborted:
199                 if (!warned_about_bad_transition) {
200                         warned_about_bad_transition = TRUE;
201                         g_warning ("[%p] Blocking call ended in running state for, this might lead to unbound GC pauses.", mono_thread_info_get_tid (info));
202                 }
203                 mono_threads_state_poll ();
204                 break;
205         case DoneBlockingOk:
206                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
207                 break;
208         case DoneBlockingWait:
209                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
210                 mono_thread_info_wait_for_resume (info);
211                 break;
212         default:
213                 g_error ("Unknown thread state");
214         }
215 }
216
217
218 void*
219 mono_threads_reset_blocking_start (void* stackdata)
220 {
221         MonoThreadInfo *info;
222
223         if (!mono_threads_is_coop_enabled ())
224                 return NULL;
225
226         info = mono_thread_info_current_unchecked ();
227
228 #ifdef ENABLE_CHECKED_BUILD_GC
229         int reset_blocking_count = InterlockedIncrement (&coop_reset_blocking_count);
230         // In this mode, the blocking count is used as the reset cookie. We would prefer
231         // (but do not require) this to be unique across invocations and threads.
232         if (reset_blocking_count == 0) // We *do* require it be nonzero
233                 reset_blocking_count = coop_reset_blocking_count = 1;
234 #else
235         ++coop_reset_blocking_count;
236 #endif
237
238         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
239         if (!info || !mono_thread_info_is_live (info))
240                 return NULL;
241
242         copy_stack_data (info, stackdata);
243
244         switch (mono_threads_transition_abort_blocking (info)) {
245         case AbortBlockingIgnore:
246                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
247                 return NULL;
248         case AbortBlockingIgnoreAndPoll:
249                 mono_threads_state_poll ();
250                 return NULL;
251         case AbortBlockingOk:
252                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
253                 break;
254         case AbortBlockingOkAndPool:
255                 mono_threads_state_poll ();
256                 break;
257         default:
258                 g_error ("Unknown thread state");
259         }
260
261 #ifdef ENABLE_CHECKED_BUILD_GC
262         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
263                 int level = coop_tls_push (reset_blocking_count);
264                 //g_warning("Entering reset nest; level %d; cookie %d\n", level, reset_blocking_count);
265                 return (void *)(intptr_t)reset_blocking_count;
266         }
267 #endif
268
269         return info;
270 }
271
272 void
273 mono_threads_reset_blocking_end (void *cookie, void* stackdata)
274 {
275         if (!mono_threads_is_coop_enabled ())
276                 return;
277
278         if (!cookie)
279                 return;
280
281 #ifdef ENABLE_CHECKED_BUILD_GC
282         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC)) {
283                 int received_cookie = (int)(intptr_t)cookie;
284                 int desired_cookie;
285                 int level = coop_tls_pop (&desired_cookie);
286                 //g_warning("Leaving reset nest; back to level %d; desired cookie %d; received cookie %d\n", level, desired_cookie, received_cookie);
287                 if (level < 0)
288                         mono_fatal_with_history ("Expected cookie %d but found no stack at all\n", desired_cookie);
289                 if (desired_cookie != received_cookie)
290                         mono_fatal_with_history ("Expected cookie %d but received %d\n", desired_cookie, received_cookie);
291         } else // Notice this matches the line after the endif
292 #endif
293         {
294                 g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
295         }
296
297         mono_threads_prepare_blocking (stackdata);
298 }
299
300 void
301 mono_threads_init_coop (void)
302 {
303         if (!mono_threads_is_coop_enabled ())
304                 return;
305
306         mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
307         mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
308         mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
309         mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
310         mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
311         //See the above for what's wrong here.
312
313 #ifdef ENABLE_CHECKED_BUILD_GC
314         mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
315 #endif
316 }
317
318 void
319 mono_threads_coop_begin_global_suspend (void)
320 {
321         if (mono_threads_is_coop_enabled ())
322                 mono_polling_required = 1;
323 }
324
325 void
326 mono_threads_coop_end_global_suspend (void)
327 {
328         if (mono_threads_is_coop_enabled ())
329                 mono_polling_required = 0;
330 }
331
332 gpointer
333 mono_threads_enter_gc_unsafe_region (gpointer* stackdata)
334 {
335         if (!mono_threads_is_coop_enabled ())
336                 return NULL;
337
338         return mono_threads_reset_blocking_start (stackdata);
339 }
340
341 void
342 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer* stackdata)
343 {
344         if (!mono_threads_is_coop_enabled ())
345                 return;
346
347         mono_threads_reset_blocking_end (cookie, stackdata);
348 }
349
350 void
351 mono_threads_assert_gc_unsafe_region (void)
352 {
353         MONO_REQ_GC_UNSAFE_MODE;
354 }
355
356 gpointer
357 mono_threads_enter_gc_safe_region (gpointer *stackdata)
358 {
359         if (!mono_threads_is_coop_enabled ())
360                 return NULL;
361
362         return mono_threads_prepare_blocking (stackdata);
363 }
364
365 void
366 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
367 {
368         if (!mono_threads_is_coop_enabled ())
369                 return;
370
371         mono_threads_finish_blocking (cookie, stackdata);
372 }
373
374 void
375 mono_threads_assert_gc_safe_region (void)
376 {
377         MONO_REQ_GC_SAFE_MODE;
378 }