mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
posix: add os.forkpty so pty.spawn does not hang (#8782)
* posix: add os.forkpty pty.fork prefers forkpty so a nested spawn is a new session with its own controlling tty. The openpty+login_tty fallback hung the jsontests collector. Assisted-by: Claude * select: rebuild fd sets after EINTR select(2) updates the sets in place. Retrying an interrupted call with empty sets and a NULL timeout blocks forever. Assisted-by: Claude * http.server: keep CGI off the body-read timeout A millisecond handler timeout races the client thread writing the request and is inherited across CGI fork, which RSTs the client under load. Assisted-by: Claude * Revert "http.server: keep CGI off the body-read timeout" Lib edits outside a CPython copy are not allowed. Assisted-by: Claude * host_env: note why forkpty is libc not nix Assisted-by: Claude * benches: run deepcopy 10x more iterations Assisted-by: Claude
This commit is contained in:
1 parent
f56bfc9a5c
commit
eb1546ae08
4 files changed
+122
-36
No files matched your search
@@ -66,6 +66,7 @@ def benchmark(n):
|
||||
dc.boolean = b
|
||||
_ = copy.deepcopy(dc)
|
||||
|
||||
benchmark(1)
|
||||
benchmark_reduce(5)
|
||||
benchmark_memo(1)
|
||||
N = 10
|
||||
benchmark(N)
|
||||
benchmark_reduce(5 * N)
|
||||
benchmark_memo(N)
|
||||
@@ -1113,6 +1113,34 @@ pub fn openpty() -> std::io::Result<(OwnedFd, OwnedFd)> {
|
||||
Ok((pty.master, pty.slave))
|
||||
}
|
||||
|
||||
/// `forkpty(3)`. The child is a session leader with the slave as its
|
||||
/// controlling terminal. The parent receives the master fd. The child may
|
||||
/// see `master_fd == -1` (Apple's `forkpty`); that is returned as `-1`.
|
||||
///
|
||||
/// rustix has no `forkpty`. `nix::pty::forkpty` wraps the master in
|
||||
/// `OwnedFd`, which panics on Apple's child `-1`, and its `Child` arm
|
||||
/// drops the master fd the caller returns as the second item.
|
||||
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
|
||||
pub fn forkpty() -> std::io::Result<(pid_t, i32)> {
|
||||
let mut master_fd: libc::c_int = -1;
|
||||
let pid = unsafe {
|
||||
libc::forkpty(
|
||||
&mut master_fd,
|
||||
core::ptr::null_mut(),
|
||||
core::ptr::null_mut(),
|
||||
core::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if pid < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
if pid != 0 && master_fd >= 0 {
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(master_fd) };
|
||||
set_inheritable(fd, false)?;
|
||||
}
|
||||
Ok((pid, master_fd))
|
||||
}
|
||||
|
||||
pub fn ttyname(fd: BorrowedFd<'_>) -> std::io::Result<OsString> {
|
||||
nix::unistd::ttyname(fd)
|
||||
.map(std::path::PathBuf::into_os_string)
|
||||
|
||||
+46
-33
@@ -112,44 +112,29 @@ mod decl {
|
||||
Ok((v, fds))
|
||||
};
|
||||
|
||||
let (rlist, mut r) = seq2set(&rlist)?;
|
||||
let (wlist, mut w) = seq2set(&wlist)?;
|
||||
let (xlist, mut x) = seq2set(&xlist)?;
|
||||
let (rlist, _) = seq2set(&rlist)?;
|
||||
let (wlist, _) = seq2set(&wlist)?;
|
||||
let (xlist, _) = seq2set(&xlist)?;
|
||||
|
||||
let nfds = cfg_select! {
|
||||
windows => 0, // value is ignored on windows
|
||||
|
||||
_ => [&mut r, &mut w, &mut x]
|
||||
.iter_mut()
|
||||
.filter_map(|set| set.highest())
|
||||
_ => rlist
|
||||
.iter()
|
||||
.chain(&wlist)
|
||||
.chain(&xlist)
|
||||
.map(|fd| fd.fno)
|
||||
.max()
|
||||
.map_or(0, |n| n + 1) as _,
|
||||
};
|
||||
|
||||
loop {
|
||||
let mut tv = timeout.map(host_select::sec_to_timeval);
|
||||
let res =
|
||||
vm.allow_threads(|| host_select::select(nfds, &mut r, &mut w, &mut x, tv.as_mut()));
|
||||
|
||||
match res {
|
||||
Ok(_) => break,
|
||||
Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(err) => return Err(err.to_pyexception(vm)),
|
||||
let fill = |list: &[Selectable]| {
|
||||
let mut fds = FdSet::new();
|
||||
for fd in list {
|
||||
fds.insert(fd.fno);
|
||||
}
|
||||
|
||||
vm.check_signals()?;
|
||||
|
||||
if let Some(ref mut timeout) = timeout {
|
||||
*timeout = deadline.unwrap() - time::time(vm).unwrap();
|
||||
if *timeout < 0.0 {
|
||||
r.clear();
|
||||
w.clear();
|
||||
x.clear();
|
||||
break;
|
||||
}
|
||||
// retry select() if we haven't reached the deadline yet
|
||||
}
|
||||
}
|
||||
fds
|
||||
};
|
||||
|
||||
let set2list = |list: Vec<Selectable>, mut set: FdSet| {
|
||||
vm.ctx.new_list(
|
||||
@@ -160,11 +145,39 @@ mod decl {
|
||||
)
|
||||
};
|
||||
|
||||
let rlist = set2list(rlist, r);
|
||||
let wlist = set2list(wlist, w);
|
||||
let xlist = set2list(xlist, x);
|
||||
loop {
|
||||
// `select(2)` updates the fd sets in place. Rebuild them on every
|
||||
// attempt, including EINTR retries; otherwise an interrupted call
|
||||
// can leave empty sets and a NULL timeout, which blocks forever.
|
||||
let mut r = fill(&rlist);
|
||||
let mut w = fill(&wlist);
|
||||
let mut x = fill(&xlist);
|
||||
|
||||
Ok((rlist, wlist, xlist))
|
||||
let mut tv = timeout.map(host_select::sec_to_timeval);
|
||||
let res =
|
||||
vm.allow_threads(|| host_select::select(nfds, &mut r, &mut w, &mut x, tv.as_mut()));
|
||||
|
||||
match res {
|
||||
Ok(_) => {
|
||||
return Ok((set2list(rlist, r), set2list(wlist, w), set2list(xlist, x)));
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(err) => return Err(err.to_pyexception(vm)),
|
||||
}
|
||||
|
||||
vm.check_signals()?;
|
||||
|
||||
if let Some(ref mut timeout) = timeout {
|
||||
*timeout = deadline.unwrap() - time::time(vm).unwrap();
|
||||
if *timeout < 0.0 {
|
||||
return Ok((
|
||||
vm.ctx.new_list(Vec::new()),
|
||||
vm.ctx.new_list(Vec::new()),
|
||||
vm.ctx.new_list(Vec::new()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -832,6 +832,50 @@ pub mod module {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
|
||||
#[pyfunction]
|
||||
fn forkpty(vm: &VirtualMachine) -> PyResult<(i32, i32)> {
|
||||
if vm
|
||||
.state
|
||||
.finalizing
|
||||
.load(core::sync::atomic::Ordering::Acquire)
|
||||
{
|
||||
return Err(vm.new_exception_msg(
|
||||
vm.ctx.exceptions.python_finalization_error.to_owned(),
|
||||
"can't fork at interpreter shutdown".into(),
|
||||
));
|
||||
}
|
||||
if !vm.state.allow_fork() {
|
||||
return Err(
|
||||
vm.new_runtime_error("fork not supported for isolated subinterpreters".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
vm.sys_module
|
||||
.get_attr("audit", vm)?
|
||||
.call(("os.forkpty",), vm)?;
|
||||
|
||||
py_os_before_fork(vm);
|
||||
let result = rustpython_host_env::posix::forkpty();
|
||||
|
||||
match result {
|
||||
Ok((0, master)) => {
|
||||
py_os_after_fork_child(vm);
|
||||
Ok((0, master))
|
||||
}
|
||||
Ok((pid, master)) => {
|
||||
let num_os_threads = get_number_of_os_threads();
|
||||
py_os_after_fork_parent(vm);
|
||||
warn_if_multi_threaded("forkpty", num_os_threads, vm);
|
||||
Ok((pid, master))
|
||||
}
|
||||
Err(err) => {
|
||||
py_os_after_fork_parent(vm);
|
||||
Err(err.into_pyexception(vm))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
const MKNOD_DIR_FD: bool = cfg!(not(target_vendor = "apple"));
|
||||
|
||||
|
||||
Reference in new issue
Block a user