Skip to content

Commit 6e1ad6d

Browse files
fengmk2claude
andcommitted
fix(package-manager): add thread synchronization to prevent race conditions
- Add global mutex lock using OnceLock for file system operations - Protect critical sections in download_package_manager function - Add test-specific lock for temp directory creation - Fix "Directory not empty" errors during concurrent test execution The synchronization ensures concurrent calls won't interfere when: - Checking if files exist - Removing old directories - Renaming temporary directories - Creating shim files 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 73cab43 commit 6e1ad6d

1 file changed

Lines changed: 32 additions & 9 deletions

File tree

crates/vite_package_manager/src/package_manager.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use std::{
44
fs::{self, File},
55
io::{BufReader, Seek, SeekFrom},
66
path::Path,
7+
sync::OnceLock,
78
};
89

910
use semver::{Version, VersionReq};
1011
use serde::{Deserialize, Serialize};
11-
use tokio::fs::remove_dir_all;
12+
use tokio::{fs::remove_dir_all, sync::Mutex};
1213
use vite_error::Error;
1314
use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf};
1415
use vite_str::Str;
@@ -19,6 +20,13 @@ use crate::{
1920
shim,
2021
};
2122

23+
// Global lock for file system operations to prevent race conditions
24+
static FS_OPERATION_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
25+
26+
fn get_fs_lock() -> &'static Mutex<()> {
27+
FS_OPERATION_LOCK.get_or_init(|| Mutex::new(()))
28+
}
29+
2230
#[derive(Serialize, Deserialize, Clone, Default)]
2331
#[serde(rename_all = "camelCase")]
2432
struct PackageJson {
@@ -440,14 +448,21 @@ async fn download_package_manager(
440448
return Ok(install_dir);
441449
}
442450

443-
// rename $target_dir_tmp to $target_dir
444-
tracing::debug!("Rename {:?} to {:?}", target_dir_tmp, target_dir);
445-
remove_dir_all_force(&target_dir).await?;
446-
tokio::fs::rename(&target_dir_tmp, &target_dir).await?;
451+
{
452+
// Acquire lock for critical file system operations
453+
let _lock = get_fs_lock().lock().await;
454+
455+
// rename $target_dir_tmp to $target_dir
456+
tracing::debug!("Rename {:?} to {:?}", target_dir_tmp, target_dir);
457+
remove_dir_all_force(&target_dir).await?;
458+
tokio::fs::rename(&target_dir_tmp, &target_dir).await?;
447459

448-
// create shim file
449-
tracing::debug!("Create shim files for {}", bin_name);
450-
create_shim_files(package_manager_type, &bin_prefix).await?;
460+
// create shim file
461+
tracing::debug!("Create shim files for {}", bin_name);
462+
create_shim_files(package_manager_type, &bin_prefix).await?;
463+
464+
// Lock is automatically released when _lock goes out of scope
465+
}
451466

452467
Ok(install_dir)
453468
}
@@ -555,13 +570,21 @@ fn format_path_env(bin_prefix: impl AsRef<Path>) -> String {
555570

556571
#[cfg(test)]
557572
mod tests {
558-
use std::fs;
573+
use std::{fs, sync::Mutex as StdMutex};
559574

560575
use tempfile::{TempDir, tempdir};
561576

562577
use super::*;
563578

579+
// Test-specific lock for temp directory creation
580+
static TEST_TEMP_DIR_LOCK: OnceLock<StdMutex<()>> = OnceLock::new();
581+
582+
fn get_test_lock() -> &'static StdMutex<()> {
583+
TEST_TEMP_DIR_LOCK.get_or_init(|| StdMutex::new(()))
584+
}
585+
564586
fn create_temp_dir() -> TempDir {
587+
let _lock = get_test_lock().lock().unwrap();
565588
tempdir().expect("Failed to create temp directory")
566589
}
567590

0 commit comments

Comments
 (0)