Skip to content

Commit a739a2f

Browse files
branchseerclaude
andauthored
refactor: make reporter traits and writers synchronous (#269)
## Summary - Remove `async_trait` from `GraphExecutionReporter` and `LeafExecutionReporter` traits, making all reporter callbacks synchronous - Switch `StdioConfig` writers and `spawn_with_tracking` writer params from `tokio::io::AsyncWrite` to `std::io::Write` - Convert reporter tests from `#[tokio::test] async` to plain `#[test]` Child process stdout/stderr reading remains async via `tokio::select!`, but all reporter callbacks and output writing are now synchronous. ## Test plan - [x] `cargo check -p vite_task` compiles cleanly - [x] `cargo check -p vite_task_bin` compiles cleanly - [x] All 6 reporter unit tests pass (`cargo test -p vite_task -- reporter`) - [ ] E2E snapshot tests pass - [ ] Plan snapshot tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent afec83c commit a739a2f

File tree

6 files changed

+137
-168
lines changed

6 files changed

+137
-168
lines changed

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

Lines changed: 54 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ pub mod fingerprint;
22
pub mod glob_inputs;
33
pub mod spawn;
44

5-
use std::{collections::BTreeMap, process::Stdio, sync::Arc};
5+
use std::{collections::BTreeMap, io::Write as _, process::Stdio, sync::Arc};
66

77
use futures_util::FutureExt;
8-
use tokio::io::AsyncWriteExt as _;
98
use vite_path::AbsolutePath;
109
use vite_task_plan::{
1110
ExecutionGraph, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, SpawnCommand,
@@ -144,21 +143,18 @@ impl ExecutionContext<'_> {
144143
LeafExecutionKind::InProcess(in_process_execution) => {
145144
// In-process (built-in) commands: caching is disabled, execute synchronously
146145
let mut stdio_config = leaf_reporter
147-
.start(CacheStatus::Disabled(CacheDisabledReason::InProcessExecution))
148-
.await;
146+
.start(CacheStatus::Disabled(CacheDisabledReason::InProcessExecution));
149147

150148
let execution_output = in_process_execution.execute();
151149
// Write output to the stdout writer from StdioConfig
152-
let _ = stdio_config.stdout_writer.write_all(&execution_output.stdout).await;
153-
let _ = stdio_config.stdout_writer.flush().await;
150+
let _ = stdio_config.stdout_writer.write_all(&execution_output.stdout);
151+
let _ = stdio_config.stdout_writer.flush();
154152

155-
leaf_reporter
156-
.finish(
157-
None,
158-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
159-
None,
160-
)
161-
.await;
153+
leaf_reporter.finish(
154+
None,
155+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
156+
None,
157+
);
162158
false
163159
}
164160
LeafExecutionKind::Spawn(spawn_execution) => {
@@ -220,13 +216,11 @@ pub async fn execute_spawn(
220216
) {
221217
Ok(inputs) => inputs,
222218
Err(err) => {
223-
leaf_reporter
224-
.finish(
225-
None,
226-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
227-
Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }),
228-
)
229-
.await;
219+
leaf_reporter.finish(
220+
None,
221+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
222+
Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }),
223+
);
230224
return SpawnOutcome::Failed;
231225
}
232226
};
@@ -247,13 +241,11 @@ pub async fn execute_spawn(
247241
Err(err) => {
248242
// Cache lookup error — report through finish.
249243
// Note: start() is NOT called because we don't have a valid cache status.
250-
leaf_reporter
251-
.finish(
252-
None,
253-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
254-
Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }),
255-
)
256-
.await;
244+
leaf_reporter.finish(
245+
None,
246+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
247+
Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }),
248+
);
257249
return SpawnOutcome::Failed;
258250
}
259251
}
@@ -263,23 +255,25 @@ pub async fn execute_spawn(
263255
};
264256

265257
// 2. Report execution start with the determined cache status.
266-
// Returns StdioConfig with the reporter's suggestion and async writers.
267-
let mut stdio_config = leaf_reporter.start(cache_status).await;
258+
// Returns StdioConfig with the reporter's suggestion and writers.
259+
let mut stdio_config = leaf_reporter.start(cache_status);
268260

269261
// 3. If cache hit, replay outputs via the StdioConfig writers and finish early.
270262
// No need to actually execute the command — just replay what was cached.
271263
if let Some(cached) = cached_value {
272264
for output in cached.std_outputs.iter() {
273-
let writer: &mut (dyn tokio::io::AsyncWrite + Unpin) = match output.kind {
265+
let writer: &mut dyn std::io::Write = match output.kind {
274266
spawn::OutputKind::StdOut => &mut stdio_config.stdout_writer,
275267
spawn::OutputKind::StdErr => &mut stdio_config.stderr_writer,
276268
};
277-
let _ = writer.write_all(&output.content).await;
278-
let _ = writer.flush().await;
269+
let _ = writer.write_all(&output.content);
270+
let _ = writer.flush();
279271
}
280-
leaf_reporter
281-
.finish(None, CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), None)
282-
.await;
272+
leaf_reporter.finish(
273+
None,
274+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit),
275+
None,
276+
);
283277
return SpawnOutcome::CacheHit;
284278
}
285279

@@ -293,29 +287,25 @@ pub async fn execute_spawn(
293287
if use_inherited {
294288
// Inherited mode: all three stdio FDs (stdin, stdout, stderr) are inherited
295289
// from the parent process. No fspy tracking, no output capture.
296-
// Drop the StdioConfig writers before spawning to avoid holding tokio::io::Stdout
290+
// Drop the StdioConfig writers before spawning to avoid holding std::io::Stdout
297291
// while the child also writes to the same FD.
298292
drop(stdio_config);
299293

300294
match spawn_inherited(&spawn_execution.spawn_command).await {
301295
Ok(result) => {
302-
leaf_reporter
303-
.finish(
304-
Some(result.exit_status),
305-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
306-
None,
307-
)
308-
.await;
296+
leaf_reporter.finish(
297+
Some(result.exit_status),
298+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
299+
None,
300+
);
309301
return SpawnOutcome::Spawned(result.exit_status);
310302
}
311303
Err(err) => {
312-
leaf_reporter
313-
.finish(
314-
None,
315-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
316-
Some(ExecutionError::Spawn(err)),
317-
)
318-
.await;
304+
leaf_reporter.finish(
305+
None,
306+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
307+
Some(ExecutionError::Spawn(err)),
308+
);
319309
return SpawnOutcome::Failed;
320310
}
321311
}
@@ -346,13 +336,11 @@ pub async fn execute_spawn(
346336
{
347337
Ok(negs) => negs,
348338
Err(err) => {
349-
leaf_reporter
350-
.finish(
351-
None,
352-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
353-
Some(ExecutionError::PostRunFingerprint(err)),
354-
)
355-
.await;
339+
leaf_reporter.finish(
340+
None,
341+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
342+
Some(ExecutionError::PostRunFingerprint(err)),
343+
);
356344
return SpawnOutcome::Failed;
357345
}
358346
}
@@ -367,8 +355,8 @@ pub async fn execute_spawn(
367355
let result = match spawn_with_tracking(
368356
&spawn_execution.spawn_command,
369357
cache_base_path,
370-
&mut stdio_config.stdout_writer,
371-
&mut stdio_config.stderr_writer,
358+
&mut *stdio_config.stdout_writer,
359+
&mut *stdio_config.stderr_writer,
372360
std_outputs.as_mut(),
373361
path_accesses.as_mut(),
374362
&resolved_negatives,
@@ -377,13 +365,11 @@ pub async fn execute_spawn(
377365
{
378366
Ok(result) => result,
379367
Err(err) => {
380-
leaf_reporter
381-
.finish(
382-
None,
383-
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
384-
Some(ExecutionError::Spawn(err)),
385-
)
386-
.await;
368+
leaf_reporter.finish(
369+
None,
370+
CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled),
371+
Some(ExecutionError::Spawn(err)),
372+
);
387373
return SpawnOutcome::Failed;
388374
}
389375
};
@@ -456,7 +442,7 @@ pub async fn execute_spawn(
456442
// 7. Finish the leaf execution with the result and optional cache error.
457443
// Cache update/fingerprint failures are reported but do not affect the outcome —
458444
// the process ran, so we return its actual exit status.
459-
leaf_reporter.finish(Some(result.exit_status), cache_update_status, cache_error).await;
445+
leaf_reporter.finish(Some(result.exit_status), cache_update_status, cache_error);
460446

461447
SpawnOutcome::Spawned(result.exit_status)
462448
}
@@ -560,6 +546,6 @@ impl Session<'_> {
560546

561547
// Leaf-level errors and non-zero exit statuses are tracked internally
562548
// by the reporter.
563-
reporter.finish().await
549+
reporter.finish()
564550
}
565551
}

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::{
44
collections::hash_map::Entry,
5+
io::Write,
56
process::{ExitStatus, Stdio},
67
time::{Duration, Instant},
78
};
@@ -10,7 +11,7 @@ use bincode::{Decode, Encode};
1011
use fspy::AccessMode;
1112
use rustc_hash::FxHashSet;
1213
use serde::Serialize;
13-
use tokio::io::{AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _};
14+
use tokio::io::AsyncReadExt as _;
1415
use vite_path::{AbsolutePath, RelativePathBuf};
1516
use vite_task_plan::SpawnCommand;
1617
use wax::Program as _;
@@ -72,8 +73,8 @@ pub struct TrackedPathAccesses {
7273
pub async fn spawn_with_tracking(
7374
spawn_command: &SpawnCommand,
7475
workspace_root: &AbsolutePath,
75-
stdout_writer: &mut (dyn AsyncWrite + Unpin),
76-
stderr_writer: &mut (dyn AsyncWrite + Unpin),
76+
stdout_writer: &mut dyn Write,
77+
stderr_writer: &mut dyn Write,
7778
std_outputs: Option<&mut Vec<StdOutput>>,
7879
path_accesses: Option<&mut TrackedPathAccesses>,
7980
resolved_negatives: &[wax::Glob<'static>],
@@ -128,9 +129,9 @@ pub async fn spawn_with_tracking(
128129
0 => stdout_done = true,
129130
n => {
130131
let content = stdout_buf[..n].to_vec();
131-
// Write to the async writer immediately
132-
stdout_writer.write_all(&content).await?;
133-
stdout_writer.flush().await?;
132+
// Write to the sync writer immediately
133+
stdout_writer.write_all(&content)?;
134+
stdout_writer.flush()?;
134135
// Store outputs for caching
135136
if let Some(outputs) = &mut outputs {
136137
if let Some(last) = outputs.last_mut()
@@ -149,9 +150,9 @@ pub async fn spawn_with_tracking(
149150
0 => stderr_done = true,
150151
n => {
151152
let content = stderr_buf[..n].to_vec();
152-
// Write to the async writer immediately
153-
stderr_writer.write_all(&content).await?;
154-
stderr_writer.flush().await?;
153+
// Write to the sync writer immediately
154+
stderr_writer.write_all(&content)?;
155+
stderr_writer.flush()?;
155156
// Store outputs for caching
156157
if let Some(outputs) = &mut outputs {
157158
if let Some(last) = outputs.last_mut()

crates/vite_task/src/session/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ impl<'a> Session<'a> {
302302

303303
let builder = LabeledReporterBuilder::new(
304304
self.workspace_path(),
305-
Box::new(tokio::io::stdout()),
305+
Box::new(std::io::stdout()),
306306
run_command.flags.verbose,
307307
Some(self.make_summary_writer()),
308308
self.program_name.clone(),
@@ -602,7 +602,7 @@ impl<'a> Session<'a> {
602602

603603
// Create a plain (standalone) reporter — no graph awareness, no summary
604604
let plain_reporter =
605-
reporter::PlainReporter::new(silent_if_cache_hit, Box::new(tokio::io::stdout()));
605+
reporter::PlainReporter::new(silent_if_cache_hit, Box::new(std::io::stdout()));
606606

607607
// Execute the spawn directly using the free function, bypassing the graph pipeline
608608
let outcome = execute::execute_spawn(

0 commit comments

Comments
 (0)