diff --git a/Cargo.toml b/Cargo.toml index 69569f5603a..b710e7625b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ flame-it = ["rustpython-vm/flame-it", "rustpython-stdlib/flame-it", "flame", "fl freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"] jit = ["rustpython-vm/jit"] threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"] +fork = ["rustpython-vm/fork", "rustpython-stdlib/fork"] sqlite = ["rustpython-stdlib/sqlite"] ssl = [] ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"] diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index a40a5bf24a8..b8f755b07c2 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -15,6 +15,7 @@ default = ["compiler", "host_env"] host_env = ["rustpython-vm/host_env"] compiler = ["rustpython-vm/compiler"] threading = ["rustpython-common/threading", "rustpython-vm/threading"] +fork = ["rustpython-vm/fork"] sqlite = ["dep:libsqlite3-sys"] # SSL backends - default to rustls ssl = [] diff --git a/crates/stdlib/src/_asyncio.rs b/crates/stdlib/src/_asyncio.rs index 2299a1c822c..e3d04b06ea3 100644 --- a/crates/stdlib/src/_asyncio.rs +++ b/crates/stdlib/src/_asyncio.rs @@ -48,7 +48,7 @@ pub(crate) mod _asyncio { }); // Register fork handler to clear task state in child process - #[cfg(unix)] + #[cfg(feature = "fork")] { let on_fork = vm .get_attribute_opt(module.to_owned().into(), vm.ctx.intern_str("_on_fork"))? diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 78b1608673e..5acc50ea8b0 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -20,6 +20,7 @@ flame-it = ["flame", "flamer"] freeze-stdlib = ["encodings"] jit = ["rustpython-jit"] threading = ["rustpython-common/threading"] +fork = ["threading"] gc = [] compiler = ["parser", "codegen", "rustpython-compiler"] ast = ["ruff_python_ast", "ruff_text_size"] diff --git a/crates/vm/src/codecs.rs b/crates/vm/src/codecs.rs index 87c3a4a0a9d..047b61aa828 100644 --- a/crates/vm/src/codecs.rs +++ b/crates/vm/src/codecs.rs @@ -158,7 +158,7 @@ impl CodecsRegistry { /// # Safety /// Must only be called after fork() in the child process when no other /// threads exist. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] pub(crate) unsafe fn reinit_after_fork(&self) { unsafe { crate::common::lock::reinit_rwlock_after_fork(&self.inner) }; } diff --git a/crates/vm/src/gc_state.rs b/crates/vm/src/gc_state.rs index e8e83bba49c..45bcdd36260 100644 --- a/crates/vm/src/gc_state.rs +++ b/crates/vm/src/gc_state.rs @@ -92,7 +92,7 @@ impl GcGeneration { /// # Safety /// Must only be called after fork() in the child process when no other /// threads exist. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] unsafe fn reinit_stats_after_fork(&self) { unsafe { crate::common::lock::reinit_mutex_after_fork(&self.stats) }; } @@ -731,7 +731,7 @@ impl GcState { /// # Safety /// Must only be called after fork() in the child process when no other /// threads exist. The calling thread must NOT hold any of these locks. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] pub unsafe fn reinit_after_fork(&self) { use crate::common::lock::{reinit_mutex_after_fork, reinit_rwlock_after_fork}; diff --git a/crates/vm/src/intern.rs b/crates/vm/src/intern.rs index 37b971b8dca..88183432618 100644 --- a/crates/vm/src/intern.rs +++ b/crates/vm/src/intern.rs @@ -36,7 +36,7 @@ impl StringPool { /// # Safety /// Must only be called after fork() in the child process when no other /// threads exist. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] pub(crate) unsafe fn reinit_after_fork(&self) { unsafe { crate::common::lock::reinit_rwlock_after_fork(&self.inner) }; } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index a8d7c09da89..61d288bdc08 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -470,7 +470,7 @@ mod weakref_lock { /// Reset all weakref stripe locks after fork in child process. /// Locks held by parent threads would cause infinite spin in the child. - #[cfg(unix)] + #[cfg(feature = "fork")] pub(crate) fn reset_all_after_fork() { for lock in &LOCKS { lock.store(0, Ordering::Release); @@ -493,7 +493,7 @@ mod weakref_lock { /// Reset weakref stripe locks after fork. Must be called before any /// Python code runs in the child process. -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub(crate) fn reset_weakref_locks_after_fork() { weakref_lock::reset_all_after_fork(); } diff --git a/crates/vm/src/signal.rs b/crates/vm/src/signal.rs index 177ba06b84d..1195b7748f2 100644 --- a/crates/vm/src/signal.rs +++ b/crates/vm/src/signal.rs @@ -98,8 +98,7 @@ pub(crate) fn is_triggered() -> bool { /// Reset all signal trigger state after fork in child process. /// Stale triggers from the parent must not fire in the child. -#[cfg(unix)] -#[cfg(feature = "host_env")] +#[cfg(feature = "fork")] pub(crate) fn clear_after_fork() { ANY_TRIGGERED.store(false, Ordering::Release); for trigger in &TRIGGERS { diff --git a/crates/vm/src/stdlib/imp.rs b/crates/vm/src/stdlib/imp.rs index 66ce5239cd2..647d2dc5c06 100644 --- a/crates/vm/src/stdlib/imp.rs +++ b/crates/vm/src/stdlib/imp.rs @@ -38,6 +38,7 @@ mod lock { IMP_LOCK.lock(); } + #[cfg(feature = "fork")] pub(super) fn release_lock_after_fork_parent() { if IMP_LOCK.is_locked() && IMP_LOCK.is_owned_by_current_thread() { unsafe { IMP_LOCK.unlock() }; @@ -53,7 +54,7 @@ mod lock { /// # Safety /// /// Must only be called from single-threaded child after fork(). - #[cfg(unix)] + #[cfg(feature = "fork")] pub(crate) unsafe fn reinit_after_fork() { if IMP_LOCK.is_locked() && !IMP_LOCK.is_owned_by_current_thread() { // Held by a dead thread — reset to unlocked. @@ -65,7 +66,7 @@ mod lock { /// behavior in the post-fork child: /// 1) if ownership metadata is stale (dead owner / changed tid), reset; /// 2) if current thread owns the lock, release it. - #[cfg(unix)] + #[cfg(feature = "fork")] pub(super) unsafe fn after_fork_child_reinit_and_release() { unsafe { reinit_after_fork() }; if IMP_LOCK.is_locked() && IMP_LOCK.is_owned_by_current_thread() { @@ -75,22 +76,22 @@ mod lock { } /// Re-export for fork safety code in posix.rs -#[cfg(feature = "threading")] +#[cfg(feature = "fork")] pub(crate) fn acquire_imp_lock_for_fork() { lock::acquire_lock_for_fork(); } -#[cfg(feature = "threading")] +#[cfg(feature = "fork")] pub(crate) fn release_imp_lock_after_fork_parent() { lock::release_lock_after_fork_parent(); } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub(crate) unsafe fn reinit_imp_lock_after_fork() { unsafe { lock::reinit_after_fork() } } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub(crate) unsafe fn after_fork_child_imp_lock_release() { unsafe { lock::after_fork_child_reinit_and_release() } } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index dadde9e8e32..d68d1741748 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -2,7 +2,7 @@ * I/O core tools. */ pub(crate) use _io::module_def; -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub(crate) use _io::reinit_std_streams_after_fork; cfg_if::cfg_if! { @@ -4999,7 +4999,7 @@ mod _io { /// /// Must only be called from the single-threaded child process immediately /// after `fork()`, before any other thread is created. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] pub unsafe fn reinit_std_streams_after_fork(vm: &VirtualMachine) { for name in ["stdin", "stdout", "stderr"] { let Ok(stream) = vm.sys_module.get_attr(name, vm) else { @@ -5009,7 +5009,7 @@ mod _io { } } - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] fn reinit_io_locks(obj: &PyObject) { use crate::common::lock::reinit_thread_mutex_after_fork; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 0deb22d6488..3ca92807499 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -31,7 +31,7 @@ pub mod module { builtins::{PyDictRef, PyInt, PyListRef, PyTupleRef, PyUtf8Str}, convert::{IntoPyException, ToPyObject, TryFromObject}, exceptions::OSErrorBuilder, - function::{ArgMapping, Either, KwArgs, OptionalArg}, + function::{ArgMapping, Either, OptionalArg}, ospath::{OsPath, OsPathOrFd}, stdlib::os::{ _os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata, @@ -681,6 +681,7 @@ pub mod module { ) } + #[cfg(feature = "fork")] #[derive(FromArgs)] struct RegisterAtForkArgs { #[pyarg(named, optional)] @@ -691,6 +692,7 @@ pub mod module { after_in_child: OptionalArg, } + #[cfg(feature = "fork")] impl RegisterAtForkArgs { fn into_validated( self, @@ -724,10 +726,11 @@ pub mod module { } } + #[cfg(feature = "fork")] #[pyfunction] fn register_at_fork( args: RegisterAtForkArgs, - _ignored: KwArgs, + _ignored: crate::function::KwArgs, vm: &VirtualMachine, ) -> PyResult<()> { let (before, after_in_parent, after_in_child) = args.into_validated(vm)?; @@ -744,6 +747,7 @@ pub mod module { Ok(()) } + #[cfg(feature = "fork")] fn run_at_forkers(mut funcs: Vec, reversed: bool, vm: &VirtualMachine) { if !funcs.is_empty() { if reversed { @@ -761,6 +765,7 @@ pub mod module { } } + #[cfg(feature = "fork")] fn py_os_before_fork(vm: &VirtualMachine) { let before_forkers: Vec = vm.state.before_forkers.lock().clone(); // functions must be executed in reversed order as they are registered @@ -768,27 +773,23 @@ pub mod module { run_at_forkers(before_forkers, true, vm); - #[cfg(feature = "threading")] crate::stdlib::imp::acquire_imp_lock_for_fork(); - #[cfg(feature = "threading")] vm.state.stop_the_world.stop_the_world(vm); } + #[cfg(feature = "fork")] fn py_os_after_fork_child(vm: &VirtualMachine) { - #[cfg(feature = "threading")] vm.state.stop_the_world.reset_after_fork(); // Phase 1: Reset all internal locks FIRST. // After fork(), locks held by dead parent threads would deadlock // if we try to acquire them. This must happen before anything else. - #[cfg(feature = "threading")] reinit_locks_after_fork(vm); // Reinit per-object IO buffer locks on std streams. // BufferedReader/Writer/TextIOWrapper use PyThreadMutex which can be // held by dead parent threads, causing deadlocks on any IO in the child. - #[cfg(feature = "threading")] unsafe { crate::stdlib::io::reinit_std_streams_after_fork(vm) }; @@ -798,17 +799,14 @@ pub mod module { crate::stdlib::signal::_signal::clear_wakeup_fd_after_fork(); // Reset weakref stripe locks that may have been held during fork. - #[cfg(feature = "threading")] crate::object::reset_weakref_locks_after_fork(); // Phase 3: Clean up thread state. Locks are now reinit'd so we can // acquire them normally instead of using try_lock(). - #[cfg(feature = "threading")] crate::stdlib::thread::after_fork_child(vm); // CPython parity: reinit import lock ownership metadata in child // and release the lock acquired by PyOS_BeforeFork(). - #[cfg(feature = "threading")] unsafe { crate::stdlib::imp::after_fork_child_imp_lock_release() }; @@ -828,7 +826,7 @@ pub mod module { /// After fork(), only the calling thread survives. Any locks held by other /// (now-dead) threads would cause deadlocks. We unconditionally reset them /// to unlocked by zeroing the raw lock bytes. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] fn reinit_locks_after_fork(vm: &VirtualMachine) { use rustpython_common::lock::reinit_mutex_after_fork; @@ -861,11 +859,10 @@ pub mod module { } } + #[cfg(feature = "fork")] fn py_os_after_fork_parent(vm: &VirtualMachine) { - #[cfg(feature = "threading")] vm.state.stop_the_world.start_the_world(vm); - #[cfg(feature = "threading")] crate::stdlib::imp::release_imp_lock_after_fork_parent(); let after_forkers_parent: Vec = vm.state.after_forkers_parent.lock().clone(); @@ -874,6 +871,7 @@ pub mod module { /// Best-effort number of OS threads in this process. /// Returns <= 0 when unavailable. + #[cfg(feature = "fork")] fn get_number_of_os_threads() -> isize { #[cfg(target_os = "macos")] { @@ -952,6 +950,7 @@ pub mod module { /// Warn if forking from a multi-threaded process. /// `num_os_threads` should be captured before parent after-fork hooks run. + #[cfg(feature = "fork")] fn warn_if_multi_threaded(name: &str, num_os_threads: isize, vm: &VirtualMachine) { let num_threads = if num_os_threads > 0 { num_os_threads as usize @@ -998,6 +997,7 @@ pub mod module { } } + #[cfg(feature = "fork")] #[pyfunction] fn fork(vm: &VirtualMachine) -> PyResult { if vm diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index a69d766ce51..d830c9e6719 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -710,7 +710,7 @@ pub(crate) mod _signal { /// Reset wakeup fd after fork in child process. /// The child must not write to the parent's wakeup fd. - #[cfg(unix)] + #[cfg(feature = "fork")] pub(crate) fn clear_wakeup_fd_after_fork() { WAKEUP.store(INVALID_WAKEUP, Ordering::Relaxed); } diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index 765f2537440..f016c77de9d 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -1,5 +1,5 @@ //! Implementation of the _thread module -#[cfg(unix)] +#[cfg(feature = "fork")] pub(crate) use _thread::after_fork_child; pub use _thread::get_ident; #[cfg_attr(target_arch = "wasm32", allow(unused_imports))] @@ -327,7 +327,7 @@ pub(crate) mod _thread { current_thread_id() } - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] #[pyfunction] fn _stop_the_world_stats(vm: &VirtualMachine) -> PyResult { let stats = vm.state.stop_the_world.stats_snapshot(); @@ -378,7 +378,7 @@ pub(crate) mod _thread { Ok(d) } - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] #[pyfunction] fn _stop_the_world_reset_stats(vm: &VirtualMachine) { vm.state.stop_the_world.reset_stats(); @@ -1050,7 +1050,7 @@ pub(crate) mod _thread { /// /// Precondition: `reinit_locks_after_fork()` has already been called, so all /// parking_lot-based locks in VmState are in unlocked state. - #[cfg(unix)] + #[cfg(feature = "fork")] pub fn after_fork_child(vm: &VirtualMachine) { let current_ident = get_ident(); @@ -1134,7 +1134,7 @@ pub(crate) mod _thread { } /// Reset a parking_lot::Mutex to unlocked state after fork. - #[cfg(unix)] + #[cfg(feature = "fork")] fn reinit_parking_lot_mutex(mutex: &parking_lot::Mutex) { unsafe { rustpython_common::lock::zero_reinit_after_fork(mutex.raw()) }; } diff --git a/crates/vm/src/vm/interpreter.rs b/crates/vm/src/vm/interpreter.rs index 5bf7436e958..8c4c1747429 100644 --- a/crates/vm/src/vm/interpreter.rs +++ b/crates/vm/src/vm/interpreter.rs @@ -1,4 +1,4 @@ -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] use super::StopTheWorldState; use super::{Context, PyConfig, PyGlobalState, VirtualMachine, setting::Settings, thread}; use crate::{ @@ -108,8 +108,11 @@ where finalizing: AtomicBool::new(false), warnings, override_frozen_modules: AtomicCell::new(0), + #[cfg(feature = "fork")] before_forkers: PyMutex::default(), + #[cfg(feature = "fork")] after_forkers_child: PyMutex::default(), + #[cfg(feature = "fork")] after_forkers_parent: PyMutex::default(), int_max_str_digits, switch_interval: AtomicCell::new(0.005), @@ -126,7 +129,7 @@ where monitoring: PyMutex::default(), monitoring_events: AtomicCell::new(0), instrumentation_version: AtomicU64::new(0), - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] stop_the_world: StopTheWorldState::new(), }); diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 72899016675..0a4f3abe5a4 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -40,7 +40,7 @@ use crate::{ warn::WarningsState, }; use alloc::{borrow::Cow, collections::BTreeMap}; -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] use core::sync::atomic::AtomicI64; use core::{ cell::{Cell, OnceCell, RefCell}, @@ -131,7 +131,7 @@ struct ExceptionStack { /// Stop-the-world state for fork safety. Before `fork()`, the requester /// stops all other Python threads so they are not holding internal locks. -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub struct StopTheWorldState { /// Fast-path flag checked in the bytecode loop (like `_PY_EVAL_PLEASE_STOP_BIT`) pub(crate) requested: AtomicBool, @@ -166,7 +166,7 @@ pub struct StopTheWorldState { stats_suspend_wait_yields: AtomicU64, } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] #[derive(Debug, Clone, Copy)] pub struct StopTheWorldStats { pub stop_calls: u64, @@ -182,14 +182,14 @@ pub struct StopTheWorldStats { pub world_stopped: bool, } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] impl Default for StopTheWorldState { fn default() -> Self { Self::new() } } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] impl StopTheWorldState { pub const fn new() -> Self { Self { @@ -523,13 +523,13 @@ impl StopTheWorldState { } } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub(super) fn stw_trace_enabled() -> bool { static ENABLED: std::sync::OnceLock = std::sync::OnceLock::new(); *ENABLED.get_or_init(|| std::env::var_os("RUSTPYTHON_STW_TRACE").is_some()) } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub(super) fn stw_trace(msg: core::fmt::Arguments<'_>) { if stw_trace_enabled() { use core::fmt::Write as _; @@ -590,8 +590,11 @@ pub struct PyGlobalState { pub finalizing: AtomicBool, pub warnings: WarningsState, pub override_frozen_modules: AtomicCell, + #[cfg(feature = "fork")] pub before_forkers: PyMutex>, + #[cfg(feature = "fork")] pub after_forkers_child: PyMutex>, + #[cfg(feature = "fork")] pub after_forkers_parent: PyMutex>, pub int_max_str_digits: AtomicCell, pub switch_interval: AtomicCell, @@ -619,7 +622,7 @@ pub struct PyGlobalState { /// local version against this to decide whether re-instrumentation is needed. pub instrumentation_version: AtomicU64, /// Stop-the-world state for pre-fork thread suspension - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] pub stop_the_world: StopTheWorldState, } @@ -1967,7 +1970,7 @@ impl VirtualMachine { return true; } - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] if thread::stop_requested_for_current_thread() { return true; } @@ -1992,7 +1995,7 @@ impl VirtualMachine { } // Suspend this thread if stop-the-world is in progress - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] thread::suspend_if_needed(&self.state.stop_the_world); #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs index 13addacd516..69ebe3038f9 100644 --- a/crates/vm/src/vm/thread.rs +++ b/crates/vm/src/vm/thread.rs @@ -18,11 +18,11 @@ use std::thread_local; // DETACHED: not executing Python bytecode (in native code, or idle) // ATTACHED: actively executing Python bytecode // SUSPENDED: parked by a stop-the-world request -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub const THREAD_DETACHED: i32 = 0; -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub const THREAD_ATTACHED: i32 = 1; -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub const THREAD_SUSPENDED: i32 = 2; /// Per-thread shared state for sys._current_frames() and sys._current_exceptions(). @@ -34,13 +34,13 @@ pub struct ThreadSlot { pub frames: parking_lot::Mutex>, pub exception: crate::PyAtomicRef>, /// Thread state for stop-the-world: DETACHED / ATTACHED / SUSPENDED - #[cfg(unix)] + #[cfg(feature = "fork")] pub state: core::sync::atomic::AtomicI32, /// Per-thread stop request bit (eval breaker equivalent). - #[cfg(unix)] + #[cfg(feature = "fork")] pub stop_requested: core::sync::atomic::AtomicBool, /// Handle for waking this thread from park in stop-the-world paths. - #[cfg(unix)] + #[cfg(feature = "fork")] pub thread: std::thread::Thread, } @@ -78,7 +78,7 @@ pub fn with_current_vm(f: impl FnOnce(&VirtualMachine) -> R) -> R { pub fn enter_vm(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { VM_STACK.with(|vms| { // Outermost enter_vm: transition DETACHED → ATTACHED - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] let was_outermost = vms.borrow().is_empty(); vms.borrow_mut().push(vm.into()); @@ -87,14 +87,14 @@ pub fn enter_vm(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { #[cfg(feature = "threading")] init_thread_slot_if_needed(vm); - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] if was_outermost { attach_thread(vm); } scopeguard::defer! { // Outermost exit: transition ATTACHED → DETACHED - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] if vms.borrow().len() == 1 { detach_thread(); } @@ -115,7 +115,7 @@ fn init_thread_slot_if_needed(vm: &VirtualMachine) { let new_slot = Arc::new(ThreadSlot { frames: parking_lot::Mutex::new(Vec::new()), exception: crate::PyAtomicRef::from(None::), - #[cfg(unix)] + #[cfg(feature = "fork")] state: core::sync::atomic::AtomicI32::new( if vm.state.stop_the_world.requested.load(Ordering::Acquire) { // Match init_threadstate(): new thread-state starts @@ -125,9 +125,9 @@ fn init_thread_slot_if_needed(vm: &VirtualMachine) { THREAD_DETACHED }, ), - #[cfg(unix)] + #[cfg(feature = "fork")] stop_requested: core::sync::atomic::AtomicBool::new(false), - #[cfg(unix)] + #[cfg(feature = "fork")] thread: std::thread::current(), }); registry.insert(thread_id, new_slot.clone()); @@ -139,7 +139,7 @@ fn init_thread_slot_if_needed(vm: &VirtualMachine) { /// Transition DETACHED → ATTACHED. Blocks if the thread was SUSPENDED by /// a stop-the-world request (like `_PyThreadState_Attach` + `tstate_wait_attach`). -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] fn wait_while_suspended(slot: &ThreadSlot) -> u64 { let mut wait_yields = 0u64; while slot.state.load(Ordering::Acquire) == THREAD_SUSPENDED { @@ -149,7 +149,7 @@ fn wait_while_suspended(slot: &ThreadSlot) -> u64 { wait_yields } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] fn attach_thread(vm: &VirtualMachine) { CURRENT_THREAD_SLOT.with(|slot| { if let Some(s) = slot.borrow().as_ref() { @@ -183,7 +183,7 @@ fn attach_thread(vm: &VirtualMachine) { } /// Transition ATTACHED → DETACHED (like `_PyThreadState_Detach`). -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] fn detach_thread() { CURRENT_THREAD_SLOT.with(|slot| { if let Some(s) = slot.borrow().as_ref() { @@ -213,7 +213,7 @@ fn detach_thread() { /// to park this thread during blocking operations. /// /// `Py_BEGIN_ALLOW_THREADS` / `Py_END_ALLOW_THREADS` equivalent. -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub fn allow_threads(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { // Preserve save/restore semantics: // only detach if this call observed ATTACHED at entry, and always restore @@ -234,8 +234,8 @@ pub fn allow_threads(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { result } -/// No-op on non-unix or non-threading builds. -#[cfg(not(all(unix, feature = "threading")))] +/// No-op when fork feature is disabled. +#[cfg(not(feature = "fork"))] pub fn allow_threads(_vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { f() } @@ -243,7 +243,7 @@ pub fn allow_threads(_vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { /// Called from check_signals when stop-the-world is requested. /// Transitions ATTACHED → SUSPENDED and waits until released /// (like `_PyThreadState_Suspend` + `_PyThreadState_Attach`). -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] pub fn suspend_if_needed(stw: &super::StopTheWorldState) { let should_suspend = CURRENT_THREAD_SLOT.with(|slot| { slot.borrow() @@ -266,7 +266,7 @@ pub fn suspend_if_needed(stw: &super::StopTheWorldState) { do_suspend(stw); } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] #[cold] fn do_suspend(stw: &super::StopTheWorldState) { CURRENT_THREAD_SLOT.with(|slot| { @@ -343,7 +343,7 @@ fn do_suspend(stw: &super::StopTheWorldState) { }); } -#[cfg(all(unix, feature = "threading"))] +#[cfg(feature = "fork")] #[inline] pub fn stop_requested_for_current_thread() -> bool { CURRENT_THREAD_SLOT.with(|slot| { @@ -429,7 +429,7 @@ pub fn cleanup_current_thread_frames(vm: &VirtualMachine) { // A dying thread should not remain logically ATTACHED while its // thread-state slot is being removed. - #[cfg(all(unix, feature = "threading"))] + #[cfg(feature = "fork")] if let Some(slot) = ¤t_slot { let _ = slot.state.compare_exchange( THREAD_ATTACHED, @@ -441,7 +441,7 @@ pub fn cleanup_current_thread_frames(vm: &VirtualMachine) { // Guard against OS thread-id reuse races: only remove the registry entry // if it still points at this thread's own slot. - let removed = if let Some(slot) = ¤t_slot { + let _removed = if let Some(slot) = ¤t_slot { let mut registry = vm.state.thread_frames.lock(); match registry.get(&thread_id) { Some(registered) if Arc::ptr_eq(registered, slot) => registry.remove(&thread_id), @@ -450,8 +450,8 @@ pub fn cleanup_current_thread_frames(vm: &VirtualMachine) { } else { None }; - #[cfg(all(unix, feature = "threading"))] - if let Some(slot) = &removed + #[cfg(feature = "fork")] + if let Some(slot) = &_removed && vm.state.stop_the_world.requested.load(Ordering::Acquire) && thread_id != vm.state.stop_the_world.requester_ident() && slot.state.load(Ordering::Relaxed) != THREAD_SUSPENDED @@ -478,11 +478,11 @@ pub fn reinit_frame_slot_after_fork(vm: &VirtualMachine) { let new_slot = Arc::new(ThreadSlot { frames: parking_lot::Mutex::new(current_frames), exception: crate::PyAtomicRef::from(vm.topmost_exception()), - #[cfg(unix)] + #[cfg(feature = "fork")] state: core::sync::atomic::AtomicI32::new(THREAD_ATTACHED), - #[cfg(unix)] + #[cfg(feature = "fork")] stop_requested: core::sync::atomic::AtomicBool::new(false), - #[cfg(unix)] + #[cfg(feature = "fork")] thread: std::thread::current(), });