diff --git a/package-lock.json b/package-lock.json index 164b6b2..105749c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@replit/ruspty", - "version": "3.1.1", + "version": "3.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@replit/ruspty", - "version": "3.1.1", + "version": "3.1.2", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.18.2", diff --git a/package.json b/package.json index b50b062..daae6b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@replit/ruspty", - "version": "3.1.1", + "version": "3.1.2", "main": "dist/wrapper.js", "types": "dist/wrapper.d.ts", "author": "Szymon Kaliski ", diff --git a/src/lib.rs b/src/lib.rs index 59cf337..fd75578 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFun use napi::Status::GenericFailure; use napi::{self, Env}; use nix::errno::Errno; -use nix::fcntl::{fcntl, FcntlArg, FdFlag}; +use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; use nix::pty::{openpty, Winsize}; use nix::sys::termios::{self, SetArg}; @@ -117,12 +117,14 @@ impl Pty { } // open pty pair, and set close-on-exec to avoid unwanted copies of the FDs from finding their - // way into subprocesses. + // way into subprocesses. Also set the nonblocking flag to avoid Node from consuming a full I/O + // thread for this. let pty_res = openpty(&window_size, None).map_err(cast_to_napi_error)?; let controller_fd = pty_res.master; let user_fd = pty_res.slave; set_close_on_exec(controller_fd.as_raw_fd(), true)?; set_close_on_exec(user_fd.as_raw_fd(), true)?; + set_nonblocking(controller_fd.as_raw_fd())?; // duplicate pty user_fd to be the child's stdin, stdout, and stderr cmd.stdin(Stdio::from(user_fd.try_clone()?)); @@ -362,3 +364,29 @@ fn get_close_on_exec(fd: i32) -> Result { )), } } + +/// Set the file descriptor to be non-blocking. +#[allow(dead_code)] +fn set_nonblocking(fd: i32) -> Result<(), napi::Error> { + let old_flags = match fcntl(fd, FcntlArg::F_GETFL) { + Ok(flags) => OFlag::from_bits_truncate(flags), + Err(err) => { + return Err(napi::Error::new( + GenericFailure, + format!("fcntl F_GETFL: {}", err), + )); + } + }; + + let mut new_flags = old_flags; + new_flags.set(OFlag::O_NONBLOCK, true); + if old_flags != new_flags { + if let Err(err) = fcntl(fd, FcntlArg::F_SETFL(new_flags)) { + return Err(napi::Error::new( + GenericFailure, + format!("fcntl F_SETFL: {}", err), + )); + } + } + Ok(()) +}