From d7920acd0fdcf0d5ba605bb1358137548698a37d Mon Sep 17 00:00:00 2001 From: Sebastian Ullrich Date: Mon, 25 Nov 2024 08:56:20 +0100 Subject: [PATCH] fix: per-toolchain installation lock (#144) ``` $ elan install nightly info: waiting for previous installation request to finish (/home/sebastian/.elan/toolchains/leanprover--lean4-nightly---nightly-2024-11-23.lock, held by PID 2508083 ) leanprover/lean4-nightly:nightly-2024-11-23 installed ``` --- Cargo.lock | 11 +++++++++++ src/elan-dist/Cargo.toml | 2 +- src/elan-dist/src/lib.rs | 1 + src/elan-dist/src/manifestation.rs | 29 ++++++++++++++++++++++------- src/elan-dist/src/notifications.rs | 5 +++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad06534..018703b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,6 +466,7 @@ dependencies = [ "error-chain", "filetime", "flate2", + "fslock", "itertools", "json", "libc", @@ -623,6 +624,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "futures" version = "0.3.31" diff --git a/src/elan-dist/Cargo.toml b/src/elan-dist/Cargo.toml index 35aba4a..25dcf40 100644 --- a/src/elan-dist/Cargo.toml +++ b/src/elan-dist/Cargo.toml @@ -26,6 +26,7 @@ json = "0.12.4" zip = "0.6" filetime = "0.2.14" time = "0.3" +fslock = "0.2.1" [target."cfg(windows)".dependencies] winapi = { version = "0.3.9", features = ["handleapi", "sysinfoapi", "tlhelp32", "winnt"] } @@ -36,4 +37,3 @@ libc = "0.2.88" [lib] name = "elan_dist" - diff --git a/src/elan-dist/src/lib.rs b/src/elan-dist/src/lib.rs index 1656056..50b1b96 100644 --- a/src/elan-dist/src/lib.rs +++ b/src/elan-dist/src/lib.rs @@ -1,5 +1,6 @@ #![recursion_limit = "1024"] +extern crate fslock; extern crate elan_utils; extern crate flate2; extern crate itertools; diff --git a/src/elan-dist/src/manifestation.rs b/src/elan-dist/src/manifestation.rs index bbb4d62..48f7eb3 100644 --- a/src/elan-dist/src/manifestation.rs +++ b/src/elan-dist/src/manifestation.rs @@ -1,9 +1,12 @@ //! Manifest a particular Lean version by installing it from a distribution server. +use std::{thread::sleep, time::Duration}; + use component::{TarGzPackage, TarZstdPackage, ZipPackage}; use download::DownloadCfg; -use elan_utils::utils; +use elan_utils::{raw::read_file, utils}; use errors::*; +use fslock::LockFile; use notifications::*; use prefix::InstallPrefix; use temp; @@ -25,11 +28,28 @@ impl Manifestation { temp_cfg: &temp::Cfg, notify_handler: &dyn Fn(Notification), ) -> Result<()> { + let prefix = self.prefix.path(); + utils::ensure_dir_exists("toolchains", prefix.parent().unwrap(), &|n| { + (notify_handler)(n.into()) + })?; + + let lockfile_path = prefix.with_extension("lock"); + let mut lockfile = LockFile::open(&lockfile_path)?; + if !lockfile.try_lock_with_pid()? { + notify_handler(Notification::WaitingForFileLock(&lockfile_path, read_file(&lockfile_path)?.trim())); + while !lockfile.try_lock_with_pid()? { + sleep(Duration::from_secs(1)); + } + } let dlcfg = DownloadCfg { temp_cfg: temp_cfg, notify_handler: notify_handler, }; + if utils::is_directory(prefix) { + return Ok(()) + } + // find correct download on HTML page (AAAAH) use regex::Regex; use std::fs; @@ -70,17 +90,11 @@ impl Manifestation { let installer_file = dlcfg.download_and_check(&url)?; - let prefix = self.prefix.path(); - notify_handler(Notification::InstallingComponent(&prefix.to_string_lossy())); // unpack into temporary place, then move atomically to guard against aborts during unpacking let unpack_dir = prefix.with_extension("tmp"); - if utils::is_directory(prefix) { - return Err(format!("'{}' is already installed", prefix.display()).into()); - } - if utils::is_directory(&unpack_dir) { utils::remove_dir("temp toolchain directory", &unpack_dir, &|n| { (notify_handler)(n.into()) @@ -103,6 +117,7 @@ impl Manifestation { } utils::rename_dir("temp toolchain directory", &unpack_dir, prefix)?; + let _ = std::fs::remove_file(&lockfile_path); Ok(()) } diff --git a/src/elan-dist/src/notifications.rs b/src/elan-dist/src/notifications.rs index a379496..f336f0a 100644 --- a/src/elan-dist/src/notifications.rs +++ b/src/elan-dist/src/notifications.rs @@ -31,6 +31,7 @@ pub enum Notification<'a> { DownloadingLegacyManifest, ManifestChecksumFailedHack, NewVersionAvailable(String), + WaitingForFileLock(&'a Path, &'a str), } impl<'a> From> for Notification<'a> { @@ -65,6 +66,7 @@ impl<'a> Notification<'a> { | RollingBack | DownloadingManifest(_) | NewVersionAvailable(_) + | WaitingForFileLock(_, _) | DownloadedManifest(_, _) => NotificationLevel::Info, CantReadUpdateHash(_) | ExtensionNotInstalled(_) @@ -125,6 +127,9 @@ impl<'a> Display for Notification<'a> { "Version {version} of elan is available! Use `elan self update` to update." ) } + WaitingForFileLock(path, pid) => { + write!(f, "waiting for previous installation request to finish ({}, held by PID {})", path.display(), pid) + } } } }