@@ -9,7 +9,7 @@ use std::{
99
1010use semver:: { Version , VersionReq } ;
1111use 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 } ;
1313use vite_error:: Error ;
1414use vite_path:: { AbsolutePath , AbsolutePathBuf } ;
1515use 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`
325330async 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.
397411async 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