mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-09 17:32:50 +00:00
lock: propagate error from lock(), remove panic from unlock path
This commit is contained in:
parent
6ffd4d4f63
commit
aaf7f33804
@ -1729,7 +1729,10 @@ impl WorkingCopy for LocalWorkingCopy {
|
|||||||
|
|
||||||
fn start_mutation(&self) -> Result<Box<dyn LockedWorkingCopy>, WorkingCopyStateError> {
|
fn start_mutation(&self) -> Result<Box<dyn LockedWorkingCopy>, WorkingCopyStateError> {
|
||||||
let lock_path = self.state_path.join("working_copy.lock");
|
let lock_path = self.state_path.join("working_copy.lock");
|
||||||
let lock = FileLock::lock(lock_path);
|
let lock = FileLock::lock(lock_path).map_err(|err| WorkingCopyStateError {
|
||||||
|
message: "Failed to lock working copy".to_owned(),
|
||||||
|
err: err.into(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let wc = LocalWorkingCopy {
|
let wc = LocalWorkingCopy {
|
||||||
store: self.store.clone(),
|
store: self.store.clone(),
|
||||||
|
@ -19,6 +19,8 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::FileLockError;
|
||||||
|
|
||||||
pub struct FileLock {
|
pub struct FileLock {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
_file: File,
|
_file: File,
|
||||||
@ -56,7 +58,7 @@ impl Iterator for BackoffIterator {
|
|||||||
// Suppress warning on platforms where specialized lock impl is available
|
// Suppress warning on platforms where specialized lock impl is available
|
||||||
#[cfg_attr(unix, allow(dead_code))]
|
#[cfg_attr(unix, allow(dead_code))]
|
||||||
impl FileLock {
|
impl FileLock {
|
||||||
pub fn lock(path: PathBuf) -> FileLock {
|
pub fn lock(path: PathBuf) -> Result<FileLock, FileLockError> {
|
||||||
let mut options = OpenOptions::new();
|
let mut options = OpenOptions::new();
|
||||||
options.create_new(true);
|
options.create_new(true);
|
||||||
options.write(true);
|
options.write(true);
|
||||||
@ -64,7 +66,7 @@ impl FileLock {
|
|||||||
loop {
|
loop {
|
||||||
match options.open(&path) {
|
match options.open(&path) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
return FileLock { path, _file: file };
|
return Ok(FileLock { path, _file: file });
|
||||||
}
|
}
|
||||||
Err(err)
|
Err(err)
|
||||||
if err.kind() == std::io::ErrorKind::AlreadyExists
|
if err.kind() == std::io::ErrorKind::AlreadyExists
|
||||||
@ -74,15 +76,19 @@ impl FileLock {
|
|||||||
if let Some(duration) = backoff_iterator.next() {
|
if let Some(duration) = backoff_iterator.next() {
|
||||||
std::thread::sleep(duration);
|
std::thread::sleep(duration);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
return Err(FileLockError {
|
||||||
"Timed out while trying to create lock file {}: {}",
|
message: "Timed out while trying to create lock file",
|
||||||
path.display(),
|
path,
|
||||||
err
|
err,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
panic!("Failed to create lock file {}: {}", path.display(), err);
|
return Err(FileLockError {
|
||||||
|
message: "Failed to create lock file",
|
||||||
|
path,
|
||||||
|
err,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,6 +98,8 @@ impl FileLock {
|
|||||||
impl Drop for FileLock {
|
impl Drop for FileLock {
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
std::fs::remove_file(&self.path).expect("Failed to delete lock file");
|
std::fs::remove_file(&self.path)
|
||||||
|
.inspect_err(|err| tracing::warn!(?err, ?self.path, "Failed to delete lock file"))
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,29 @@ mod fallback;
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod unix;
|
mod unix;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub use self::fallback::FileLock;
|
pub use self::fallback::FileLock;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub use self::unix::FileLock;
|
pub use self::unix::FileLock;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("{message}: {path}")]
|
||||||
|
pub struct FileLockError {
|
||||||
|
pub message: &'static str,
|
||||||
|
pub path: PathBuf,
|
||||||
|
#[source]
|
||||||
|
pub err: io::Error,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -37,12 +50,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test_case(FileLock::lock)]
|
#[test_case(FileLock::lock)]
|
||||||
#[cfg_attr(unix, test_case(fallback::FileLock::lock))]
|
#[cfg_attr(unix, test_case(fallback::FileLock::lock))]
|
||||||
fn lock_basic<T>(lock_fn: fn(PathBuf) -> T) {
|
fn lock_basic<T>(lock_fn: fn(PathBuf) -> Result<T, FileLockError>) {
|
||||||
let temp_dir = testutils::new_temp_dir();
|
let temp_dir = testutils::new_temp_dir();
|
||||||
let lock_path = temp_dir.path().join("test.lock");
|
let lock_path = temp_dir.path().join("test.lock");
|
||||||
assert!(!lock_path.exists());
|
assert!(!lock_path.exists());
|
||||||
{
|
{
|
||||||
let _lock = lock_fn(lock_path.clone());
|
let _lock = lock_fn(lock_path.clone()).unwrap();
|
||||||
assert!(lock_path.exists());
|
assert!(lock_path.exists());
|
||||||
}
|
}
|
||||||
assert!(!lock_path.exists());
|
assert!(!lock_path.exists());
|
||||||
@ -50,7 +63,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test_case(FileLock::lock)]
|
#[test_case(FileLock::lock)]
|
||||||
#[cfg_attr(unix, test_case(fallback::FileLock::lock))]
|
#[cfg_attr(unix, test_case(fallback::FileLock::lock))]
|
||||||
fn lock_concurrent<T>(lock_fn: fn(PathBuf) -> T) {
|
fn lock_concurrent<T>(lock_fn: fn(PathBuf) -> Result<T, FileLockError>) {
|
||||||
let temp_dir = testutils::new_temp_dir();
|
let temp_dir = testutils::new_temp_dir();
|
||||||
let data_path = temp_dir.path().join("test");
|
let data_path = temp_dir.path().join("test");
|
||||||
let lock_path = temp_dir.path().join("test.lock");
|
let lock_path = temp_dir.path().join("test.lock");
|
||||||
@ -59,7 +72,7 @@ mod tests {
|
|||||||
thread::scope(|s| {
|
thread::scope(|s| {
|
||||||
for _ in 0..num_threads {
|
for _ in 0..num_threads {
|
||||||
s.spawn(|| {
|
s.spawn(|| {
|
||||||
let _lock = lock_fn(lock_path.clone());
|
let _lock = lock_fn(lock_path.clone()).unwrap();
|
||||||
let data = fs::read(&data_path).unwrap();
|
let data = fs::read(&data_path).unwrap();
|
||||||
let value = u32::from_le_bytes(data.try_into().unwrap());
|
let value = u32::from_le_bytes(data.try_into().unwrap());
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
@ -20,21 +20,36 @@ use std::path::PathBuf;
|
|||||||
use rustix::fs::FlockOperation;
|
use rustix::fs::FlockOperation;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::FileLockError;
|
||||||
|
|
||||||
pub struct FileLock {
|
pub struct FileLock {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileLock {
|
impl FileLock {
|
||||||
pub fn lock(path: PathBuf) -> FileLock {
|
pub fn lock(path: PathBuf) -> Result<FileLock, FileLockError> {
|
||||||
loop {
|
loop {
|
||||||
// Create lockfile, or open pre-existing one
|
// Create lockfile, or open pre-existing one
|
||||||
let file = File::create(&path).expect("failed to open lockfile");
|
let file = File::create(&path).map_err(|err| FileLockError {
|
||||||
|
message: "Failed to open lock file",
|
||||||
|
path: path.clone(),
|
||||||
|
err,
|
||||||
|
})?;
|
||||||
// If the lock was already held, wait for it to be released
|
// If the lock was already held, wait for it to be released
|
||||||
rustix::fs::flock(&file, FlockOperation::LockExclusive)
|
rustix::fs::flock(&file, FlockOperation::LockExclusive).map_err(|errno| {
|
||||||
.expect("failed to lock lockfile");
|
FileLockError {
|
||||||
|
message: "Failed to lock lock file",
|
||||||
|
path: path.clone(),
|
||||||
|
err: errno.into(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
let stat = rustix::fs::fstat(&file).expect("failed to stat lockfile");
|
let stat = rustix::fs::fstat(&file).map_err(|errno| FileLockError {
|
||||||
|
message: "failed to stat lock file",
|
||||||
|
path: path.clone(),
|
||||||
|
err: errno.into(),
|
||||||
|
})?;
|
||||||
if stat.st_nlink == 0 {
|
if stat.st_nlink == 0 {
|
||||||
// Lockfile was deleted, probably by the previous holder's `Drop` impl; create a
|
// Lockfile was deleted, probably by the previous holder's `Drop` impl; create a
|
||||||
// new one so our ownership is visible, rather than hidden in an
|
// new one so our ownership is visible, rather than hidden in an
|
||||||
@ -43,7 +58,7 @@ impl FileLock {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Self { path, file };
|
return Ok(Self { path, file });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ pub enum OpHeadsStoreError {
|
|||||||
new_op_id: OperationId,
|
new_op_id: OperationId,
|
||||||
source: Box<dyn std::error::Error + Send + Sync>,
|
source: Box<dyn std::error::Error + Send + Sync>,
|
||||||
},
|
},
|
||||||
|
#[error("Failed to lock operation heads store")]
|
||||||
|
Lock(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@ -68,7 +70,7 @@ pub trait OpHeadsStore: Send + Sync + Debug {
|
|||||||
/// is to prevent concurrent processes from resolving the same divergent
|
/// is to prevent concurrent processes from resolving the same divergent
|
||||||
/// operations. It is not needed for correctness; implementations are free
|
/// operations. It is not needed for correctness; implementations are free
|
||||||
/// to return a type that doesn't hold a lock.
|
/// to return a type that doesn't hold a lock.
|
||||||
fn lock(&self) -> Box<dyn OpHeadsStoreLock + '_>;
|
fn lock(&self) -> Result<Box<dyn OpHeadsStoreLock + '_>, OpHeadsStoreError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given an OpHeadsStore, fetch and resolve its op heads down to one under a
|
// Given an OpHeadsStore, fetch and resolve its op heads down to one under a
|
||||||
@ -102,7 +104,7 @@ where
|
|||||||
// Note that the locking isn't necessary for correctness of merge; we take
|
// Note that the locking isn't necessary for correctness of merge; we take
|
||||||
// the lock only to prevent other concurrent processes from doing the same
|
// the lock only to prevent other concurrent processes from doing the same
|
||||||
// work (and producing another set of divergent heads).
|
// work (and producing another set of divergent heads).
|
||||||
let _lock = op_heads_store.lock();
|
let _lock = op_heads_store.lock()?;
|
||||||
let op_head_ids = op_heads_store.get_op_heads()?;
|
let op_head_ids = op_heads_store.get_op_heads()?;
|
||||||
|
|
||||||
if op_head_ids.is_empty() {
|
if op_head_ids.is_empty() {
|
||||||
|
@ -132,9 +132,9 @@ impl OpHeadsStore for SimpleOpHeadsStore {
|
|||||||
Ok(op_heads)
|
Ok(op_heads)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lock(&self) -> Box<dyn OpHeadsStoreLock + '_> {
|
fn lock(&self) -> Result<Box<dyn OpHeadsStoreLock + '_>, OpHeadsStoreError> {
|
||||||
Box::new(SimpleOpHeadsStoreLock {
|
let lock = FileLock::lock(self.dir.join("lock"))
|
||||||
_lock: FileLock::lock(self.dir.join("lock")),
|
.map_err(|err| OpHeadsStoreError::Lock(err.into()))?;
|
||||||
})
|
Ok(Box::new(SimpleOpHeadsStoreLock { _lock: lock }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,8 @@ impl TableStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lock(&self) -> FileLock {
|
fn lock(&self) -> FileLock {
|
||||||
FileLock::lock(self.dir.join("lock"))
|
// TODO: propagate error
|
||||||
|
FileLock::lock(self.dir.join("lock")).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_table(&self, name: String) -> TableStoreResult<Arc<ReadonlyTable>> {
|
fn load_table(&self, name: String) -> TableStoreResult<Arc<ReadonlyTable>> {
|
||||||
|
@ -200,7 +200,7 @@ impl UnpublishedOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn publish(self) -> Result<Arc<ReadonlyRepo>, OpHeadsStoreError> {
|
pub fn publish(self) -> Result<Arc<ReadonlyRepo>, OpHeadsStoreError> {
|
||||||
let _lock = self.op_heads_store.lock();
|
let _lock = self.op_heads_store.lock()?;
|
||||||
self.op_heads_store
|
self.op_heads_store
|
||||||
.update_op_heads(self.operation().parent_ids(), self.operation().id())?;
|
.update_op_heads(self.operation().parent_ids(), self.operation().id())?;
|
||||||
Ok(self.repo)
|
Ok(self.repo)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user