diff --git a/helix-view/src/session/mod.rs b/helix-view/src/session/mod.rs index 49c686e935b79..0da2f8d89ce20 100644 --- a/helix-view/src/session/mod.rs +++ b/helix-view/src/session/mod.rs @@ -7,12 +7,14 @@ use std::{ }; use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; use sha1_smol::Sha1; +// Needs to mimic borrowing rules. +// Allow multiple read-only references, and only one mutable reference w/ no read-only. +// Should not lock unless actively used. And should be unlocked automatically when all file handles are dropped. pub struct Session { path: PathBuf, - guard: Option, + lock: Option, } impl Session { @@ -21,16 +23,30 @@ impl Session { let bytes = sys::path_as_bytes(path.as_path()); let hash = Sha1::from(bytes).digest().to_string(); let path = helix_loader::cache_dir().join("sessions").join(hash); - Ok(Self { path, guard: None }) + Ok(Self { path, lock: None }) } - // TODO: Return a FileLockGuard instead. pub fn get(&mut self, filename: String) -> Result { - if self.guard.is_none() { - let lock = FileLock::new(self.path.join(".helix.lock"))?; + if self.lock.is_none() { + let lock = FileLock::shared(self.path.join(".helix.lock"))?; + lock.lock()?; + + self.lock = Some(lock); + } + + OpenOptions::new() + .read(true) + .open(self.path.join(filename)) + .context("failed to open file") + } + + // TODO: Return a FileLockGuard instead. + pub fn get_mut(&mut self, filename: String) -> Result { + if self.lock.is_none() { + let lock = FileLock::exclusive(self.path.join(".helix.lock"))?; lock.lock()?; - self.guard = Some(lock); + self.lock = Some(lock); } OpenOptions::new() @@ -44,21 +60,34 @@ impl Session { pub struct FileLock { file: File, + shared: bool, } impl FileLock { - pub fn new(path: PathBuf) -> Result { + pub fn exclusive(path: PathBuf) -> Result { + let file = Self::open_lock(path)?; + Ok(Self { + file, + shared: false, + }) + } + + pub fn shared(path: PathBuf) -> Result { + let file = Self::open_lock(path)?; + Ok(Self { file, shared: true }) + } + + pub fn lock(&self) -> Result<()> { + sys::lock(&self.file, self.shared) + } + + fn open_lock(path: PathBuf) -> std::io::Result { if let Some(parent) = path.parent() { if !parent.exists() { std::fs::DirBuilder::new().recursive(true).create(parent)?; } } - let file = OpenOptions::new().write(true).create(true).open(path)?; - Ok(Self { file }) - } - - pub fn lock(&self) -> Result<()> { - sys::lock(&self.file) + OpenOptions::new().write(true).create(true).open(path) } } @@ -145,17 +174,11 @@ mod sys { } /// Blocks until the lock is acquired. - pub(super) fn lock(file: &File) -> anyhow::Result<()> { + pub(super) fn lock(file: &File, shared: bool) -> anyhow::Result<()> { + let flag = if shared { 0 } else { LOCKFILE_EXCLUSIVE_LOCK }; unsafe { let mut overlapped = std::mem::zeroed(); - let ret = LockFileEx( - file.as_raw_handle(), - LOCKFILE_EXCLUSIVE_LOCK, - 0, - !0, - !0, - &mut overlapped, - ); + let ret = LockFileEx(file.as_raw_handle(), flag, 0, !0, !0, &mut overlapped); if ret == 0 { anyhow::bail!(Error::last_os_error()) } else {