Skip to content
Merged
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/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ module.exports.shutdownAsyncRuntime = nativeBinding.shutdownAsyncRuntime;
module.exports.startAsyncRuntime = nativeBinding.startAsyncRuntime;
module.exports.detectWorkspace = nativeBinding.detectWorkspace;
module.exports.downloadPackageManager = nativeBinding.downloadPackageManager;
module.exports.ensureBlockingStdio = nativeBinding.ensureBlockingStdio;
module.exports.hasConfigKey = nativeBinding.hasConfigKey;
module.exports.mergeJsonConfig = nativeBinding.mergeJsonConfig;
module.exports.mergeTsdownConfig = nativeBinding.mergeTsdownConfig;
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/binding/index.d.cts
Original file line number Diff line number Diff line change
Expand Up @@ -3366,6 +3366,9 @@ export interface DownloadPackageManagerResult {
version: string;
}

/** Re-enable blocking stdio after Node.js has initialized its lazy standard streams. */
export declare function ensureBlockingStdio(): void;

/**
* Whether `config_key` is already declared as a top-level property in the
* vite config's `defineConfig({...})` (or equivalent) object literal.
Expand Down
7 changes: 7 additions & 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();
Comment thread
wan9chi marked this conversation as resolved.
crate::cli::init_tracing();

// Install a Vite+ panic hook so panics are correctly attributed to Vite+.
Expand All @@ -55,6 +56,12 @@ pub fn init() {
}));
}

/// Re-enable blocking stdio after Node.js has initialized its lazy standard streams.
#[napi]
pub fn ensure_blocking_stdio() {
vite_shared::ensure_blocking_stdio();
}

/// Configuration options passed from JavaScript to Rust.
#[napi(object, object_to_js = false)]
pub struct CliOptions {
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import path from 'node:path';

import { run } from '../binding/index.js';
import { ensureBlockingStdio, run } from '../binding/index.js';
import { applyToolInitConfigToViteConfig, inspectInitCommand } from './init-config.ts';
import { doc } from './resolve-doc.ts';
import { fmt } from './resolve-fmt.ts';
Expand All @@ -23,6 +23,12 @@ import { resolveUniversalViteConfig } from './resolve-vite-config.ts';
import { vite } from './resolve-vite.ts';
import { accent, errorMsg, log } from './utils/terminal.ts';

// Node.js sets O_NONBLOCK when pipe-backed stdio is first accessed. Materialize
// the output streams before restoring the blocking semantics expected by Rust.
void process.stdout;
void process.stderr;
ensureBlockingStdio();

function getErrorMessage(err: unknown): string {
if (err instanceof Error) {
return err.message;
Expand Down
Loading