Skip to content

Commit

Permalink
fix(core): handle blocking stdin (#21672)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cammisuli authored Feb 7, 2024
1 parent 0f1207e commit 1dd3637
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 40 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/nx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ crossterm = "0.27.0"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["fileapi"] }

[target.'cfg(not(windows))'.dependencies]
mio = "0.8"

[lib]
crate-type = ['cdylib']

Expand Down
51 changes: 11 additions & 40 deletions packages/nx/src/native/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ use napi::{Env, JsFunction};
use portable_pty::{ChildKiller, CommandBuilder, NativePtySystem, PtySize, PtySystem};
use tracing::trace;

#[cfg_attr(windows, path = "command/windows.rs")]
#[cfg_attr(not(windows), path = "command/unix.rs")]
mod os;

fn command_builder() -> CommandBuilder {
if cfg!(target_os = "windows") {
if cfg!(windows) {
let comspec = std::env::var("COMSPEC");
let shell = comspec
.as_ref()
Expand Down Expand Up @@ -191,12 +195,13 @@ pub fn run_command(

// Stdin -> pty stdin
if std::io::stdout().is_tty() {
enable_raw_mode().expect("Failed to enter raw terminal mode");
std::thread::spawn(move || {
enable_raw_mode().expect("Failed to enter raw terminal mode");
let mut stdin = std::io::stdin();
#[allow(clippy::redundant_pattern_matching)]
// ignore errors that come from copying the stream
if let Ok(_) = std::io::copy(&mut stdin, &mut writer) {}

if let Err(e) = os::write_to_pty(&mut stdin, &mut writer) {
trace!("Error writing to pty: {:?}", e);
}
});
}

Expand Down Expand Up @@ -224,45 +229,11 @@ pub fn nx_fork(
) -> napi::Result<ChildProcess> {
let command = format!(
"node {} {} {}",
handle_path_space(fork_script),
os::handle_path_space(fork_script),
psuedo_ipc_path,
id
);

trace!("nx_fork command: {}", &command);
run_command(command, command_dir, js_env, Some(quiet))
}

#[cfg(target_os = "windows")]
pub fn handle_path_space(path: String) -> String {
use std::os::windows::ffi::OsStrExt;
use std::{ffi::OsString, os::windows::ffi::OsStringExt};

use winapi::um::fileapi::GetShortPathNameW;
let wide: Vec<u16> = std::path::PathBuf::from(&path)
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect();
let mut buffer: Vec<u16> = vec![0; wide.len() * 2];
let result =
unsafe { GetShortPathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) };
if result == 0 {
path
} else {
let len = buffer.iter().position(|&x| x == 0).unwrap();
let short_path: String = OsString::from_wide(&buffer[..len])
.to_string_lossy()
.into_owned();
short_path
}
}

#[cfg(not(target_os = "windows"))]
fn handle_path_space(path: String) -> String {
if path.contains(' ') {
format!("'{}'", path)
} else {
path
}
}
67 changes: 67 additions & 0 deletions packages/nx/src/native/command/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::{
io::{Read, Stdin, Write},
os::fd::AsRawFd,
};

use mio::{unix::SourceFd, Events};
use tracing::trace;

pub fn handle_path_space(path: String) -> String {
if path.contains(' ') {
format!("'{}'", path)
} else {
path
}
}

pub fn write_to_pty(stdin: &mut Stdin, writer: &mut impl Write) -> anyhow::Result<()> {
let mut buffer = [0; 1024];

let mut poll = mio::Poll::new()?;
let mut events = Events::with_capacity(3);

// Register stdin to the poll instance
let token = mio::Token(0);
poll.registry()
.register(
&mut SourceFd(&stdin.as_raw_fd()),
token,
mio::Interest::READABLE,
)
.map_err(|e| anyhow::anyhow!("Failed to register stdin to poll: {}", e))?;

loop {
// Poll for events
if let Err(e) = poll.poll(&mut events, None) {
if e.kind() == std::io::ErrorKind::Interrupted {
continue;
}
trace!("poll error: {:?}", e);
anyhow::bail!("Failed to poll for events: {}", e);
}

for event in events.iter().map(|x| x.token()) {
match event {
mio::Token(0) => {
// Read data from stdin
loop {
match stdin.read(&mut buffer) {
Ok(n) => {
writer.write_all(&buffer[..n])?;
writer.flush()?;
}
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
break;
} else if e.kind() == std::io::ErrorKind::Interrupted {
continue;
}
}
}
}
}
_ => unreachable!(),
}
}
}
}
31 changes: 31 additions & 0 deletions packages/nx/src/native/command/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::io::{Stdin, Write};
use std::os::windows::ffi::OsStrExt;
use std::{ffi::OsString, os::windows::ffi::OsStringExt};

use winapi::um::fileapi::GetShortPathNameW;

pub fn handle_path_space(path: String) -> String {
let wide: Vec<u16> = std::path::PathBuf::from(&path)
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect();
let mut buffer: Vec<u16> = vec![0; wide.len() * 2];
let result =
unsafe { GetShortPathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) };
if result == 0 {
path
} else {
let len = buffer.iter().position(|&x| x == 0).unwrap();
let short_path: String = OsString::from_wide(&buffer[..len])
.to_string_lossy()
.into_owned();
short_path
}
}

pub fn write_to_pty(stdin: &mut Stdin, writer: &mut impl Write) -> anyhow::Result<()> {
std::io::copy(stdin, writer)
.map_err(|e| anyhow::anyhow!(e))
.map(|_| ())
}

0 comments on commit 1dd3637

Please sign in to comment.