Skip to content

Commit c2e9b26

Browse files
[threads] Switch foreign threads to GC Safe in mono_thread_detach (#42758)
* [test] Invoke from foreign threads then try to GC and shutdown Even if the non-Mono threads that once called Mono are looping or deadlocked, they shouldn't prevent us from doing a GC (in hybrid suspend mode) or from shutting down if they detached from the runtime. * [tests] update .gitignore Ignore AOT build artifacts in subdirectories of mono/tests/, too * [threads] Make mono_thread_attach external only Runtime should use mono_thread_internal_attach * [threads] Mark mono_thread_detach external only; switch to GC Safe Runtime threads should call mono_thread_internal_detach Addresses mono/mono#20290 and mono/mono#20283 If a foreign thread (that was created outside the runtime) calls mono_thread_detach, leave it in a preemptively-suspendable state, since we can't expect it to coop suspend. Conversely in mono_thread_attach (external only), ensure that we always leave the thread in GC Unsafe (aka RUNNING) state, for cases like while (cond) { t = mono_thread_attach (domain); <...> mono_thread_detach (t); } * Tests fixup Delete test that invokes the runtime in a loop forever. This is just exercising a race between mono_thread_attach and the runtime shutdown. Instead update the invoke_foreign_thread test to loop a few times to check that attach/detach loops are ok. In the deadlock test, wait for the foreign thread to finish calling the runtime before the test thread returns from native back to managed to avoid a race between shutdown and the invoke. * [interp] Set context to null when freeing If a foreign thread runs this loop for (...) { mono_thread_attach; mono_runtime_invoke; mono_thread_detach; } on the second iteration it will get a ThreadContext that was already freed during the detach. Set the TLS variable to null before freeing the context. * [threads] Switch to GC Unsafe before creating managed thread object For a re-attaching move the thread state transition to happen earlier so that create_internal_thread_object (which does a managed allocation) is always done in GC Unsafe mode. * fixup eventpipe to use mono_thread_internal_{attach,detach} Co-authored-by: lambdageek <[email protected]> Co-authored-by: Aleksey Kliger <[email protected]>
1 parent ad88f42 commit c2e9b26

File tree

15 files changed

+412
-35
lines changed

15 files changed

+412
-35
lines changed

src/mono/mono/eventpipe/ep-rt-mono.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,7 +1528,7 @@ ep_rt_thread_setup (bool background_thread)
15281528
#ifdef EP_RT_MONO_USE_STATIC_RUNTIME
15291529
// NOTE, under netcore, only root domain exists.
15301530
if (!mono_thread_current ()) {
1531-
MonoThread *thread = mono_thread_attach (mono_get_root_domain ());
1531+
MonoThread *thread = mono_thread_internal_attach (mono_get_root_domain ());
15321532
if (background_thread && thread) {
15331533
mono_thread_set_state (thread, ThreadState_Background);
15341534
mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_SAMPLE);
@@ -1547,7 +1547,7 @@ ep_rt_thread_teardown (void)
15471547
#ifdef EP_RT_MONO_USE_STATIC_RUNTIME
15481548
MonoThread *current_thread = mono_thread_current ();
15491549
if (current_thread)
1550-
mono_thread_detach (current_thread);
1550+
mono_thread_internal_detach (current_thread);
15511551
#else
15521552
ep_rt_mono_func_table_get ()->ep_rt_mono_thread_detach ();
15531553
#endif

src/mono/mono/metadata/appdomain.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ mono_runtime_init_checked (MonoDomain *domain, MonoThreadStartCB start_cb, MonoT
349349
domain->setup = MONO_HANDLE_RAW (setup);
350350
}
351351

352-
mono_thread_attach (domain);
352+
mono_thread_internal_attach (domain);
353353

354354
#if defined(ENABLE_PERFTRACING) && !defined(DISABLE_EVENTPIPE)
355355
ds_server_init ();

src/mono/mono/metadata/cominterop.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,7 +2859,7 @@ cominterop_ccw_queryinterface_impl (MonoCCWInterface* ccwe, const guint8* riid,
28592859
*ppv = NULL;
28602860

28612861
if (!mono_domain_get ())
2862-
mono_thread_attach (mono_get_root_domain ());
2862+
mono_thread_attach_external_native_thread (mono_get_root_domain (), FALSE);
28632863

28642864
/* handle IUnknown special */
28652865
if (cominterop_class_guid_equal (riid, mono_class_get_iunknown_class ())) {
@@ -2990,7 +2990,7 @@ cominterop_ccw_get_ids_of_names_impl (MonoCCWInterface* ccwe, gpointer riid,
29902990
klass = mono_object_class (object);
29912991

29922992
if (!mono_domain_get ())
2993-
mono_thread_attach (mono_get_root_domain ());
2993+
mono_thread_attach_external_native_thread (mono_get_root_domain (), FALSE);
29942994

29952995
for (i=0; i < cNames; i++) {
29962996
methodname = mono_unicode_to_external (rgszNames[i]);

src/mono/mono/metadata/icall-eventpipe.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ eventpipe_thread_attach (gboolean background_thread)
113113

114114
// NOTE, under netcore, only root domain exists.
115115
if (!mono_thread_current ()) {
116-
thread = mono_thread_attach (mono_get_root_domain ());
116+
thread = mono_thread_internal_attach (mono_get_root_domain ());
117117
if (background_thread) {
118118
mono_thread_set_state (thread, ThreadState_Background);
119119
mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_SAMPLE);
@@ -129,7 +129,7 @@ eventpipe_thread_detach (void)
129129
{
130130
MonoThread *current_thread = mono_thread_current ();
131131
if (current_thread)
132-
mono_thread_detach (current_thread);
132+
mono_thread_internal_detach (current_thread);
133133
}
134134

135135
void

src/mono/mono/metadata/threads-types.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,16 @@ void mono_threads_add_joinable_thread (gpointer tid);
425425
void mono_threads_join_threads (void);
426426
void mono_thread_join (gpointer tid);
427427

428+
MONO_PROFILER_API MonoThread*
429+
mono_thread_internal_attach (MonoDomain *domain);
430+
431+
MONO_PROFILER_API void
432+
mono_thread_internal_detach (MonoThread *thread);
433+
434+
MonoThread *
435+
mono_thread_attach_external_native_thread (MonoDomain *domain, gboolean background);
436+
437+
428438
MONO_API gpointer
429439
mono_threads_attach_coop (MonoDomain *domain, gpointer *dummy);
430440

src/mono/mono/metadata/threads.c

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,8 @@ init_internal_thread_object (MonoInternalThread *thread)
702702
static MonoInternalThread*
703703
create_internal_thread_object (void)
704704
{
705+
MONO_REQ_GC_UNSAFE_MODE;
706+
705707
ERROR_DECL (error);
706708
MonoInternalThread *thread;
707709
MonoVTable *vt;
@@ -1525,6 +1527,58 @@ mono_thread_create_checked (MonoDomain *domain, gpointer func, gpointer arg, Mon
15251527
*/
15261528
MonoThread *
15271529
mono_thread_attach (MonoDomain *domain)
1530+
{
1531+
return mono_thread_attach_external_native_thread (domain, FALSE);
1532+
}
1533+
1534+
/**
1535+
* mono_thread_attach_external_native_thread:
1536+
*
1537+
* Attach the current thread (that was created outside the runtime or managed
1538+
* code) to the runtime. If \p background is TRUE, set the IsBackground
1539+
* property on the thread.
1540+
*
1541+
* COOP: On return, the thread is in GC Unsafe mode
1542+
*/
1543+
MonoThread *
1544+
mono_thread_attach_external_native_thread (MonoDomain *domain, gboolean background)
1545+
{
1546+
MonoThread *thread = mono_thread_internal_attach (domain);
1547+
1548+
if (background)
1549+
mono_thread_set_state (mono_thread_internal_current (), ThreadState_Background);
1550+
1551+
#if 0
1552+
/* Can't do this - would break embedders who do their own GC thread
1553+
* state transitions. Also while the conversion of MONO_API entry
1554+
* points to do a transition to GC Unsafe is not complete, doing a
1555+
* transition here potentially means running runtime code while in GC
1556+
* Safe mode.
1557+
*/
1558+
if (mono_threads_is_blocking_transition_enabled ()) {
1559+
/* mono_jit_thread_attach and mono_thread_attach are external-only and
1560+
* not called by the runtime on any of our own threads. So if we get
1561+
* here, the thread is running native code - leave it in GC Safe mode
1562+
* and leave it to the n2m invoke wrappers or MONO_API entry points to
1563+
* switch to GC Unsafe.
1564+
*/
1565+
MONO_STACKDATA (stackdata);
1566+
mono_threads_enter_gc_safe_region_unbalanced_internal (&stackdata);
1567+
}
1568+
#endif
1569+
return thread;
1570+
}
1571+
1572+
/**
1573+
* mono_thread_internal_attach:
1574+
*
1575+
* Attach the current thread to the runtime. The thread was created on behalf
1576+
* of the runtime and the runtime is responsible for it.
1577+
*
1578+
* COOP: On return, the thread is in GC Unsafe mode
1579+
*/
1580+
MonoThread *
1581+
mono_thread_internal_attach (MonoDomain *domain)
15281582
{
15291583
MonoInternalThread *internal;
15301584
MonoThread *thread;
@@ -1538,7 +1592,34 @@ mono_thread_attach (MonoDomain *domain)
15381592
return mono_thread_current ();
15391593
}
15401594

1541-
info = mono_thread_info_attach ();
1595+
if (G_UNLIKELY ((info = mono_thread_info_current_unchecked ()))) {
1596+
/*
1597+
* We are not attached currently, but we were earlier. Ensure the thread is in GC Unsafe mode.
1598+
* Have to do this before creating the managed thread object.
1599+
*
1600+
*/
1601+
if (mono_threads_is_blocking_transition_enabled ()) {
1602+
/*
1603+
* Ensure the thread is in RUNNING state.
1604+
* If the thread is doing something like this
1605+
*
1606+
* while (cond) {
1607+
* t = mono_thread_attach (domain);
1608+
* <...>
1609+
* mono_thread_detach (t);
1610+
* }
1611+
*
1612+
* The call to mono_thread_detach will put it in GC Safe
1613+
* (blocking, preemptive suspend) mode, so the next time we
1614+
* come back to attach, we need to switch to GC Unsafe
1615+
* (running, cooperative suspend) mode.
1616+
*/
1617+
MONO_STACKDATA (stackdata);
1618+
mono_threads_enter_gc_unsafe_region_unbalanced_internal (&stackdata);
1619+
}
1620+
} else {
1621+
info = mono_thread_info_attach ();
1622+
}
15421623
g_assert (info);
15431624

15441625
tid=mono_native_thread_id_get ();
@@ -1596,12 +1677,39 @@ mono_threads_attach_tools_thread (void)
15961677

15971678
/**
15981679
* mono_thread_detach:
1680+
*
1681+
* COOP: On return, the thread is in GC Safe mode
15991682
*/
16001683
void
16011684
mono_thread_detach (MonoThread *thread)
16021685
{
1603-
if (thread)
1604-
mono_thread_detach_internal (thread->internal_thread);
1686+
if (!thread)
1687+
return;
1688+
mono_thread_internal_detach (thread);
1689+
/*
1690+
* If the thread wasn't created by the runtime, leave it in GC
1691+
* Safe mode. Under hybrid and coop suspend, we don't want to
1692+
* wait for it to cooperatively suspend.
1693+
*/
1694+
if (mono_threads_is_blocking_transition_enabled ()) {
1695+
MONO_STACKDATA (stackdata);
1696+
mono_threads_enter_gc_safe_region_unbalanced_internal (&stackdata);
1697+
}
1698+
}
1699+
1700+
/**
1701+
* mono_thread_internal_detach:
1702+
*
1703+
* COOP: GC thread state is unchanged
1704+
*/
1705+
void
1706+
mono_thread_internal_detach (MonoThread *thread)
1707+
{
1708+
if (!thread)
1709+
return;
1710+
MONO_ENTER_GC_UNSAFE;
1711+
mono_thread_detach_internal (thread->internal_thread);
1712+
MONO_EXIT_GC_UNSAFE;
16051713
}
16061714

16071715

@@ -3789,6 +3897,8 @@ mono_thread_manage_internal (void)
37893897
* Also abort all the background threads
37903898
* */
37913899
do {
3900+
THREAD_DEBUG (g_message ("%s: abort phase", __func__));
3901+
37923902
mono_threads_lock ();
37933903

37943904
wait->num = 0;
@@ -6074,15 +6184,15 @@ mono_threads_attach_coop_internal (MonoDomain *domain, gpointer *cookie, MonoSta
60746184
external = !(info = mono_thread_info_current_unchecked ()) || !mono_thread_info_is_live (info);
60756185

60766186
if (!mono_thread_internal_current ()) {
6077-
mono_thread_attach (domain);
6187+
mono_thread_internal_attach (domain);
60786188

60796189
// #678164
60806190
mono_thread_set_state (mono_thread_internal_current (), ThreadState_Background);
60816191
}
60826192

60836193
if (mono_threads_is_blocking_transition_enabled ()) {
60846194
if (external) {
6085-
/* mono_thread_attach put the thread in RUNNING mode from STARTING, but we need to
6195+
/* mono_thread_internal_attach put the thread in RUNNING mode from STARTING, but we need to
60866196
* return the right cookie. */
60876197
*cookie = mono_threads_enter_gc_unsafe_region_cookie ();
60886198
} else {

src/mono/mono/metadata/threads.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ MONO_API void mono_thread_new_init (intptr_t tid, void* stack_start,
4040
MONO_API MONO_RT_EXTERNAL_ONLY void
4141
mono_thread_create (MonoDomain *domain, void* func, void* arg);
4242

43-
MONO_API MonoThread *mono_thread_attach (MonoDomain *domain);
44-
MONO_API void mono_thread_detach (MonoThread *thread);
43+
MONO_API MONO_RT_EXTERNAL_ONLY MonoThread *
44+
mono_thread_attach (MonoDomain *domain);
45+
MONO_API MONO_RT_EXTERNAL_ONLY void
46+
mono_thread_detach (MonoThread *thread);
4547
MONO_API void mono_thread_exit (void);
4648

4749
MONO_API MONO_RT_EXTERNAL_ONLY void

src/mono/mono/mini/interp/interp.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,14 @@ interp_free_context (gpointer ctx)
407407
{
408408
ThreadContext *context = (ThreadContext*)ctx;
409409

410+
ThreadContext *current_context = (ThreadContext *) mono_native_tls_get_value (thread_context_id);
411+
/* at thread exit, we can be called from the JIT TLS key destructor with current_context == NULL */
412+
if (current_context != NULL) {
413+
/* check that the context we're freeing is the current one before overwriting TLS */
414+
g_assert (context == current_context);
415+
set_context (NULL);
416+
}
417+
410418
mono_vfree (context->stack_start, INTERP_STACK_SIZE, MONO_MEM_ACCOUNT_INTERP_STACK);
411419
/* Prevent interp_mark_stack from trying to scan the data_stack, before freeing it */
412420
context->stack_start = NULL;

src/mono/mono/mini/mini-runtime.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -866,10 +866,9 @@ mono_jit_thread_attach (MonoDomain *domain)
866866
attached = mono_tls_get_jit_tls () != NULL;
867867

868868
if (!attached) {
869-
mono_thread_attach (domain);
870-
871869
// #678164
872-
mono_thread_set_state (mono_thread_internal_current (), ThreadState_Background);
870+
gboolean background = TRUE;
871+
mono_thread_attach_external_native_thread (domain, background);
873872

874873
/* mono_jit_thread_attach is external-only and not called by
875874
* the runtime on any of our own threads. So if we get here,
@@ -4649,7 +4648,7 @@ mini_init (const char *filename, const char *runtime_version)
46494648
mono_install_runtime_cleanup (runtime_cleanup);
46504649
mono_runtime_init_checked (domain, (MonoThreadStartCB)mono_thread_start_cb, mono_thread_attach_cb, error);
46514650
mono_error_assert_ok (error);
4652-
mono_thread_attach (domain);
4651+
mono_thread_internal_attach (domain);
46534652
MONO_PROFILER_RAISE (thread_name, (MONO_NATIVE_THREAD_ID_TO_UINT (mono_native_thread_id_get ()), "Main"));
46544653
#endif
46554654
mono_threads_set_runtime_startup_finished ();

src/mono/mono/profiler/aot.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ static void prof_save (MonoProfiler *prof, FILE* file);
213213
static void *
214214
helper_thread (void *arg)
215215
{
216-
mono_thread_attach (mono_get_root_domain ());
216+
mono_thread_internal_attach (mono_get_root_domain ());
217217

218218
mono_thread_set_name_constant_ignore_error (mono_thread_internal_current (), "AOT Profiler Helper", MonoSetThreadNameFlag_None);
219219

@@ -312,7 +312,7 @@ helper_thread (void *arg)
312312
prof_shutdown (&aot_profiler);
313313

314314
mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE);
315-
mono_thread_detach (mono_thread_current ());
315+
mono_thread_internal_detach (mono_thread_current ());
316316

317317
return NULL;
318318
}

src/mono/mono/profiler/log.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,7 +3150,7 @@ profiler_thread_begin_function (const char *name8, const gunichar2* name16, size
31503150
mono_thread_info_attach ();
31513151
MonoProfilerThread *thread = init_thread (FALSE);
31523152

3153-
mono_thread_attach (mono_get_root_domain ());
3153+
mono_thread_internal_attach (mono_get_root_domain ());
31543154

31553155
MonoInternalThread *internal = mono_thread_internal_current ();
31563156

@@ -3199,7 +3199,7 @@ profiler_thread_check_detach (MonoProfilerThread *thread)
31993199
thread->did_detach = TRUE;
32003200

32013201
mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE);
3202-
mono_thread_detach (mono_thread_current ());
3202+
mono_thread_internal_detach (mono_thread_current ());
32033203

32043204
mono_os_sem_post (&log_profiler.detach_threads_sem);
32053205
}

src/mono/mono/tests/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/*.pdb
2020
/*.o
2121
/*.lo
22-
/*.so
22+
**.so
2323
**.dylib
2424
**.dylib.dSYM
2525
/*.netmodule

src/mono/mono/tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ TESTS_CS_SRC= \
342342
pinvoke11.cs \
343343
pinvoke13.cs \
344344
pinvoke17.cs \
345+
pinvoke-detach-1.cs \
345346
invoke.cs \
346347
invoke2.cs \
347348
runtime-invoke.cs \

0 commit comments

Comments
 (0)