mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-07 00:00:09 +02:00
signal: restore MSRV by removing OnceLock::wait from the Windows handler (#8300)
OnceLock::wait was stabilized in Rust 1.86, so its use in the Windows console ctrl handler broke tokio's declared rust-version of 1.71 on windows targets in 1.53.0 (any cargo check with a 1.71..1.86 toolchain fails with E0599). The wait existed only because SetConsoleCtrlHandler was called inside REGISTRY's get_or_init closure, i.e. before the OnceLock was actually initialized, leaving a window where an invoked handler could observe an uninitialized REGISTRY. Initialize the registry first and register the OS handler afterwards (exactly once, through a second OnceLock that also caches a registration failure so every subsequent call reports the same error, matching the previous behavior). The handler can then rely on plain get(): registration happens-after initialization, so an invoked handler always finds the registry. Verified with cargo +1.71 check -p tokio --features full --target x86_64-pc-windows-msvc (fails with the reported E0599 before this change, clean after) and --all-targets on stable for the same target. Fixes #8299
This commit is contained in:
@@ -57,15 +57,20 @@ pub(super) fn ctrl_shutdown() -> io::Result<RxFuture> {
|
||||
}
|
||||
|
||||
fn new(signal: SignalKind) -> io::Result<RxFuture> {
|
||||
let registry = REGISTRY
|
||||
// Initialize the registry BEFORE registering the OS handler: the
|
||||
// handler thread can then always observe an initialized `REGISTRY`
|
||||
// (`SetConsoleCtrlHandler` happens-after the initialization below),
|
||||
// so it needs no blocking wait.
|
||||
let registry = REGISTRY.get_or_init(Registry::default);
|
||||
|
||||
HANDLER_RESULT
|
||||
.get_or_init(
|
||||
|| match unsafe { console::SetConsoleCtrlHandler(Some(handler), 1) } {
|
||||
0 => Err(Error::last_os_error().raw_os_error().expect("unreachable")),
|
||||
_ => Ok(Registry::default()),
|
||||
_ => Ok(()),
|
||||
},
|
||||
)
|
||||
.as_ref()
|
||||
.map_err(|&code| Error::from_raw_os_error(code))?;
|
||||
.map_err(Error::from_raw_os_error)?;
|
||||
|
||||
let rx = registry[signal].subscribe();
|
||||
Ok(RxFuture::new(rx))
|
||||
@@ -94,7 +99,11 @@ impl Index<SignalKind> for Registry {
|
||||
}
|
||||
}
|
||||
|
||||
static REGISTRY: OnceLock<Result<Registry, i32>> = OnceLock::new();
|
||||
static REGISTRY: OnceLock<Registry> = OnceLock::new();
|
||||
|
||||
/// Whether `SetConsoleCtrlHandler` succeeded, initialized (once) only
|
||||
/// after `REGISTRY` — see `new` for the ordering argument.
|
||||
static HANDLER_RESULT: OnceLock<Result<(), i32>> = OnceLock::new();
|
||||
|
||||
unsafe extern "system" fn handler(ty: u32) -> BOOL {
|
||||
let signal = match ty {
|
||||
@@ -107,11 +116,13 @@ unsafe extern "system" fn handler(ty: u32) -> BOOL {
|
||||
_ => return 0,
|
||||
};
|
||||
|
||||
// Note that `OnceLock::get` does not handle the small window between calling
|
||||
// `SetConsoleCtrlHandler` and `REGISTRY` being initialized.
|
||||
let Ok(registry) = REGISTRY.wait().as_ref() else {
|
||||
// Technically unreachable since `handler` is only called if
|
||||
// `SetConsoleCtrlHandler` succeeded.
|
||||
// `new` initializes `REGISTRY` before it registers this handler with
|
||||
// the OS, so an invoked handler always finds it initialized —
|
||||
// `get()` suffices and no blocking wait is needed (using `get`
|
||||
// also keeps the crate's MSRV: `OnceLock::wait` needs Rust 1.86).
|
||||
let Some(registry) = REGISTRY.get() else {
|
||||
// Unreachable by the ordering above; kept as a defensive
|
||||
// fallback that lets the OS run the next handler.
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user