Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,25 @@ vp check exposes the stdout EAGAIN failure when a large diagnostic replay meets

## `vpt backpressure-run --digest 6,8 -- vp check`

**Exit code:** 1

```
backpressure-run detected truncated child output under stdio backpressure
--- stdout ---
stdout: 1282 lines
pass: All 3 files are correctly formatted (<duration>, <n> threads)
! eslint(no-unused-vars): Variable 'unused000' is declared but never used. Unused variables should start with a '_'.
,-[src/index.js:2:9]
1 | export function emitDiagnostics() {
2 | const unused000 = 0;
: ^^^^|^^^^
... 1268 lines elided ...
129 | const unused127 = 127;
: ^^^^|^^^^
: `-- 'unused127' is declared here
130 | }
`----
help: Consider removing this declaration.

Found 0 errors and 128 warnings in 2 files (<duration>, <n> threads)
--- stderr ---
stderr: 1 lines
warn: Lint warnings found
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,25 @@ vp check exposes the stdout EAGAIN failure when a large diagnostic replay meets

## `vpt backpressure-run --digest 6,8 -- vp check`

**Exit code:** 1

```
backpressure-run detected truncated child output under stdio backpressure
--- stdout ---
stdout: 1282 lines
pass: All 3 files are correctly formatted (<duration>, <n> threads)
! eslint(no-unused-vars): Variable 'unused000' is declared but never used. Unused variables should start with a '_'.
,-[src/index.js:2:9]
1 | export function emitDiagnostics() {
2 | const unused000 = 0;
: ^^^^|^^^^
... 1268 lines elided ...
129 | const unused127 = 127;
: ^^^^|^^^^
: `-- 'unused127' is declared here
130 | }
`----
help: Consider removing this declaration.

Found 0 errors and 128 warnings in 2 files (<duration>, <n> threads)
--- stderr ---
stderr: 1 lines
warn: Lint warnings found
```
2 changes: 2 additions & 0 deletions crates/vite_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ fn print_unknown_argument_error(error: &clap::Error) -> bool {

#[tokio::main]
async fn main() -> ExitCode {
vite_shared::ensure_blocking_stdio();

// Initialize tracing
vite_shared::init_tracing();

Expand Down
2 changes: 1 addition & 1 deletion crates/vite_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ rust-version.workspace = true

[dependencies]
directories = { workspace = true }
nix = { workspace = true, features = ["poll", "term"] }
nix = { workspace = true, features = ["fs", "poll", "term"] }
owo-colors = { workspace = true }
serde = { workspace = true }
# use `preserve_order` feature to preserve the order of the fields in `package.json`
Expand Down
2 changes: 2 additions & 0 deletions crates/vite_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod json_edit;
pub mod output;
mod package_json;
mod path_env;
mod stdio;
pub mod string_similarity;
mod tls;
mod tracing;
Expand All @@ -33,5 +34,6 @@ pub use path_env::{
PrependOptions, PrependResult, format_path_prepended, format_path_with_prepend,
prepend_to_path_env,
};
pub use stdio::ensure_blocking_stdio;
pub use tls::ensure_tls_provider;
pub use tracing::init_tracing;
62 changes: 62 additions & 0 deletions crates/vite_shared/src/stdio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/// Ensure inherited standard streams use the blocking semantics expected by
/// Rust's standard library and by the tools embedded in Vite+.
///
/// Node.js marks non-TTY stdio as non-blocking, and child processes inherit
/// that flag through the shared open file description. Clear `O_NONBLOCK`
/// once at process entry so unowned writers do not panic or truncate output
/// when a consumer applies backpressure.
#[cfg(unix)]
pub fn ensure_blocking_stdio() {
use std::{io, os::fd::AsFd};

let stdin = io::stdin();
let stdout = io::stdout();
let stderr = io::stderr();

for fd in [stdin.as_fd(), stdout.as_fd(), stderr.as_fd()] {
ensure_blocking_fd(fd);
}
}

#[cfg(unix)]
fn ensure_blocking_fd(fd: std::os::fd::BorrowedFd<'_>) {
use nix::{
fcntl::{FcntlArg, OFlag, fcntl},
unistd::isatty,
};

if isatty(fd).unwrap_or(false) {
return;
}

let Ok(flags) = fcntl(fd, FcntlArg::F_GETFL) else {
return;
};
let flags = OFlag::from_bits_retain(flags);
if flags.contains(OFlag::O_NONBLOCK) {
let _ = fcntl(fd, FcntlArg::F_SETFL(flags.difference(OFlag::O_NONBLOCK)));
}
}

#[cfg(not(unix))]
pub fn ensure_blocking_stdio() {}

#[cfg(all(test, unix))]
mod tests {
use std::os::{fd::AsFd, unix::net::UnixStream};

use nix::fcntl::{FcntlArg, OFlag, fcntl};

use super::ensure_blocking_fd;

#[test]
fn clears_nonblocking_from_a_non_tty_file_description() {
let (stream, _peer) = UnixStream::pair().unwrap();
stream.set_nonblocking(true).unwrap();

ensure_blocking_fd(stream.as_fd());

let flags = fcntl(&stream, FcntlArg::F_GETFL).unwrap();
assert!(!OFlag::from_bits_retain(flags).contains(OFlag::O_NONBLOCK));
}
}
1 change: 1 addition & 0 deletions packages/cli/binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use crate::cli::{
#[napi_derive::module_init]
#[allow(clippy::disallowed_macros)]
pub fn init() {
vite_shared::ensure_blocking_stdio();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-clear stdio before NAPI-spawned commands

Fresh evidence beyond the earlier module-init concern: Node sets O_NONBLOCK lazily when process.stdout/stderr is first used, so clearing it only here can be undone by later JS output before another NAPI export spawns a child. For example vp create --no-interactive --verbose <remote-template> logs Running: ... in packages/cli/src/create/templates/remote.ts before calling runCommandAndDetectProjectDir, which then enters the runCommand NAPI path and spawns with inherited stdio; under the same backpressured pipe scenario, that child can still inherit nonblocking stdout/stderr. Re-run this guard immediately before NAPI entrypoints that spawn or write through stdio, not just at module load.

Useful? React with 👍 / 👎.

crate::cli::init_tracing();

// Install a Vite+ panic hook so panics are correctly attributed to Vite+.
Expand Down
Loading