decdn-cache 0.1.0

Cache engine wrapping iroh-blobs with origin pull-through for deCDN
//! Pluggable cache admission and eviction policy traits (ADR 040).
//!
//! The engine owns the store and every safety exemption (pins, probe-holds,
//! deny/takedown). A policy is a pure decision function fed observations: it
//! ranks eligible candidates or classifies an admission, and never reaches
//! into the store.
use crate::{EvictionCandidates, Hash};
use std::collections::HashMap;

pub mod lru;
pub mod sketch;
pub mod tinylfu;

/// Where a freshly admitted blob is stored.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Segment {
    /// One-hit-wonder holding area, capped and evicted first.
    Probation,
    /// Promoted, full-citizen cache.
    Main,
}

/// A policy's admission verdict for a miss about to be served.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdmissionDecision {
    /// Keep the blob, in this segment.
    Store {
        /// Which segment the blob enters on admission.
        segment: Segment,
    },
    /// Reserved: serve without storing. No shipped impl returns it; the engine
    /// treats it as `Store { Probation }` until a pass-through leg exists.
    PassThrough,
}

/// Inputs available to an admission decision.
#[derive(Debug, Clone)]
pub struct AdmissionContext {
    /// The blob being admitted.
    pub hash: Hash,
    /// Total blob size when known ahead of the fill, else `None`.
    pub known_size: Option<u64>,
}

/// Decides whether a miss about to be served is worth keeping, and where.
pub trait AdmissionPolicy: Send + Sync + std::fmt::Debug {
    /// Judge one miss. Called on the serve path, so it must not block.
    fn admit(&self, ctx: &AdmissionContext) -> AdmissionDecision;
}

/// Everything a policy needs to plan one eviction sweep. The engine assembles
/// it and never interprets the policy's semantics — `candidates` is already
/// stripped of pins and probe-holds (a deny-listed hash stays a candidate even
/// when pinned: deny wins over pin), and `segments` is the generic
/// `hash -> Segment` membership the engine tracks with no meaning attached.
#[derive(Debug)]
pub struct EvictionContext<'a> {
    /// Eligible hashes with their last-access `Instant` (pin- and hold-filtered;
    /// deny-listed hashes stay).
    pub candidates: &'a EvictionCandidates,
    /// Whole-store per-hash byte size.
    pub sizes: &'a HashMap<Hash, u64>,
    /// Generic segment membership the engine tracks (absent = `Segment::Main`).
    pub segments: &'a HashMap<Hash, Segment>,
    /// Effective footprint the sweep is deciding against.
    pub total_bytes: u64,
    /// Evict down to this many bytes.
    pub target_bytes: u64,
    /// Maximum releases this sweep.
    pub budget: u64,
    /// Configured cache size; a policy sizes its own caps from it (LRU ignores).
    pub cache_bytes: u64,
}

/// The single sweep-time decision: what leaves, and what graduates to a new
/// segment. `LruEviction` returns `promote` empty.
#[derive(Debug, Default)]
pub struct EvictionPlan {
    /// Release these hashes, in order.
    pub evict: Vec<Hash>,
    /// Move these hashes to a new segment (e.g. `Probation -> Main`).
    pub promote: Vec<(Hash, Segment)>,
}

/// Decides what the engine releases when the store is over its target.
pub trait EvictionPolicy: Send + Sync + std::fmt::Debug {
    /// The one sweep-time decision: what leaves, what graduates. A policy that
    /// ranks by frequency reads the shared [`FrequencyEstimator`] directly — the
    /// engine feeds that estimator on every serve. `candidates` is already
    /// stripped of pins and probe-holds by the engine; a deny-listed hash stays
    /// a candidate even when pinned (deny wins over pin).
    fn plan(&self, ctx: &EvictionContext<'_>) -> EvictionPlan;
}

/// Decayed frequency estimate shared by admission and eviction.
pub trait FrequencyEstimator: Send + Sync + std::fmt::Debug {
    /// Record one access to `hash`. The engine calls this on every serve.
    fn observe(&self, hash: Hash);
    /// The current decayed access count for `hash`.
    fn estimate(&self, hash: Hash) -> u32;
}

pub use lru::{AlwaysAdmit, LruEviction};
pub use tinylfu::{ProbationAdmission, TinyLfuEstimator, TinyLfuEviction};