enforce filesystem-synchronization during worktree updates and prolonged reads

That way it's assured that reads and writes don't intersect, but assure we only
hold such lock for the shortest amount of time for reads and and for the
full duration of writes.
This commit is contained in:
Sebastian Thiel 2024-07-14 21:51:28 +02:00
parent b3b87b34a5
commit d7948db628
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
15 changed files with 35 additions and 12 deletions

1
Cargo.lock generated
View File

@ -2175,6 +2175,7 @@ name = "gitbutler-project"
version = "0.0.0"
dependencies = [
"anyhow",
"fslock",
"git2",
"gitbutler-error",
"gitbutler-id",

View File

@ -43,6 +43,7 @@ serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0.61"
tokio = { version = "1.38.0", default-features = false }
keyring = "2.3.3"
fslock = "0.2.1"
anyhow = "1.0.86"
gitbutler-id = { path = "crates/gitbutler-id" }

View File

@ -5,8 +5,7 @@ use gitbutler_branch::{
use gitbutler_command_context::ProjectRepository;
use gitbutler_oplog::{
entry::{OperationKind, SnapshotDetails},
oplog::OplogExt,
snapshot::SnapshotExt,
OplogExt, SnapshotExt,
};
use gitbutler_project::{FetchResult, Project};
use gitbutler_reference::ReferenceName;

View File

@ -11,7 +11,7 @@ use gitbutler_branch::{
};
use gitbutler_commit::commit_headers::HasCommitHeaders;
use gitbutler_error::error::Marker;
use gitbutler_oplog::snapshot::SnapshotExt;
use gitbutler_oplog::SnapshotExt;
use gitbutler_reference::Refname;
use gitbutler_repo::{rebase::cherry_rebase, RepoActionsExt, RepositoryExt};
use gitbutler_time::time::now_since_unix_epoch_ms;

View File

@ -8,7 +8,7 @@ use anyhow::{anyhow, Context, Result};
use git2::build::TreeUpdateBuilder;
use gitbutler_branch::{Branch, BranchExt, BranchId};
use gitbutler_commit::commit_headers::CommitHeadersV2;
use gitbutler_oplog::snapshot::SnapshotExt;
use gitbutler_oplog::SnapshotExt;
use gitbutler_reference::ReferenceName;
use gitbutler_reference::{normalize_branch_name, Refname};
use gitbutler_repo::{RepoActionsExt, RepositoryExt};

View File

@ -1,6 +1,6 @@
use super::*;
use gitbutler_branch::{BranchCreateRequest, VirtualBranchesHandle};
use gitbutler_oplog::oplog::OplogExt;
use gitbutler_oplog::OplogExt;
use itertools::Itertools;
use std::io::Write;
use std::path::Path;

View File

@ -1,5 +1,5 @@
use anyhow::Result;
use gitbutler_oplog::oplog::OplogExt;
use gitbutler_oplog::OplogExt;
use clap::{arg, Command};
use gitbutler_project::Project;

View File

@ -1,7 +1,9 @@
pub mod entry;
pub mod oplog;
mod oplog;
pub use oplog::OplogExt;
mod reflog;
pub mod snapshot;
mod snapshot;
pub use snapshot::SnapshotExt;
mod state;
/// The name of the file holding our state, useful for watching for changes.

View File

@ -287,6 +287,7 @@ impl OplogExt for Project {
#[instrument(skip(details), err(Debug))]
fn create_snapshot(&self, details: SnapshotDetails) -> Result<Option<git2::Oid>> {
let _lock = self.exclusive_worktree_access()?;
let tree_id = self.prepare_snapshot()?;
self.commit_snapshot(tree_id, details)
}
@ -399,6 +400,7 @@ impl OplogExt for Project {
let worktree_dir = self.path.as_path();
let repo = git2::Repository::open(worktree_dir)?;
let _lock = self.exclusive_worktree_access()?;
let before_restore_snapshot_result = self.prepare_snapshot();
let snapshot_commit = repo.find_commit(snapshot_commit_id)?;

View File

@ -7,6 +7,7 @@ publish = false
[dependencies]
anyhow = "1.0.86"
fslock.workspace = true
serde = { workspace = true, features = ["std"]}
serde_json = { version = "1.0", features = [ "std", "arbitrary_precision" ] }
gitbutler-error.workspace = true

View File

@ -118,4 +118,21 @@ impl Project {
pub fn worktree_path(&self) -> PathBuf {
self.path.clone()
}
/// Return a guard for exclusive worktree access, blocking while waiting for someone else,
/// in the same or in a difference process, to release it.
/// It's undefined how fair the lock is, and it's also undefined in which order locks are acquired.
///
/// Note that the kind of lock used cannot go stale, at as long as it's not used on a network drive
/// where things might stop working correctly.
pub fn exclusive_worktree_access(&self) -> anyhow::Result<WorktreeFileLockGuard> {
let mut lock_file =
fslock::LockFile::open(self.gb_dir().join("worktree.lock").as_os_str())?;
lock_file.lock()?;
Ok(WorktreeFileLockGuard(lock_file))
}
}
/// Note that the contained `LockFile` unlocks on drop automatically.
#[allow(dead_code)]
pub struct WorktreeFileLockGuard(fslock::LockFile);

View File

@ -8,7 +8,7 @@ use gitbutler_branch::VirtualBranchesHandle;
use gitbutler_command_context::ProjectRepository;
use gitbutler_error::error::Code;
use gitbutler_id::id::Id;
use gitbutler_oplog::oplog::OplogExt;
use gitbutler_oplog::OplogExt;
use gitbutler_project as projects;
use gitbutler_project::{CodePushState, Project};
use gitbutler_reference::Refname;

View File

@ -26,7 +26,7 @@ anyhow = "1.0.86"
backtrace = { version = "0.3.72", optional = true }
console-subscriber = "0.2.0"
dirs = "5.0.1"
fslock = "0.2.1"
fslock.workspace = true
futures = "0.3"
git2.workspace = true
once_cell = "1.19"

View File

@ -2,7 +2,7 @@ use crate::error::Error;
use anyhow::Context;
use gitbutler_branch::diff::FileDiff;
use gitbutler_oplog::entry::Snapshot;
use gitbutler_oplog::oplog::OplogExt;
use gitbutler_oplog::OplogExt;
use gitbutler_project as projects;
use gitbutler_project::ProjectId;
use std::collections::HashMap;

View File

@ -7,7 +7,7 @@ use gitbutler_command_context::ProjectRepository;
use gitbutler_error::error::Marker;
use gitbutler_oplog::{
entry::{OperationKind, SnapshotDetails},
oplog::OplogExt,
OplogExt,
};
use gitbutler_project as projects;
use gitbutler_project::ProjectId;