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