OSX: fix #57534 registering thread dtors while running thread dtors
This commit is contained in:
parent
e2f221c759
commit
1a51bb8174
@ -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
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user