Skip to content

Commit a4f30d5

Browse files
committed
[threads] Switch foreign threads to GC Safe in mono_thread_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); } --- To make this work, we also make `mono_thread_detach` and `mono_thread_attach` external only. The runtime should use `mono_thread_internal_detach` and `mono_thread_internal_attach`.
1 parent d1c0fa8 commit a4f30d5

File tree

13 files changed

+408
-31
lines changed

13 files changed

+408
-31
lines changed

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
@@ -2856,7 +2856,7 @@ cominterop_ccw_queryinterface_impl (MonoCCWInterface* ccwe, const guint8* riid,
28562856
*ppv = NULL;
28572857

28582858
if (!mono_domain_get ())
2859-
mono_thread_attach (mono_get_root_domain ());
2859+
mono_thread_attach_external_native_thread (mono_get_root_domain (), FALSE);
28602860

28612861
/* handle IUnknown special */
28622862
if (cominterop_class_guid_equal (riid, mono_class_get_iunknown_class ())) {
@@ -2987,7 +2987,7 @@ cominterop_ccw_get_ids_of_names_impl (MonoCCWInterface* ccwe, gpointer riid,
29872987
klass = mono_object_class (object);
29882988

29892989
if (!mono_domain_get ())
2990-
mono_thread_attach (mono_get_root_domain ());
2990+
mono_thread_attach_external_native_thread (mono_get_root_domain (), FALSE);
29912991

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

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
@@ -698,6 +698,8 @@ init_internal_thread_object (MonoInternalThread *thread)
698698
static MonoInternalThread*
699699
create_internal_thread_object (void)
700700
{
701+
MONO_REQ_GC_UNSAFE_MODE;
702+
701703
ERROR_DECL (error);
702704
MonoInternalThread *thread;
703705
MonoVTable *vt;
@@ -1521,6 +1523,58 @@ mono_thread_create_checked (MonoDomain *domain, gpointer func, gpointer arg, Mon
15211523
*/
15221524
MonoThread *
15231525
mono_thread_attach (MonoDomain *domain)
1526+
{
1527+
return mono_thread_attach_external_native_thread (domain, FALSE);
1528+
}
1529+
1530+
/**
1531+
* mono_thread_attach_external_native_thread:
1532+
*
1533+
* Attach the current thread (that was created outside the runtime or managed
1534+
* code) to the runtime. If \p background is TRUE, set the IsBackground
1535+
* property on the thread.
1536+
*
1537+
* COOP: On return, the thread is in GC Unsafe mode
1538+
*/
1539+
MonoThread *
1540+
mono_thread_attach_external_native_thread (MonoDomain *domain, gboolean background)
1541+
{
1542+
MonoThread *thread = mono_thread_internal_attach (domain);
1543+
1544+
if (background)
1545+
mono_thread_set_state (mono_thread_internal_current (), ThreadState_Background);
1546+
1547+
#if 0
1548+
/* Can't do this - would break embedders who do their own GC thread
1549+
* state transitions. Also while the conversion of MONO_API entry
1550+
* points to do a transition to GC Unsafe is not complete, doing a
1551+
* transition here potentially means running runtime code while in GC
1552+
* Safe mode.
1553+
*/
1554+
if (mono_threads_is_blocking_transition_enabled ()) {
1555+
/* mono_jit_thread_attach and mono_thread_attach are external-only and
1556+
* not called by the runtime on any of our own threads. So if we get
1557+
* here, the thread is running native code - leave it in GC Safe mode
1558+
* and leave it to the n2m invoke wrappers or MONO_API entry points to
1559+
* switch to GC Unsafe.
1560+
*/
1561+
MONO_STACKDATA (stackdata);
1562+
mono_threads_enter_gc_safe_region_unbalanced_internal (&stackdata);
1563+
}
1564+
#endif
1565+
return thread;
1566+
}
1567+
1568+
/**
1569+
* mono_thread_internal_attach:
1570+
*
1571+
* Attach the current thread to the runtime. The thread was created on behalf
1572+
* of the runtime and the runtime is responsible for it.
1573+
*
1574+
* COOP: On return, the thread is in GC Unsafe mode
1575+
*/
1576+
MonoThread *
1577+
mono_thread_internal_attach (MonoDomain *domain)
15241578
{
15251579
MonoInternalThread *internal;
15261580
MonoThread *thread;
@@ -1534,7 +1588,34 @@ mono_thread_attach (MonoDomain *domain)
15341588
return mono_thread_current ();
15351589
}
15361590

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

15401621
tid=mono_native_thread_id_get ();
@@ -1592,12 +1673,39 @@ mono_threads_attach_tools_thread (void)
15921673

15931674
/**
15941675
* mono_thread_detach:
1676+
*
1677+
* COOP: On return, the thread is in GC Safe mode
15951678
*/
15961679
void
15971680
mono_thread_detach (MonoThread *thread)
15981681
{
1599-
if (thread)
1600-
mono_thread_detach_internal (thread->internal_thread);
1682+
if (!thread)
1683+
return;
1684+
mono_thread_internal_detach (thread);
1685+
/*
1686+
* If the thread wasn't created by the runtime, leave it in GC
1687+
* Safe mode. Under hybrid and coop suspend, we don't want to
1688+
* wait for it to cooperatively suspend.
1689+
*/
1690+
if (mono_threads_is_blocking_transition_enabled ()) {
1691+
MONO_STACKDATA (stackdata);
1692+
mono_threads_enter_gc_safe_region_unbalanced_internal (&stackdata);
1693+
}
1694+
}
1695+
1696+
/**
1697+
* mono_thread_internal_detach:
1698+
*
1699+
* COOP: GC thread state is unchanged
1700+
*/
1701+
void
1702+
mono_thread_internal_detach (MonoThread *thread)
1703+
{
1704+
if (!thread)
1705+
return;
1706+
MONO_ENTER_GC_UNSAFE;
1707+
mono_thread_detach_internal (thread->internal_thread);
1708+
MONO_EXIT_GC_UNSAFE;
16011709
}
16021710

16031711

@@ -3788,6 +3896,8 @@ mono_thread_manage_internal (void)
37883896
* Also abort all the background threads
37893897
* */
37903898
do {
3899+
THREAD_DEBUG (g_message ("%s: abort phase", __func__));
3900+
37913901
mono_threads_lock ();
37923902

37933903
wait->num = 0;
@@ -6073,15 +6183,15 @@ mono_threads_attach_coop_internal (MonoDomain *domain, gpointer *cookie, MonoSta
60736183
external = !(info = mono_thread_info_current_unchecked ()) || !mono_thread_info_is_live (info);
60746184

60756185
if (!mono_thread_internal_current ()) {
6076-
mono_thread_attach (domain);
6186+
mono_thread_internal_attach (domain);
60776187

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

60826192
if (mono_threads_is_blocking_transition_enabled ()) {
60836193
if (external) {
6084-
/* mono_thread_attach put the thread in RUNNING mode from STARTING, but we need to
6194+
/* mono_thread_internal_attach put the thread in RUNNING mode from STARTING, but we need to
60856195
* return the right cookie. */
60866196
*cookie = mono_threads_enter_gc_unsafe_region_cookie ();
60876197
} 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

0 commit comments

Comments
 (0)