Skip to content

Commit 62b34d4

Browse files
Rollup merge of rust-lang#161451 - ohadravid:windows-tls-avoid-registering-dtors-in-fibers, r=ChrisDenton
Avoid arming the Windows TLS destructor guard in fibers After rust-lang#157645, we use FLS to trigger destructors for thread locals. In bytecodealliance/wasmtime#14184, it turned out that there's an edge case we didn't cover: if `thread_local/guard/windows.rs::enable` is called from a fiber and later, in a different fiber: (1) the fiber is converted back to a thread and (2) the thread deletes the original fiber, we incorrectly triggers the FLS destructors prematurely. The fix is simple - avoid arming the FLS slot (setting it to 1) if the calling thread is a fiber. The issue happened on mingw because it does have target thread local. Tested locally by forcing `registered = false` and checked that the new test fails without the fix. r? @ChrisDenton
2 parents 47585b4 + 373266b commit 62b34d4

3 files changed

Lines changed: 60 additions & 11 deletions

File tree

library/std/src/sys/thread_local/guard/windows.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ pub fn enable() {
176176
}
177177
};
178178

179+
// We must not set the key if we are in a fiber, since deleting that fiber from a thread
180+
// will cause the destructors to run before thread exit.
181+
if is_thread_a_fiber() {
182+
return;
183+
}
184+
179185
// Setting the key's value to non-zero will cause the dtor callback to be called when the thread exits.
180186
unsafe { set(key, ptr::without_provenance(1)) };
181187
}

library/std/src/thread/local.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,16 @@ use crate::fmt;
9898
/// run on the thread that causes the process to exit. This is because the
9999
/// other threads may be forcibly terminated.
100100
///
101-
/// If a thread is [converted into a fiber], destructors will not be run unless
102-
/// the fiber is [converted back into a thread] before the underlying thread exits.
101+
/// TLS destructors may be leaked if a thread exits while [converted into a fiber],
102+
/// or if Rust TLS destructor support is first needed while running in a fiber.
103103
///
104104
/// If a process loads a Rust `cdylib`, it must not cause the Rust TLS destructor support
105-
// to be initialized for the first time during process shutdown.
105+
/// to be initialized for the first time during process shutdown.
106106
///
107107
/// When dynamically unloading a Rust `cdylib`, pending TLS destructors may run
108-
// during the unload or may be leaked.
108+
/// during the unload or may be leaked.
109109
///
110110
/// [converted into a fiber]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-convertthreadtofiber
111-
/// [converted back into a thread]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-convertfibertothread
112111
/// [loader lock]: https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
113112
/// [`with`]: LocalKey::with
114113
#[cfg_attr(not(test), rustc_diagnostic_item = "LocalKey")]

library/std/tests/thread_local/tests.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -416,9 +416,17 @@ fn fiber_does_not_trigger_dtor() {
416416
unsafe extern "system" {
417417
fn ConvertFiberToThread() -> i32;
418418
fn ConvertThreadToFiber(lpParameter: *const c_void) -> *mut c_void;
419+
fn CreateFiber(
420+
dwStackSize: usize,
421+
lpStartAddress: unsafe extern "system" fn(*mut c_void),
422+
lpParameter: *mut c_void,
423+
) -> *mut c_void;
424+
fn DeleteFiber(lpFiber: *mut c_void);
425+
fn SwitchToFiber(lpFiber: *mut c_void);
419426
}
420427

421428
thread_local!(static FOO: UnsafeCell<Option<NotifyOnDrop>> = UnsafeCell::new(None));
429+
422430
let signal = Signal::default();
423431

424432
let signal2 = signal.clone();
@@ -438,13 +446,49 @@ fn fiber_does_not_trigger_dtor() {
438446
// As long as we stop using fibers before thread teardown, everything works as expected.
439447
let signal2 = signal.clone();
440448
let t = thread::spawn(move || unsafe {
441-
let mut signal = Some(signal2);
442-
let _ = ConvertThreadToFiber(ptr::null());
443-
FOO.with(|f| {
444-
*f.get() = Some(NotifyOnDrop(signal.take().unwrap()));
445-
});
446-
let _ = ConvertFiberToThread();
449+
struct FiberData {
450+
main: *mut c_void,
451+
signal: Signal,
452+
}
453+
454+
unsafe extern "system" fn fiber_start(data: *mut c_void) {
455+
let data = unsafe { &mut *data.cast::<FiberData>() };
456+
457+
// Set the value while this fiber is current.
458+
// This must NOT arm the FLS cleanup guard for the fiber.
459+
FOO.with(|f| unsafe {
460+
*f.get() = Some(NotifyOnDrop(data.signal.clone()));
461+
});
462+
463+
unsafe {
464+
SwitchToFiber(data.main);
465+
}
466+
}
467+
468+
let main = ConvertThreadToFiber(ptr::null());
469+
assert!(!main.is_null());
470+
471+
let mut data = FiberData { main, signal: signal2.clone() };
472+
let foo = CreateFiber(0, fiber_start, ptr::from_mut(&mut data).cast());
473+
assert!(!foo.is_null());
474+
475+
// Run `foo`, which sets FOO while `foo` is the current fiber,
476+
// then switches back to main.
477+
SwitchToFiber(foo);
478+
479+
// Convert main back to a thread before deleting `foo`.
480+
assert_ne!(ConvertFiberToThread(), 0);
481+
482+
// Deleting `foo` must not trigger dtors like a thread teardown.
483+
DeleteFiber(foo);
484+
assert!(!signal2.is_set());
485+
486+
// Arm the guard now from the normal thread.
487+
// `FOO`'s destructor is already registered, so it will run when the thread exits.
488+
thread_local!(static BAR: UnsafeCell<Option<NotifyOnDrop>> = UnsafeCell::new(None));
489+
BAR.with(|_| {});
447490
});
491+
448492
signal.wait();
449493
assert!(signal.is_set());
450494
t.join().unwrap();

0 commit comments

Comments
 (0)