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