[wasm] Implement GC support. Embedder must provide main loop pumping function request...
[mono.git] / mono / metadata / sgen-stw.c
1 /**
2  * \file
3  * Stop the world functionality
4  *
5  * Author:
6  *      Paolo Molaro (lupus@ximian.com)
7  *  Rodrigo Kumpera (kumpera@gmail.com)
8  *
9  * Copyright 2005-2011 Novell, Inc (http://www.novell.com)
10  * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
11  * Copyright 2011 Xamarin, Inc.
12  * Copyright (C) 2012 Xamarin Inc
13  *
14  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
15  */
16
17 #include "config.h"
18 #ifdef HAVE_SGEN_GC
19
20 #include "sgen/sgen-gc.h"
21 #include "sgen/sgen-protocol.h"
22 #include "sgen/sgen-memory-governor.h"
23 #include "sgen/sgen-workers.h"
24 #include "metadata/profiler-private.h"
25 #include "sgen/sgen-client.h"
26 #include "metadata/sgen-bridge-internals.h"
27 #include "metadata/gc-internals.h"
28 #include "utils/mono-threads.h"
29 #include "utils/mono-threads-debug.h"
30
31 #define TV_DECLARE SGEN_TV_DECLARE
32 #define TV_GETTIME SGEN_TV_GETTIME
33 #define TV_ELAPSED SGEN_TV_ELAPSED
34
35 static void sgen_unified_suspend_restart_world (void);
36 static void sgen_unified_suspend_stop_world (void);
37
38 static TV_DECLARE (end_of_last_stw);
39
40 guint64 mono_time_since_last_stw ()
41 {
42         if (end_of_last_stw == 0)
43                 return 0;
44
45         TV_DECLARE (current_time);
46         TV_GETTIME (current_time);
47         return TV_ELAPSED (end_of_last_stw, current_time);
48 }
49
50 unsigned int sgen_global_stop_count = 0;
51
52 inline static void*
53 align_pointer (void *ptr)
54 {
55         mword p = (mword)ptr;
56         p += sizeof (gpointer) - 1;
57         p &= ~ (sizeof (gpointer) - 1);
58         return (void*)p;
59 }
60
61 static void
62 update_current_thread_stack (void *start)
63 {
64         int stack_guard = 0;
65         SgenThreadInfo *info = mono_thread_info_current ();
66
67         info->client_info.stack_start = align_pointer (&stack_guard);
68         g_assert (info->client_info.stack_start);
69         g_assert (info->client_info.stack_start >= info->client_info.info.stack_start_limit && info->client_info.stack_start < info->client_info.info.stack_end);
70
71 #if !defined(MONO_CROSS_COMPILE) && MONO_ARCH_HAS_MONO_CONTEXT
72         MONO_CONTEXT_GET_CURRENT (info->client_info.ctx);
73 #elif defined (HOST_WASM)
74         //nothing
75 #else
76         g_error ("Sgen STW requires a working mono-context");
77 #endif
78
79         if (mono_gc_get_gc_callbacks ()->thread_suspend_func)
80                 mono_gc_get_gc_callbacks ()->thread_suspend_func (info->client_info.runtime_data, NULL, &info->client_info.ctx);
81 }
82
83 static void
84 acquire_gc_locks (void)
85 {
86         LOCK_INTERRUPTION;
87         mono_thread_info_suspend_lock ();
88 }
89
90 static void
91 release_gc_locks (void)
92 {
93         mono_thread_info_suspend_unlock ();
94         UNLOCK_INTERRUPTION;
95 }
96
97 static TV_DECLARE (stop_world_time);
98 static unsigned long max_pause_usec = 0;
99
100 static guint64 time_stop_world;
101 static guint64 time_restart_world;
102
103 /* LOCKING: assumes the GC lock is held */
104 void
105 sgen_client_stop_world (int generation)
106 {
107         TV_DECLARE (end_handshake);
108
109         MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_STOP_WORLD, generation));
110
111         acquire_gc_locks ();
112
113         MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_STOP_WORLD_LOCKED, generation));
114
115         /* We start to scan after locks are taking, this ensures we won't be interrupted. */
116         sgen_process_togglerefs ();
117
118         update_current_thread_stack (&generation);
119
120         sgen_global_stop_count++;
121         SGEN_LOG (3, "stopping world n %d from %p %p", sgen_global_stop_count, mono_thread_info_current (), (gpointer) (gsize) mono_native_thread_id_get ());
122         TV_GETTIME (stop_world_time);
123
124         sgen_unified_suspend_stop_world ();
125
126         SGEN_LOG (3, "world stopped");
127
128         MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_POST_STOP_WORLD, generation));
129
130         TV_GETTIME (end_handshake);
131         time_stop_world += TV_ELAPSED (stop_world_time, end_handshake);
132
133         sgen_memgov_collection_start (generation);
134         if (sgen_need_bridge_processing ())
135                 sgen_bridge_reset_data ();
136 }
137
138 /* LOCKING: assumes the GC lock is held */
139 void
140 sgen_client_restart_world (int generation, gint64 *stw_time)
141 {
142         TV_DECLARE (end_sw);
143         TV_DECLARE (start_handshake);
144         unsigned long usec;
145
146         /* notify the profiler of the leftovers */
147         /* FIXME this is the wrong spot at we can STW for non collection reasons. */
148         if (MONO_PROFILER_ENABLED (gc_moves))
149                 mono_sgen_gc_event_moves ();
150
151         MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_START_WORLD, generation));
152
153         FOREACH_THREAD (info) {
154                 info->client_info.stack_start = NULL;
155                 memset (&info->client_info.ctx, 0, sizeof (MonoContext));
156         } FOREACH_THREAD_END
157
158         TV_GETTIME (start_handshake);
159
160         sgen_unified_suspend_restart_world ();
161
162         TV_GETTIME (end_sw);
163         time_restart_world += TV_ELAPSED (start_handshake, end_sw);
164         usec = TV_ELAPSED (stop_world_time, end_sw);
165         max_pause_usec = MAX (usec, max_pause_usec);
166         end_of_last_stw = end_sw;
167
168         SGEN_LOG (2, "restarted (pause time: %d usec, max: %d)", (int)usec, (int)max_pause_usec);
169
170         MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_POST_START_WORLD, generation));
171
172         /*
173          * We must release the thread info suspend lock after doing
174          * the thread handshake.  Otherwise, if the GC stops the world
175          * and a thread is in the process of starting up, but has not
176          * yet registered (it's not in the thread_list), it is
177          * possible that the thread does register while the world is
178          * stopped.  When restarting the GC will then try to restart
179          * said thread, but since it never got the suspend signal, it
180          * cannot answer the restart signal, so a deadlock results.
181          */
182         release_gc_locks ();
183
184         MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_POST_START_WORLD_UNLOCKED, generation));
185
186         *stw_time = usec;
187 }
188
189 void
190 mono_sgen_init_stw (void)
191 {
192         mono_counters_register ("World stop", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_stop_world);
193         mono_counters_register ("World restart", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_restart_world);
194 }
195
196 /* Unified suspend code */
197
198 static gboolean
199 sgen_is_thread_in_current_stw (SgenThreadInfo *info, int *reason)
200 {
201         /*
202         A thread explicitly asked to be skiped because it holds no managed state.
203         This is used by TP and finalizer threads.
204         FIXME Use an atomic variable for this to avoid everyone taking the GC LOCK.
205         */
206         if (info->client_info.gc_disabled) {
207                 if (reason)
208                         *reason = 1;
209                 return FALSE;
210         }
211
212         /*
213         We have detected that this thread is failing/dying, ignore it.
214         FIXME: can't we merge this with thread_is_dying?
215         */
216         if (info->client_info.skip) {
217                 if (reason)
218                         *reason = 2;
219                 return FALSE;
220         }
221
222         /*
223         Suspending the current thread will deadlock us, bad idea.
224         */
225         if (info == mono_thread_info_current ()) {
226                 if (reason)
227                         *reason = 3;
228                 return FALSE;
229         }
230
231         /*
232         We can't suspend the workers that will do all the heavy lifting.
233         FIXME Use some state bit in SgenThreadInfo for this.
234         */
235         if (sgen_thread_pool_is_thread_pool_thread (mono_thread_info_get_tid (info))) {
236                 if (reason)
237                         *reason = 4;
238                 return FALSE;
239         }
240
241         /*
242         The thread has signaled that it started to detach, ignore it.
243         FIXME: can't we merge this with skip
244         */
245         if (!mono_thread_info_is_live (info)) {
246                 if (reason)
247                         *reason = 5;
248                 return FALSE;
249         }
250
251         return TRUE;
252 }
253
254 static void
255 sgen_unified_suspend_stop_world (void)
256 {
257         int sleep_duration = -1;
258
259         mono_threads_begin_global_suspend ();
260         THREADS_STW_DEBUG ("[GC-STW-BEGIN][%p] *** BEGIN SUSPEND *** \n", mono_thread_info_get_tid (mono_thread_info_current ()));
261
262         FOREACH_THREAD (info) {
263                 info->client_info.skip = FALSE;
264                 info->client_info.suspend_done = FALSE;
265
266                 int reason;
267                 if (!sgen_is_thread_in_current_stw (info, &reason)) {
268                         THREADS_STW_DEBUG ("[GC-STW-BEGIN-SUSPEND] IGNORE thread %p skip %s reason %d\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false", reason);
269                         continue;
270                 }
271
272                 info->client_info.skip = !mono_thread_info_begin_suspend (info);
273
274                 THREADS_STW_DEBUG ("[GC-STW-BEGIN-SUSPEND] SUSPEND thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
275         } FOREACH_THREAD_END
276
277         mono_thread_info_current ()->client_info.suspend_done = TRUE;
278         mono_threads_wait_pending_operations ();
279
280         for (;;) {
281                 gint restart_counter = 0;
282
283                 FOREACH_THREAD (info) {
284                         gint suspend_count;
285
286                         int reason = 0;
287                         if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) {
288                                 THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE RESUME thread %p not been processed done %d current %d reason %d\n", mono_thread_info_get_tid (info), info->client_info.suspend_done, !sgen_is_thread_in_current_stw (info, NULL), reason);
289                                 continue;
290                         }
291
292                         /*
293                         All threads that reach here are pristine suspended. This means the following:
294
295                         - We haven't accepted the previous suspend as good.
296                         - We haven't gave up on it for this STW (it's either bad or asked not to)
297                         */
298                         if (!mono_thread_info_in_critical_location (info)) {
299                                 info->client_info.suspend_done = TRUE;
300
301                                 THREADS_STW_DEBUG ("[GC-STW-RESTART] DONE thread %p deemed fully suspended\n", mono_thread_info_get_tid (info));
302                                 continue;
303                         }
304
305                         suspend_count = mono_thread_info_suspend_count (info);
306                         if (!(suspend_count == 1))
307                                 g_error ("[%p] suspend_count = %d, but should be 1", mono_thread_info_get_tid (info), suspend_count);
308
309                         info->client_info.skip = !mono_thread_info_begin_resume (info);
310                         if (!info->client_info.skip)
311                                 restart_counter += 1;
312
313                         THREADS_STW_DEBUG ("[GC-STW-RESTART] RESTART thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
314                 } FOREACH_THREAD_END
315
316                 mono_threads_wait_pending_operations ();
317
318                 if (restart_counter == 0)
319                         break;
320
321                 if (sleep_duration < 0) {
322                         mono_thread_info_yield ();
323                         sleep_duration = 0;
324                 } else {
325                         g_usleep (sleep_duration);
326                         sleep_duration += 10;
327                 }
328
329                 FOREACH_THREAD (info) {
330                         int reason = 0;
331                         if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) {
332                                 THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE SUSPEND thread %p not been processed done %d current %d reason %d\n", mono_thread_info_get_tid (info), info->client_info.suspend_done, !sgen_is_thread_in_current_stw (info, NULL), reason);
333                                 continue;
334                         }
335
336                         if (!mono_thread_info_is_running (info)) {
337                                 THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE SUSPEND thread %p not running\n", mono_thread_info_get_tid (info));
338                                 continue;
339                         }
340
341                         info->client_info.skip = !mono_thread_info_begin_suspend (info);
342
343                         THREADS_STW_DEBUG ("[GC-STW-RESTART] SUSPEND thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
344                 } FOREACH_THREAD_END
345
346                 mono_threads_wait_pending_operations ();
347         }
348
349         FOREACH_THREAD (info) {
350                 gpointer stopped_ip;
351
352                 int reason = 0;
353                 if (!sgen_is_thread_in_current_stw (info, &reason)) {
354                         g_assert (!info->client_info.suspend_done || info == mono_thread_info_current ());
355
356                         THREADS_STW_DEBUG ("[GC-STW-SUSPEND-END] thread %p is NOT suspended, reason %d\n", mono_thread_info_get_tid (info), reason);
357                         continue;
358                 }
359
360                 g_assert (info->client_info.suspend_done);
361
362                 info->client_info.ctx = mono_thread_info_get_suspend_state (info)->ctx;
363
364                 /* Once we remove the old suspend code, we should move sgen to directly access the state in MonoThread */
365                 info->client_info.stack_start = (gpointer) ((char*)MONO_CONTEXT_GET_SP (&info->client_info.ctx) - REDZONE_SIZE);
366
367                 if (info->client_info.stack_start < info->client_info.info.stack_start_limit
368                          || info->client_info.stack_start >= info->client_info.info.stack_end) {
369                         /*
370                          * Thread context is in unhandled state, most likely because it is
371                          * dying. We don't scan it.
372                          * FIXME We should probably rework and check the valid flag instead.
373                          */
374                         info->client_info.stack_start = NULL;
375                 }
376
377                 stopped_ip = (gpointer) (MONO_CONTEXT_GET_IP (&info->client_info.ctx));
378
379                 binary_protocol_thread_suspend ((gpointer) mono_thread_info_get_tid (info), stopped_ip);
380
381                 THREADS_STW_DEBUG ("[GC-STW-SUSPEND-END] thread %p is suspended, stopped_ip = %p, stack = %p -> %p\n",
382                         mono_thread_info_get_tid (info), stopped_ip, info->client_info.stack_start, info->client_info.stack_start ? info->client_info.info.stack_end : NULL);
383         } FOREACH_THREAD_END
384 }
385
386 static void
387 sgen_unified_suspend_restart_world (void)
388 {
389         THREADS_STW_DEBUG ("[GC-STW-END] *** BEGIN RESUME ***\n");
390         FOREACH_THREAD (info) {
391                 int reason = 0;
392                 if (sgen_is_thread_in_current_stw (info, &reason)) {
393                         g_assert (mono_thread_info_begin_resume (info));
394                         THREADS_STW_DEBUG ("[GC-STW-RESUME-WORLD] RESUME thread %p\n", mono_thread_info_get_tid (info));
395
396                         binary_protocol_thread_restart ((gpointer) mono_thread_info_get_tid (info));
397                 } else {
398                         THREADS_STW_DEBUG ("[GC-STW-RESUME-WORLD] IGNORE thread %p, reason %d\n", mono_thread_info_get_tid (info), reason);
399                 }
400         } FOREACH_THREAD_END
401
402         mono_threads_wait_pending_operations ();
403         mono_threads_end_global_suspend ();
404 }
405 #endif