Use try_lock in py_os_after_fork_child (#7178)

after_forkers_child.lock() can deadlock in the forked child
if another thread held the mutex at the time of fork.
Use try_lock and skip at-fork callbacks when the lock is
unavailable, matching the pattern used in after_fork_child
for thread_handles.
This commit is contained in:
Jeong, YunWon
2026-02-18 02:24:06 +09:00
committed by GitHub
parent b5785e2777
commit e81a0fc765
3 changed files with 40 additions and 36 deletions

View File

@@ -6,7 +6,7 @@ pub use module::raw_set_handle_inheritable;
#[pymodule(name = "nt", with(super::os::_os))]
pub(crate) mod module {
use crate::{
Py, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
Py, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef},
common::{crt_fd, suppress_iph, windows::ToWideString},
convert::ToPyException,
@@ -1212,21 +1212,6 @@ pub(crate) mod module {
}
}
fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> {
let obj = env.obj();
if let Some(dict) = obj.downcast_ref_if_exact::<crate::builtins::PyDict>(vm) {
return Ok(dict.to_owned());
}
let keys = vm.call_method(obj, "keys", ())?;
let dict = vm.ctx.new_dict();
for key in keys.get_iter(vm)?.into_iter::<PyObjectRef>(vm)? {
let key = key?;
let val = obj.get_item(&*key, vm)?;
dict.set_item(&*key, val, vm)?;
}
Ok(dict)
}
#[cfg(target_env = "msvc")]
#[pyfunction]
fn execve(
@@ -1261,7 +1246,7 @@ pub(crate) mod module {
.chain(once(core::ptr::null()))
.collect();
let env = envobj_to_dict(env, vm)?;
let env = crate::stdlib::os::envobj_to_dict(env, vm)?;
// Build environment strings as "KEY=VALUE\0" wide strings
let mut env_strings: Vec<widestring::WideCString> = Vec::new();
for (key, value) in env.into_iter() {

View File

@@ -2,10 +2,10 @@
use crate::{
AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
builtins::{PyModule, PySet},
builtins::{PyDictRef, PyModule, PySet},
common::crt_fd,
convert::{IntoPyException, ToPyException, ToPyObject},
function::{ArgumentError, FromArgs, FuncArgs},
function::{ArgMapping, ArgumentError, FromArgs, FuncArgs},
};
use std::{fs, io, path::Path};
@@ -2038,6 +2038,32 @@ pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
Ok(())
}
/// Convert a mapping (e.g. os._Environ) to a plain dict for use by execve/posix_spawn.
///
/// For `os._Environ`, accesses the internal `_data` dict directly at the Rust level.
/// This avoids Python-level method calls that can deadlock after fork() when
/// parking_lot locks are held by threads that no longer exist.
pub(crate) fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> {
let obj = env.obj();
if let Some(dict) = obj.downcast_ref_if_exact::<crate::builtins::PyDict>(vm) {
return Ok(dict.to_owned());
}
if let Some(inst_dict) = obj.dict()
&& let Ok(Some(data)) = inst_dict.get_item_opt("_data", vm)
&& let Some(dict) = data.downcast_ref_if_exact::<crate::builtins::PyDict>(vm)
{
return Ok(dict.to_owned());
}
let keys = vm.call_method(obj, "keys", ())?;
let dict = vm.ctx.new_dict();
for key in keys.get_iter(vm)?.into_iter::<PyObjectRef>(vm)? {
let key = key?;
let val = obj.get_item(&*key, vm)?;
dict.set_item(&*key, val, vm)?;
}
Ok(dict)
}
#[cfg(not(windows))]
use super::posix as platform;

View File

@@ -716,7 +716,15 @@ pub mod module {
vm.signal_handlers
.get_or_init(crate::signal::new_signal_handlers);
let after_forkers_child: Vec<PyObjectRef> = vm.state.after_forkers_child.lock().clone();
let after_forkers_child = match vm.state.after_forkers_child.try_lock() {
Some(guard) => guard.clone(),
None => {
// SAFETY: After fork in child process, only the current thread
// exists. The lock holder no longer exists.
unsafe { vm.state.after_forkers_child.force_unlock() };
vm.state.after_forkers_child.lock().clone()
}
};
run_at_forkers(after_forkers_child, false, vm);
}
@@ -1073,21 +1081,6 @@ pub mod module {
.map_err(|err| err.into_pyexception(vm))
}
fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> {
let obj = env.obj();
if let Some(dict) = obj.downcast_ref_if_exact::<crate::builtins::PyDict>(vm) {
return Ok(dict.to_owned());
}
let keys = vm.call_method(obj, "keys", ())?;
let dict = vm.ctx.new_dict();
for key in keys.get_iter(vm)?.into_iter::<PyObjectRef>(vm)? {
let key = key?;
let val = obj.get_item(&*key, vm)?;
dict.set_item(&*key, val, vm)?;
}
Ok(dict)
}
#[pyfunction]
fn execve(
path: OsPath,
@@ -1110,7 +1103,7 @@ pub mod module {
return Err(vm.new_value_error("execve() arg 2 first element cannot be empty"));
}
let env = envobj_to_dict(env, vm)?;
let env = crate::stdlib::os::envobj_to_dict(env, vm)?;
let env = env
.into_iter()
.map(|(k, v)| -> PyResult<_> {