Skip to content

Commit d38d6be

Browse files
committed
Auto merge of #57655 - mtak-:fix-tls-dtors-macos, r=alexcrichton
OSX: fix #57534 registering thread dtors while running thread dtors r? @alexcrichton - "fast" `thread_local` destructors get run even on the main thread - "fast" `thread_local` dtors, can initialize other `thread_local`'s One corner case where this fix doesn't work, is when a C++ `thread_local` triggers the initialization of a rust `thread_local`. I did not add any std::thread specific flag to indicate that the thread is currently exiting, which would be checked before registering a new dtor (I didn't really know where to stick that). I think this does the trick tho! Let me know if anything needs tweaking/fixing/etc. resolves this for macos: #28129 fixes: #57534
2 parents 2ab5d8a + 1a51bb8 commit d38d6be

File tree

2 files changed

+45
-24
lines changed

2 files changed

+45
-24
lines changed

src/libstd/sys/unix/fast_thread_local.rs

+44-17
Original file line numberDiff line numberDiff line change
@@ -33,30 +33,57 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
3333
register_dtor_fallback(t, dtor);
3434
}
3535

36-
// macOS's analog of the above linux function is this _tlv_atexit function.
37-
// The disassembly of thread_local globals in C++ (at least produced by
38-
// clang) will have this show up in the output.
36+
// This implementation is very similar to register_dtor_fallback in
37+
// sys_common/thread_local.rs. The main difference is that we want to hook into
38+
// macOS's analog of the above linux function, _tlv_atexit. OSX will run the
39+
// registered dtors before any TLS slots get freed, and when the main thread
40+
// exits.
41+
//
42+
// Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The
43+
// workaround below is to register, via _tlv_atexit, a custom DTOR list once per
44+
// thread. thread_local dtors are pushed to the DTOR list without calling
45+
// _tlv_atexit.
3946
#[cfg(target_os = "macos")]
4047
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
48+
use cell::Cell;
49+
use ptr;
50+
51+
#[thread_local]
52+
static REGISTERED: Cell<bool> = Cell::new(false);
53+
if !REGISTERED.get() {
54+
_tlv_atexit(run_dtors, ptr::null_mut());
55+
REGISTERED.set(true);
56+
}
57+
58+
type List = Vec<(*mut u8, unsafe extern fn(*mut u8))>;
59+
60+
#[thread_local]
61+
static DTORS: Cell<*mut List> = Cell::new(ptr::null_mut());
62+
if DTORS.get().is_null() {
63+
let v: Box<List> = box Vec::new();
64+
DTORS.set(Box::into_raw(v));
65+
}
66+
4167
extern {
4268
fn _tlv_atexit(dtor: unsafe extern fn(*mut u8),
4369
arg: *mut u8);
4470
}
45-
_tlv_atexit(dtor, t);
71+
72+
let list: &mut List = &mut *DTORS.get();
73+
list.push((t, dtor));
74+
75+
unsafe extern fn run_dtors(_: *mut u8) {
76+
let mut ptr = DTORS.replace(ptr::null_mut());
77+
while !ptr.is_null() {
78+
let list = Box::from_raw(ptr);
79+
for (ptr, dtor) in list.into_iter() {
80+
dtor(ptr);
81+
}
82+
ptr = DTORS.replace(ptr::null_mut());
83+
}
84+
}
4685
}
4786

4887
pub fn requires_move_before_drop() -> bool {
49-
// The macOS implementation of TLS apparently had an odd aspect to it
50-
// where the pointer we have may be overwritten while this destructor
51-
// is running. Specifically if a TLS destructor re-accesses TLS it may
52-
// trigger a re-initialization of all TLS variables, paving over at
53-
// least some destroyed ones with initial values.
54-
//
55-
// This means that if we drop a TLS value in place on macOS that we could
56-
// revert the value to its original state halfway through the
57-
// destructor, which would be bad!
58-
//
59-
// Hence, we use `ptr::read` on macOS (to move to a "safe" location)
60-
// instead of drop_in_place.
61-
cfg!(target_os = "macos")
88+
false
6289
}

src/libstd/thread/local.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,6 @@ use mem;
6969
/// destroyed, but not all platforms have this guard. Those platforms that do
7070
/// not guard typically have a synthetic limit after which point no more
7171
/// destructors are run.
72-
/// 3. On macOS, initializing TLS during destruction of other TLS slots can
73-
/// sometimes cancel *all* destructors for the current thread, whether or not
74-
/// the slots have already had their destructors run or not.
7572
///
7673
/// [`with`]: ../../std/thread/struct.LocalKey.html#method.with
7774
/// [`thread_local!`]: ../../std/macro.thread_local.html
@@ -604,11 +601,8 @@ mod tests {
604601
}
605602

606603
// Note that this test will deadlock if TLS destructors aren't run (this
607-
// requires the destructor to be run to pass the test). macOS has a known bug
608-
// where dtors-in-dtors may cancel other destructors, so we just ignore this
609-
// test on macOS.
604+
// requires the destructor to be run to pass the test).
610605
#[test]
611-
#[cfg_attr(target_os = "macos", ignore)]
612606
fn dtors_in_dtors_in_dtors() {
613607
struct S1(Sender<()>);
614608
thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell::new(None));

0 commit comments

Comments
 (0)