Skip to main content

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}