From e81a1df1a24c317c4c3d0c14389c28585bdae481 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 18 Mar 2026 19:39:25 +0800 Subject: [PATCH 1/3] refactor: make reporter traits and writers synchronous Replace async_trait on GraphExecutionReporter and LeafExecutionReporter with plain sync traits. Switch StdioConfig writers and spawn_with_tracking writer params from tokio AsyncWrite to std::io::Write. Child process stdout/stderr reading remains async via tokio::select, but all reporter callbacks and output writing are now synchronous. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/vite_task/src/session/execute/mod.rs | 122 ++++++++---------- crates/vite_task/src/session/execute/spawn.rs | 19 +-- crates/vite_task/src/session/mod.rs | 4 +- .../vite_task/src/session/reporter/labeled.rs | 84 +++++------- crates/vite_task/src/session/reporter/mod.rs | 25 ++-- .../vite_task/src/session/reporter/plain.rs | 49 ++++--- 6 files changed, 136 insertions(+), 167 deletions(-) diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs index be936386..c944f88d 100644 --- a/crates/vite_task/src/session/execute/mod.rs +++ b/crates/vite_task/src/session/execute/mod.rs @@ -2,10 +2,9 @@ pub mod fingerprint; pub mod glob_inputs; pub mod spawn; -use std::{collections::BTreeMap, process::Stdio, sync::Arc}; +use std::{collections::BTreeMap, io::Write as _, process::Stdio, sync::Arc}; use futures_util::FutureExt; -use tokio::io::AsyncWriteExt as _; use vite_path::AbsolutePath; use vite_task_plan::{ ExecutionGraph, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, SpawnCommand, @@ -144,21 +143,18 @@ impl ExecutionContext<'_> { LeafExecutionKind::InProcess(in_process_execution) => { // In-process (built-in) commands: caching is disabled, execute synchronously let mut stdio_config = leaf_reporter - .start(CacheStatus::Disabled(CacheDisabledReason::InProcessExecution)) - .await; + .start(CacheStatus::Disabled(CacheDisabledReason::InProcessExecution)); let execution_output = in_process_execution.execute(); // Write output to the stdout writer from StdioConfig - let _ = stdio_config.stdout_writer.write_all(&execution_output.stdout).await; - let _ = stdio_config.stdout_writer.flush().await; + let _ = stdio_config.stdout_writer.write_all(&execution_output.stdout); + let _ = stdio_config.stdout_writer.flush(); - leaf_reporter - .finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - None, - ) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + None, + ); false } LeafExecutionKind::Spawn(spawn_execution) => { @@ -220,13 +216,11 @@ pub async fn execute_spawn( ) { Ok(inputs) => inputs, Err(err) => { - leaf_reporter - .finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }), - ) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }), + ); return SpawnOutcome::Failed; } }; @@ -247,13 +241,11 @@ pub async fn execute_spawn( Err(err) => { // Cache lookup error — report through finish. // Note: start() is NOT called because we don't have a valid cache status. - leaf_reporter - .finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }), - ) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }), + ); return SpawnOutcome::Failed; } } @@ -263,23 +255,25 @@ pub async fn execute_spawn( }; // 2. Report execution start with the determined cache status. - // Returns StdioConfig with the reporter's suggestion and async writers. - let mut stdio_config = leaf_reporter.start(cache_status).await; + // Returns StdioConfig with the reporter's suggestion and writers. + let mut stdio_config = leaf_reporter.start(cache_status); // 3. If cache hit, replay outputs via the StdioConfig writers and finish early. // No need to actually execute the command — just replay what was cached. if let Some(cached) = cached_value { for output in cached.std_outputs.iter() { - let writer: &mut (dyn tokio::io::AsyncWrite + Unpin) = match output.kind { + let writer: &mut dyn std::io::Write = match output.kind { spawn::OutputKind::StdOut => &mut stdio_config.stdout_writer, spawn::OutputKind::StdErr => &mut stdio_config.stderr_writer, }; - let _ = writer.write_all(&output.content).await; - let _ = writer.flush().await; + let _ = writer.write_all(&output.content); + let _ = writer.flush(); } - leaf_reporter - .finish(None, CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), None) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), + None, + ); return SpawnOutcome::CacheHit; } @@ -293,29 +287,25 @@ pub async fn execute_spawn( if use_inherited { // Inherited mode: all three stdio FDs (stdin, stdout, stderr) are inherited // from the parent process. No fspy tracking, no output capture. - // Drop the StdioConfig writers before spawning to avoid holding tokio::io::Stdout + // Drop the StdioConfig writers before spawning to avoid holding std::io::Stdout // while the child also writes to the same FD. drop(stdio_config); match spawn_inherited(&spawn_execution.spawn_command).await { Ok(result) => { - leaf_reporter - .finish( - Some(result.exit_status), - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - None, - ) - .await; + leaf_reporter.finish( + Some(result.exit_status), + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + None, + ); return SpawnOutcome::Spawned(result.exit_status); } Err(err) => { - leaf_reporter - .finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::Spawn(err)), - ) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::Spawn(err)), + ); return SpawnOutcome::Failed; } } @@ -346,13 +336,11 @@ pub async fn execute_spawn( { Ok(negs) => negs, Err(err) => { - leaf_reporter - .finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::PostRunFingerprint(err)), - ) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::PostRunFingerprint(err)), + ); return SpawnOutcome::Failed; } } @@ -367,8 +355,8 @@ pub async fn execute_spawn( let result = match spawn_with_tracking( &spawn_execution.spawn_command, cache_base_path, - &mut stdio_config.stdout_writer, - &mut stdio_config.stderr_writer, + &mut *stdio_config.stdout_writer, + &mut *stdio_config.stderr_writer, std_outputs.as_mut(), path_accesses.as_mut(), &resolved_negatives, @@ -377,13 +365,11 @@ pub async fn execute_spawn( { Ok(result) => result, Err(err) => { - leaf_reporter - .finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::Spawn(err)), - ) - .await; + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::Spawn(err)), + ); return SpawnOutcome::Failed; } }; @@ -456,7 +442,7 @@ pub async fn execute_spawn( // 7. Finish the leaf execution with the result and optional cache error. // Cache update/fingerprint failures are reported but do not affect the outcome — // the process ran, so we return its actual exit status. - leaf_reporter.finish(Some(result.exit_status), cache_update_status, cache_error).await; + leaf_reporter.finish(Some(result.exit_status), cache_update_status, cache_error); SpawnOutcome::Spawned(result.exit_status) } @@ -560,6 +546,6 @@ impl Session<'_> { // Leaf-level errors and non-zero exit statuses are tracked internally // by the reporter. - reporter.finish().await + reporter.finish() } } diff --git a/crates/vite_task/src/session/execute/spawn.rs b/crates/vite_task/src/session/execute/spawn.rs index fba313a6..99ebae87 100644 --- a/crates/vite_task/src/session/execute/spawn.rs +++ b/crates/vite_task/src/session/execute/spawn.rs @@ -2,6 +2,7 @@ use std::{ collections::hash_map::Entry, + io::Write, process::{ExitStatus, Stdio}, time::{Duration, Instant}, }; @@ -10,7 +11,7 @@ use bincode::{Decode, Encode}; use fspy::AccessMode; use rustc_hash::FxHashSet; use serde::Serialize; -use tokio::io::{AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _}; +use tokio::io::AsyncReadExt as _; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_task_plan::SpawnCommand; use wax::Program as _; @@ -72,8 +73,8 @@ pub struct TrackedPathAccesses { pub async fn spawn_with_tracking( spawn_command: &SpawnCommand, workspace_root: &AbsolutePath, - stdout_writer: &mut (dyn AsyncWrite + Unpin), - stderr_writer: &mut (dyn AsyncWrite + Unpin), + stdout_writer: &mut dyn Write, + stderr_writer: &mut dyn Write, std_outputs: Option<&mut Vec>, path_accesses: Option<&mut TrackedPathAccesses>, resolved_negatives: &[wax::Glob<'static>], @@ -128,9 +129,9 @@ pub async fn spawn_with_tracking( 0 => stdout_done = true, n => { let content = stdout_buf[..n].to_vec(); - // Write to the async writer immediately - stdout_writer.write_all(&content).await?; - stdout_writer.flush().await?; + // Write to the sync writer immediately + stdout_writer.write_all(&content)?; + stdout_writer.flush()?; // Store outputs for caching if let Some(outputs) = &mut outputs { if let Some(last) = outputs.last_mut() @@ -149,9 +150,9 @@ pub async fn spawn_with_tracking( 0 => stderr_done = true, n => { let content = stderr_buf[..n].to_vec(); - // Write to the async writer immediately - stderr_writer.write_all(&content).await?; - stderr_writer.flush().await?; + // Write to the sync writer immediately + stderr_writer.write_all(&content)?; + stderr_writer.flush()?; // Store outputs for caching if let Some(outputs) = &mut outputs { if let Some(last) = outputs.last_mut() diff --git a/crates/vite_task/src/session/mod.rs b/crates/vite_task/src/session/mod.rs index d52c5ecd..11f687a5 100644 --- a/crates/vite_task/src/session/mod.rs +++ b/crates/vite_task/src/session/mod.rs @@ -302,7 +302,7 @@ impl<'a> Session<'a> { let builder = LabeledReporterBuilder::new( self.workspace_path(), - Box::new(tokio::io::stdout()), + Box::new(std::io::stdout()), run_command.flags.verbose, Some(self.make_summary_writer()), self.program_name.clone(), @@ -602,7 +602,7 @@ impl<'a> Session<'a> { // Create a plain (standalone) reporter — no graph awareness, no summary let plain_reporter = - reporter::PlainReporter::new(silent_if_cache_hit, Box::new(tokio::io::stdout())); + reporter::PlainReporter::new(silent_if_cache_hit, Box::new(std::io::stdout())); // Execute the spawn directly using the free function, bypassing the graph pipeline let outcome = execute::execute_spawn( diff --git a/crates/vite_task/src/session/reporter/labeled.rs b/crates/vite_task/src/session/reporter/labeled.rs index f41d4241..ab24339a 100644 --- a/crates/vite_task/src/session/reporter/labeled.rs +++ b/crates/vite_task/src/session/reporter/labeled.rs @@ -6,9 +6,8 @@ //! Tracks statistics across multiple leaf executions, prints command lines with cache //! status indicators, and renders a summary with per-task details at the end. -use std::{cell::RefCell, process::ExitStatus as StdExitStatus, rc::Rc, sync::Arc}; +use std::{cell::RefCell, io::Write, process::ExitStatus as StdExitStatus, rc::Rc, sync::Arc}; -use tokio::io::{AsyncWrite, AsyncWriteExt as _}; use vite_path::AbsolutePath; use vite_str::Str; use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; @@ -53,7 +52,7 @@ struct SharedReporterState { /// - Shows full Statistics, Performance, and Task Details sections pub struct LabeledReporterBuilder { workspace_path: Arc, - writer: Box, + writer: Box, /// Whether to render the full detailed summary (`--verbose` flag). show_details: bool, /// Callback to persist the summary (e.g., write `last-summary.json`). @@ -66,13 +65,13 @@ impl LabeledReporterBuilder { /// Create a new labeled reporter builder. /// /// - `workspace_path`: The workspace root, used to compute relative cwds in display. - /// - `writer`: Async writer for reporter display output. + /// - `writer`: Writer for reporter display output. /// - `show_details`: Whether to render the full detailed summary. /// - `write_summary`: Callback to persist the summary, or `None` to skip. /// - `program_name`: The CLI binary name (e.g. `"vt"`) used in summary output. pub fn new( workspace_path: Arc, - writer: Box, + writer: Box, show_details: bool, write_summary: Option, program_name: Str, @@ -101,19 +100,13 @@ impl GraphExecutionReporterBuilder for LabeledReporterBuilder { /// share mutable state with this reporter via `Rc>`. pub struct LabeledGraphReporter { shared: Rc>, - writer: Rc>>, + writer: Rc>>, workspace_path: Arc, show_details: bool, write_summary: Option, program_name: Str, } -#[async_trait::async_trait(?Send)] -#[expect( - clippy::await_holding_refcell_ref, - reason = "writer RefCell borrow across await is safe: reporter is !Send, single-threaded, \ - and finish() is called once after all leaf reporters are dropped" -)] impl GraphExecutionReporter for LabeledGraphReporter { fn new_leaf_execution( &mut self, @@ -137,7 +130,7 @@ impl GraphExecutionReporter for LabeledGraphReporter { }) } - async fn finish(self: Box) -> Result<(), ExitStatus> { + fn finish(self: Box) -> Result<(), ExitStatus> { // Take tasks from shared state — all leaf reporters have been dropped by now. let tasks = { let mut shared = self.shared.borrow_mut(); @@ -190,16 +183,16 @@ impl GraphExecutionReporter for LabeledGraphReporter { write_summary(&summary); } - // Write the summary buffer asynchronously. + // Write the summary buffer. // Always flush the writer — even when the summary is empty, a preceding // spawned process may have written to the same fd via Stdio::inherit() // and the data must be flushed before the caller reads the output. { let mut writer = self.writer.borrow_mut(); if !summary_buf.is_empty() { - let _ = writer.write_all(&summary_buf).await; + let _ = writer.write_all(&summary_buf); } - let _ = writer.flush().await; + let _ = writer.flush(); } result @@ -208,11 +201,11 @@ impl GraphExecutionReporter for LabeledGraphReporter { /// Leaf-level reporter created by [`LabeledGraphReporter::new_leaf_execution`]. /// -/// Writes display output in real-time to the shared async writer and builds +/// Writes display output in real-time to the shared writer and builds /// [`TaskSummary`] entries that are pushed to [`SharedReporterState`] on completion. struct LabeledLeafReporter { shared: Rc>, - writer: Rc>>, + writer: Rc>>, /// Display info for this execution, looked up from the graph via the path. display: ExecutionItemDisplay, workspace_path: Arc, @@ -223,14 +216,8 @@ struct LabeledLeafReporter { cache_status: Option, } -#[async_trait::async_trait(?Send)] -#[expect( - clippy::await_holding_refcell_ref, - reason = "writer RefCell borrow across await is safe: reporter is !Send, single-threaded, \ - and only one leaf is active at a time (no re-entrant access during write_all)" -)] impl LeafExecutionReporter for LabeledLeafReporter { - async fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { + fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { // Format command line with cache status before storing it. let line = format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); @@ -238,17 +225,17 @@ impl LeafExecutionReporter for LabeledLeafReporter { self.cache_status = Some(cache_status); let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(line.as_bytes()).await; - let _ = writer.flush().await; + let _ = writer.write_all(line.as_bytes()); + let _ = writer.flush(); StdioConfig { suggestion: self.stdio_suggestion, - stdout_writer: Box::new(tokio::io::stdout()), - stderr_writer: Box::new(tokio::io::stderr()), + stdout_writer: Box::new(std::io::stdout()), + stderr_writer: Box::new(std::io::stderr()), } } - async fn finish( + fn finish( self: Box, status: Option, cache_update_status: CacheUpdateStatus, @@ -287,7 +274,7 @@ impl LeafExecutionReporter for LabeledLeafReporter { shared.borrow_mut().tasks.push(task_summary); } - // Build all display output into a buffer, then write once asynchronously. + // Build all display output into a buffer, then write once. let mut buf = Vec::new(); if let Some(ref message) = error_display { @@ -303,8 +290,8 @@ impl LeafExecutionReporter for LabeledLeafReporter { if !buf.is_empty() { let mut writer = writer.borrow_mut(); - let _ = writer.write_all(&buf).await; - let _ = writer.flush().await; + let _ = writer.write_all(&buf); + let _ = writer.flush(); } } } @@ -338,7 +325,7 @@ mod tests { ) -> Box { let builder = Box::new(LabeledReporterBuilder::new( test_path(), - Box::new(tokio::io::sink()), + Box::new(std::io::sink()), false, None, Str::from("vt"), @@ -347,53 +334,52 @@ mod tests { reporter.new_leaf_execution(display, leaf_kind, all_ancestors_single_node) } - async fn suggestion_for( + fn suggestion_for( display: &ExecutionItemDisplay, leaf_kind: &LeafExecutionKind, all_ancestors_single_node: bool, ) -> StdioSuggestion { let mut leaf = build_labeled_leaf(display, leaf_kind, all_ancestors_single_node); - let stdio_config = - leaf.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)).await; + let stdio_config = leaf.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); stdio_config.suggestion } - #[tokio::test] - async fn spawn_with_all_single_node_ancestors_suggests_inherited() { + #[test] + fn spawn_with_all_single_node_ancestors_suggests_inherited() { let task = spawn_task("build"); let item = &task.items[0]; assert_eq!( - suggestion_for(&item.execution_item_display, leaf_kind(item), true).await, + suggestion_for(&item.execution_item_display, leaf_kind(item), true), StdioSuggestion::Inherited ); } - #[tokio::test] - async fn spawn_without_all_single_node_ancestors_suggests_piped() { + #[test] + fn spawn_without_all_single_node_ancestors_suggests_piped() { let task = spawn_task("build"); let item = &task.items[0]; assert_eq!( - suggestion_for(&item.execution_item_display, leaf_kind(item), false).await, + suggestion_for(&item.execution_item_display, leaf_kind(item), false), StdioSuggestion::Piped ); } - #[tokio::test] - async fn in_process_leaf_suggests_piped_even_with_single_node_ancestors() { + #[test] + fn in_process_leaf_suggests_piped_even_with_single_node_ancestors() { let task = in_process_task("echo"); let item = &task.items[0]; assert_eq!( - suggestion_for(&item.execution_item_display, leaf_kind(item), true).await, + suggestion_for(&item.execution_item_display, leaf_kind(item), true), StdioSuggestion::Piped ); } - #[tokio::test] - async fn in_process_leaf_suggests_piped_without_single_node_ancestors() { + #[test] + fn in_process_leaf_suggests_piped_without_single_node_ancestors() { let task = in_process_task("echo"); let item = &task.items[0]; assert_eq!( - suggestion_for(&item.execution_item_display, leaf_kind(item), false).await, + suggestion_for(&item.execution_item_display, leaf_kind(item), false), StdioSuggestion::Piped ); } diff --git a/crates/vite_task/src/session/reporter/mod.rs b/crates/vite_task/src/session/reporter/mod.rs index 23d69b8c..2837eedf 100644 --- a/crates/vite_task/src/session/reporter/mod.rs +++ b/crates/vite_task/src/session/reporter/mod.rs @@ -26,12 +26,11 @@ pub mod summary; // Re-export the concrete implementations so callers can use `reporter::PlainReporter` // and `reporter::LabeledReporterBuilder` without navigating into child modules. -use std::{process::ExitStatus as StdExitStatus, sync::LazyLock}; +use std::{io::Write, process::ExitStatus as StdExitStatus, sync::LazyLock}; pub use labeled::LabeledReporterBuilder; use owo_colors::{Style, Styled}; pub use plain::PlainReporter; -use tokio::io::AsyncWrite; use vite_path::AbsolutePath; use vite_str::Str; use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; @@ -71,7 +70,7 @@ impl ExitStatus { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StdioSuggestion { /// stdin is `/dev/null`, stdout and stderr are piped into the reporter's - /// [`AsyncWrite`] streams. Used when multiple tasks run concurrently and + /// [`Write`](std::io::Write) streams. Used when multiple tasks run concurrently and /// stdio should not be shared. Piped, /// All three file descriptors (stdin, stdout, stderr) are inherited from the @@ -83,17 +82,17 @@ pub enum StdioSuggestion { /// Stdio configuration returned by [`LeafExecutionReporter::start`]. /// /// Contains the reporter's suggestion for the stdio mode together with two -/// async writers that receive the child process's stdout and stderr when the +/// writers that receive the child process's stdout and stderr when the /// execution engine decides to use piped mode. The writers are always provided /// because the engine may override the suggestion (e.g. when caching forces /// piped mode even though the reporter suggested inherited). pub struct StdioConfig { /// The reporter's preferred stdio mode. pub suggestion: StdioSuggestion, - /// Async writer for the child process's stdout (used in piped mode and cache replay). - pub stdout_writer: Box, - /// Async writer for the child process's stderr (used in piped mode and cache replay). - pub stderr_writer: Box, + /// Writer for the child process's stdout (used in piped mode and cache replay). + pub stdout_writer: Box, + /// Writer for the child process's stderr (used in piped mode and cache replay). + pub stderr_writer: Box, } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -113,7 +112,6 @@ pub trait GraphExecutionReporterBuilder { /// /// Creates [`LeafExecutionReporter`] instances for individual leaf executions /// and finalizes the session with `finish()`. -#[async_trait::async_trait(?Send)] pub trait GraphExecutionReporter { /// Create a new leaf execution reporter for the given leaf. /// @@ -136,7 +134,7 @@ pub trait GraphExecutionReporter { /// /// Returns `Ok(())` if all tasks succeeded, or `Err(ExitStatus)` to indicate the /// process should exit with the given status code. - async fn finish(self: Box) -> Result<(), ExitStatus>; + fn finish(self: Box) -> Result<(), ExitStatus>; } /// Reporter for a single leaf execution (one spawned process or in-process command). @@ -145,21 +143,20 @@ pub trait GraphExecutionReporter { /// /// `start()` may not be called before `finish()` if an error occurs before the cache /// status is determined (e.g., cache lookup failure). -#[async_trait::async_trait(?Send)] pub trait LeafExecutionReporter { /// Report that execution is starting with the given cache status. /// /// Called after the cache lookup completes, before any output is produced. /// Returns a [`StdioConfig`] containing: /// - The reporter's stdio mode suggestion (inherited or piped). - /// - Two [`AsyncWrite`] streams for receiving the child's stdout and stderr + /// - Two [`Write`] streams for receiving the child's stdout and stderr /// (used when the execution engine decides on piped mode, or for cache replay). /// /// The execution engine decides the actual stdio mode based on the suggestion /// AND whether caching is enabled — inherited stdio is only used when the /// suggestion is [`StdioSuggestion::Inherited`] AND the task has no cache /// metadata (caching disabled). - async fn start(&mut self, cache_status: CacheStatus) -> StdioConfig; + fn start(&mut self, cache_status: CacheStatus) -> StdioConfig; /// Finalize this leaf execution. /// @@ -169,7 +166,7 @@ pub trait LeafExecutionReporter { /// failure, spawn failure, fingerprint creation failure, cache update failure). /// /// This method consumes the reporter — no further calls are possible after `finish()`. - async fn finish( + fn finish( self: Box, status: Option, cache_update_status: CacheUpdateStatus, diff --git a/crates/vite_task/src/session/reporter/plain.rs b/crates/vite_task/src/session/reporter/plain.rs index 94e74ee0..860e3817 100644 --- a/crates/vite_task/src/session/reporter/plain.rs +++ b/crates/vite_task/src/session/reporter/plain.rs @@ -3,7 +3,7 @@ //! Used for synthetic executions (e.g., auto-install) where there is no execution graph //! and no summary is needed. Writes directly to the provided writer with no shared state. -use tokio::io::{AsyncWrite, AsyncWriteExt as _}; +use std::io::Write; use super::{ LeafExecutionReporter, StdioConfig, StdioSuggestion, format_cache_hit_message, @@ -15,7 +15,7 @@ use crate::session::event::{CacheStatus, CacheUpdateStatus, ExecutionError}; /// (e.g., `execute_synthetic`). /// /// This reporter: -/// - Writes display output (errors, cache-hit messages) to the provided async writer +/// - Writes display output (errors, cache-hit messages) to the provided writer /// - Has no display info (synthetic executions have no task display) /// - Does not track stats or print summaries /// - Supports `silent_if_cache_hit` to suppress output for cached executions @@ -23,8 +23,8 @@ use crate::session::event::{CacheStatus, CacheUpdateStatus, ExecutionError}; /// The exit status is determined by the caller from the `execute_spawn` return value, /// not from the reporter. pub struct PlainReporter { - /// Async writer for reporter display output (errors, cache-hit messages). - writer: Box, + /// Writer for reporter display output (errors, cache-hit messages). + writer: Box, /// When true, suppresses all output (command line, process output, cache hit message) /// for executions that are cache hits. silent_if_cache_hit: bool, @@ -36,8 +36,8 @@ impl PlainReporter { /// Create a new plain reporter. /// /// - `silent_if_cache_hit`: If true, suppress all output when the execution is a cache hit. - /// - `writer`: Async writer for reporter display output. - pub fn new(silent_if_cache_hit: bool, writer: Box) -> Self { + /// - `writer`: Writer for reporter display output. + pub fn new(silent_if_cache_hit: bool, writer: Box) -> Self { Self { writer, silent_if_cache_hit, is_cache_hit: false } } @@ -47,9 +47,8 @@ impl PlainReporter { } } -#[async_trait::async_trait(?Send)] impl LeafExecutionReporter for PlainReporter { - async fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { + fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { self.is_cache_hit = matches!(cache_status, CacheStatus::Hit { .. }); // PlainReporter is used for single-leaf synthetic executions (e.g., auto-install). // Always suggest inherited stdio so the spawned process can be interactive. @@ -63,19 +62,19 @@ impl LeafExecutionReporter for PlainReporter { if self.silent_if_cache_hit && self.is_cache_hit { StdioConfig { suggestion: StdioSuggestion::Inherited, - stdout_writer: Box::new(tokio::io::sink()), - stderr_writer: Box::new(tokio::io::sink()), + stdout_writer: Box::new(std::io::sink()), + stderr_writer: Box::new(std::io::sink()), } } else { StdioConfig { suggestion: StdioSuggestion::Inherited, - stdout_writer: Box::new(tokio::io::stdout()), - stderr_writer: Box::new(tokio::io::stderr()), + stdout_writer: Box::new(std::io::stdout()), + stderr_writer: Box::new(std::io::stderr()), } } } - async fn finish( + fn finish( mut self: Box, _status: Option, _cache_update_status: CacheUpdateStatus, @@ -85,16 +84,16 @@ impl LeafExecutionReporter for PlainReporter { if let Some(error) = error { let message = vite_str::format!("{:#}", anyhow::Error::from(error)); let line = format_error_message(&message); - let _ = self.writer.write_all(line.as_bytes()).await; - let _ = self.writer.flush().await; + let _ = self.writer.write_all(line.as_bytes()); + let _ = self.writer.flush(); return; } // For cache hits, print the "cache hit" message (unless silent) if self.is_cache_hit && !self.is_silent() { let line = format_cache_hit_message(); - let _ = self.writer.write_all(line.as_bytes()).await; - let _ = self.writer.flush().await; + let _ = self.writer.write_all(line.as_bytes()); + let _ = self.writer.flush(); } } } @@ -104,19 +103,19 @@ mod tests { use super::*; use crate::session::event::CacheDisabledReason; - #[tokio::test] - async fn plain_reporter_always_suggests_inherited() { - let mut reporter = PlainReporter::new(false, Box::new(tokio::io::sink())); + #[test] + fn plain_reporter_always_suggests_inherited() { + let mut reporter = PlainReporter::new(false, Box::new(std::io::sink())); let stdio_config = - reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)).await; + reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); assert_eq!(stdio_config.suggestion, StdioSuggestion::Inherited); } - #[tokio::test] - async fn plain_reporter_suggests_inherited_even_when_silent() { - let mut reporter = PlainReporter::new(true, Box::new(tokio::io::sink())); + #[test] + fn plain_reporter_suggests_inherited_even_when_silent() { + let mut reporter = PlainReporter::new(true, Box::new(std::io::sink())); let stdio_config = - reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)).await; + reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); assert_eq!(stdio_config.suggestion, StdioSuggestion::Inherited); } } From 37a257f51a9286ac91063b670786d0cb6f24741c Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 18 Mar 2026 19:46:13 +0800 Subject: [PATCH 2/3] fix: remove redundant clone caught by clippy Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/vite_task/src/session/reporter/labeled.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_task/src/session/reporter/labeled.rs b/crates/vite_task/src/session/reporter/labeled.rs index ab24339a..2b51bd1a 100644 --- a/crates/vite_task/src/session/reporter/labeled.rs +++ b/crates/vite_task/src/session/reporter/labeled.rs @@ -261,7 +261,7 @@ impl LeafExecutionReporter for LabeledLeafReporter { let task_summary = TaskSummary { package_name: display.task_display.package_name.clone(), task_name: display.task_display.task_name.clone(), - command: display.command.clone(), + command: display.command, cwd: cwd_relative, result: TaskResult::from_execution( &cache_status, From f8eefc23c76b973ddd597f44845fb5f6e0ad2bef Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 18 Mar 2026 19:54:12 +0800 Subject: [PATCH 3/3] fix: remove redundant explicit link target in rustdoc Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/vite_task/src/session/reporter/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_task/src/session/reporter/mod.rs b/crates/vite_task/src/session/reporter/mod.rs index 2837eedf..b61c2cb4 100644 --- a/crates/vite_task/src/session/reporter/mod.rs +++ b/crates/vite_task/src/session/reporter/mod.rs @@ -70,7 +70,7 @@ impl ExitStatus { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StdioSuggestion { /// stdin is `/dev/null`, stdout and stderr are piped into the reporter's - /// [`Write`](std::io::Write) streams. Used when multiple tasks run concurrently and + /// [`Write`] streams. Used when multiple tasks run concurrently and /// stdio should not be shared. Piped, /// All three file descriptors (stdin, stdout, stderr) are inherited from the