Skip to content

Commit 0a5124a

Browse files
b5rklaehn
andauthored
feat(MemStore)!: expose garbage collection (#177)
## Description This was a smaller change than expected. * There is already a `gc_smoke_mem` test, so we know GC works with a memstore * `store::mem::Options` was already defined, just empty. I've added a public `gc: Option<GcConfig>` field * expanded `MemStore::new` to call `MemStore::new_with_opts` to expose providing custom GC options ## Breaking Changes None. ## Notes & open questions I haven't actually ensured that `MemStore::new_with_opts` works & is publically exposed. Should add a test for that ## Change checklist - [ ] Self-review. - [ ] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [ ] Tests if relevant. - [ ] All breaking changes documented. --------- Co-authored-by: Ruediger Klaehn <[email protected]>
1 parent b7aa852 commit 0a5124a

File tree

11 files changed

+65
-34
lines changed

11 files changed

+65
-34
lines changed

deny.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ name = "ring"
3939
[[licenses.clarify.license-files]]
4040
hash = 3171872035
4141
path = "LICENSE"
42+
43+
[sources]
44+
allow-git = [
45+
"https://github.com/n0-computer/iroh",
46+
]

src/api/blobs.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,13 @@ impl Blobs {
142142
/// clears the protections before.
143143
///
144144
/// Users should rely only on garbage collection for blob deletion.
145-
#[cfg(feature = "fs-store")]
146145
pub(crate) async fn delete_with_opts(&self, options: DeleteOptions) -> RequestResult<()> {
147146
trace!("{options:?}");
148147
self.client.rpc(options).await??;
149148
Ok(())
150149
}
151150

152151
/// See [`Self::delete_with_opts`].
153-
#[cfg(feature = "fs-store")]
154152
pub(crate) async fn delete(
155153
&self,
156154
hashes: impl IntoIterator<Item = impl Into<Hash>>,

src/api/blobs/reader.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,11 @@ mod tests {
225225
protocol::ChunkRangesExt,
226226
store::{
227227
fs::{
228-
tests::{create_n0_bao, test_data, INTERESTING_SIZES},
228+
tests::{test_data, INTERESTING_SIZES},
229229
FsStore,
230230
},
231231
mem::MemStore,
232+
util::tests::create_n0_bao,
232233
},
233234
};
234235

src/api/remote.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1073,10 +1073,11 @@ mod tests {
10731073
protocol::{ChunkRangesExt, ChunkRangesSeq, GetRequest},
10741074
store::{
10751075
fs::{
1076-
tests::{create_n0_bao, test_data, INTERESTING_SIZES},
1076+
tests::{test_data, INTERESTING_SIZES},
10771077
FsStore,
10781078
},
10791079
mem::MemStore,
1080+
util::tests::create_n0_bao,
10801081
},
10811082
tests::{add_test_hash_seq, add_test_hash_seq_incomplete},
10821083
};

src/store/fs.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ use bytes::Bytes;
9191
use delete_set::{BaoFilePart, ProtectHandle};
9292
use entity_manager::{EntityManagerState, SpawnArg};
9393
use entry_state::{DataLocation, OutboardLocation};
94-
use gc::run_gc;
9594
use import::{ImportEntry, ImportSource};
9695
use irpc::{channel::mpsc, RpcMessage};
9796
use meta::list_blobs;
@@ -120,6 +119,7 @@ use crate::{
120119
},
121120
util::entity_manager::{self, ActiveEntityState},
122121
},
122+
gc::run_gc,
123123
util::{BaoTreeSender, FixedSize, MemOrFile, ValueOrPoisioned},
124124
IROH_BLOCK_SIZE,
125125
},
@@ -141,7 +141,6 @@ use entry_state::EntryState;
141141
use import::{import_byte_stream, import_bytes, import_path, ImportEntryMsg};
142142
use options::Options;
143143
use tracing::Instrument;
144-
mod gc;
145144

146145
use crate::{
147146
api::{
@@ -1498,10 +1497,7 @@ pub mod tests {
14981497
use core::panic;
14991498
use std::collections::{HashMap, HashSet};
15001499

1501-
use bao_tree::{
1502-
io::{outboard::PreOrderMemOutboard, round_up_to_chunks_groups},
1503-
ChunkRanges,
1504-
};
1500+
use bao_tree::{io::round_up_to_chunks_groups, ChunkRanges};
15051501
use n0_future::{stream, Stream, StreamExt};
15061502
use testresult::TestResult;
15071503
use walkdir::WalkDir;
@@ -1510,7 +1506,7 @@ pub mod tests {
15101506
use crate::{
15111507
api::blobs::Bitfield,
15121508
store::{
1513-
util::{read_checksummed, SliceInfoExt, Tag},
1509+
util::{read_checksummed, tests::create_n0_bao, SliceInfoExt, Tag},
15141510
IROH_BLOCK_SIZE,
15151511
},
15161512
};
@@ -1527,17 +1523,6 @@ pub mod tests {
15271523
1024 * 1024 * 8, // data file, outboard file
15281524
];
15291525

1530-
/// Create n0 flavoured bao. Note that this can be used to request ranges below a chunk group size,
1531-
/// which can not be exported via bao because we don't store hashes below the chunk group level.
1532-
pub fn create_n0_bao(data: &[u8], ranges: &ChunkRanges) -> anyhow::Result<(Hash, Vec<u8>)> {
1533-
let outboard = PreOrderMemOutboard::create(data, IROH_BLOCK_SIZE);
1534-
let mut encoded = Vec::new();
1535-
let size = data.len() as u64;
1536-
encoded.extend_from_slice(&size.to_le_bytes());
1537-
bao_tree::io::sync::encode_ranges_validated(data, &outboard, ranges, &mut encoded)?;
1538-
Ok((outboard.root.into(), encoded))
1539-
}
1540-
15411526
pub fn round_up_request(size: u64, ranges: &ChunkRanges) -> ChunkRanges {
15421527
let last_chunk = ChunkNum::chunks(size);
15431528
let data_range = ChunkRanges::from(..last_chunk);

src/store/fs/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use std::{
44
time::Duration,
55
};
66

7-
pub use super::gc::{GcConfig, ProtectCb, ProtectOutcome};
87
use super::{meta::raw_outboard_size, temp_name};
8+
pub use crate::store::gc::{GcConfig, ProtectCb, ProtectOutcome};
99
use crate::Hash;
1010

1111
/// Options for directories used by the file store.

src/store/fs/gc.rs renamed to src/store/gc.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,20 +240,16 @@ pub async fn run_gc(store: Store, config: GcConfig) {
240240

241241
#[cfg(test)]
242242
mod tests {
243-
use std::{
244-
io::{self},
245-
path::Path,
246-
};
243+
use std::io::{self};
247244

248-
use bao_tree::{io::EncodeError, ChunkNum};
245+
use bao_tree::io::EncodeError;
249246
use range_collections::RangeSet2;
250247
use testresult::TestResult;
251248

252249
use super::*;
253250
use crate::{
254251
api::{blobs::AddBytesOptions, ExportBaoError, RequestError, Store},
255252
hashseq::HashSeq,
256-
store::fs::{options::PathOptions, tests::create_n0_bao},
257253
BlobFormat,
258254
};
259255

@@ -266,13 +262,15 @@ mod tests {
266262
let et = blobs.add_slice("e").temp_tag().await?;
267263
let ft = blobs.add_slice("f").temp_tag().await?;
268264
let gt = blobs.add_slice("g").temp_tag().await?;
265+
let ht = blobs.add_slice("h").with_named_tag("h").await?;
269266
let a = *at.hash();
270267
let b = *bt.hash();
271268
let c = *ct.hash();
272269
let d = *dt.hash();
273270
let e = *et.hash();
274271
let f = *ft.hash();
275272
let g = *gt.hash();
273+
let h = ht.hash;
276274
store.tags().set("c", *ct.hash_and_format()).await?;
277275
let dehs = [d, e].into_iter().collect::<HashSeq>();
278276
let hehs = blobs
@@ -292,6 +290,7 @@ mod tests {
292290
store.tags().set("fg", *fghs.hash_and_format()).await?;
293291
drop(fghs);
294292
drop(bt);
293+
store.tags().delete("h").await?;
295294
let mut live = HashSet::new();
296295
gc_run_once(store, &mut live).await?;
297296
// a is protected because we keep the temp tag
@@ -313,12 +312,19 @@ mod tests {
313312
assert!(store.has(f).await?);
314313
assert!(live.contains(&g));
315314
assert!(store.has(g).await?);
315+
// h is not protected because we deleted the tag before gc ran
316+
assert!(!live.contains(&h));
317+
assert!(!store.has(h).await?);
316318
drop(at);
317319
drop(hehs);
318320
Ok(())
319321
}
320322

321-
async fn gc_file_delete(path: &Path, store: &Store) -> TestResult<()> {
323+
#[cfg(feature = "fs-store")]
324+
async fn gc_file_delete(path: &std::path::Path, store: &Store) -> TestResult<()> {
325+
use bao_tree::ChunkNum;
326+
327+
use crate::store::{fs::options::PathOptions, util::tests::create_n0_bao};
322328
let mut live = HashSet::new();
323329
let options = PathOptions::new(&path.join("db"));
324330
// create a large complete file and check that the data and outboard files are deleted by gc
@@ -366,6 +372,7 @@ mod tests {
366372
}
367373

368374
#[tokio::test]
375+
#[cfg(feature = "fs-store")]
369376
async fn gc_smoke_fs() -> TestResult {
370377
tracing_subscriber::fmt::try_init().ok();
371378
let testdir = tempfile::tempdir()?;
@@ -385,6 +392,7 @@ mod tests {
385392
}
386393

387394
#[tokio::test]
395+
#[cfg(feature = "fs-store")]
388396
async fn gc_check_deletion_fs() -> TestResult {
389397
tracing_subscriber::fmt::try_init().ok();
390398
let testdir = tempfile::tempdir()?;

src/store/mem.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ use crate::{
5858
},
5959
protocol::ChunkRangesExt,
6060
store::{
61+
gc::{run_gc, GcConfig},
6162
util::{SizeInfo, SparseMemFile, Tag},
6263
IROH_BLOCK_SIZE,
6364
},
@@ -66,7 +67,9 @@ use crate::{
6667
};
6768

6869
#[derive(Debug, Default)]
69-
pub struct Options {}
70+
pub struct Options {
71+
pub gc_config: Option<GcConfig>,
72+
}
7073

7174
#[derive(Debug, Clone)]
7275
#[repr(transparent)]
@@ -113,6 +116,10 @@ impl MemStore {
113116
}
114117

115118
pub fn new() -> Self {
119+
Self::new_with_opts(Options::default())
120+
}
121+
122+
pub fn new_with_opts(opts: Options) -> Self {
116123
let (sender, receiver) = tokio::sync::mpsc::channel(32);
117124
tokio::spawn(
118125
Actor {
@@ -130,7 +137,13 @@ impl MemStore {
130137
}
131138
.run(),
132139
);
133-
Self::from_sender(sender.into())
140+
141+
let store = Self::from_sender(sender.into());
142+
if let Some(gc_config) = opts.gc_config {
143+
tokio::spawn(run_gc(store.deref().clone(), gc_config));
144+
}
145+
146+
store
134147
}
135148
}
136149

src/store/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use bao_tree::BlockSize;
88
#[cfg(feature = "fs-store")]
99
pub mod fs;
10+
mod gc;
1011
pub mod mem;
1112
pub mod readonly_mem;
1213
mod test;

src/store/util.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,22 @@ impl bao_tree::io::mixed::Sender for BaoTreeSender {
404404
self.0.send(item).await
405405
}
406406
}
407+
408+
#[cfg(test)]
409+
#[cfg(feature = "fs-store")]
410+
pub mod tests {
411+
use bao_tree::{io::outboard::PreOrderMemOutboard, ChunkRanges};
412+
413+
use crate::{hash::Hash, store::IROH_BLOCK_SIZE};
414+
415+
/// Create n0 flavoured bao. Note that this can be used to request ranges below a chunk group size,
416+
/// which can not be exported via bao because we don't store hashes below the chunk group level.
417+
pub fn create_n0_bao(data: &[u8], ranges: &ChunkRanges) -> anyhow::Result<(Hash, Vec<u8>)> {
418+
let outboard = PreOrderMemOutboard::create(data, IROH_BLOCK_SIZE);
419+
let mut encoded = Vec::new();
420+
let size = data.len() as u64;
421+
encoded.extend_from_slice(&size.to_le_bytes());
422+
bao_tree::io::sync::encode_ranges_validated(data, &outboard, ranges, &mut encoded)?;
423+
Ok((outboard.root.into(), encoded))
424+
}
425+
}

0 commit comments

Comments
 (0)