iroh_blobs/store/fs/delete_set.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
use std::{
collections::BTreeSet,
sync::{Arc, Mutex},
};
use tracing::warn;
use super::options::PathOptions;
use crate::Hash;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum BaoFilePart {
Outboard,
Data,
Sizes,
Bitfield,
}
/// Creates a pair of a protect handle and a delete handle.
///
/// The protect handle can be used to protect files from deletion.
/// The delete handle can be used to create transactions in which files can be marked for deletion.
pub(super) fn pair(options: Arc<PathOptions>) -> (ProtectHandle, DeleteHandle) {
let ds = Arc::new(Mutex::new(DeleteSet::default()));
(ProtectHandle(ds.clone()), DeleteHandle::new(ds, options))
}
/// Helper to keep track of files to delete after a transaction is committed.
#[derive(Debug, Default)]
struct DeleteSet(BTreeSet<(Hash, BaoFilePart)>);
impl DeleteSet {
/// Mark a file as to be deleted after the transaction is committed.
fn delete(&mut self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
for part in parts {
self.0.insert((hash, part));
}
}
/// Mark a file as to be kept after the transaction is committed.
///
/// This will cancel any previous delete for the same file in the same transaction.
fn protect(&mut self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
for part in parts {
self.0.remove(&(hash, part));
}
}
/// Apply the delete set and clear it.
///
/// This will delete all files marked for deletion and then clear the set.
/// Errors will just be logged.
fn commit(&mut self, options: &PathOptions) {
for (hash, to_delete) in &self.0 {
tracing::debug!("deleting {:?} for {hash}", to_delete);
let path = match to_delete {
BaoFilePart::Data => options.data_path(hash),
BaoFilePart::Outboard => options.outboard_path(hash),
BaoFilePart::Sizes => options.sizes_path(hash),
BaoFilePart::Bitfield => options.bitfield_path(hash),
};
if let Err(cause) = std::fs::remove_file(&path) {
// Ignore NotFound errors, if the file is already gone that's fine.
if cause.kind() != std::io::ErrorKind::NotFound {
warn!(
"failed to delete {:?} {}: {}",
to_delete,
path.display(),
cause
);
}
}
}
self.0.clear();
}
fn clear(&mut self) {
self.0.clear();
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Debug, Clone)]
pub(super) struct ProtectHandle(Arc<Mutex<DeleteSet>>);
/// Protect handle, to be used concurrently with transactions to mark files for keeping.
impl ProtectHandle {
/// Inside or outside a transaction, mark files as to be kept
///
/// If we are not inside a transaction, this will do nothing.
pub(super) fn protect(&self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
let mut guard = self.0.lock().unwrap();
guard.protect(hash, parts);
}
}
/// A delete handle. The only thing you can do with this is to open transactions that keep track of files to delete.
#[derive(Debug)]
pub(super) struct DeleteHandle {
ds: Arc<Mutex<DeleteSet>>,
options: Arc<PathOptions>,
}
impl DeleteHandle {
fn new(ds: Arc<Mutex<DeleteSet>>, options: Arc<PathOptions>) -> Self {
Self { ds, options }
}
/// Open a file transaction. You can open only one transaction at a time.
pub(super) fn begin_write(&mut self) -> FileTransaction<'_> {
FileTransaction::new(self)
}
}
/// A file transaction. Inside a transaction, you can mark files for deletion.
///
/// Dropping a transaction will clear the delete set. Committing a transaction will apply the delete set by actually deleting the files.
#[derive(Debug)]
pub(super) struct FileTransaction<'a>(&'a DeleteHandle);
impl<'a> FileTransaction<'a> {
fn new(inner: &'a DeleteHandle) -> Self {
let guard = inner.ds.lock().unwrap();
debug_assert!(guard.is_empty());
drop(guard);
Self(inner)
}
/// Mark files as to be deleted
pub fn delete(&self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
let mut guard = self.0.ds.lock().unwrap();
guard.delete(hash, parts);
}
/// Apply the delete set and clear it.
pub fn commit(self) {
let mut guard = self.0.ds.lock().unwrap();
guard.commit(&self.0.options);
}
}
impl Drop for FileTransaction<'_> {
fn drop(&mut self) {
self.0.ds.lock().unwrap().clear();
}
}