Skip to content

Commit

Permalink
fetch: remove .keep files
Browse files Browse the repository at this point in the history
A `.keep` file in the `objects/pack` directory acts as a lock to ensure that
operations like `git gc` do not clean up `.idx` files while a fetch is being
performed. It is safe to remove the `.keep` file once a fetch has completed and
a reference points to a given object in the packfile.

To ensure this happens in `radicle-fetch`, a newtype `Keepfile` is introduced.
It uses special `Drop` semantics by attempting to remove the file when the
object is dropped -- logging a warning if it fails to do so.

`FetchState` is augmented to keep a set of the `Keepfile`s. This ensures that
when the fetch completes, either successfully or with a failure, when
`FetchState` is dropped then so are the `Keepfile`s.

We ensure that the expected behaviour of removing the `.keep`files occurs by
adding a check in `e2e::test_replication` to check Alice's repository's packs.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
  • Loading branch information
FintanH authored and cloudhead committed May 16, 2024
1 parent 8402b85 commit d56d619
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 5 deletions.
1 change: 1 addition & 0 deletions radicle-fetch/src/git.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod mem;
pub(crate) mod repository;
pub(crate) mod packfile;

pub mod refs;

Expand Down
32 changes: 32 additions & 0 deletions radicle-fetch/src/git/packfile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::fs;
use std::path::{Path, PathBuf};

/// The [`PathBuf`] which points to a `*.keep` file, which should correspond to
/// a packfile.
///
/// Upon drop, it attempts to remove the [`PathBuf`] to release the lock on the
/// packfile index, allowing it to be garbage collected.
#[derive(Clone, Debug)]
pub struct Keepfile {
path: PathBuf,
}

impl Keepfile {
pub fn new<P: AsRef<Path>>(path: P) -> Option<Self> {
let path = path.as_ref();
match path.extension() {
Some(ext) if ext == "keep" => Some(Self {
path: path.to_path_buf(),
}),
_ => None,
}
}
}

impl Drop for Keepfile {
fn drop(&mut self) {
if let Err(e) = fs::remove_file(&self.path) {
log::warn!(target: "fetch", "Failed to remove {:?}: {e}", self.path);
}
}
}
13 changes: 10 additions & 3 deletions radicle-fetch/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use radicle::storage::{
};

use crate::git;
use crate::git::packfile::Keepfile;
use crate::git::refs::{Applied, Update};
use crate::git::repository;
use crate::sigrefs::SignedRefsAt;
Expand Down Expand Up @@ -160,6 +161,10 @@ pub struct FetchState {
sigrefs: SigrefTips,
/// Seen reference tips, per remote.
tips: BTreeMap<PublicKey, Vec<Update<'static>>>,
/// The `.keep` files created during packfile transfers. They are kept
/// within the state, so that when the state is dropped, it also attempts to
/// delete the files to release the locks on the packfiles.
keepfiles: Vec<Keepfile>,
}

impl FetchState {
Expand Down Expand Up @@ -233,9 +238,11 @@ impl FetchState {

let wants_haves = step.wants_haves(&handle.repo, &refs)?;
if !wants_haves.wants.is_empty() {
handle
.transport
.fetch(wants_haves, handle.interrupt.clone(), handshake)?;
let keepfile =
handle
.transport
.fetch(wants_haves, handle.interrupt.clone(), handshake)?;
self.keepfiles.extend(keepfile);
} else {
log::trace!(target: "fetch", "Nothing to fetch")
};
Expand Down
5 changes: 3 additions & 2 deletions radicle-fetch/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use radicle::storage::git::Repository;
use thiserror::Error;

use crate::git::oid;
use crate::git::packfile::Keepfile;
use crate::git::repository;

/// Open a reader and writer stream to pass to the ls-refs and fetch
Expand Down Expand Up @@ -120,7 +121,7 @@ where
wants_haves: WantsHaves,
interrupt: Arc<AtomicBool>,
handshake: &handshake::Outcome,
) -> io::Result<()> {
) -> io::Result<Option<Keepfile>> {
log::trace!(
target: "fetch",
"Running fetch wants={:?}, haves={:?}",
Expand Down Expand Up @@ -170,7 +171,7 @@ where
}
}

Ok(())
Ok(out.keepfile)
}

/// Signal to the server side that we are done sending ls-refs and
Expand Down
5 changes: 5 additions & 0 deletions radicle-fetch/src/transport/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use gix_transport::client;
use gix_transport::client::{ExtendedBufRead, MessageKind};
use gix_transport::Protocol;

use crate::git::packfile;

use super::{agent_name, indicate_end_of_interaction, Connection, WantsHaves};

pub type Error = gix_protocol::fetch::Error;
Expand Down Expand Up @@ -102,6 +104,7 @@ pub struct Fetch {
pub struct FetchOut {
pub refs: Vec<Ref>,
pub pack: Option<pack::bundle::write::Outcome>,
pub keepfile: Option<packfile::Keepfile>,
}

// FIXME: the delegate pattern will be removed in the near future and
Expand All @@ -127,6 +130,7 @@ impl<'a> Delegate for &'a mut Fetch {
.pack_writer
.write_pack(input, progress)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
self.out.keepfile = pack.keep_path.as_ref().and_then(packfile::Keepfile::new);
self.out.pack = Some(pack);
Ok(())
}
Expand Down Expand Up @@ -204,6 +208,7 @@ where
out: FetchOut {
refs: Vec::new(),
pack: None,
keepfile: None,
},
};

Expand Down
15 changes: 15 additions & 0 deletions radicle-node/src/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,21 @@ fn test_replication() {
alice.storage.repository(acme).unwrap().validate(),
Ok(validations) if validations.is_empty()
);

// Ensure that .keep files are deleted upon replication
{
let repo = alice.storage.repository(acme).unwrap();
let pack_dir = repo.path().join("objects").join("pack");
for entry in std::fs::read_dir(pack_dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
assert_ne!(
path.extension(),
Some("keep".as_ref()),
"found .keep file after fetch: {path:?}"
);
}
}
}

#[test]
Expand Down

0 comments on commit d56d619

Please sign in to comment.