1use std::sync::Arc;
2use std::time::Duration;
3
4use tokio::task::JoinHandle;
5
6use crate::storage::HashtreeStore;
7
8pub const BACKGROUND_EVICTION_INTERVAL: Duration = Duration::from_secs(300);
9
10pub fn spawn_background_eviction_task(
11 store: Arc<HashtreeStore>,
12 interval: Duration,
13 context: &'static str,
14) -> JoinHandle<()> {
15 tokio::spawn(async move {
16 let mut ticker = tokio::time::interval(interval);
17 loop {
18 ticker.tick().await;
19 run_background_eviction_pass(store.as_ref(), context);
20 }
21 })
22}
23
24fn run_background_eviction_pass(store: &HashtreeStore, context: &str) {
25 match store.evict_if_needed() {
26 Ok(freed) => {
27 if freed > 0 {
28 tracing::info!("{} background eviction freed {} bytes", context, freed);
29 }
30 }
31 Err(err) => {
32 tracing::warn!("{} background eviction error: {}", context, err);
33 }
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use std::sync::Arc;
40 use std::time::Duration;
41
42 use hashtree_core::from_hex;
43 use tempfile::TempDir;
44
45 use super::spawn_background_eviction_task;
46 use crate::storage::{HashtreeStore, PRIORITY_OTHER};
47
48 #[tokio::test]
49 async fn background_eviction_task_evicts_over_limit_tree() {
50 let temp_dir = TempDir::new().expect("temp dir");
51 let store = Arc::new(
52 HashtreeStore::with_options(temp_dir.path(), None, 512).expect("create store"),
53 );
54
55 let hash_hex = store.put_blob(&vec![7u8; 1024]).expect("put blob");
56 let hash = from_hex(&hash_hex).expect("decode hash");
57
58 store
59 .index_tree(&hash, "owner", Some("tree"), PRIORITY_OTHER, None)
60 .expect("index tree");
61 assert!(store.get_tree_meta(&hash).expect("read meta").is_some());
62
63 let handle =
64 spawn_background_eviction_task(Arc::clone(&store), Duration::from_millis(10), "test");
65
66 tokio::time::timeout(Duration::from_secs(1), async {
67 loop {
68 if store.get_tree_meta(&hash).expect("read meta").is_none() {
69 break;
70 }
71 tokio::time::sleep(Duration::from_millis(10)).await;
72 }
73 })
74 .await
75 .expect("background eviction timed out");
76
77 handle.abort();
78 }
79}