Skip to content

Commit ce00640

Browse files
fix input to check pty child (#6553)
* fix input to check pty child * Auto-format: cargo fmt --all --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 9b2ad34 commit ce00640

File tree

2 files changed

+30
-7
lines changed

2 files changed

+30
-7
lines changed

crates/vm/src/py_io.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,14 @@ pub fn file_readline(obj: &PyObject, size: Option<usize>, vm: &VirtualMachine) -
7070
};
7171
let ret = match_class!(match ret {
7272
s @ PyStr => {
73-
let s_val = s.as_str();
74-
if s_val.is_empty() {
73+
// Use as_wtf8() to handle strings with surrogates (e.g., surrogateescape)
74+
let s_wtf8 = s.as_wtf8();
75+
if s_wtf8.is_empty() {
7576
return Err(eof_err());
7677
}
77-
if let Some(no_nl) = s_val.strip_suffix('\n') {
78+
// '\n' is ASCII, so we can check bytes directly
79+
if s_wtf8.as_bytes().last() == Some(&b'\n') {
80+
let no_nl = &s_wtf8[..s_wtf8.len() - 1];
7881
vm.ctx.new_str(no_nl).into()
7982
} else {
8083
s.into()

crates/vm/src/stdlib/builtins.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ pub use builtins::{ascii, print, reversed};
77

88
#[pymodule]
99
mod builtins {
10-
use std::io::IsTerminal;
11-
1210
use crate::{
1311
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
1412
builtins::{
@@ -464,6 +462,8 @@ mod builtins {
464462

465463
#[pyfunction]
466464
fn input(prompt: OptionalArg<PyStrRef>, vm: &VirtualMachine) -> PyResult {
465+
use std::io::IsTerminal;
466+
467467
let stdin = sys::get_stdin(vm)?;
468468
let stdout = sys::get_stdout(vm)?;
469469
let stderr = sys::get_stderr(vm)?;
@@ -476,8 +476,13 @@ mod builtins {
476476
.is_ok_and(|fd| fd == expected)
477477
};
478478

479-
// everything is normal, we can just rely on rustyline to use stdin/stdout
480-
if fd_matches(&stdin, 0) && fd_matches(&stdout, 1) && std::io::stdin().is_terminal() {
479+
// Check if we should use rustyline (interactive terminal, not PTY child)
480+
let use_rustyline = fd_matches(&stdin, 0)
481+
&& fd_matches(&stdout, 1)
482+
&& std::io::stdin().is_terminal()
483+
&& !is_pty_child();
484+
485+
if use_rustyline {
481486
let prompt = prompt.as_ref().map_or("", |s| s.as_str());
482487
let mut readline = Readline::new(());
483488
match readline.readline(prompt) {
@@ -502,6 +507,21 @@ mod builtins {
502507
}
503508
}
504509

510+
/// Check if we're running in a PTY child process (e.g., after pty.fork()).
511+
/// pty.fork() calls setsid(), making the child a session leader.
512+
/// In this case, rustyline may hang because it uses raw mode.
513+
#[cfg(unix)]
514+
fn is_pty_child() -> bool {
515+
use nix::unistd::{getpid, getsid};
516+
// If this process is a session leader, we're likely in a PTY child
517+
getsid(None) == Ok(getpid())
518+
}
519+
520+
#[cfg(not(unix))]
521+
fn is_pty_child() -> bool {
522+
false
523+
}
524+
505525
#[pyfunction]
506526
fn isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
507527
obj.is_instance(&typ, vm)

0 commit comments

Comments
 (0)