Skip to content

Commit 97e7aac

Browse files
fengmk2branchseer
andauthored
fix: clear FD_CLOEXEC on stdio fds in spawn_inherited (#172)
libuv (used by Node.js) marks stdin/stdout/stderr as close-on-exec, which causes them to be closed when the child process calls exec(). This resulted in the child's fds 0-2 being closed after exec, and Node.js reopening them as /dev/null, silently discarding all output. Add a pre_exec hook to spawn_inherited() that clears FD_CLOEXEC on fds 0, 1, 2 before exec, matching the existing fix in vite-plus's vite_command::fix_stdio_streams(). Ref: libuv/libuv#2062 --------- Co-authored-by: branchseer <3612422+branchseer@users.noreply.github.com>
1 parent 261c567 commit 97e7aac

File tree

1 file changed

+34
-1
lines changed
  • crates/vite_task/src/session/execute

1 file changed

+34
-1
lines changed

crates/vite_task/src/session/execute/mod.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,40 @@ async fn spawn_inherited(spawn_command: &SpawnCommand) -> anyhow::Result<SpawnRe
379379
cmd.stdin(Stdio::inherit()).stdout(Stdio::inherit()).stderr(Stdio::inherit());
380380

381381
let start = std::time::Instant::now();
382-
let mut child = cmd.into_tokio_command().spawn()?;
382+
let mut tokio_cmd = cmd.into_tokio_command();
383+
384+
// Clear FD_CLOEXEC on stdio fds before exec. libuv (used by Node.js) marks
385+
// stdin/stdout/stderr as close-on-exec, which causes them to be closed when
386+
// the child process calls exec(). Without this fix, the child's fds 0-2 are
387+
// closed after exec and Node.js reopens them as /dev/null, losing all output.
388+
// See: https://github.com/libuv/libuv/issues/2062
389+
// SAFETY: The pre_exec closure only performs fcntl operations to clear
390+
// FD_CLOEXEC flags on stdio fds, which is safe in a post-fork context.
391+
#[cfg(unix)]
392+
unsafe {
393+
tokio_cmd.pre_exec(|| {
394+
use std::os::fd::BorrowedFd;
395+
396+
use nix::{
397+
fcntl::{FcntlArg, FdFlag, fcntl},
398+
libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO},
399+
};
400+
for fd in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] {
401+
// SAFETY: fds 0-2 are always valid in a post-fork context
402+
let borrowed = BorrowedFd::borrow_raw(fd);
403+
if let Ok(flags) = fcntl(borrowed, FcntlArg::F_GETFD) {
404+
let mut fd_flags = FdFlag::from_bits_retain(flags);
405+
if fd_flags.contains(FdFlag::FD_CLOEXEC) {
406+
fd_flags.remove(FdFlag::FD_CLOEXEC);
407+
let _ = fcntl(borrowed, FcntlArg::F_SETFD(fd_flags));
408+
}
409+
}
410+
}
411+
Ok(())
412+
});
413+
}
414+
415+
let mut child = tokio_cmd.spawn()?;
383416
let exit_status = child.wait().await?;
384417

385418
Ok(SpawnResult { exit_status, duration: start.elapsed() })

0 commit comments

Comments
 (0)