Skip to content

Commit

Permalink
Auto merge of #57655 - mtak-:fix-tls-dtors-macos, r=alexcrichton
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed Jan 20, 2019
2 parents 2ab5d8a + 1a51bb8 commit d38d6be
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 24 deletions.
61 changes: 44 additions & 17 deletions src/libstd/sys/unix/fast_thread_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,57 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
register_dtor_fallback(t, dtor);
}

// macOS's analog of the above linux function is this _tlv_atexit function.
// The disassembly of thread_local globals in C++ (at least produced by
// clang) will have this show up in the output.
// This implementation is very similar to register_dtor_fallback in
// sys_common/thread_local.rs. The main difference is that we want to hook into
// macOS's analog of the above linux function, _tlv_atexit. OSX will run the
// registered dtors before any TLS slots get freed, and when the main thread
// exits.
//
// Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The
// workaround below is to register, via _tlv_atexit, a custom DTOR list once per
// thread. thread_local dtors are pushed to the DTOR list without calling
// _tlv_atexit.
#[cfg(target_os = "macos")]
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
use cell::Cell;
use ptr;

#[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false);
if !REGISTERED.get() {
_tlv_atexit(run_dtors, ptr::null_mut());
REGISTERED.set(true);
}

type List = Vec<(*mut u8, unsafe extern fn(*mut u8))>;

#[thread_local]
static DTORS: Cell<*mut List> = Cell::new(ptr::null_mut());
if DTORS.get().is_null() {
let v: Box<List> = box Vec::new();
DTORS.set(Box::into_raw(v));
}

extern {
fn _tlv_atexit(dtor: unsafe extern fn(*mut u8),
arg: *mut u8);
}
_tlv_atexit(dtor, t);

let list: &mut List = &mut *DTORS.get();
list.push((t, dtor));

unsafe extern fn run_dtors(_: *mut u8) {
let mut ptr = DTORS.replace(ptr::null_mut());
while !ptr.is_null() {
let list = Box::from_raw(ptr);
for (ptr, dtor) in list.into_iter() {
dtor(ptr);
}
ptr = DTORS.replace(ptr::null_mut());
}
}
}

pub fn requires_move_before_drop() -> bool {
// The macOS implementation of TLS apparently had an odd aspect to it
// where the pointer we have may be overwritten while this destructor
// is running. Specifically if a TLS destructor re-accesses TLS it may
// trigger a re-initialization of all TLS variables, paving over at
// least some destroyed ones with initial values.
//
// This means that if we drop a TLS value in place on macOS that we could
// revert the value to its original state halfway through the
// destructor, which would be bad!
//
// Hence, we use `ptr::read` on macOS (to move to a "safe" location)
// instead of drop_in_place.
cfg!(target_os = "macos")
false
}
8 changes: 1 addition & 7 deletions src/libstd/thread/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ use mem;
/// destroyed, but not all platforms have this guard. Those platforms that do
/// not guard typically have a synthetic limit after which point no more
/// destructors are run.
/// 3. On macOS, initializing TLS during destruction of other TLS slots can
/// sometimes cancel *all* destructors for the current thread, whether or not
/// the slots have already had their destructors run or not.
///
/// [`with`]: ../../std/thread/struct.LocalKey.html#method.with
/// [`thread_local!`]: ../../std/macro.thread_local.html
Expand Down Expand Up @@ -604,11 +601,8 @@ mod tests {
}

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

0 comments on commit d38d6be

Please sign in to comment.