Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/vite_task/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ derive_more = { workspace = true, features = ["from"] }
diff-struct = { workspace = true }
fspy = { workspace = true }
futures-util = { workspace = true }
once_cell = "1.19"
owo-colors = { workspace = true }
petgraph = { workspace = true }
rayon = { workspace = true }
Expand Down
8 changes: 7 additions & 1 deletion crates/vite_task/src/session/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pub mod display;

use std::{fmt::Display, io::Write, sync::Arc, time::Duration};
use std::{fmt::Display, fs::File, io::Write, sync::Arc, time::Duration};

use bincode::{Decode, Encode, decode_from_slice, encode_to_vec};
// Re-export display functions for convenience
Expand Down Expand Up @@ -71,6 +71,11 @@ impl ExecutionCache {
tracing::info!("Creating task cache directory at {:?}", path);
std::fs::create_dir_all(path)?;

// Use file lock to prevent race conditions when multiple processes initialize the database
let lock_path = path.join("db_open.lock");
let lock_file = File::create(lock_path.as_path())?;
lock_file.lock()?;

let db_path = path.join("cache.db");
let conn = Connection::open(db_path.as_path())?;
conn.execute_batch("PRAGMA journal_mode=WAL;")?;
Expand Down Expand Up @@ -101,6 +106,7 @@ impl ExecutionCache {
}
}
}
// Lock is released when lock_file is dropped
Ok(Self { conn: Mutex::new(conn) })
}

Expand Down
24 changes: 19 additions & 5 deletions crates/vite_task/src/session/execute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod spawn;
use std::sync::Arc;

use futures_util::FutureExt;
use petgraph::{algo::toposort, graph::DiGraph};
use petgraph::{algo::toposort, stable_graph::StableGraph};
use vite_path::AbsolutePath;
use vite_task_plan::{
ExecutionItemKind, ExecutionPlan, LeafExecutionKind, SpawnExecution, TaskExecution,
Expand Down Expand Up @@ -46,9 +46,9 @@ impl ExecutionContext<'_> {
) -> Result<(), ExecutionAborted> {
match item_kind {
ExecutionItemKind::Expanded(graph) => {
// clone for reversing edges and removing nodes
let mut graph: DiGraph<&TaskExecution, (), ExecutionIx> =
graph.map(|_, task_execution| task_execution, |_, ()| ());
// Use StableGraph to preserve node indices during removal
let mut graph: StableGraph<&TaskExecution, (), _, ExecutionIx> =
graph.map(|_, task_execution| task_execution, |_, ()| ()).into();

// To be consistent with the package graph in vite_package_manager and the dependency graph definition in Wikipedia
// https://en.wikipedia.org/wiki/Dependency_graph, we construct the graph with edges from dependents to dependencies
Expand Down Expand Up @@ -339,10 +339,24 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
plan: ExecutionPlan,
mut reporter: Box<dyn Reporter>,
) -> Result<(), ExitStatus> {
// Lazily initialize the cache on first execution
let cache = match self.cache() {
Ok(cache) => cache,
Err(err) => {
reporter.handle_event(ExecutionEvent {
execution_id: ExecutionId::zero(),
kind: ExecutionEventKind::Error {
message: format!("Failed to initialize cache: {err}"),
},
});
return Err(ExitStatus(1));
}
};

let mut execution_context = ExecutionContext {
event_handler: &mut *reporter,
current_execution_id: ExecutionId::zero(),
cache: &self.cache,
cache,
cache_base_path: &self.workspace_path,
};

Expand Down
33 changes: 22 additions & 11 deletions crates/vite_task/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use cache::ExecutionCache;
pub use cache::{CacheMiss, FingerprintMismatch};
use clap::{Parser, Subcommand};
pub use event::ExecutionEvent;
use once_cell::sync::OnceCell;
pub use reporter::{LabeledReporter, Reporter};
use vite_path::{AbsolutePath, AbsolutePathBuf};
use vite_str::Str;
Expand Down Expand Up @@ -142,7 +143,10 @@ pub struct Session<'a, CustomSubcommand> {

plan_request_parser: PlanRequestParser<'a, CustomSubcommand>,

cache: ExecutionCache,
/// Cache is lazily initialized to avoid SQLite race conditions when multiple
/// processes (e.g., parallel `vite lib` commands) start simultaneously.
cache: OnceCell<ExecutionCache>,
cache_path: AbsolutePathBuf,
}

fn get_cache_path_of_workspace(workspace_root: &AbsolutePath) -> AbsolutePathBuf {
Expand Down Expand Up @@ -181,13 +185,7 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
let workspace_node_modules_bin = workspace_root.path.join("node_modules").join(".bin");
prepend_path_env(&mut envs, &workspace_node_modules_bin)?;

if !cache_path.as_path().exists()
&& let Some(cache_dir) = cache_path.as_path().parent()
{
tracing::info!("Creating task cache directory at {}", cache_dir.display());
std::fs::create_dir_all(cache_dir)?;
}
let cache = ExecutionCache::load_from_path(cache_path)?;
// Cache is lazily initialized on first access to avoid SQLite race conditions
Ok(Self {
workspace_path: Arc::clone(&workspace_root.path),
lazy_task_graph: LazyTaskGraph::Uninitialized {
Expand All @@ -197,12 +195,25 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
envs: Arc::new(envs),
cwd,
plan_request_parser: PlanRequestParser { task_synthesizer: callbacks.task_synthesizer },
cache,
cache: OnceCell::new(),
cache_path,
})
}

pub fn cache(&self) -> &ExecutionCache {
&self.cache
/// Lazily initializes and returns the execution cache.
/// The cache is only created when first accessed to avoid SQLite race conditions
/// when multiple processes start simultaneously.
pub fn cache(&self) -> anyhow::Result<&ExecutionCache> {
self.cache.get_or_try_init(|| {
// Create cache directory if needed
if !self.cache_path.as_path().exists() {
if let Some(cache_dir) = self.cache_path.as_path().parent() {
tracing::info!("Creating task cache directory at {}", cache_dir.display());
std::fs::create_dir_all(cache_dir)?;
}
}
ExecutionCache::load_from_path(self.cache_path.clone())
})
}

pub fn workspace_path(&self) -> Arc<AbsolutePath> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"scripts": {
"lint": "vite run a#lint"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "a",
"scripts": {
"lint": "vite lint"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
packages:
- 'packages/*'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[plan]]
name = "synthetic-in-subpackage"
args = ["run", "lint"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
source: crates/vite_task_bin/tests/test_snapshots/main.rs
expression: "&plan_json"
input_file: crates/vite_task_bin/tests/test_snapshots/fixtures/synthetic-in-subpackage
---
{
"root_node": {
"Expanded": [
{
"key": [
"<workspace>/",
"lint"
],
"node": {
"task_display": {
"package_name": "",
"task_name": "lint",
"package_path": "<workspace>/"
},
"items": [
{
"execution_item_display": {
"task_display": {
"package_name": "",
"task_name": "lint",
"package_path": "<workspace>/"
},
"command": "vite run a#lint",
"and_item_index": null,
"cwd": "<workspace>/"
},
"kind": {
"Expanded": [
{
"key": [
"<workspace>/packages/a",
"lint"
],
"node": {
"task_display": {
"package_name": "a",
"task_name": "lint",
"package_path": "<workspace>/packages/a"
},
"items": [
{
"execution_item_display": {
"task_display": {
"package_name": "a",
"task_name": "lint",
"package_path": "<workspace>/packages/a"
},
"command": "vite lint",
"and_item_index": null,
"cwd": "<workspace>/packages/a"
},
"kind": {
"Leaf": {
"Spawn": {
"cache_metadata": {
"spawn_fingerprint": {
"cwd": "packages/a",
"program_fingerprint": {
"OutsideWorkspace": {
"program_name": "oxlint"
}
},
"args": [],
"env_fingerprints": {
"fingerprinted_envs": {},
"pass_through_env_config": [
"<default pass-through envs>"
]
},
"fingerprint_ignores": null
},
"execution_cache_key": {
"kind": {
"UserTask": {
"task_name": "lint",
"and_item_index": 0,
"extra_args": []
}
},
"origin_path": "packages/a"
}
},
"spawn_command": {
"program_path": "<manifest_dir>/test_bins/node_modules/.bin/oxlint",
"args": [],
"all_envs": {
"NO_COLOR": "1",
"PATH": "<workspace>/packages/a/node_modules/.bin:<workspace>/node_modules/.bin:<manifest_dir>/test_bins/node_modules/.bin"
},
"cwd": "<workspace>/packages/a"
}
}
}
}
}
]
},
"neighbors": []
}
]
}
}
]
},
"neighbors": []
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
source: crates/vite_task_bin/tests/test_snapshots/main.rs
expression: task_graph_json
input_file: crates/vite_task_bin/tests/test_snapshots/fixtures/synthetic-in-subpackage
---
[
{
"key": [
"<workspace>/",
"lint"
],
"node": {
"task_display": {
"package_name": "",
"task_name": "lint",
"package_path": "<workspace>/"
},
"resolved_config": {
"command": "vite run a#lint",
"resolved_options": {
"cwd": "<workspace>/",
"cache_config": {
"env_config": {
"fingerprinted_envs": [],
"pass_through_envs": [
"<default pass-through envs>"
]
}
}
}
}
},
"neighbors": []
},
{
"key": [
"<workspace>/packages/a",
"lint"
],
"node": {
"task_display": {
"package_name": "a",
"task_name": "lint",
"package_path": "<workspace>/packages/a"
},
"resolved_config": {
"command": "vite lint",
"resolved_options": {
"cwd": "<workspace>/packages/a",
"cache_config": {
"env_config": {
"fingerprinted_envs": [],
"pass_through_envs": [
"<default pass-through envs>"
]
}
}
}
}
},
"neighbors": []
}
]
4 changes: 0 additions & 4 deletions crates/vite_task_plan/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,6 @@ impl<'a> PlanContext<'a> {
}
}

pub fn cwd(&self) -> &Arc<AbsolutePath> {
&self.cwd
}

pub fn envs(&self) -> &HashMap<Arc<OsStr>, Arc<OsStr>> {
&self.envs
}
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_task_plan/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ async fn plan_task_as_execution_node(
&and_item.envs,
synthetic_plan_request,
Some(task_execution_cache_key),
context.cwd(),
&cwd,
)
.with_plan_context(&context)?;
ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn_execution))
Expand Down