From a5da7b21f4b6dbc5eaa09c2addee91b84dc1dbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 27 Feb 2018 18:36:26 +0100 Subject: [PATCH] [runtime] Implement thread info flags and get rid of tools threads. (#7226) * [acceptance-tests] Revert part of 1216780e830e521b8a4f61a8ca3c505ab63b9e2f. This didn't work as well as I'd hoped. * [libgc] Export GC_start_blocking () and GC_end_blocking (). * [libgc] Allow GC_start_blocking ()/GC_end_blocking () to be called unbalanced. This makes things a bit easier in Mono. * [libgc] Implement GC_start_blocking ()/GC_end_blocking () for Windows. * [utils/event] Export the MonoOSEvent APIs for use in the profiler. * [runtime] Implement thread info flags and get rid of tools threads. The concept of a tools thread was originally a good solution to the problem of some internal threads needing to be exempt from stop-the-world. Since they were introduced, however, we've added more code in the profiler's internal threads which require certain properties that don't hold true for tools threads. In particular, we call into some APIs in the metadata layer that, while not needing the managed heap, do need a domain to be set under certain conditions (see #6188). To avoid further complicating the semantics of tools thread by trying to set a domain for them in a way that doesn't break things horribly, we'll instead do away with the concept entirely. We already had mono_gc_set_skip_thread () to flag a thread as being exempt from stop-the-world, which we used in thread pool threads. This means we already have the infrastructure that is necessary for a thread to be fully attached to the runtime as a managed thread while still being exempt from stop-the-world. So, we do the following: 1. Introduce a set of flags that can be set on a MonoThreadInfo at arbitrary points (although not in async context). These can indicate that the thread doesn't want to participate in stop-the-world, doesn't want to receive sampling signals, etc. 2. Change the thread iteration macros to allow skipping threads with specific flags set and use this where appropriate (e.g. SGen stop-the-world code). 3. Get rid of mono_gc_set_skip_thread () and make runtime code use the new mono_thread_info_set_flags () instead. 4. Switch all internal profiler threads to using mono_thread_attach () and mono_thread_detach (). Use mono_thread_info_set_flags () to disable stop-the-world and sampling signals for these threads immediately after they start. With these changes, internal profiler threads are now fully attached threads and can call any APIs in the runtime as long as they don't touch the managed heap. * [libgc] Add a note about how GC_start_blocking ()/GC_end_blocking () differ from upstream. --- acceptance-tests/profiler-stress/runner.cs | 21 +-- libgc/include/gc.h | 12 ++ libgc/pthread_support.c | 2 - libgc/win32_threads.c | 48 ++++- mono/metadata/boehm-gc.c | 19 +- mono/metadata/gc-internals.h | 4 +- mono/metadata/gc.c | 4 +- mono/metadata/null-gc.c | 9 +- mono/metadata/object-internals.h | 2 +- mono/metadata/sgen-client-mono.h | 9 - mono/metadata/sgen-mono.c | 36 ++-- mono/metadata/sgen-stw.c | 24 +-- mono/metadata/threadpool-io-epoll.c | 4 +- mono/metadata/threadpool-io-kqueue.c | 4 +- mono/metadata/threadpool-io-poll.c | 4 +- mono/metadata/threads-types.h | 4 +- mono/metadata/threads.c | 14 ++ mono/mini/mini-posix.c | 43 +++-- mono/profiler/log.c | 210 +++++++++++++++------ mono/sgen/sgen-alloc.c | 2 +- mono/sgen/sgen-debug.c | 4 +- mono/utils/mono-linked-list-set.h | 18 +- mono/utils/mono-threads.c | 55 +++--- mono/utils/mono-threads.h | 78 ++++++-- mono/utils/os-event.h | 13 +- 25 files changed, 439 insertions(+), 204 deletions(-) diff --git a/acceptance-tests/profiler-stress/runner.cs b/acceptance-tests/profiler-stress/runner.cs index 84bb9846a561..bd9912659a2f 100644 --- a/acceptance-tests/profiler-stress/runner.cs +++ b/acceptance-tests/profiler-stress/runner.cs @@ -40,7 +40,6 @@ sealed class TestResult { public int? ExitCode { get; set; } public string StandardOutput { get; set; } public string StandardError { get; set; } - public string CombinedOutput { get; set; } } static class Program { @@ -191,22 +190,17 @@ static int Main () var stdout = new StringBuilder (); var stderr = new StringBuilder (); - var combined = new StringBuilder (); proc.OutputDataReceived += (sender, args) => { if (args.Data != null) - lock (result) { + lock (result) stdout.AppendLine (args.Data); - combined.AppendLine (args.Data); - } }; proc.ErrorDataReceived += (sender, args) => { if (args.Data != null) - lock (result) { + lock (result) stderr.AppendLine (args.Data); - combined.AppendLine (args.Data); - } }; result.Stopwatch.Start (); @@ -233,7 +227,6 @@ static int Main () lock (result) { result.StandardOutput = stdout.ToString (); result.StandardError = stderr.ToString (); - result.CombinedOutput = combined.ToString (); } } @@ -245,10 +238,16 @@ static int Main () if (result.ExitCode != 0) { Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine ("===== stdout + stderr ====="); + Console.WriteLine ("===== stdout ====="); Console.ResetColor (); - Console.WriteLine (result.CombinedOutput); + Console.WriteLine (result.StandardOutput); + + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine ("===== stderr ====="); + Console.ResetColor (); + + Console.WriteLine (result.StandardError); } results.Add (result); diff --git a/libgc/include/gc.h b/libgc/include/gc.h index e7929918ad0b..c9c20e65a6c3 100644 --- a/libgc/include/gc.h +++ b/libgc/include/gc.h @@ -835,6 +835,18 @@ typedef GC_PTR (*GC_fn_type) GC_PROTO((GC_PTR client_data)); GC_API GC_PTR GC_call_with_alloc_lock GC_PROTO((GC_fn_type fn, GC_PTR client_data)); +/* + * These are similar to GC_do_blocking () in upstream bdwgc. The design is + * simpler in that there is no distinction between active and inactive stack + * frames; instead, while a thread is in blocking state, it promises to not + * interact with the GC at all, and to not keep any pointers to GC memory + * around from before entering blocking state. Additionally, these can be + * called unbalanced (they simply set a flag internally). + */ +GC_API void GC_start_blocking GC_PROTO((void)); + +GC_API void GC_end_blocking GC_PROTO((void)); + /* The following routines are primarily intended for use with a */ /* preprocessor which inserts calls to check C pointer arithmetic. */ /* They indicate failure by invoking the corresponding _print_proc. */ diff --git a/libgc/pthread_support.c b/libgc/pthread_support.c index 6d240f65f0ee..3ff89b292220 100644 --- a/libgc/pthread_support.c +++ b/libgc/pthread_support.c @@ -1245,7 +1245,6 @@ void GC_start_blocking(void) { GC_thread me; LOCK(); me = GC_lookup_thread(pthread_self()); - GC_ASSERT(!(me -> thread_blocked)); # ifdef SPARC me -> stop_info.stack_ptr = (ptr_t)GC_save_regs_in_stack(); # else @@ -1273,7 +1272,6 @@ void GC_end_blocking(void) { GC_thread me; LOCK(); /* This will block if the world is stopped. */ me = GC_lookup_thread(pthread_self()); - GC_ASSERT(me -> thread_blocked); me -> thread_blocked = FALSE; UNLOCK(); } diff --git a/libgc/win32_threads.c b/libgc/win32_threads.c index 5533b8f2e252..425d41a98922 100644 --- a/libgc/win32_threads.c +++ b/libgc/win32_threads.c @@ -48,6 +48,7 @@ struct GC_thread_Rep { /* 0 ==> entry not valid. */ /* !in_use ==> stack_base == 0 */ GC_bool suspended; + GC_bool thread_blocked; # ifdef CYGWIN32 void *status; /* hold exit value until join in case it's a pointer */ @@ -174,6 +175,7 @@ static GC_thread GC_new_thread(void) { /* the world, wait here. Hopefully this can't happen on any */ /* systems that don't allow us to block here. */ while (GC_please_stop) Sleep(20); + GC_ASSERT(!thread_table[i]->thread_blocked); return thread_table + i; } @@ -223,7 +225,6 @@ static void GC_delete_thread(DWORD thread_id) { } } - #ifdef CYGWIN32 /* Return a GC_thread corresponding to a given pthread_t. */ @@ -245,6 +246,20 @@ static GC_thread GC_lookup_thread(pthread_t id) return thread_table + i; } +#else + +static GC_thread GC_lookup_thread(DWORD id) +{ + int i; + LONG max = GC_get_max_thread_index(); + + for (i = 0; i <= max; i++) + if (thread_table[i].in_use && thread_table[i].id == id) + return &thread_table[i]; + + return NULL; +} + #endif /* CYGWIN32 */ void GC_push_thread_structures GC_PROTO((void)) @@ -264,6 +279,35 @@ void GC_push_thread_structures GC_PROTO((void)) # endif } +/* Wrappers for functions that are likely to block for an appreciable */ +/* length of time. Must be called in pairs, if at all. */ +/* Nothing much beyond the system call itself should be executed */ +/* between these. */ + +void GC_start_blocking(void) { + GC_thread me; + LOCK(); +#ifdef CYGWIN32 + me = GC_lookup_thread(pthread_self()); +#else + me = GC_lookup_thread(GetCurrentThreadId()); +#endif + me->thread_blocked = TRUE; + UNLOCK(); +} + +void GC_end_blocking(void) { + GC_thread me; + LOCK(); /* This will block if the world is stopped. */ +#ifdef CYGWIN32 + me = GC_lookup_thread(pthread_self()); +#else + me = GC_lookup_thread(GetCurrentThreadId()); +#endif + me->thread_blocked = FALSE; + UNLOCK(); +} + /* Defined in misc.c */ extern CRITICAL_SECTION GC_write_cs; @@ -281,6 +325,8 @@ void GC_stop_world() for (i = 0; i <= GC_get_max_thread_index(); i++) if (thread_table[i].stack_base != 0 && thread_table[i].id != thread_id) { + if (thread_table [i].thread_blocked) + continue; # ifdef MSWINCE /* SuspendThread will fail if thread is running kernel code */ while (SuspendThread(thread_table[i].handle) == (DWORD)-1) diff --git a/mono/metadata/boehm-gc.c b/mono/metadata/boehm-gc.c index 60bcbd0e8815..ff6057ff9c4d 100644 --- a/mono/metadata/boehm-gc.c +++ b/mono/metadata/boehm-gc.c @@ -611,7 +611,7 @@ static void mono_push_other_roots (void) { g_hash_table_foreach (roots, push_root, NULL); - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { HandleStack* stack = (HandleStack*)info->handle_stack; if (stack) push_handle_stack (stack); @@ -1478,7 +1478,22 @@ mono_gc_set_stack_end (void *stack_end) { } -void mono_gc_set_skip_thread (gboolean value) +void +mono_gc_skip_thread_changing (gboolean skip) +{ + /* + * Unlike SGen, Boehm doesn't respect our thread info flags. We need to + * inform Boehm manually to skip/not skip the current thread. + */ + + if (skip) + GC_start_blocking (); + else + GC_end_blocking (); +} + +void +mono_gc_skip_thread_changed (gboolean skip) { } diff --git a/mono/metadata/gc-internals.h b/mono/metadata/gc-internals.h index 34885e37d7eb..8273912284af 100644 --- a/mono/metadata/gc-internals.h +++ b/mono/metadata/gc-internals.h @@ -319,7 +319,9 @@ gboolean mono_gc_card_table_nursery_check (void); void* mono_gc_get_nursery (int *shift_bits, size_t *size); -void mono_gc_set_skip_thread (gboolean skip); +// Don't use directly; set/unset MONO_THREAD_INFO_FLAGS_NO_GC instead. +void mono_gc_skip_thread_changing (gboolean skip); +void mono_gc_skip_thread_changed (gboolean skip); #ifndef HOST_WIN32 int mono_gc_pthread_create (pthread_t *new_thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); diff --git a/mono/metadata/gc.c b/mono/metadata/gc.c index 32e7ba6228e1..041667d260c4 100644 --- a/mono/metadata/gc.c +++ b/mono/metadata/gc.c @@ -893,7 +893,7 @@ finalizer_thread (gpointer unused) */ g_assert (mono_domain_get () == mono_get_root_domain ()); - mono_gc_set_skip_thread (TRUE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC); if (wait) { /* An alertable wait is required so this thread can be suspended on windows */ @@ -901,7 +901,7 @@ finalizer_thread (gpointer unused) } wait = TRUE; - mono_gc_set_skip_thread (FALSE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); mono_runtime_do_background_work (); diff --git a/mono/metadata/null-gc.c b/mono/metadata/null-gc.c index 6e35625cbeb9..20f3441bdf6a 100644 --- a/mono/metadata/null-gc.c +++ b/mono/metadata/null-gc.c @@ -520,7 +520,14 @@ mono_gc_pthread_create (pthread_t *new_thread, const pthread_attr_t *attr, void } #endif -void mono_gc_set_skip_thread (gboolean value) +void +mono_gc_skip_thread_changing (gboolean skip) +{ + // No STW, nothing needs to be done. +} + +void +mono_gc_skip_thread_changed (gboolean skip) { } diff --git a/mono/metadata/object-internals.h b/mono/metadata/object-internals.h index 4fe3aa48138f..74c38c28c22a 100644 --- a/mono/metadata/object-internals.h +++ b/mono/metadata/object-internals.h @@ -1849,7 +1849,7 @@ mono_ldstr_checked (MonoDomain *domain, MonoImage *image, uint32_t str_index, Mo MonoString* mono_string_new_len_checked (MonoDomain *domain, const char *text, guint length, MonoError *error); -MonoString* +MONO_PROFILER_API MonoString* mono_string_new_checked (MonoDomain *domain, const char *text, MonoError *merror); MonoString* diff --git a/mono/metadata/sgen-client-mono.h b/mono/metadata/sgen-client-mono.h index 2792d99fc7c8..e20022579977 100644 --- a/mono/metadata/sgen-client-mono.h +++ b/mono/metadata/sgen-client-mono.h @@ -40,15 +40,6 @@ struct _SgenClientThreadInfo { gboolean skip, suspend_done; volatile int in_critical_region; - /* - This is set the argument of mono_gc_set_skip_thread. - - A thread that knowingly holds no managed state can call this - function around blocking loops to reduce the GC burden by not - been scanned. - */ - gboolean gc_disabled; - #ifdef SGEN_POSIX_STW /* This is -1 until the first suspend. */ int signal; diff --git a/mono/metadata/sgen-mono.c b/mono/metadata/sgen-mono.c index 281d57f9c673..f85bcb4356bb 100644 --- a/mono/metadata/sgen-mono.c +++ b/mono/metadata/sgen-mono.c @@ -879,7 +879,7 @@ mono_gc_clear_domain (MonoDomain * domain) sgen_clear_nursery_fragments (); - FOREACH_THREAD (info) { + FOREACH_THREAD_ALL (info) { mono_handle_stack_free_domain ((HandleStack*)info->client_info.info.handle_stack, domain); } FOREACH_THREAD_END @@ -2074,13 +2074,11 @@ static void report_stack_roots (void) { GCRootReport report = {0}; - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { void *aligned_stack_start; if (info->client_info.skip) { continue; - } else if (info->client_info.gc_disabled) { - continue; } else if (!mono_thread_info_is_live (info)) { continue; } else if (!info->client_info.stack_start) { @@ -2450,7 +2448,7 @@ sgen_client_thread_attach (SgenThreadInfo* info) { mono_tls_set_sgen_thread_info (info); - info->client_info.skip = 0; + info->client_info.skip = FALSE; info->client_info.stack_start = NULL; @@ -2502,23 +2500,32 @@ sgen_client_thread_detach_with_lock (SgenThreadInfo *p) } void -mono_gc_set_skip_thread (gboolean skip) +mono_gc_skip_thread_changing (gboolean skip) { - SgenThreadInfo *info = mono_thread_info_current (); - + /* + * SGen's STW will respect the thread info flags, but we do need to take + * the GC lock when changing them. If we don't do this, SGen might end up + * trying to resume a thread that wasn't suspended because it had + * MONO_THREAD_INFO_FLAGS_NO_GC set when STW began. + */ LOCK_GC; - info->client_info.gc_disabled = skip; - UNLOCK_GC; if (skip) { - /* If we skip scanning a thread with a non-empty handle stack, we may move an + /* + * If we skip scanning a thread with a non-empty handle stack, we may move an * object but fail to update the reference in the handle. */ - HandleStack *stack = info->client_info.info.handle_stack; + HandleStack *stack = mono_thread_info_current ()->client_info.info.handle_stack; g_assert (stack == NULL || mono_handle_stack_is_empty (stack)); } } +void +mono_gc_skip_thread_changed (gboolean skip) +{ + UNLOCK_GC; +} + gboolean mono_gc_thread_in_critical_region (SgenThreadInfo *info) { @@ -2591,16 +2598,13 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p return; #endif - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { int skip_reason = 0; void *aligned_stack_start; if (info->client_info.skip) { SGEN_LOG (3, "Skipping dead thread %p, range: %p-%p, size: %zd", info, info->client_info.stack_start, info->client_info.info.stack_end, (char*)info->client_info.info.stack_end - (char*)info->client_info.stack_start); skip_reason = 1; - } else if (info->client_info.gc_disabled) { - SGEN_LOG (3, "GC disabled for thread %p, range: %p-%p, size: %zd", info, info->client_info.stack_start, info->client_info.info.stack_end, (char*)info->client_info.info.stack_end - (char*)info->client_info.stack_start); - skip_reason = 2; } else if (!mono_thread_info_is_live (info)) { SGEN_LOG (3, "Skipping non-running thread %p, range: %p-%p, size: %zd (state %x)", info, info->client_info.stack_start, info->client_info.info.stack_end, (char*)info->client_info.info.stack_end - (char*)info->client_info.stack_start, info->client_info.info.thread_state); skip_reason = 3; diff --git a/mono/metadata/sgen-stw.c b/mono/metadata/sgen-stw.c index 14eedd749680..f9405b82e87d 100644 --- a/mono/metadata/sgen-stw.c +++ b/mono/metadata/sgen-stw.c @@ -157,7 +157,7 @@ sgen_client_restart_world (int generation, gboolean serial_collection, gint64 *s MONO_PROFILER_RAISE (gc_event, (MONO_GC_EVENT_PRE_START_WORLD, generation)); MONO_PROFILER_RAISE (gc_event2, (MONO_GC_EVENT_PRE_START_WORLD, generation, serial_collection)); - FOREACH_THREAD (info) { + FOREACH_THREAD_ALL (info) { info->client_info.stack_start = NULL; memset (&info->client_info.ctx, 0, sizeof (MonoContext)); } FOREACH_THREAD_END @@ -208,15 +208,9 @@ static gboolean sgen_is_thread_in_current_stw (SgenThreadInfo *info, int *reason) { /* - A thread explicitly asked to be skiped because it holds no managed state. - This is used by TP and finalizer threads. - FIXME Use an atomic variable for this to avoid everyone taking the GC LOCK. - */ - if (info->client_info.gc_disabled) { - if (reason) - *reason = 1; - return FALSE; - } + * No need to check MONO_THREAD_INFO_FLAGS_NO_GC here as we rely on the + * FOREACH_THREAD_EXCLUDE macro to skip such threads for us. + */ /* We have detected that this thread is failing/dying, ignore it. @@ -268,7 +262,7 @@ sgen_unified_suspend_stop_world (void) mono_threads_begin_global_suspend (); THREADS_STW_DEBUG ("[GC-STW-BEGIN][%p] *** BEGIN SUSPEND *** \n", mono_thread_info_get_tid (mono_thread_info_current ())); - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { info->client_info.skip = FALSE; info->client_info.suspend_done = FALSE; @@ -289,7 +283,7 @@ sgen_unified_suspend_stop_world (void) for (;;) { gint restart_counter = 0; - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { gint suspend_count; int reason = 0; @@ -335,7 +329,7 @@ sgen_unified_suspend_stop_world (void) sleep_duration += 10; } - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { int reason = 0; if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) { 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); @@ -355,7 +349,7 @@ sgen_unified_suspend_stop_world (void) mono_threads_wait_pending_operations (); } - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { gpointer stopped_ip; int reason = 0; @@ -396,7 +390,7 @@ static void sgen_unified_suspend_restart_world (void) { THREADS_STW_DEBUG ("[GC-STW-END] *** BEGIN RESUME ***\n"); - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { int reason = 0; if (sgen_is_thread_in_current_stw (info, &reason)) { g_assert (mono_thread_info_begin_resume (info)); diff --git a/mono/metadata/threadpool-io-epoll.c b/mono/metadata/threadpool-io-epoll.c index 67d555707a59..40efee6b42d8 100644 --- a/mono/metadata/threadpool-io-epoll.c +++ b/mono/metadata/threadpool-io-epoll.c @@ -85,13 +85,13 @@ epoll_event_wait (void (*callback) (gint fd, gint events, gpointer user_data), g memset (epoll_events, 0, sizeof (struct epoll_event) * EPOLL_NEVENTS); - mono_gc_set_skip_thread (TRUE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC); MONO_ENTER_GC_SAFE; ready = epoll_wait (epoll_fd, epoll_events, EPOLL_NEVENTS, -1); MONO_EXIT_GC_SAFE; - mono_gc_set_skip_thread (FALSE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); if (ready == -1) { switch (errno) { diff --git a/mono/metadata/threadpool-io-kqueue.c b/mono/metadata/threadpool-io-kqueue.c index 657fad7e3597..89d46ae40553 100644 --- a/mono/metadata/threadpool-io-kqueue.c +++ b/mono/metadata/threadpool-io-kqueue.c @@ -82,13 +82,13 @@ kqueue_event_wait (void (*callback) (gint fd, gint events, gpointer user_data), memset (kqueue_events, 0, sizeof (struct kevent) * KQUEUE_NEVENTS); - mono_gc_set_skip_thread (TRUE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC); MONO_ENTER_GC_SAFE; ready = kevent (kqueue_fd, NULL, 0, kqueue_events, KQUEUE_NEVENTS, NULL); MONO_EXIT_GC_SAFE; - mono_gc_set_skip_thread (FALSE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); if (ready == -1) { switch (errno) { diff --git a/mono/metadata/threadpool-io-poll.c b/mono/metadata/threadpool-io-poll.c index 8ff6b13c6975..e086835577cd 100644 --- a/mono/metadata/threadpool-io-poll.c +++ b/mono/metadata/threadpool-io-poll.c @@ -140,13 +140,13 @@ poll_event_wait (void (*callback) (gint fd, gint events, gpointer user_data), gp for (i = 0; i < poll_fds_size; ++i) poll_fds [i].revents = 0; - mono_gc_set_skip_thread (TRUE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC); MONO_ENTER_GC_SAFE; ready = mono_poll (poll_fds, poll_fds_size, -1); MONO_EXIT_GC_SAFE; - mono_gc_set_skip_thread (FALSE); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); if (ready == -1) { /* diff --git a/mono/metadata/threads-types.h b/mono/metadata/threads-types.h index afac049c09b4..5c79075506f6 100644 --- a/mono/metadata/threads-types.h +++ b/mono/metadata/threads-types.h @@ -195,7 +195,7 @@ mono_threads_release_app_context (MonoAppContext* ctx, MonoError *error); void ves_icall_System_Runtime_Remoting_Contexts_Context_RegisterContext (MonoAppContextHandle ctx, MonoError *error); void ves_icall_System_Runtime_Remoting_Contexts_Context_ReleaseContext (MonoAppContextHandle ctx, MonoError *error); -MonoInternalThread *mono_thread_internal_current (void); +MONO_PROFILER_API MonoInternalThread *mono_thread_internal_current (void); void mono_thread_internal_abort (MonoInternalThread *thread, gboolean appdomain_unload); void mono_thread_internal_suspend_for_shutdown (MonoInternalThread *thread); @@ -225,7 +225,7 @@ gunichar2* mono_thread_get_name (MonoInternalThread *this_obj, guint32 *name_len MONO_API MonoException* mono_thread_get_undeniable_exception (void); void mono_thread_self_abort (void); -void mono_thread_set_name_internal (MonoInternalThread *this_obj, MonoString *name, gboolean permanent, gboolean reset, MonoError *error); +MONO_PROFILER_API void mono_thread_set_name_internal (MonoInternalThread *this_obj, MonoString *name, gboolean permanent, gboolean reset, MonoError *error); void mono_thread_suspend_all_other_threads (void); gboolean mono_threads_abort_appdomain_threads (MonoDomain *domain, int timeout); diff --git a/mono/metadata/threads.c b/mono/metadata/threads.c index 4cc459437e6a..142a48e13e6e 100644 --- a/mono/metadata/threads.c +++ b/mono/metadata/threads.c @@ -3110,6 +3110,18 @@ ip_in_critical_region (MonoDomain *domain, gpointer ip) return mono_gc_is_critical_method (method); } +static void +thread_flags_changing (MonoThreadInfoFlags old, MonoThreadInfoFlags new_) +{ + mono_gc_skip_thread_changing (!!(new_ & MONO_THREAD_INFO_FLAGS_NO_GC)); +} + +static void +thread_flags_changed (MonoThreadInfoFlags old, MonoThreadInfoFlags new_) +{ + mono_gc_skip_thread_changed (!!(new_ & MONO_THREAD_INFO_FLAGS_NO_GC)); +} + void mono_thread_callbacks_init (void) { @@ -3121,6 +3133,8 @@ mono_thread_callbacks_init (void) cb.thread_detach_with_lock = thread_detach_with_lock; cb.ip_in_critical_region = ip_in_critical_region; cb.thread_in_critical_region = thread_in_critical_region; + cb.thread_flags_changing = thread_flags_changing; + cb.thread_flags_changed = thread_flags_changed; mono_thread_info_callbacks_init (&cb); } diff --git a/mono/mini/mini-posix.c b/mono/mini/mini-posix.c index a15921949ac7..a23912d4bafe 100644 --- a/mono/mini/mini-posix.c +++ b/mono/mini/mini-posix.c @@ -58,6 +58,7 @@ #include #include #include +#include #include "mini.h" #include @@ -606,12 +607,23 @@ clock_sleep_ns_abs (guint64 ns_abs) static int profiler_signal; static volatile gint32 sampling_thread_exiting; +static MonoOSEvent sampling_thread_exited; -static mono_native_thread_return_t -sampling_thread_func (void *data) +static gsize +sampling_thread_func (gpointer unused) { - mono_threads_attach_tools_thread (); - mono_native_thread_set_name (mono_native_thread_id_get (), "Profiler sampler"); + MonoInternalThread *thread = mono_thread_internal_current (); + + thread->flags |= MONO_THREAD_FLAG_DONT_MANAGE; + + ERROR_DECL (error); + + MonoString *name = mono_string_new_checked (mono_get_root_domain (), "Profiler Sampler", error); + mono_error_assert_ok (error); + mono_thread_set_name_internal (thread, name, FALSE, FALSE, error); + mono_error_assert_ok (error); + + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC | MONO_THREAD_INFO_FLAGS_NO_SAMPLE); int old_policy; struct sched_param old_sched; @@ -663,9 +675,8 @@ sampling_thread_func (void *data) sleep += 1000000000 / freq; - FOREACH_THREAD_SAFE (info) { - /* info should never be this thread as we're a tools thread. */ - g_assert (mono_thread_info_get_tid (info) != mono_native_thread_id_get ()); + FOREACH_THREAD_SAFE_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_SAMPLE) { + g_assert (mono_thread_info_get_tid (info) != sampling_thread); /* * Require an ack for the last sampling signal sent to the thread @@ -687,9 +698,11 @@ sampling_thread_func (void *data) pthread_setschedparam (pthread_self (), old_policy, &old_sched); - mono_thread_info_detach (); + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); - return NULL; + mono_os_event_set (&sampling_thread_exited); + + return 0; } void @@ -727,7 +740,8 @@ mono_runtime_shutdown_stat_profiler (void) } #endif - mono_native_thread_join (sampling_thread); + mono_os_event_wait_one (&sampling_thread_exited, MONO_INFINITE_WAIT, FALSE); + mono_os_event_destroy (&sampling_thread_exited); /* * We can't safely remove the signal handler because we have no guarantee @@ -769,8 +783,15 @@ mono_runtime_setup_stat_profiler (void) mono_counters_register ("Sampling signals accepted", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &profiler_signals_accepted); mono_counters_register ("Shutdown signals received", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &profiler_interrupt_signals_received); + mono_os_event_init (&sampling_thread_exited, FALSE); + mono_atomic_store_i32 (&sampling_thread_running, 1); - mono_native_thread_create (&sampling_thread, sampling_thread_func, NULL); + + MonoError error; + MonoInternalThread *thread = mono_thread_create_internal (mono_get_root_domain (), sampling_thread_func, NULL, MONO_THREAD_CREATE_FLAGS_NONE, &error); + mono_error_assert_ok (&error); + + sampling_thread = MONO_UINT_TO_NATIVE_THREAD_ID (thread->tid); } #else diff --git a/mono/profiler/log.c b/mono/profiler/log.c index 84fe2240ce28..99ea14b7b418 100644 --- a/mono/profiler/log.c +++ b/mono/profiler/log.c @@ -19,7 +19,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -37,6 +40,7 @@ #include #include #include +#include #include "log.h" #ifdef HAVE_DLFCN_H @@ -146,6 +150,9 @@ typedef struct { // Was this thread added to the LLS? gboolean attached; + // Did this thread detach from the runtime? Only used for internal profiler threads. + gboolean did_detach; + // The current log buffer for this thread. LogBuffer *buffer; @@ -317,12 +324,18 @@ struct _MonoProfiler { volatile gint32 buffer_lock_exclusive_intent; volatile gint32 runtime_inited; + volatile gint32 detach_threads; volatile gint32 in_shutdown; + MonoSemType attach_threads_sem; + MonoSemType detach_threads_sem; + MonoNativeThreadId helper_thread; + MonoOSEvent helper_thread_exited; MonoNativeThreadId writer_thread; volatile gint32 run_writer_thread; + MonoOSEvent writer_thread_exited; MonoLockFreeQueue writer_queue; MonoSemType writer_queue_sem; @@ -334,6 +347,7 @@ struct _MonoProfiler { MonoNativeThreadId dumper_thread; volatile gint32 run_dumper_thread; + MonoOSEvent dumper_thread_exited; MonoLockFreeQueue dumper_queue; MonoSemType dumper_queue_sem; @@ -520,6 +534,7 @@ init_thread (gboolean add_to_lls) thread = g_malloc (sizeof (MonoProfilerThread)); thread->node.key = thread_id (); thread->attached = add_to_lls; + thread->did_detach = FALSE; thread->call_depth = 0; thread->busy = FALSE; thread->ended = FALSE; @@ -1088,15 +1103,6 @@ dump_buffer (LogBuffer *buf) free_buffer (buf, buf->size); } -static void -dump_buffer_threadless (LogBuffer *buf) -{ - for (LogBuffer *iter = buf; iter; iter = iter->next) - iter->thread_id = 0; - - dump_buffer (buf); -} - // Only valid if init_thread () was called with add_to_lls = FALSE. static void send_log_unsafe (gboolean if_needed) @@ -1104,10 +1110,6 @@ send_log_unsafe (gboolean if_needed) MonoProfilerThread *thread = get_thread (); if (!if_needed || (if_needed && thread->buffer->next)) { - if (!thread->attached) - for (LogBuffer *iter = thread->buffer; iter; iter = iter->next) - iter->thread_id = 0; - send_buffer (thread); init_buffer_state (thread); } @@ -2084,10 +2086,13 @@ thread_end (MonoProfiler *prof, uintptr_t tid) MonoProfilerThread *thread = get_thread (); - thread->ended = TRUE; - remove_thread (thread); + // Internal profiler threads will clean up manually. + if (thread->attached) { + thread->ended = TRUE; + remove_thread (thread); - PROF_TLS_SET (MONO_PROFILER_THREAD_DEAD); + PROF_TLS_SET (MONO_PROFILER_THREAD_DEAD); + } } static void @@ -2851,6 +2856,15 @@ cleanup_reusable_samples (void) mono_thread_hazardous_try_free (sample, free_sample_hit); } +static void +signal_helper_thread (char c) +{ + if (write (log_profiler.pipes [1], &c, 1) != 1) { + mono_profiler_printf_err ("Could not write to log profiler pipe: %s", g_strerror (errno)); + exit (1); + } +} + static void log_early_shutdown (MonoProfiler *prof) { @@ -2858,6 +2872,23 @@ log_early_shutdown (MonoProfiler *prof) mono_atomic_store_i32 (&log_profiler.heapshot_requested, 1); mono_gc_collect (mono_gc_max_generation ()); } + + /* + * We need to detach the internal threads early on. log_shutdown () is + * called after the threading subsystem has been cleaned up, so detaching + * there would crash. + */ + mono_os_sem_init (&log_profiler.detach_threads_sem, 0); + mono_atomic_store_i32 (&log_profiler.detach_threads, 1); + + signal_helper_thread (2); + mono_os_sem_post (&prof->dumper_queue_sem); + mono_os_sem_post (&prof->writer_queue_sem); + + for (int i = 0; i < 3; i++) + mono_os_sem_wait (&log_profiler.detach_threads_sem, MONO_SEM_FLAGS_NONE); + + mono_os_sem_destroy (&log_profiler.detach_threads_sem); } static void @@ -2868,14 +2899,9 @@ log_shutdown (MonoProfiler *prof) if (ENABLED (PROFLOG_COUNTER_EVENTS)) counters_and_perfcounters_sample (); - char c = 1; - - if (write (prof->pipes [1], &c, 1) != 1) { - mono_profiler_printf_err ("Could not write to log profiler pipe: %s", g_strerror (errno)); - exit (1); - } - - mono_native_thread_join (prof->helper_thread); + signal_helper_thread (1); + mono_os_event_wait_one (&prof->helper_thread_exited, MONO_INFINITE_WAIT, FALSE); + mono_os_event_destroy (&prof->helper_thread_exited); mono_os_mutex_destroy (&log_profiler.counters_mutex); @@ -2914,12 +2940,14 @@ log_shutdown (MonoProfiler *prof) mono_atomic_store_i32 (&prof->run_dumper_thread, 0); mono_os_sem_post (&prof->dumper_queue_sem); - mono_native_thread_join (prof->dumper_thread); + mono_os_event_wait_one (&prof->dumper_thread_exited, MONO_INFINITE_WAIT, FALSE); + mono_os_event_destroy (&prof->dumper_thread_exited); mono_os_sem_destroy (&prof->dumper_queue_sem); mono_atomic_store_i32 (&prof->run_writer_thread, 0); mono_os_sem_post (&prof->writer_queue_sem); - mono_native_thread_join (prof->writer_thread); + mono_os_event_wait_one (&prof->writer_thread_exited, MONO_INFINITE_WAIT, FALSE); + mono_os_event_destroy (&prof->writer_thread_exited); mono_os_sem_destroy (&prof->writer_queue_sem); /* @@ -3021,6 +3049,62 @@ new_filename (const char* filename) return res; } +static MonoProfilerThread * +profiler_thread_begin (const char *name) +{ + MonoProfilerThread *thread = init_thread (FALSE); + + mono_thread_attach (mono_get_root_domain ()); + + MonoInternalThread *internal = mono_thread_internal_current (); + + /* + * Don't let other threads try to suspend internal profiler threads during + * shutdown. This can happen if a program calls Environment.Exit () which + * calls mono_thread_suspend_all_other_threads (). + */ + internal->flags |= MONO_THREAD_FLAG_DONT_MANAGE; + + ERROR_DECL (error); + + MonoString *name_str = mono_string_new_checked (mono_get_root_domain (), name, error); + mono_error_assert_ok (error); + mono_thread_set_name_internal (internal, name_str, FALSE, FALSE, error); + mono_error_assert_ok (error); + + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC | MONO_THREAD_INFO_FLAGS_NO_SAMPLE); + + mono_os_sem_post (&log_profiler.attach_threads_sem); + + return thread; +} + +static void +profiler_thread_end (MonoProfilerThread *thread, MonoOSEvent *event, gboolean send) +{ + if (send) + send_log_unsafe (FALSE); + else + dump_buffer (thread->buffer); + + deinit_thread (thread); + + mono_os_event_set (event); +} + +static void +profiler_thread_check_detach (MonoProfilerThread *thread) +{ + if (mono_atomic_load_i32 (&log_profiler.detach_threads) && !thread->did_detach) { + thread->did_detach = TRUE; + + mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); + mono_thread_detach (mono_thread_current ()); + + mono_os_sem_post (&log_profiler.detach_threads_sem); + } +} + static void add_to_fd_set (fd_set *set, int fd, int *max_fd) { @@ -3044,10 +3128,7 @@ add_to_fd_set (fd_set *set, int fd, int *max_fd) static void * helper_thread (void *arg) { - mono_threads_attach_tools_thread (); - mono_native_thread_set_name (mono_native_thread_id_get (), "Profiler helper"); - - MonoProfilerThread *thread = init_thread (FALSE); + MonoProfilerThread *thread = profiler_thread_begin ("Profiler Helper"); GArray *command_sockets = g_array_new (FALSE, FALSE, sizeof (int)); @@ -3083,11 +3164,14 @@ helper_thread (void *arg) buffer_unlock_excl (); - // Are we shutting down? + // Did we get a shutdown or detach signal? if (FD_ISSET (log_profiler.pipes [0], &rfds)) { char c; + read (log_profiler.pipes [0], &c, 1); - break; + + if (c == 1) + break; } for (gint i = 0; i < command_sockets->len; i++) { @@ -3126,6 +3210,8 @@ helper_thread (void *arg) g_array_append_val (command_sockets, fd); } } + + profiler_thread_check_detach (thread); } for (gint i = 0; i < command_sockets->len; i++) @@ -3133,10 +3219,7 @@ helper_thread (void *arg) g_array_free (command_sockets, TRUE); - send_log_unsafe (FALSE); - deinit_thread (thread); - - mono_thread_info_detach (); + profiler_thread_end (thread, &log_profiler.helper_thread_exited, TRUE); return NULL; } @@ -3276,7 +3359,7 @@ handle_writer_queue_entry (void) if (wrote_methods) { MonoProfilerThread *thread = get_thread (); - dump_buffer_threadless (thread->buffer); + dump_buffer (thread->buffer); init_buffer_state (thread); } @@ -3294,25 +3377,21 @@ handle_writer_queue_entry (void) static void * writer_thread (void *arg) { - mono_threads_attach_tools_thread (); - mono_native_thread_set_name (mono_native_thread_id_get (), "Profiler writer"); - dump_header (); - MonoProfilerThread *thread = init_thread (FALSE); + MonoProfilerThread *thread = profiler_thread_begin ("Profiler Writer"); while (mono_atomic_load_i32 (&log_profiler.run_writer_thread)) { mono_os_sem_wait (&log_profiler.writer_queue_sem, MONO_SEM_FLAGS_NONE); handle_writer_queue_entry (); + + profiler_thread_check_detach (thread); } /* Drain any remaining entries on shutdown. */ while (handle_writer_queue_entry ()); - free_buffer (thread->buffer, thread->buffer->size); - deinit_thread (thread); - - mono_thread_info_detach (); + profiler_thread_end (thread, &log_profiler.writer_thread_exited, FALSE); return NULL; } @@ -3401,10 +3480,7 @@ handle_dumper_queue_entry (void) static void * dumper_thread (void *arg) { - mono_threads_attach_tools_thread (); - mono_native_thread_set_name (mono_native_thread_id_get (), "Profiler dumper"); - - MonoProfilerThread *thread = init_thread (FALSE); + MonoProfilerThread *thread = profiler_thread_begin ("Profiler Dumper"); while (mono_atomic_load_i32 (&log_profiler.run_dumper_thread)) { /* @@ -3415,15 +3491,14 @@ dumper_thread (void *arg) send_log_unsafe (FALSE); handle_dumper_queue_entry (); + + profiler_thread_check_detach (thread); } /* Drain any remaining entries on shutdown. */ while (handle_dumper_queue_entry ()); - send_log_unsafe (FALSE); - deinit_thread (thread); - - mono_thread_info_detach (); + profiler_thread_end (thread, &log_profiler.dumper_thread_exited, TRUE); return NULL; } @@ -3445,6 +3520,11 @@ register_counter (const char *name, gint32 *counter) mono_counters_register (name, MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, counter); } +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif + ICALL_EXPORT gint32 proflog_icall_GetMaxStackTraceFrames (void) { @@ -3770,6 +3850,10 @@ proflog_icall_SetJitEvents (MonoBoolean value) mono_coop_mutex_unlock (&log_profiler.api_mutex); } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + static void runtime_initialized (MonoProfiler *profiler) { @@ -3825,15 +3909,27 @@ runtime_initialized (MonoProfiler *profiler) counters_init (); + mono_os_sem_init (&log_profiler.attach_threads_sem, 0); + /* * We must start the helper thread before the writer thread. This is - * because the helper thread sets up the command port which is written to - * the log header by the writer thread. + * because start_helper_thread () sets up the command port which is written + * to the log header by the writer thread. */ start_helper_thread (); start_writer_thread (); start_dumper_thread (); + /* + * Wait for all the internal threads to be started. If we don't do this, we + * might shut down before they finish initializing, which could lead to + * various deadlocks when waiting for them to exit during shutdown. + */ + for (int i = 0; i < 3; i++) + mono_os_sem_wait (&log_profiler.attach_threads_sem, MONO_SEM_FLAGS_NONE); + + mono_os_sem_destroy (&log_profiler.attach_threads_sem); + mono_coop_mutex_init (&log_profiler.api_mutex); #define ADD_ICALL(NAME) \ @@ -3934,9 +4030,13 @@ create_profiler (const char *args, const char *filename, GPtrArray *filters) mono_lock_free_allocator_init_size_class (&log_profiler.writer_entry_size_class, sizeof (WriterQueueEntry), WRITER_ENTRY_BLOCK_SIZE); mono_lock_free_allocator_init_allocator (&log_profiler.writer_entry_allocator, &log_profiler.writer_entry_size_class, MONO_MEM_ACCOUNT_PROFILER); + mono_os_event_init (&log_profiler.helper_thread_exited, FALSE); + + mono_os_event_init (&log_profiler.writer_thread_exited, FALSE); mono_lock_free_queue_init (&log_profiler.writer_queue); mono_os_sem_init (&log_profiler.writer_queue_sem, 0); + mono_os_event_init (&log_profiler.dumper_thread_exited, FALSE); mono_lock_free_queue_init (&log_profiler.dumper_queue); mono_os_sem_init (&log_profiler.dumper_queue_sem, 0); diff --git a/mono/sgen/sgen-alloc.c b/mono/sgen/sgen-alloc.c index 0e6278fea23e..8c9d202fa1ce 100644 --- a/mono/sgen/sgen-alloc.c +++ b/mono/sgen/sgen-alloc.c @@ -477,7 +477,7 @@ sgen_alloc_obj_mature (GCVTable vtable, size_t size) void sgen_clear_tlabs (void) { - FOREACH_THREAD (info) { + FOREACH_THREAD_ALL (info) { /* A new TLAB will be allocated when the thread does its first allocation */ info->tlab_start = NULL; info->tlab_next = NULL; diff --git a/mono/sgen/sgen-debug.c b/mono/sgen/sgen-debug.c index f8d3aaa05011..bb32843f8b90 100644 --- a/mono/sgen/sgen-debug.c +++ b/mono/sgen/sgen-debug.c @@ -501,10 +501,10 @@ find_pinning_ref_from_thread (char *obj, size_t size) #ifndef SGEN_WITHOUT_MONO char *endobj = obj + size; - FOREACH_THREAD (info) { + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { mword *ctxstart, *ctxcurrent, *ctxend; char **start = (char**)info->client_info.stack_start; - if (info->client_info.skip || info->client_info.gc_disabled) + if (info->client_info.skip) continue; while (start < (char**)info->client_info.info.stack_end) { if (*start >= obj && *start < endobj) diff --git a/mono/utils/mono-linked-list-set.h b/mono/utils/mono-linked-list-set.h index ca441ffc487a..890f0cad8dca 100644 --- a/mono/utils/mono-linked-list-set.h +++ b/mono/utils/mono-linked-list-set.h @@ -61,7 +61,7 @@ MONO_API gpointer mono_lls_get_hazardous_pointer_with_mask (gpointer volatile *pp, MonoThreadHazardPointers *hp, int hazard_index); static inline gboolean -mono_lls_filter_accept_all (gpointer elem) +mono_lls_filter_accept_all (gpointer elem, gpointer dummy) { return TRUE; } @@ -70,13 +70,13 @@ mono_lls_filter_accept_all (gpointer elem) * These macros assume that no other threads are actively modifying the list. */ -#define MONO_LLS_FOREACH_FILTERED(list, type, elem, filter) \ +#define MONO_LLS_FOREACH_FILTERED(list, type, elem, filter, ...) \ do { \ MonoLinkedListSet *list__ = (list); \ for (MonoLinkedListSetNode *cur__ = list__->head; cur__; cur__ = (MonoLinkedListSetNode *) mono_lls_pointer_unmask (cur__->next)) { \ if (!mono_lls_pointer_get_mark (cur__->next)) { \ type *elem = (type *) cur__; \ - if (filter (elem)) { + if (filter (elem, __VA_ARGS__)) { #define MONO_LLS_FOREACH_END \ } \ @@ -85,18 +85,18 @@ mono_lls_filter_accept_all (gpointer elem) } while (0); #define MONO_LLS_FOREACH(list, type, elem) \ - MONO_LLS_FOREACH_FILTERED ((list), type, elem, mono_lls_filter_accept_all) + MONO_LLS_FOREACH_FILTERED ((list), type, elem, mono_lls_filter_accept_all, NULL) /* * These macros can be used while other threads are potentially modifying the * list, but they only provide a snapshot of the list as a result. * * NOTE: Do NOT break out of the loop through any other means than a break - * statement, as other ways of breaking the loop will skip past important - * cleanup work. + * statement, as other ways of breaking the loop (return, goto, etc) will skip + * past important cleanup work. */ -#define MONO_LLS_FOREACH_FILTERED_SAFE(list, type, elem, filter) \ +#define MONO_LLS_FOREACH_FILTERED_SAFE(list, type, elem, filter, ...) \ do { \ /* NOTE: Keep this macro's code in sync with the mono_lls_find () logic. */ \ MonoLinkedListSet *list__ = (list); \ @@ -125,7 +125,7 @@ mono_lls_filter_accept_all (gpointer elem) progress__ = TRUE; \ hkey__ = ckey__; \ type *elem = (type *) cur__; \ - if (filter (elem)) { \ + if (filter (elem, __VA_ARGS__)) { \ gboolean broke__ = TRUE; \ gboolean done__ = FALSE; \ do { \ @@ -169,6 +169,6 @@ mono_lls_filter_accept_all (gpointer elem) } while (0); #define MONO_LLS_FOREACH_SAFE(list, type, elem) \ - MONO_LLS_FOREACH_FILTERED_SAFE ((list), type, elem, mono_lls_filter_accept_all) + MONO_LLS_FOREACH_FILTERED_SAFE ((list), type, elem, mono_lls_filter_accept_all, NULL) #endif /* __MONO_SPLIT_ORDERED_LIST_H__ */ diff --git a/mono/utils/mono-threads.c b/mono/utils/mono-threads.c index 3f21b370236f..2ab8071f5799 100644 --- a/mono/utils/mono-threads.c +++ b/mono/utils/mono-threads.c @@ -217,7 +217,7 @@ dump_threads (void) MOSTLY_ASYNC_SAFE_PRINTF ("\t0x*07\t- blocking (GOOD)\n"); MOSTLY_ASYNC_SAFE_PRINTF ("\t0x?08\t- blocking with pending suspend (GOOD)\n"); - FOREACH_THREAD_SAFE (info) { + FOREACH_THREAD_SAFE_ALL (info) { #ifdef TARGET_MACH char thread_name [256] = { 0 }; pthread_getname_np (mono_thread_info_get_tid (info), thread_name, 255); @@ -597,37 +597,7 @@ mono_thread_info_list_head (void) return &thread_list; } -/** - * mono_threads_attach_tools_thread - * - * Attach the current thread as a tool thread. DON'T USE THIS FUNCTION WITHOUT READING ALL DISCLAIMERS. - * - * A tools thread is a very special kind of thread that needs access to core runtime facilities but should - * not be counted as a regular thread for high order facilities such as executing managed code or accessing - * the managed heap. - * - * This is intended only to tools such as a profiler than needs to be able to use our lock-free support when - * doing things like resolving backtraces in their background processing thread. - */ -void -mono_threads_attach_tools_thread (void) -{ - MonoThreadInfo *info; - - /* Must only be called once */ - g_assert (!mono_native_tls_get_value (thread_info_key)); - - while (!mono_threads_inited) { - mono_thread_info_usleep (10); - } - - info = mono_thread_info_attach (); - g_assert (info); - - info->tools_thread = TRUE; -} - -MonoThreadInfo* +MonoThreadInfo * mono_thread_info_attach (void) { MonoThreadInfo *info; @@ -738,6 +708,27 @@ thread_info_key_dtor (void *arg) } #endif +MonoThreadInfoFlags +mono_thread_info_get_flags (MonoThreadInfo *info) +{ + return mono_atomic_load_i32 (&info->flags); +} + +void +mono_thread_info_set_flags (MonoThreadInfoFlags flags) +{ + MonoThreadInfo *info = mono_thread_info_current (); + MonoThreadInfoFlags old = mono_atomic_load_i32 (&info->flags); + + if (threads_callbacks.thread_flags_changing) + threads_callbacks.thread_flags_changing (old, flags); + + mono_atomic_store_i32 (&info->flags, flags); + + if (threads_callbacks.thread_flags_changed) + threads_callbacks.thread_flags_changed (old, flags); +} + void mono_thread_info_init (size_t info_size) { diff --git a/mono/utils/mono-threads.h b/mono/utils/mono-threads.h index ade9380d5123..178ede51a1d8 100644 --- a/mono/utils/mono-threads.h +++ b/mono/utils/mono-threads.h @@ -148,18 +148,43 @@ enum { typedef struct _MonoThreadInfoInterruptToken MonoThreadInfoInterruptToken; +/* + * These flags control how the rest of the runtime will see and interact with + * a thread. + */ +typedef enum { + /* + * No flags means it's a normal thread that takes part in all runtime + * functionality. + */ + MONO_THREAD_INFO_FLAGS_NONE = 0, + /* + * The thread will not be suspended by the STW machinery. The thread is not + * allowed to allocate or access managed memory at all, nor execute managed + * code. + */ + MONO_THREAD_INFO_FLAGS_NO_GC = 1, + /* + * The thread will not be subject to profiler sampling signals. + */ + MONO_THREAD_INFO_FLAGS_NO_SAMPLE = 2, +} MonoThreadInfoFlags; + typedef struct { MonoLinkedListSetNode node; guint32 small_id; /*Used by hazard pointers */ MonoNativeThreadHandle native_handle; /* Valid on mach and android */ int thread_state; + /* + * Must not be changed directly, and especially not by other threads. Use + * mono_thread_info_get/set_flags () to manipulate this. + */ + volatile gint32 flags; + /*Tells if this thread was created by the runtime or not.*/ gboolean runtime_thread; - /* Tells if this thread should be ignored or not by runtime services such as GC and profiling */ - gboolean tools_thread; - /* Max stack bounds, all valid addresses must be between [stack_start_limit, stack_end[ */ void *stack_start_limit, *stack_end; @@ -255,6 +280,10 @@ typedef struct { void (*thread_detach_with_lock)(THREAD_INFO_TYPE *info); gboolean (*ip_in_critical_region) (MonoDomain *domain, gpointer ip); gboolean (*thread_in_critical_region) (THREAD_INFO_TYPE *info); + + // Called on the affected thread. + void (*thread_flags_changing) (MonoThreadInfoFlags old, MonoThreadInfoFlags new_); + void (*thread_flags_changed) (MonoThreadInfoFlags old, MonoThreadInfoFlags new_); } MonoThreadInfoCallbacks; typedef struct { @@ -272,26 +301,41 @@ typedef enum { typedef SuspendThreadResult (*MonoSuspendThreadCallback) (THREAD_INFO_TYPE *info, gpointer user_data); +MONO_API MonoThreadInfoFlags +mono_thread_info_get_flags (THREAD_INFO_TYPE *info); + +/* + * Sets the thread info flags for the current thread. This function may invoke + * callbacks containing arbitrary code (e.g. locks) so it must be assumed to be + * async unsafe. + */ +MONO_API void +mono_thread_info_set_flags (MonoThreadInfoFlags flags); + static inline gboolean -mono_threads_filter_tools_threads (THREAD_INFO_TYPE *info) +mono_threads_filter_exclude_flags (THREAD_INFO_TYPE *info, MonoThreadInfoFlags flags) { - return !((MonoThreadInfo*)info)->tools_thread; + return !(mono_thread_info_get_flags (info) & flags); } -/* -Requires the world to be stoped -*/ -#define FOREACH_THREAD(thread) \ - MONO_LLS_FOREACH_FILTERED (mono_thread_info_list_head (), THREAD_INFO_TYPE, thread, mono_threads_filter_tools_threads) +/* Normal iteration; requires the world to be stopped. */ + +#define FOREACH_THREAD_ALL(thread) \ + MONO_LLS_FOREACH_FILTERED (mono_thread_info_list_head (), THREAD_INFO_TYPE, thread, mono_lls_filter_accept_all, NULL) + +#define FOREACH_THREAD_EXCLUDE(thread, not_flags) \ + MONO_LLS_FOREACH_FILTERED (mono_thread_info_list_head (), THREAD_INFO_TYPE, thread, mono_threads_filter_exclude_flags, not_flags) #define FOREACH_THREAD_END \ MONO_LLS_FOREACH_END -/* -Snapshot iteration. -*/ -#define FOREACH_THREAD_SAFE(thread) \ - MONO_LLS_FOREACH_FILTERED_SAFE (mono_thread_info_list_head (), THREAD_INFO_TYPE, thread, mono_threads_filter_tools_threads) +/* Snapshot iteration; can be done anytime but is slower. */ + +#define FOREACH_THREAD_SAFE_ALL(thread) \ + MONO_LLS_FOREACH_FILTERED_SAFE (mono_thread_info_list_head (), THREAD_INFO_TYPE, thread, mono_lls_filter_accept_all, NULL) + +#define FOREACH_THREAD_SAFE_EXCLUDE(thread, not_flags) \ + MONO_LLS_FOREACH_FILTERED_SAFE (mono_thread_info_list_head (), THREAD_INFO_TYPE, thread, mono_threads_filter_exclude_flags, not_flags) #define FOREACH_THREAD_SAFE_END \ MONO_LLS_FOREACH_SAFE_END @@ -444,10 +488,6 @@ mono_threads_open_thread_handle (MonoThreadHandle *handle); void mono_threads_close_thread_handle (MonoThreadHandle *handle); -MONO_API void -mono_threads_attach_tools_thread (void); - - #if !defined(HOST_WIN32) /*Use this instead of pthread_kill */ diff --git a/mono/utils/os-event.h b/mono/utils/os-event.h index 9ab5524f0a57..16a421aa6216 100644 --- a/mono/utils/os-event.h +++ b/mono/utils/os-event.h @@ -8,6 +8,7 @@ #include #include +#include #include "mono-os-mutex.h" #ifndef MONO_INFINITE_WAIT @@ -35,22 +36,22 @@ struct _MonoOSEvent { #endif }; -void +MONO_API void mono_os_event_init (MonoOSEvent *event, gboolean initial); -void +MONO_API void mono_os_event_destroy (MonoOSEvent *event); -void +MONO_API void mono_os_event_set (MonoOSEvent *event); -void +MONO_API void mono_os_event_reset (MonoOSEvent *event); -MonoOSEventWaitRet +MONO_API MonoOSEventWaitRet mono_os_event_wait_one (MonoOSEvent *event, guint32 timeout, gboolean alertable); -MonoOSEventWaitRet +MONO_API MonoOSEventWaitRet mono_os_event_wait_multiple (MonoOSEvent **events, gsize nevents, gboolean waitall, guint32 timeout, gboolean alertable); #endif /* _MONO_UTILS_OS_EVENT_H_ */