Skip to content

Commit e4e2643

Browse files
branchseerclaude
andcommitted
fix(session): make cache lazily loaded to avoid SQLite race condition
When multiple parallel processes (e.g., `pnpm build` running multiple `vite lib` commands simultaneously) start at the same time, they can all try to create the SQLite cache database concurrently, causing: "table spawn_fingerprint_cache already exists" The fix makes the cache lazily initialized using `once_cell::OnceCell`, so it's only created when first accessed by commands that actually need caching. Commands like `vite lib` that don't use caching will no longer trigger cache initialization. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6622ea5 commit e4e2643

4 files changed

Lines changed: 39 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vite_task/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ derive_more = { workspace = true, features = ["from"] }
2121
diff-struct = { workspace = true }
2222
fspy = { workspace = true }
2323
futures-util = { workspace = true }
24+
once_cell = "1.19"
2425
owo-colors = { workspace = true }
2526
petgraph = { workspace = true }
2627
rayon = { workspace = true }

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,24 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
339339
plan: ExecutionPlan,
340340
mut reporter: Box<dyn Reporter>,
341341
) -> Result<(), ExitStatus> {
342+
// Lazily initialize the cache on first execution
343+
let cache = match self.cache() {
344+
Ok(cache) => cache,
345+
Err(err) => {
346+
reporter.handle_event(ExecutionEvent {
347+
execution_id: ExecutionId::zero(),
348+
kind: ExecutionEventKind::Error {
349+
message: format!("Failed to initialize cache: {err}"),
350+
},
351+
});
352+
return Err(ExitStatus(1));
353+
}
354+
};
355+
342356
let mut execution_context = ExecutionContext {
343357
event_handler: &mut *reporter,
344358
current_execution_id: ExecutionId::zero(),
345-
cache: &self.cache,
359+
cache,
346360
cache_base_path: &self.workspace_path,
347361
};
348362

crates/vite_task/src/session/mod.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use cache::ExecutionCache;
1010
pub use cache::{CacheMiss, FingerprintMismatch};
1111
use clap::{Parser, Subcommand};
1212
pub use event::ExecutionEvent;
13+
use once_cell::sync::OnceCell;
1314
pub use reporter::{LabeledReporter, Reporter};
1415
use vite_path::{AbsolutePath, AbsolutePathBuf};
1516
use vite_str::Str;
@@ -142,7 +143,10 @@ pub struct Session<'a, CustomSubcommand> {
142143

143144
plan_request_parser: PlanRequestParser<'a, CustomSubcommand>,
144145

145-
cache: ExecutionCache,
146+
/// Cache is lazily initialized to avoid SQLite race conditions when multiple
147+
/// processes (e.g., parallel `vite lib` commands) start simultaneously.
148+
cache: OnceCell<ExecutionCache>,
149+
cache_path: AbsolutePathBuf,
146150
}
147151

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

184-
if !cache_path.as_path().exists()
185-
&& let Some(cache_dir) = cache_path.as_path().parent()
186-
{
187-
tracing::info!("Creating task cache directory at {}", cache_dir.display());
188-
std::fs::create_dir_all(cache_dir)?;
189-
}
190-
let cache = ExecutionCache::load_from_path(cache_path)?;
188+
// Cache is lazily initialized on first access to avoid SQLite race conditions
191189
Ok(Self {
192190
workspace_path: Arc::clone(&workspace_root.path),
193191
lazy_task_graph: LazyTaskGraph::Uninitialized {
@@ -197,12 +195,25 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
197195
envs: Arc::new(envs),
198196
cwd,
199197
plan_request_parser: PlanRequestParser { task_synthesizer: callbacks.task_synthesizer },
200-
cache,
198+
cache: OnceCell::new(),
199+
cache_path,
201200
})
202201
}
203202

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

208219
pub fn workspace_path(&self) -> Arc<AbsolutePath> {

0 commit comments

Comments
 (0)