OSX: fix #57534 registering thread dtors while running thread dtors

This commit is contained in:
tyler 2019-01-15 20:09:06 -08:00
parent e2f221c759
commit 1a51bb8174
2 changed files with 45 additions and 24 deletions

View File

@ -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
}

View File

@ -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
@ -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));