diff --git a/benches/benchmarks/deepcopy.py b/benches/benchmarks/deepcopy.py index bcae0d1567d..24f512e6977 100644 --- a/benches/benchmarks/deepcopy.py +++ b/benches/benchmarks/deepcopy.py @@ -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) diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index 928f8df3963..4eb52aa6825 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -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 { nix::unistd::ttyname(fd) .map(std::path::PathBuf::into_os_string) diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index dd4d484e03c..9de8bafc27b 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -112,27 +112,55 @@ 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 _, }; + let fill = |list: &[Selectable]| { + let mut fds = FdSet::new(); + for fd in list { + fds.insert(fd.fno); + } + fds + }; + + let set2list = |list: Vec, mut set: FdSet| { + vm.ctx.new_list( + list.into_iter() + .filter(|fd| set.contains(fd.fno)) + .map(|fd| fd.obj) + .collect(), + ) + }; + 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); + 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, + 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)), } @@ -142,29 +170,14 @@ mod decl { 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; + return Ok(( + vm.ctx.new_list(Vec::new()), + vm.ctx.new_list(Vec::new()), + vm.ctx.new_list(Vec::new()), + )); } - // retry select() if we haven't reached the deadline yet } } - - let set2list = |list: Vec, mut set: FdSet| { - vm.ctx.new_list( - list.into_iter() - .filter(|fd| set.contains(fd.fno)) - .map(|fd| fd.obj) - .collect(), - ) - }; - - let rlist = set2list(rlist, r); - let wlist = set2list(wlist, w); - let xlist = set2list(xlist, x); - - Ok((rlist, wlist, xlist)) } #[cfg(unix)] diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 6ad0df60310..4076523c8fd 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -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"));