From c065b52778668d17aa9acfefd905305ce98e6b88 Mon Sep 17 00:00:00 2001 From: no382001 <102482527+no382001@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:40:20 +0200 Subject: [PATCH] fix read/1 on non-TTY stdin blocking until newline #3262 --- src/machine/streams.rs | 27 ++++++++++++++++++++++----- tests-pl/issue3262.pl | 4 ++++ tests/scryer/issues.rs | 8 ++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests-pl/issue3262.pl diff --git a/src/machine/streams.rs b/src/machine/streams.rs index 58d685a0..a2ac58a4 100644 --- a/src/machine/streams.rs +++ b/src/machine/streams.rs @@ -23,7 +23,7 @@ use std::fmt::Debug; use std::fs::{File, OpenOptions}; use std::hash::Hash; use std::io; -use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write}; +use std::io::{Cursor, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write}; use std::mem::ManuallyDrop; use std::net::{Shutdown, TcpStream}; use std::ops::{Deref, DerefMut}; @@ -657,10 +657,27 @@ impl Stream { #[inline] pub fn stdin(arena: &mut Arena, add_history: bool) -> Stream { - Stream::Readline(arena_alloc!( - StreamLayout::new(ReadlineStream::new("", add_history)), - arena - )) + if std::io::stdin().is_terminal() { + Stream::Readline(arena_alloc!( + StreamLayout::new(ReadlineStream::new("", add_history)), + arena + )) + } else { + #[cfg(unix)] + { + use std::os::unix::io::{FromRawFd, RawFd}; + // dup fd 0 so the File can be owned (and closed later) without + // closing the real stdin. + let fd = unsafe { libc::dup(0 as RawFd) }; + let file = unsafe { File::from_raw_fd(fd) }; + return Stream::from_file_as_input(atom!("user_input"), file, arena); + } + #[allow(unreachable_code)] + Stream::Readline(arena_alloc!( + StreamLayout::new(ReadlineStream::new("", add_history)), + arena + )) + } } pub fn from_tag(tag: ArenaHeaderTag, ptr: UntypedArenaPtr) -> Self { diff --git a/tests-pl/issue3262.pl b/tests-pl/issue3262.pl new file mode 100644 index 00000000..15e54aee --- /dev/null +++ b/tests-pl/issue3262.pl @@ -0,0 +1,4 @@ +:- initialization(main). +main :- + read(Term), + write(Term). diff --git a/tests/scryer/issues.rs b/tests/scryer/issues.rs index 10ca8e24..bdd50027 100644 --- a/tests/scryer/issues.rs +++ b/tests/scryer/issues.rs @@ -1,4 +1,5 @@ use crate::helper::load_module_test; +use crate::helper::load_module_test_with_input; #[cfg(not(target_arch = "wasm32"))] use crate::helper::load_module_test_with_tokio_runtime; use serial_test::serial; @@ -148,6 +149,13 @@ fn issue_rename_file() { load_module_test("tests-pl/issue_rename_file.pl", "file_renamed"); } +// issue #3262: read/1 on non-TTY stdin should resolve without requiring a newline +#[test] +#[cfg_attr(miri, ignore = "unsupported operation when isolation is enabled")] +fn issue3262_read_from_stdin_no_newline() { + load_module_test_with_input("tests-pl/issue3262.pl", "hello.", "hello"); +} + #[test] #[cfg(feature = "http")] #[cfg(not(target_arch = "wasm32"))] -- 2.54.0