From 7897b7f82df13729672c4b12dbb7934ed47a024a Mon Sep 17 00:00:00 2001 From: Alexander Mills Date: Sat, 8 Aug 2026 12:33:14 -0500 Subject: [PATCH] refactor: move remaining local locks behind zed-lock Semantically rebase the six-file DEN-2076 migration onto main@0ecd5b00cdd4c5df66824bd9ffdeb079b592a651 while preserving the independently verified product blobs and the non-overlapping publish-ignore work from #247. --- Cargo.lock | 1 - Cargo.toml | 1 - src/auth.rs | 11 ++++++++--- src/global.rs | 30 +++++++++++------------------- src/install_graph.rs | 38 +++++++++++++++----------------------- src/managed_install.rs | 24 +++++++++++------------- 6 files changed, 45 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c7979be..64ad0bf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2034,7 +2034,6 @@ dependencies = [ "dirs", "flags2env", "flate2", - "fs2", "globset", "hex", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index bcfec479..3acb147e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,5 @@ tempfile = "3" toml = "1" uuid = { version = "1", features = ["serde", "v4"] } walkdir = "2" -fs2 = "0.4.3" flags2env = { git = "https://github.com/ORESoftware/flags-2-env.git", rev = "8c8465561075a8d7ebb58074b3a8087138e97d8f" } zip = { version = "8.6.0", default-features = false, features = ["deflate"] } diff --git a/src/auth.rs b/src/auth.rs index 2b8e508a..3a28bdb2 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -13,11 +13,11 @@ use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{Context, Result, anyhow, bail}; -use fs2::FileExt as _; use reqwest::StatusCode; use reqwest::blocking::{Client, Response}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use zed_lock::{LockClass, LockManager, LockRequest}; use crate::cli::AuthProvider; use crate::config::Config; @@ -827,8 +827,13 @@ fn with_locked_store( ) -> Result { secure_create_dir(&auth_dir(home))?; let lock_path = auth_dir(home).join(".lock"); - let lock = secure_open(&lock_path, false)?; - lock.lock_exclusive() + let _lock = LockManager::global() + .acquire_blocking( + LockRequest::exclusive(&lock_path) + .operation("auth session store") + .class(LockClass::Custom(5)) + .queue_same_process(), + ) .with_context(|| format!("locking {}", lock_path.display()))?; let mut store = load_store(home)?; let result = operation(&mut store); diff --git a/src/global.rs b/src/global.rs index c70d7412..313562c7 100644 --- a/src/global.rs +++ b/src/global.rs @@ -17,12 +17,12 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result, anyhow, bail}; use clap::{Args, Parser, Subcommand}; -use fs2::FileExt; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use zed_interfaces::lockfile::Lockfile; use zed_interfaces::manifest::is_slug; use zed_interfaces::paths::{BIN_DIR, LOCKFILE_FILE, MODULES_DIR}; +use zed_lock::{LockClass, LockGuard, LockManager, LockRequest}; use crate::cli::{Adapter, Globals, InstallMode}; use crate::config::Config; @@ -188,14 +188,6 @@ struct StagedExecutable { desired: DesiredBin, } -struct GlobalLock(fs::File); - -impl Drop for GlobalLock { - fn drop(&mut self) { - let _ = FileExt::unlock(&self.0); - } -} - /// Route the modular global-package command before the typed project command /// parser. The canonical form is `zed global install`; the npm/cargo-style /// `zed install --global` spelling is rewritten to the same parser and codepath. @@ -366,18 +358,18 @@ fn global_flag(args: &[OsString]) -> Option { .position(|value| value == OsStr::new("--global")) } -fn acquire_lock(cfg: &Config) -> Result { +fn acquire_lock(cfg: &Config) -> Result { let root = global_root(cfg); fs::create_dir_all(&root)?; - let file = fs::OpenOptions::new() - .create(true) - .truncate(false) - .read(true) - .write(true) - .open(root.join(LOCK_FILE))?; - file.lock_exclusive() - .context("locking global package profiles")?; - Ok(GlobalLock(file)) + let lock_path = root.join(LOCK_FILE); + LockManager::global() + .acquire_blocking( + LockRequest::exclusive(&lock_path) + .operation("global package profiles") + .class(LockClass::Custom(5)) + .queue_same_process(), + ) + .context("locking global package profiles") } fn global_root(cfg: &Config) -> PathBuf { diff --git a/src/install_graph.rs b/src/install_graph.rs index 7ec9e6e7..a0898bbd 100644 --- a/src/install_graph.rs +++ b/src/install_graph.rs @@ -15,13 +15,13 @@ use std::sync::{Arc, Condvar, Mutex, MutexGuard, mpsc}; use std::thread::{self, JoinHandle}; use anyhow::{Context, Result, bail}; -use fs2::FileExt; use globset::Glob; use zed_interfaces::lockfile::Lockfile; use zed_interfaces::manifest::{Manifest, is_slug}; use zed_interfaces::paths::{LOCKFILE_FILE, MANIFEST_FILE}; use zed_interfaces::registry::VersionMetadata; use zed_interfaces::version::{self, Requirement}; +use zed_lock::{LockClass, LockGuard, LockManager, LockRequest}; use crate::config::{Config, read_manifest}; use crate::pack::sha256_file; @@ -187,36 +187,28 @@ impl FetchPool { } } -/// A process lock for one content hash. `lock_exclusive` blocks in the kernel; -/// it does not wake up periodically to retry. The file descriptor owns the -/// lock, so process exit or panic releases it automatically. +/// A process lock for one content hash. `zed-lock` issues one blocking native +/// request; it does not wake periodically to retry. The returned guard owns the +/// descriptor/handle, so process exit or panic releases it automatically. struct ArtifactProcessLock { - file: fs::File, + _guard: LockGuard, } impl ArtifactProcessLock { fn acquire(home: &Path, sha256: &str) -> Result { require_sha256(sha256)?; let path = home.join("locks").join(format!("artifact-{sha256}.lock")); - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - let file = fs::OpenOptions::new() - .create(true) - .read(true) - .write(true) - .truncate(false) - .open(&path) - .with_context(|| format!("opening artifact lock {}", path.display()))?; - FileExt::lock_exclusive(&file) + let guard = LockManager::global() + .acquire_blocking( + LockRequest::exclusive(&path) + .operation(format!("artifact preparation {sha256}")) + // This outer download/publication guard remains held while + // Store::add_artifact acquires its rank-30 extraction lock. + .class(LockClass::Custom(25)) + .queue_same_process(), + ) .with_context(|| format!("waiting for artifact lock {}", path.display()))?; - Ok(Self { file }) - } -} - -impl Drop for ArtifactProcessLock { - fn drop(&mut self) { - let _ = FileExt::unlock(&self.file); + Ok(Self { _guard: guard }) } } diff --git a/src/managed_install.rs b/src/managed_install.rs index 8503a117..e394fd51 100644 --- a/src/managed_install.rs +++ b/src/managed_install.rs @@ -9,12 +9,11 @@ //! restored) rather than leaving a half-adopted project. use std::collections::BTreeMap; -use std::fs::{self, File, OpenOptions}; +use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; use anyhow::{Context, Result, bail}; -use fs2::FileExt; use sha2::{Digest, Sha256}; use tempfile::NamedTempFile; use walkdir::{DirEntry, WalkDir}; @@ -24,6 +23,7 @@ use zed_interfaces::manifest::{ use zed_interfaces::paths::{LOCKFILE_FILE, MANIFEST_FILE}; use zed_interfaces::vcs::Vcs; use zed_interfaces::version::{self, VersionScheme}; +use zed_lock::{LockClass, LockGuard, LockManager, LockRequest}; use crate::cli::{Adapter, InstallMode}; use crate::config::{self, Config}; @@ -542,7 +542,7 @@ fn slugify(value: &str) -> String { slug.trim_matches('-').to_string() } -fn lock_project_manifest(cfg: &Config, project: &Path) -> Result { +fn lock_project_manifest(cfg: &Config, project: &Path) -> Result { let canonical = fs::canonicalize(project).unwrap_or_else(|_| project.to_path_buf()); let mut hasher = Sha256::new(); hasher.update(canonical.to_string_lossy().as_bytes()); @@ -551,16 +551,14 @@ fn lock_project_manifest(cfg: &Config, project: &Path) -> Result { fs::create_dir_all(&lock_dir) .with_context(|| format!("creating project lock directory {}", lock_dir.display()))?; let lock_path = lock_dir.join(format!("{key}.manifest.lock")); - let file = OpenOptions::new() - .create(true) - .truncate(false) - .read(true) - .write(true) - .open(&lock_path) - .with_context(|| format!("opening project manifest lock {}", lock_path.display()))?; - file.lock_exclusive() - .with_context(|| format!("locking project manifest {}", project.display()))?; - Ok(file) + LockManager::global() + .acquire_blocking( + LockRequest::exclusive(&lock_path) + .operation(format!("project manifest {}", project.display())) + .class(LockClass::Custom(5)) + .queue_same_process(), + ) + .with_context(|| format!("locking project manifest {}", project.display())) } fn temporary_with_contents(path: &Path, contents: &[u8]) -> Result {