Skip to content

Commit e81a0fc

Browse files
authored
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.
1 parent b5785e2 commit e81a0fc

File tree

3 files changed

+40
-36
lines changed

3 files changed

+40
-36
lines changed

crates/vm/src/stdlib/nt.rs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use module::raw_set_handle_inheritable;
66
#[pymodule(name = "nt", with(super::os::_os))]
77
pub(crate) mod module {
88
use crate::{
9-
Py, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
9+
Py, PyResult, TryFromObject, VirtualMachine,
1010
builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef},
1111
common::{crt_fd, suppress_iph, windows::ToWideString},
1212
convert::ToPyException,
@@ -1212,21 +1212,6 @@ pub(crate) mod module {
12121212
}
12131213
}
12141214

1215-
fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> {
1216-
let obj = env.obj();
1217-
if let Some(dict) = obj.downcast_ref_if_exact::<crate::builtins::PyDict>(vm) {
1218-
return Ok(dict.to_owned());
1219-
}
1220-
let keys = vm.call_method(obj, "keys", ())?;
1221-
let dict = vm.ctx.new_dict();
1222-
for key in keys.get_iter(vm)?.into_iter::<PyObjectRef>(vm)? {
1223-
let key = key?;
1224-
let val = obj.get_item(&*key, vm)?;
1225-
dict.set_item(&*key, val, vm)?;
1226-
}
1227-
Ok(dict)
1228-
}
1229-
12301215
#[cfg(target_env = "msvc")]
12311216
#[pyfunction]
12321217
fn execve(
@@ -1261,7 +1246,7 @@ pub(crate) mod module {
12611246
.chain(once(core::ptr::null()))
12621247
.collect();
12631248

1264-
let env = envobj_to_dict(env, vm)?;
1249+
let env = crate::stdlib::os::envobj_to_dict(env, vm)?;
12651250
// Build environment strings as "KEY=VALUE\0" wide strings
12661251
let mut env_strings: Vec<widestring::WideCString> = Vec::new();
12671252
for (key, value) in env.into_iter() {

crates/vm/src/stdlib/os.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
use crate::{
44
AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
5-
builtins::{PyModule, PySet},
5+
builtins::{PyDictRef, PyModule, PySet},
66
common::crt_fd,
77
convert::{IntoPyException, ToPyException, ToPyObject},
8-
function::{ArgumentError, FromArgs, FuncArgs},
8+
function::{ArgMapping, ArgumentError, FromArgs, FuncArgs},
99
};
1010
use std::{fs, io, path::Path};
1111

@@ -2038,6 +2038,32 @@ pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
20382038
Ok(())
20392039
}
20402040

2041+
/// Convert a mapping (e.g. os._Environ) to a plain dict for use by execve/posix_spawn.
2042+
///
2043+
/// For `os._Environ`, accesses the internal `_data` dict directly at the Rust level.
2044+
/// This avoids Python-level method calls that can deadlock after fork() when
2045+
/// parking_lot locks are held by threads that no longer exist.
2046+
pub(crate) fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> {
2047+
let obj = env.obj();
2048+
if let Some(dict) = obj.downcast_ref_if_exact::<crate::builtins::PyDict>(vm) {
2049+
return Ok(dict.to_owned());
2050+
}
2051+
if let Some(inst_dict) = obj.dict()
2052+
&& let Ok(Some(data)) = inst_dict.get_item_opt("_data", vm)
2053+
&& let Some(dict) = data.downcast_ref_if_exact::<crate::builtins::PyDict>(vm)
2054+
{
2055+
return Ok(dict.to_owned());
2056+
}
2057+
let keys = vm.call_method(obj, "keys", ())?;
2058+
let dict = vm.ctx.new_dict();
2059+
for key in keys.get_iter(vm)?.into_iter::<PyObjectRef>(vm)? {
2060+
let key = key?;
2061+
let val = obj.get_item(&*key, vm)?;
2062+
dict.set_item(&*key, val, vm)?;
2063+
}
2064+
Ok(dict)
2065+
}
2066+
20412067
#[cfg(not(windows))]
20422068
use super::posix as platform;
20432069

crates/vm/src/stdlib/posix.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,15 @@ pub mod module {
716716
vm.signal_handlers
717717
.get_or_init(crate::signal::new_signal_handlers);
718718

719-
let after_forkers_child: Vec<PyObjectRef> = vm.state.after_forkers_child.lock().clone();
719+
let after_forkers_child = match vm.state.after_forkers_child.try_lock() {
720+
Some(guard) => guard.clone(),
721+
None => {
722+
// SAFETY: After fork in child process, only the current thread
723+
// exists. The lock holder no longer exists.
724+
unsafe { vm.state.after_forkers_child.force_unlock() };
725+
vm.state.after_forkers_child.lock().clone()
726+
}
727+
};
720728
run_at_forkers(after_forkers_child, false, vm);
721729
}
722730

@@ -1073,21 +1081,6 @@ pub mod module {
10731081
.map_err(|err| err.into_pyexception(vm))
10741082
}
10751083

1076-
fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> {
1077-
let obj = env.obj();
1078-
if let Some(dict) = obj.downcast_ref_if_exact::<crate::builtins::PyDict>(vm) {
1079-
return Ok(dict.to_owned());
1080-
}
1081-
let keys = vm.call_method(obj, "keys", ())?;
1082-
let dict = vm.ctx.new_dict();
1083-
for key in keys.get_iter(vm)?.into_iter::<PyObjectRef>(vm)? {
1084-
let key = key?;
1085-
let val = obj.get_item(&*key, vm)?;
1086-
dict.set_item(&*key, val, vm)?;
1087-
}
1088-
Ok(dict)
1089-
}
1090-
10911084
#[pyfunction]
10921085
fn execve(
10931086
path: OsPath,
@@ -1110,7 +1103,7 @@ pub mod module {
11101103
return Err(vm.new_value_error("execve() arg 2 first element cannot be empty"));
11111104
}
11121105

1113-
let env = envobj_to_dict(env, vm)?;
1106+
let env = crate::stdlib::os::envobj_to_dict(env, vm)?;
11141107
let env = env
11151108
.into_iter()
11161109
.map(|(k, v)| -> PyResult<_> {

0 commit comments

Comments
 (0)