Skip to content

Commit aedc548

Browse files
authored
Added a thread_local guard to trigger dtor hook before fibers are used (#12426)
* Added a thread_local guard to trigger dtor hook before fibers are used * Add a test for async function that use a tls variable with a destructor
1 parent c0e4207 commit aedc548

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

crates/fiber/src/windows.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use alloc::boxed::Box;
33
use std::cell::Cell;
44
use std::ffi::c_void;
55
use std::io;
6+
use std::mem::needs_drop;
67
use std::ops::Range;
78
use std::ptr;
89
use windows_sys::Win32::Foundation::*;
@@ -128,6 +129,21 @@ impl Fiber {
128129
let parent_fiber = if is_fiber {
129130
wasmtime_fiber_get_current()
130131
} else {
132+
// Newer Rust versions use fiber local storage to register an internal hook that
133+
// calls thread locals' destructors on thread exit.
134+
// This has a limitation: the hook only runs in a regular thread (not in a fiber).
135+
// We convert back into a thread once execution returns to this function,
136+
// but we must also ensure that the hook is registered before converting into a fiber.
137+
// Otherwise, a different fiber could be the first to register the hook,
138+
// causing the hook to be called (and skipped) prematurely when that fiber is deleted.
139+
struct Guard;
140+
141+
impl Drop for Guard {
142+
fn drop(&mut self) {}
143+
}
144+
assert!(needs_drop::<Guard>());
145+
thread_local!(static GUARD: Guard = Guard);
146+
GUARD.with(|_g| {});
131147
ConvertThreadToFiber(ptr::null_mut())
132148
};
133149
assert!(

tests/all/async_functions.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::future::Future;
22
use std::pin::Pin;
3+
use std::sync::atomic::AtomicUsize;
4+
use std::sync::atomic::Ordering;
35
use std::sync::{Arc, Mutex};
46
use std::task::{Context, Poll, Waker};
57
use wasmtime::*;
@@ -615,6 +617,49 @@ async fn resume_separate_thread3() {
615617
assert!(f.call(&mut store, &[], &mut []).is_err());
616618
}
617619

620+
#[tokio::test]
621+
#[cfg_attr(miri, ignore)]
622+
async fn resume_separate_thread_tls() {
623+
static COUNTER: AtomicUsize = AtomicUsize::new(0);
624+
625+
struct IncOnDrop;
626+
impl Drop for IncOnDrop {
627+
fn drop(&mut self) {
628+
COUNTER.fetch_add(1, Ordering::SeqCst);
629+
}
630+
}
631+
632+
thread_local!(static FOO: IncOnDrop = IncOnDrop);
633+
634+
// This test will poll the following future on two threads.
635+
// We verify that TLS destructors are run correctly.
636+
execute_across_threads(async move {
637+
let mut store = async_store();
638+
let module = Module::new(
639+
store.engine(),
640+
"
641+
(module
642+
(import \"\" \"\" (func))
643+
(start 0)
644+
)
645+
",
646+
)
647+
.unwrap();
648+
let func = Func::wrap_async(&mut store, |_, _: ()| {
649+
Box::new(async {
650+
tokio::task::yield_now().await;
651+
FOO.with(|_f| {});
652+
Err::<(), _>(format_err!("test"))
653+
})
654+
});
655+
let result = Instance::new_async(&mut store, &module, &[func.into()]).await;
656+
assert!(result.is_err());
657+
})
658+
.await;
659+
660+
assert_eq!(COUNTER.load(Ordering::SeqCst), 1);
661+
}
662+
618663
#[tokio::test]
619664
#[cfg_attr(miri, ignore)]
620665
async fn recursive_async() -> Result<()> {

0 commit comments

Comments
 (0)