Apply changes from https://github.com/dotnet/runtime/commit/eb1756e97d23df13bc6fe798e...
[mono-project.git] / mono / metadata / sgen-stw.c
blobe59c0c73552be46884c125ef61e8155eb280f88e
1 /**
2 * \file
3 * Stop the world functionality
5 * Author:
6 * Paolo Molaro (lupus@ximian.com)
7 * Rodrigo Kumpera (kumpera@gmail.com)
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
14 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
17 #include "config.h"
18 #ifdef HAVE_SGEN_GC
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"
31 #if _MSC_VER
32 #pragma warning(disable:4312) // FIXME pointer cast to different size
33 #endif
35 #define TV_DECLARE SGEN_TV_DECLARE
36 #define TV_GETTIME SGEN_TV_GETTIME
37 #define TV_ELAPSED SGEN_TV_ELAPSED
39 static void sgen_unified_suspend_restart_world (void);
40 static void sgen_unified_suspend_stop_world (void);
42 static TV_DECLARE (end_of_last_stw);
44 guint64 mono_time_since_last_stw ()
46 if (end_of_last_stw == 0)
47 return 0;
49 TV_DECLARE (current_time);
50 TV_GETTIME (current_time);
51 return TV_ELAPSED (end_of_last_stw, current_time);
54 unsigned int sgen_global_stop_count = 0;
56 inline static void*
57 align_pointer (void *ptr)
59 mword p = (mword)ptr;
60 p += sizeof (gpointer) - 1;
61 p &= ~ (sizeof (gpointer) - 1);
62 return (void*)p;
65 static void
66 update_current_thread_stack (void *start)
68 int stack_guard = 0;
69 SgenThreadInfo *info = mono_thread_info_current ();
71 info->client_info.stack_start = align_pointer (&stack_guard);
72 g_assert (info->client_info.stack_start);
73 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);
75 #if !defined(MONO_CROSS_COMPILE) && MONO_ARCH_HAS_MONO_CONTEXT
76 MONO_CONTEXT_GET_CURRENT (info->client_info.ctx);
77 #elif defined (HOST_WASM)
78 //nothing
79 #else
80 g_error ("Sgen STW requires a working mono-context");
81 #endif
83 if (mono_gc_get_gc_callbacks ()->thread_suspend_func)
84 mono_gc_get_gc_callbacks ()->thread_suspend_func (info->client_info.runtime_data, NULL, &info->client_info.ctx);
87 static void
88 acquire_gc_locks (void)
90 LOCK_INTERRUPTION;
91 mono_thread_info_suspend_lock ();
94 static void
95 release_gc_locks (void)
97 mono_thread_info_suspend_unlock ();
98 UNLOCK_INTERRUPTION;
101 static TV_DECLARE (stop_world_time);
102 static unsigned long max_stw_pause_time = 0;
104 static guint64 time_stop_world;
105 static guint64 time_restart_world;
107 /* LOCKING: assumes the GC lock is held */
108 void
109 sgen_client_stop_world (int generation, gboolean serial_collection)
111 TV_DECLARE (end_handshake);
113 MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_STOP_WORLD, generation, serial_collection));
115 acquire_gc_locks ();
117 MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_STOP_WORLD_LOCKED, generation, serial_collection));
119 update_current_thread_stack (&generation);
121 /* We start to scan after locks are taking, this ensures we won't be interrupted. */
122 sgen_process_togglerefs ();
124 sgen_global_stop_count++;
125 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 ());
126 TV_GETTIME (stop_world_time);
128 sgen_unified_suspend_stop_world ();
130 SGEN_LOG (3, "world stopped");
132 MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_POST_STOP_WORLD, generation, serial_collection));
134 TV_GETTIME (end_handshake);
136 unsigned long stop_world_tv_elapsed = TV_ELAPSED (stop_world_time, end_handshake);
137 SGEN_LOG (2, "stopping world (time: %d usec)", (int)stop_world_tv_elapsed / 10);
138 time_stop_world += stop_world_tv_elapsed;
140 sgen_memgov_collection_start (generation);
141 if (sgen_need_bridge_processing ())
142 sgen_bridge_reset_data ();
145 /* LOCKING: assumes the GC lock is held */
146 void
147 sgen_client_restart_world (int generation, gboolean serial_collection, gint64 *stw_time)
149 TV_DECLARE (end_sw);
150 TV_DECLARE (start_handshake);
152 /* notify the profiler of the leftovers */
153 /* FIXME this is the wrong spot at we can STW for non collection reasons. */
154 if (MONO_PROFILER_ENABLED (gc_moves))
155 mono_sgen_gc_event_moves ();
157 if (MONO_PROFILER_ENABLED (gc_resize))
158 mono_sgen_gc_event_resize ();
160 MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_START_WORLD, generation, serial_collection));
162 FOREACH_THREAD_ALL (info) {
163 info->client_info.stack_start = NULL;
164 memset (&info->client_info.ctx, 0, sizeof (MonoContext));
165 } FOREACH_THREAD_END
167 TV_GETTIME (start_handshake);
169 sgen_unified_suspend_restart_world ();
171 TV_GETTIME (end_sw);
173 unsigned long restart_world_tv_elapsed = TV_ELAPSED (start_handshake, end_sw);
174 SGEN_LOG (2, "restarting world (time: %d usec)", (int)restart_world_tv_elapsed / 10);
175 time_restart_world += restart_world_tv_elapsed;
177 unsigned long stw_pause_time = TV_ELAPSED (stop_world_time, end_sw);
178 max_stw_pause_time = MAX (stw_pause_time, max_stw_pause_time);
179 end_of_last_stw = end_sw;
181 SGEN_LOG (1, "restarted (pause time: %d usec, max: %d usec)", (int)stw_pause_time / 10, (int)max_stw_pause_time / 10);
183 MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_POST_START_WORLD, generation, serial_collection));
186 * We must release the thread info suspend lock after doing
187 * the thread handshake. Otherwise, if the GC stops the world
188 * and a thread is in the process of starting up, but has not
189 * yet registered (it's not in the thread_list), it is
190 * possible that the thread does register while the world is
191 * stopped. When restarting the GC will then try to restart
192 * said thread, but since it never got the suspend signal, it
193 * cannot answer the restart signal, so a deadlock results.
195 release_gc_locks ();
197 MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_POST_START_WORLD_UNLOCKED, generation, serial_collection));
199 *stw_time = stw_pause_time;
202 void
203 mono_sgen_init_stw (void)
205 mono_counters_register ("World stop", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_stop_world);
206 mono_counters_register ("World restart", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_restart_world);
209 /* Unified suspend code */
211 static gboolean
212 sgen_is_thread_in_current_stw (SgenThreadInfo *info, int *reason)
215 * No need to check MONO_THREAD_INFO_FLAGS_NO_GC here as we rely on the
216 * FOREACH_THREAD_EXCLUDE macro to skip such threads for us.
220 We have detected that this thread is failing/dying, ignore it.
221 FIXME: can't we merge this with thread_is_dying?
223 if (info->client_info.skip) {
224 if (reason)
225 *reason = 2;
226 return FALSE;
230 Suspending the current thread will deadlock us, bad idea.
232 if (info == mono_thread_info_current ()) {
233 if (reason)
234 *reason = 3;
235 return FALSE;
239 We can't suspend the workers that will do all the heavy lifting.
240 FIXME Use some state bit in SgenThreadInfo for this.
242 if (sgen_thread_pool_is_thread_pool_thread (mono_thread_info_get_tid (info))) {
243 if (reason)
244 *reason = 4;
245 return FALSE;
249 The thread has signaled that it started to detach, ignore it.
250 FIXME: can't we merge this with skip
252 if (!mono_thread_info_is_live (info)) {
253 if (reason)
254 *reason = 5;
255 return FALSE;
258 return TRUE;
261 static void
262 sgen_unified_suspend_stop_world (void)
264 int sleep_duration = -1;
266 // we can't lead STW if we promised not to safepoint.
267 g_assert (!mono_thread_info_will_not_safepoint (mono_thread_info_current ()));
269 mono_threads_begin_global_suspend ();
270 THREADS_STW_DEBUG ("[GC-STW-BEGIN][%p] *** BEGIN SUSPEND *** \n", mono_thread_info_get_tid (mono_thread_info_current ()));
272 for (MonoThreadSuspendPhase phase = MONO_THREAD_SUSPEND_PHASE_INITIAL; phase < MONO_THREAD_SUSPEND_PHASE_COUNT; phase++) {
273 gboolean need_next_phase = FALSE;
274 FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
275 /* look at every thread in the first phase. */
276 if (phase == MONO_THREAD_SUSPEND_PHASE_INITIAL) {
277 info->client_info.skip = FALSE;
278 info->client_info.suspend_done = FALSE;
279 } else {
280 /* skip threads suspended by previous phase. */
281 /* threads with info->client_info->skip set to TRUE will be skipped by sgen_is_thread_in_current_stw. */
282 if (info->client_info.suspend_done)
283 continue;
286 int reason;
287 if (!sgen_is_thread_in_current_stw (info, &reason)) {
288 THREADS_STW_DEBUG ("[GC-STW-BEGIN-SUSPEND-%d] IGNORE thread %p skip %s reason %d\n", (int)phase, mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false", reason);
289 continue;
292 switch (mono_thread_info_begin_suspend (info, phase)) {
293 case MONO_THREAD_BEGIN_SUSPEND_SUSPENDED:
294 info->client_info.skip = FALSE;
295 break;
296 case MONO_THREAD_BEGIN_SUSPEND_SKIP:
297 info->client_info.skip = TRUE;
298 break;
299 case MONO_THREAD_BEGIN_SUSPEND_NEXT_PHASE:
300 need_next_phase = TRUE;
301 break;
302 default:
303 g_assert_not_reached ();
306 THREADS_STW_DEBUG ("[GC-STW-BEGIN-SUSPEND-%d] SUSPEND thread %p skip %s\n", (int)phase, mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
307 } FOREACH_THREAD_END;
309 mono_thread_info_current ()->client_info.suspend_done = TRUE;
310 mono_threads_wait_pending_operations ();
312 if (!need_next_phase)
313 break;
316 for (;;) {
317 gint restart_counter = 0;
319 FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
320 gint suspend_count;
322 int reason = 0;
323 if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) {
324 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);
325 continue;
329 All threads that reach here are pristine suspended. This means the following:
331 - We haven't accepted the previous suspend as good.
332 - We haven't gave up on it for this STW (it's either bad or asked not to)
334 if (!mono_thread_info_in_critical_location (info)) {
335 info->client_info.suspend_done = TRUE;
337 THREADS_STW_DEBUG ("[GC-STW-RESTART] DONE thread %p deemed fully suspended\n", mono_thread_info_get_tid (info));
338 continue;
341 suspend_count = mono_thread_info_suspend_count (info);
342 if (!(suspend_count == 1))
343 g_error ("[%p] suspend_count = %d, but should be 1", mono_thread_info_get_tid (info), suspend_count);
345 info->client_info.skip = !mono_thread_info_begin_pulse_resume_and_request_suspension (info);
346 if (!info->client_info.skip)
347 restart_counter += 1;
349 THREADS_STW_DEBUG ("[GC-STW-RESTART] RESTART thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
350 } FOREACH_THREAD_END
352 mono_threads_wait_pending_operations ();
354 if (restart_counter == 0)
355 break;
357 if (sleep_duration < 0) {
358 mono_thread_info_yield ();
359 sleep_duration = 0;
360 } else {
361 g_usleep (sleep_duration);
362 sleep_duration += 10;
365 FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
366 int reason = 0;
367 if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) {
368 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);
369 continue;
372 if (!mono_thread_info_is_running (info)) {
373 THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE SUSPEND thread %p not running\n", mono_thread_info_get_tid (info));
374 continue;
377 switch (mono_thread_info_begin_suspend (info, MONO_THREAD_SUSPEND_PHASE_MOPUP)) {
378 case MONO_THREAD_BEGIN_SUSPEND_SUSPENDED:
379 info->client_info.skip = FALSE;
380 break;
381 case MONO_THREAD_BEGIN_SUSPEND_SKIP:
382 info->client_info.skip = TRUE;
383 break;
384 case MONO_THREAD_BEGIN_SUSPEND_NEXT_PHASE:
385 g_assert_not_reached ();
386 default:
387 g_assert_not_reached ();
390 THREADS_STW_DEBUG ("[GC-STW-RESTART] SUSPEND thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
391 } FOREACH_THREAD_END
393 mono_threads_wait_pending_operations ();
396 FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
397 gpointer stopped_ip;
399 int reason = 0;
400 if (!sgen_is_thread_in_current_stw (info, &reason)) {
401 g_assert (!info->client_info.suspend_done || info == mono_thread_info_current ());
403 THREADS_STW_DEBUG ("[GC-STW-SUSPEND-END] thread %p is NOT suspended, reason %d\n", mono_thread_info_get_tid (info), reason);
404 continue;
407 g_assert (info->client_info.suspend_done);
409 info->client_info.ctx = mono_thread_info_get_suspend_state (info)->ctx;
411 /* Once we remove the old suspend code, we should move sgen to directly access the state in MonoThread */
412 info->client_info.stack_start = (gpointer) ((char*)MONO_CONTEXT_GET_SP (&info->client_info.ctx) - REDZONE_SIZE);
414 if (info->client_info.stack_start < info->client_info.info.stack_start_limit
415 || info->client_info.stack_start >= info->client_info.info.stack_end) {
417 * Thread context is in unhandled state, most likely because it is
418 * dying. We don't scan it.
419 * FIXME We should probably rework and check the valid flag instead.
421 info->client_info.stack_start = NULL;
424 stopped_ip = (gpointer) (MONO_CONTEXT_GET_IP (&info->client_info.ctx));
426 sgen_binary_protocol_thread_suspend ((gpointer)(gsize)mono_thread_info_get_tid (info), stopped_ip);
428 THREADS_STW_DEBUG ("[GC-STW-SUSPEND-END] thread %p is suspended, stopped_ip = %p, stack = %p -> %p\n",
429 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);
430 } FOREACH_THREAD_END
433 static void
434 sgen_unified_suspend_restart_world (void)
436 THREADS_STW_DEBUG ("[GC-STW-END] *** BEGIN RESUME ***\n");
437 FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
438 int reason = 0;
439 if (sgen_is_thread_in_current_stw (info, &reason)) {
440 g_assert (mono_thread_info_begin_resume (info));
441 THREADS_STW_DEBUG ("[GC-STW-RESUME-WORLD] RESUME thread %p\n", mono_thread_info_get_tid (info));
443 sgen_binary_protocol_thread_restart ((gpointer) mono_thread_info_get_tid (info));
444 } else {
445 THREADS_STW_DEBUG ("[GC-STW-RESUME-WORLD] IGNORE thread %p, reason %d\n", mono_thread_info_get_tid (info), reason);
447 } FOREACH_THREAD_END
449 mono_threads_wait_pending_operations ();
450 mono_threads_end_global_suspend ();
452 #endif