Skip to content

Commit 2888f33

Browse files
committed
fix: lock to ensure atomicity of remove + rename operations
1 parent 4bb4def commit 2888f33

1 file changed

Lines changed: 20 additions & 5 deletions

File tree

crates/vite_install/src/package_manager.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99

1010
use semver::{Version, VersionReq};
1111
use serde::{Deserialize, Serialize};
12-
use tokio::{fs::remove_dir_all, process::Command};
12+
use tokio::{fs::remove_dir_all, process::Command, sync::Mutex as TokioMutex};
1313
use vite_error::Error;
1414
use vite_path::{AbsolutePath, AbsolutePathBuf};
1515
use vite_str::Str;
@@ -320,6 +320,11 @@ async fn get_latest_version(package_manager_type: PackageManagerType) -> Result<
320320
Ok(package_json.version)
321321
}
322322

323+
/// Global lock for atomic remove + rename operations during package manager download.
324+
/// This ensures that multiple concurrent threads won't conflict when
325+
/// installing package managers.
326+
static DOWNLOAD_LOCK: TokioMutex<()> = TokioMutex::const_new(());
327+
323328
/// Download the package manager and extract it to the cache directory.
324329
/// Return the install directory, e.g. $`CACHE_DIR/vite/package_manager/pnpm/10.0.0/pnpm`
325330
async fn download_package_manager(
@@ -374,9 +379,18 @@ async fn download_package_manager(
374379
tracing::debug!("Rename package dir to {}", bin_name);
375380
tokio::fs::rename(&target_dir_tmp.join("package"), &target_dir_tmp.join(&bin_name)).await?;
376381

377-
// check bin_file again, for the concurrent download cases
378-
if is_exists_file(&bin_file)? {
379-
tracing::debug!("bin_file already exists, skip rename");
382+
// Use a global lock to ensure atomicity of remove + rename operations
383+
// This prevents DirectoryNotEmpty exceptions when multiple threads try to install
384+
// the same package manager version concurrently.
385+
let _guard = DOWNLOAD_LOCK.lock().await;
386+
387+
// Check again after acquiring the lock, in case another thread completed
388+
// the installation while we were downloading
389+
if is_exists_file(&bin_file)?
390+
&& is_exists_file(bin_file.with_extension("cmd"))?
391+
&& is_exists_file(bin_file.with_extension("ps1"))?
392+
{
393+
tracing::debug!("bin_file already exists after lock acquisition, skip rename");
380394
return Ok(install_dir);
381395
}
382396

@@ -395,12 +409,13 @@ async fn download_package_manager(
395409
/// Remove the directory and all its contents.
396410
/// Ignore the error if the directory is not found.
397411
async fn remove_dir_all_force(path: impl AsRef<Path>) -> Result<(), std::io::Error> {
398-
match remove_dir_all(path).await {
412+
match remove_dir_all(path.as_ref()).await {
399413
Ok(()) => Ok(()),
400414
Err(e) => {
401415
if e.kind() == std::io::ErrorKind::NotFound {
402416
Ok(())
403417
} else {
418+
tracing::error!("remove_dir_all_force path: {:?} error: {e:?}", path.as_ref());
404419
Err(e)
405420
}
406421
}

0 commit comments

Comments
 (0)