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