loonfs_core/gc/config.rs
1//! GC configuration.
2
3use crate::error::{CoreError, Result};
4use crate::limits::GC_MIN_GRACE_WINDOW_MS;
5use serde::{Deserialize, Serialize};
6
7/// The grace window for the sweep (format spec, "Garbage collection"). It is
8/// wall-clock cleanup policy, never a validity input, and the default is
9/// conservative: every object gets one hour of unconditional protection.
10/// Abandoned fork records are not under it — a fork attempt carries its own
11/// lease, and letting that pass is the whole proof
12/// (`gc/fork_checkpoints.rs`) — and neither are upload sessions or the
13/// content they leave behind: a session carries its own lease, and the
14/// window a completed session's content is protected for is derived in
15/// `limits`, not configured.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct GcConfig {
18 pub grace_window_ms: u64,
19 /// Maximum objects this invocation may read or decide. `None` keeps the
20 /// run-to-completion behavior. What one unit buys is spelled out on
21 /// `gc::budget::PassBudget`; the short version is that the content
22 /// reference scan pays out of the same purse as candidate enumeration,
23 /// so a budget smaller than that scan defers content reclamation
24 /// instead of finishing it, pass after pass, while the rest of the
25 /// sweep proceeds normally.
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub max_objects: Option<u64>,
28 /// Opaque enumeration cursor returned by an earlier invocation.
29 ///
30 /// The cursor is valid only for the same namespace. Resuming always
31 /// rebuilds the live roots and safety floors; a stale cursor can only
32 /// re-examine work or defer keys that moved before it until the next
33 /// full pass, never authorize deletion of a newly live object.
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub cursor: Option<String>,
36}
37
38impl Default for GcConfig {
39 fn default() -> Self {
40 Self {
41 grace_window_ms: 60 * 60 * 1000,
42 max_objects: None,
43 cursor: None,
44 }
45 }
46}
47
48impl GcConfig {
49 pub(super) fn validate(&self) -> Result<()> {
50 // The minimum grace window is derived from the publication budgets
51 // and provider deadlines in `limits`. Below it, a publish still in
52 // flight could have written objects that already look old enough to
53 // delete, so the configuration is rejected outright.
54 if self.grace_window_ms < GC_MIN_GRACE_WINDOW_MS {
55 return Err(CoreError::InvalidGcConfig(format!(
56 "grace_window_ms {} is below the derived safety minimum {}",
57 self.grace_window_ms, GC_MIN_GRACE_WINDOW_MS
58 )));
59 }
60 if self.max_objects == Some(0) {
61 return Err(CoreError::InvalidGcConfig(
62 "max_objects must be greater than zero".to_owned(),
63 ));
64 }
65 Ok(())
66 }
67}