mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
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:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<_> {
|
||||
|
||||
Reference in New Issue
Block a user