Merge pull request #2101 from tritao/stack-coop-gc
[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/metadata/mempool.h>
18 #include <mono/utils/mono-compiler.h>
19 #include <mono/utils/mono-semaphore.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
29 #ifdef TARGET_OSX
30 #include <mono/utils/mach-support.h>
31 #endif
32
33 #ifdef _MSC_VER
34 // TODO: Find MSVC replacement for __builtin_unwind_init
35 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
36 #else 
37 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
38 #endif
39
40 #ifdef USE_COOP_BACKEND
41
42 volatile size_t mono_polling_required;
43
44 static int coop_reset_blocking_count, coop_try_blocking_count, coop_do_blocking_count, coop_do_polling_count, coop_save_count;
45
46 void
47 mono_threads_state_poll (void)
48 {
49         MonoThreadInfo *info;
50         ++coop_do_polling_count;
51
52         info = mono_thread_info_current_unchecked ();
53         if (!info)
54                 return;
55         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
56
57         /* Fast check for pending suspend requests */
58         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
59                 return;
60
61         ++coop_save_count;
62         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
63
64         /* commit the saved state and notify others if needed */
65         switch (mono_threads_transition_state_poll (info)) {
66         case SelfSuspendResumed:
67                 return;
68         case SelfSuspendWait:
69                 mono_thread_info_wait_for_resume (info);
70                 break;
71         case SelfSuspendNotifyAndWait:
72                 mono_threads_notify_initiator_of_suspend (info);
73                 mono_thread_info_wait_for_resume (info);
74                 break;
75         }
76 }
77
78 static void *
79 return_stack_ptr ()
80 {
81         int i;
82         return &i;
83 }
84
85 static void
86 copy_stack_data (MonoThreadInfo *info, void* stackdata_begin)
87 {
88         MonoThreadUnwindState *state;
89         int stackdata_size;
90         void* stackdata_end = return_stack_ptr ();
91
92         SAVE_REGS_ON_STACK;
93
94         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
95
96         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
97         g_assert (stackdata_size > 0);
98
99         g_byte_array_set_size (info->stackdata, stackdata_size);
100         state->gc_stackdata = info->stackdata->data;
101         memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
102
103         state->gc_stackdata_size = stackdata_size;
104 }
105
106 void*
107 mono_threads_prepare_blocking (void* stackdata)
108 {
109         MonoThreadInfo *info;
110         ++coop_do_blocking_count;
111
112         info = mono_thread_info_current_unchecked ();
113         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
114         if (!info || !mono_thread_info_is_live (info)) {
115                 THREADS_SUSPEND_DEBUG ("PREPARE-BLOCKING failed %p\n", mono_thread_info_get_tid (info));
116                 return NULL;
117         }
118
119         copy_stack_data (info, stackdata);
120
121 retry:
122         ++coop_save_count;
123         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
124
125         switch (mono_threads_transition_do_blocking (info)) {
126         case DoBlockingContinue:
127                 break;
128         case DoBlockingPollAndRetry:
129                 mono_threads_state_poll ();
130                 goto retry;
131         }
132
133         return info;
134 }
135
136 void
137 mono_threads_finish_blocking (void *cookie, void* stackdata)
138 {
139         static gboolean warned_about_bad_transition;
140         MonoThreadInfo *info = cookie;
141
142         if (!info)
143                 return;
144
145         g_assert (info == mono_thread_info_current_unchecked ());
146
147         switch (mono_threads_transition_done_blocking (info)) {
148         case DoneBlockingAborted:
149                 if (!warned_about_bad_transition) {
150                         warned_about_bad_transition = TRUE;
151                         g_warning ("[%p] Blocking call ended in running state for, this might lead to unbound GC pauses.", mono_thread_info_get_tid (info));
152                 }
153                 mono_threads_state_poll ();
154                 break;
155         case DoneBlockingOk:
156                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
157                 break;
158         case DoneBlockingWait:
159                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
160                 mono_thread_info_wait_for_resume (info);
161                 break;
162         default:
163                 g_error ("Unknown thread state");
164         }
165 }
166
167
168 void*
169 mono_threads_reset_blocking_start (void* stackdata)
170 {
171         MonoThreadInfo *info = mono_thread_info_current_unchecked ();
172         ++coop_reset_blocking_count;
173
174         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
175         if (!info || !mono_thread_info_is_live (info))
176                 return NULL;
177
178         copy_stack_data (info, stackdata);
179
180         switch (mono_threads_transition_abort_blocking (info)) {
181         case AbortBlockingIgnore:
182                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
183                 return NULL;
184         case AbortBlockingIgnoreAndPoll:
185                 mono_threads_state_poll ();
186                 return NULL;
187         case AbortBlockingOk:
188                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
189                 return info;
190         case AbortBlockingOkAndPool:
191                 mono_threads_state_poll ();
192                 return info;
193         default:
194                 g_error ("Unknown thread state");
195         }
196 }
197
198 void
199 mono_threads_reset_blocking_end (void *cookie, void* stackdata)
200 {
201         MonoThreadInfo *info = cookie;
202
203         if (!info)
204                 return;
205
206         g_assert (info == mono_thread_info_current_unchecked ());
207         mono_threads_prepare_blocking (stackdata);
208 }
209
210 void*
211 mono_threads_try_prepare_blocking (void* stackdata)
212 {
213         MonoThreadInfo *info;
214         ++coop_try_blocking_count;
215
216         info = mono_thread_info_current_unchecked ();
217         /* If the thread is not attached, it doesn't make sense prepare for suspend. */
218         if (!info || !mono_thread_info_is_live (info) || mono_thread_info_current_state (info) == STATE_BLOCKING) {
219                 THREADS_SUSPEND_DEBUG ("PREPARE-TRY-BLOCKING failed %p\n", mono_thread_info_get_tid (info));
220                 return NULL;
221         }
222
223         copy_stack_data (info, stackdata);
224
225 retry:
226         ++coop_save_count;
227         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
228
229         switch (mono_threads_transition_do_blocking (info)) {
230         case DoBlockingContinue:
231                 break;
232         case DoBlockingPollAndRetry:
233                 mono_threads_state_poll ();
234                 goto retry;
235         }
236
237         return info;
238 }
239
240 void
241 mono_threads_finish_try_blocking (void* cookie, void* stackdata)
242 {
243         mono_threads_finish_blocking (cookie, stackdata);
244 }
245
246 gboolean
247 mono_threads_core_begin_async_resume (MonoThreadInfo *info)
248 {
249         g_error ("FIXME");
250         return FALSE;
251 }
252
253 gboolean
254 mono_threads_core_begin_async_suspend (MonoThreadInfo *info, gboolean interrupt_kernel)
255 {       
256         mono_threads_add_to_pending_operation_set (info);
257         /* There's nothing else to do after we async request the thread to suspend */
258         return TRUE;
259 }
260
261 gboolean
262 mono_threads_core_check_suspend_result (MonoThreadInfo *info)
263 {
264         /* Async suspend can't async fail on coop */
265         return TRUE;
266 }
267
268 void
269 mono_threads_init_platform (void)
270 {
271         mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
272         mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
273         mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
274         mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
275         mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
276         //See the above for what's wrong here.
277 }
278
279 void
280 mono_threads_platform_free (MonoThreadInfo *info)
281 {
282 #ifdef TARGET_MACH
283         mach_port_deallocate (current_task (), info->native_handle);
284 #endif
285
286         //See the above for what's wrong here.
287 }
288
289 void
290 mono_threads_platform_register (MonoThreadInfo *info)
291 {
292 #ifdef TARGET_MACH
293         char thread_name [64];
294
295         info->native_handle = mach_thread_self ();
296         snprintf (thread_name, 64, "tid_%x", (int)info->native_handle);
297         pthread_setname_np (thread_name);
298 #endif
299
300         //See the above for what's wrong here.
301 }
302
303 void
304 mono_threads_core_begin_global_suspend (void)
305 {
306         mono_polling_required = 1;
307 }
308
309 void
310 mono_threads_core_end_global_suspend (void)
311 {
312         mono_polling_required = 0;
313 }
314
315 void*
316 mono_threads_enter_gc_unsafe_region (void* stackdata)
317 {
318         return mono_threads_reset_blocking_start (stackdata);
319 }
320
321 void
322 mono_threads_exit_gc_unsafe_region (void *regions_cookie, void* stackdata)
323 {
324         mono_threads_reset_blocking_end (regions_cookie, stackdata);
325 }
326
327 #else
328
329 void*
330 mono_threads_enter_gc_unsafe_region (void* stackdata)
331 {
332         return NULL;
333 }
334
335 void
336 mono_threads_exit_gc_unsafe_region (void *regions_cookie, void* stackdata)
337 {
338 }
339
340 #endif