mod bootstrap;
mod census;
mod task;
pub use bootstrap::{
BindingReport, DetectedUrl, EnvSource, L3Binding, Ladder, MapEnv, ProcessEnv, assemble_ladder,
detect_redis_endpoint,
};
pub use census::{CensusKey, KeyCensus};
pub use task::GovernorHandle;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use crate::continuity::ContinuityState;
use crate::control::cachelito::PlaneStats;
use crate::entry::EntryState;
use crate::error::CacheError;
use crate::manager::CacheManager;
use crate::policy::CachePolicy;
use crate::telemetry::{KeyIdentity, Outcome, RingTelemetry};
use crate::tier::hot_rung::HotRungTier;
use crate::tier::{CacheTier, TierId};
pub type Rebinder<V> = Arc<
dyn Fn(TierId) -> Result<Option<Arc<dyn CacheTier<V>>>, CacheError> + Send + Sync + 'static,
>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GovernorMode {
#[default]
Manual,
SemiAutonomous,
FullyAutonomous,
}
impl GovernorMode {
#[must_use]
pub const fn executes_maintenance(self) -> bool {
matches!(self, Self::SemiAutonomous | Self::FullyAutonomous)
}
#[must_use]
pub const fn executes_moves(self) -> bool {
matches!(self, Self::FullyAutonomous)
}
}
#[derive(Debug, Clone)]
pub struct GovernorSettings {
pub mode: GovernorMode,
pub interval: Duration,
pub max_moves_per_tick: usize,
pub max_rebinds_per_tick: usize,
pub pressure_threshold: f64,
pub hot_heat: u64,
pub recover_after: Option<Duration>,
}
impl Default for GovernorSettings {
fn default() -> Self {
GovernorSettings {
mode: GovernorMode::Manual,
interval: Duration::from_secs(30),
max_moves_per_tick: 8,
max_rebinds_per_tick: 2,
pressure_threshold: 0.85,
hot_heat: 8,
recover_after: None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GovernorPlan {
pub recovery_sweep: bool,
pub expire_sweep: bool,
pub rebind: Vec<TierId>,
pub promote: Vec<usize>,
pub demote: Vec<usize>,
pub skipped: Vec<Skip>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Skip {
NoHeatSource,
NoCensus,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GovernorReport {
pub plan: GovernorPlan,
pub recovered_intents: usize,
pub needs_reconciliation: usize,
pub expired_demoted: usize,
pub rebinds_done: usize,
pub rebind_failures: usize,
pub rebinding_not_configured: bool,
pub promoted: usize,
pub demoted: usize,
pub move_failures: usize,
}
pub struct Governor<K, V, P> {
manager: Arc<CacheManager<K, V, P>>,
settings: GovernorSettings,
census: Option<Arc<KeyCensus<K>>>,
heat: Option<Arc<RingTelemetry>>,
hot_rungs: Vec<Arc<HotRungTier<V>>>,
rebinder: Option<Rebinder<V>>,
}
impl<K, V, P> Governor<K, V, P>
where
K: crate::key::Key,
V: Clone + Send + Sync + 'static,
P: CachePolicy<K, V>,
{
#[must_use]
pub fn new(manager: Arc<CacheManager<K, V, P>>, settings: GovernorSettings) -> Self {
Governor {
manager,
settings,
census: None,
heat: None,
hot_rungs: Vec::new(),
rebinder: None,
}
}
#[must_use]
pub fn with_census(mut self, census: Arc<KeyCensus<K>>) -> Self {
self.census = Some(census);
self
}
#[must_use]
pub fn with_heat(mut self, heat: Arc<RingTelemetry>) -> Self {
self.heat = Some(heat);
self
}
#[must_use]
pub fn with_hot_rung(mut self, tier: Arc<HotRungTier<V>>) -> Self {
self.hot_rungs.push(tier);
self
}
#[must_use]
pub fn with_rebinder(mut self, rebinder: Rebinder<V>) -> Self {
self.rebinder = Some(rebinder);
self
}
#[must_use]
pub const fn settings(&self) -> &GovernorSettings {
&self.settings
}
#[must_use]
pub fn manager(&self) -> &Arc<CacheManager<K, V, P>> {
&self.manager
}
pub fn spawn(self: Arc<Self>) -> Result<GovernorHandle, CacheError>
where
K: 'static,
P: CachePolicy<K, V> + 'static,
{
task::spawn(self)
}
#[must_use]
pub fn plan(&self) -> GovernorPlan {
let observation = self.observe();
let heat = self.measure_heat();
plan_tick(&observation, &heat, &self.settings)
}
pub async fn tick(&self) -> Result<GovernorReport, CacheError> {
let plan = self.plan();
if self.settings.mode.executes_maintenance() {
Ok(self.apply_plan(&plan).await)
} else {
Ok(GovernorReport {
plan,
..GovernorReport::default()
})
}
}
pub async fn apply_plan(&self, plan: &GovernorPlan) -> GovernorReport {
let mut report = GovernorReport {
plan: plan.clone(),
..GovernorReport::default()
};
if !self.settings.mode.executes_maintenance() {
return report;
}
if plan.recovery_sweep {
let age = self
.settings
.recover_after
.unwrap_or(crate::continuity::INTENT_RECOVERY_AGE);
let r = self.manager.recover_older_than(age);
report.recovered_intents = r.recovered;
report.needs_reconciliation = r.needs_reconciliation;
}
if plan.expire_sweep {
report.expired_demoted = self.manager.sweep_expired();
}
match &self.rebinder {
None => report.rebinding_not_configured |= !plan.rebind.is_empty(),
Some(rebinder) => {
for rung in &plan.rebind {
let Some(hot) = self.hot_rungs.iter().find(|h| h.rung() == *rung) else {
continue;
};
let rebinder = Arc::clone(rebinder);
let rung = *rung;
let outcome = tokio::task::spawn_blocking(move || rebinder(rung))
.await
.map_err(|_| CacheError::Cancelled)
.and_then(std::convert::identity);
match outcome {
Ok(Some(replacement)) => match hot.hot_swap(replacement) {
Ok(_) => report.rebinds_done += 1,
Err(_) => report.rebind_failures += 1,
},
Ok(None) => report.rebinding_not_configured |= true,
Err(_) => report.rebind_failures += 1,
}
}
}
}
if !self.settings.mode.executes_moves() {
return report;
}
let Some(census) = &self.census else {
return report;
};
let entries = census.entries();
let ctx = census.context().clone();
let mut budget = self.settings.max_moves_per_tick;
for &idx in &plan.demote {
if budget == 0 {
break;
}
let Some(entry) = entries.get(idx) else {
continue;
};
match self.manager.demote(&entry.key, &ctx).await {
Ok(()) => {
report.demoted += 1;
budget -= 1;
}
Err(CacheError::CapacityExhausted) => {
report.move_failures += 1;
break;
}
Err(err) => {
let _ = err;
report.move_failures += 1;
budget -= 1;
}
}
}
for &idx in &plan.promote {
if budget == 0 {
break;
}
let Some(entry) = entries.get(idx) else {
continue;
};
match self.manager.promote(&entry.key, &ctx).await {
Ok(()) => {
report.promoted += 1;
budget -= 1;
}
Err(_) => report.move_failures += 1,
}
}
report
}
fn observe(&self) -> Observation {
let stats = self.manager.plane_stats();
let continuity = self.manager.continuity();
let mut rung_capacity = [None; 7];
let mut rung_state = [ContinuityState::Healthy; 7];
for &tier in TierId::ALL.iter().rev() {
if let Some(state) = continuity.states.get(&tier) {
rung_state[tier.as_usize()] = *state;
}
if let Some(bound) = self.manager.bound_tier(&tier) {
rung_capacity[tier.as_usize()] = bound.capacity();
}
}
let mut census_view = Vec::new();
if let Some(census) = &self.census {
for (idx, entry) in census.entries().into_iter().enumerate() {
let view = match self.manager.cachelito().peek(&entry.framed) {
Ok(s) => CensusEntryView {
index: idx,
identity: entry.identity,
tier: s.tier,
state: s.state,
},
Err(_) => CensusEntryView {
index: idx,
identity: entry.identity,
tier: TierId::L0,
state: EntryState::Absent,
},
};
census_view.push(view);
}
}
Observation {
stats,
rung_capacity,
rung_state,
census: census_view,
}
}
fn measure_heat(&self) -> HashMap<KeyIdentity, u64> {
let Some(ring) = &self.heat else {
return HashMap::new();
};
let mut heat: HashMap<KeyIdentity, u64> = HashMap::new();
for record in ring.records() {
if record.outcome != Outcome::Served {
continue;
}
if !matches!(
record.operation,
crate::telemetry::Operation::Get
| crate::telemetry::Operation::GetOrFetch
| crate::telemetry::Operation::Refresh
) {
continue;
}
*heat.entry(record.key).or_insert(0) += 1;
}
heat
}
}
#[derive(Debug, Clone, Copy)]
struct CensusEntryView {
index: usize,
identity: KeyIdentity,
tier: TierId,
state: EntryState,
}
struct Observation {
stats: PlaneStats,
rung_capacity: [Option<usize>; 7],
rung_state: [ContinuityState; 7],
census: Vec<CensusEntryView>,
}
fn rung_occupancy(observation: &Observation, rung: TierId) -> Option<f64> {
let cap = observation.rung_capacity[rung.as_usize()]?;
if cap == 0 {
return None;
}
let used = observation.stats.by_tier[rung.as_usize()];
Some(
f64::from(u32::try_from(used).unwrap_or(u32::MAX))
/ f64::from(u32::try_from(cap).unwrap_or(u32::MAX)),
)
}
fn plan_tick(
observation: &Observation,
heat: &HashMap<KeyIdentity, u64>,
settings: &GovernorSettings,
) -> GovernorPlan {
let mut plan = GovernorPlan {
recovery_sweep: true,
expire_sweep: true,
..GovernorPlan::default()
};
let mut degraded: Vec<TierId> = TierId::ALL
.iter()
.filter(|t| t.is_cache_rung())
.filter(|t| {
matches!(
observation.rung_state[t.as_usize()],
ContinuityState::Degraded | ContinuityState::Unavailable
)
})
.copied()
.collect();
degraded.sort_by_key(|t| std::cmp::Reverse(t.as_usize()));
plan.rebind = degraded
.into_iter()
.take(settings.max_rebinds_per_tick)
.collect();
if observation.census.is_empty() {
plan.skipped.push(Skip::NoCensus);
return plan;
}
if heat.is_empty() {
plan.skipped.push(Skip::NoHeatSource);
return plan;
}
let mut budget = settings.max_moves_per_tick;
let mut hot: Vec<&CensusEntryView> = observation
.census
.iter()
.filter(|v| {
v.state == EntryState::Ready
&& v.tier.is_cache_rung()
&& v.tier.as_usize() > 0
&& heat.get(&v.identity).copied().unwrap_or(0) >= settings.hot_heat
})
.collect();
hot.sort_by_key(|v| std::cmp::Reverse((heat.get(&v.identity).copied().unwrap_or(0), v.index)));
let mut promoted = Vec::new();
for view in hot {
if budget == 0 {
break;
}
budget -= 1;
promoted.push(view.index);
}
plan.promote = promoted;
let rung_pressure: Vec<TierId> = TierId::ALL
.iter()
.filter(|t| t.is_cache_rung())
.copied()
.filter(|t| {
rung_occupancy(observation, *t)
.is_some_and(|ratio| ratio + f64::EPSILON >= settings.pressure_threshold)
})
.collect();
if budget > 0 {
plan.demote = observation
.census
.iter()
.filter(|v| {
v.state == EntryState::Ready
&& v.tier.is_cache_rung()
&& v.tier.as_usize() < crate::policy::LAST_CACHE_TIER.as_usize()
&& rung_pressure.contains(&v.tier)
&& heat.get(&v.identity).copied().unwrap_or(0) == 0
})
.map(|v| v.index)
.collect();
}
plan
}
#[cfg(test)]
mod tests {
use super::*;
fn observation_of(states: &[ContinuityState]) -> Observation {
use crate::continuity::ContinuityState as C;
let mut rung_state = [C::Healthy; 7];
for &t in &TierId::ALL {
if let Some(s) = states.get(t.as_usize()) {
rung_state[t.as_usize()] = *s;
}
}
Observation {
stats: PlaneStats::default(),
rung_capacity: [None; 7],
rung_state,
census: Vec::new(),
}
}
#[test]
fn the_planner_never_targets_the_authority_rung() {
testkit::proves!(
"autonomy.movement.respects_last_cache_rung",
"autonomy.sweeps.maintenance_legs_are_planned_unconditionally"
);
let mut obs = observation_of(&[]);
obs.rung_state[TierId::L5.as_usize()] = ContinuityState::Unavailable;
let plan = plan_tick(&obs, &HashMap::new(), &GovernorSettings::default());
for tier in &plan.rebind {
assert!(
tier.is_cache_rung(),
"{tier} is not a cache rung and must never be rebound or moved to"
);
}
}
#[test]
fn rebind_binds_worst_rungs_under_budget() {
let mut obs = observation_of(&[]);
obs.rung_state[TierId::L2.as_usize()] = ContinuityState::Unavailable;
obs.rung_state[TierId::L1.as_usize()] = ContinuityState::Degraded;
obs.rung_state[TierId::L0.as_usize()] = ContinuityState::Degraded;
let settings = GovernorSettings {
max_rebinds_per_tick: 2,
..GovernorSettings::default()
};
let plan = plan_tick(&obs, &HashMap::new(), &settings);
assert_eq!(plan.rebind.len(), 2);
assert!(plan.rebind.contains(&TierId::L2), "unavailable runs first");
}
#[test]
fn a_missing_census_is_a_stated_skip() {
testkit::proves!("autonomy.movement.has_no_key_of_its_own");
let obs = observation_of(&[]);
let plan = plan_tick(&obs, &HashMap::new(), &GovernorSettings::default());
assert_eq!(plan.skipped, vec![Skip::NoCensus]);
}
#[test]
fn a_missing_heat_source_is_a_stated_skip() {
testkit::proves!("autonomy.movement.requires_observed_heat");
let mut obs = observation_of(&[]);
obs.census.push(CensusEntryView {
index: 0,
identity: KeyIdentity::of(b"k"),
tier: TierId::L1,
state: EntryState::Ready,
});
let plan = plan_tick(&obs, &HashMap::new(), &GovernorSettings::default());
assert_eq!(plan.skipped, vec![Skip::NoHeatSource]);
}
#[test]
fn occupancy_is_none_for_a_rung_without_capacity_accounting() {
let mut obs = observation_of(&[]);
obs.rung_capacity[TierId::L0.as_usize()] = Some(10);
obs.rung_capacity[TierId::L3.as_usize()] = None;
obs.stats.by_tier[TierId::L0.as_usize()] = 3;
assert!(rung_occupancy(&obs, TierId::L3).is_none());
assert!(rung_occupancy(&obs, TierId::L0).is_some());
}
#[test]
fn hot_keys_are_promoted_coldest_ones_demoted_only_under_pressure() {
let mut obs = observation_of(&[]);
let hot_id = KeyIdentity::of(b"hot");
let cold_id = KeyIdentity::of(b"cold");
obs.census.push(CensusEntryView {
index: 0,
identity: hot_id,
tier: TierId::L2,
state: EntryState::Ready,
});
obs.census.push(CensusEntryView {
index: 1,
identity: cold_id,
tier: TierId::L2,
state: EntryState::Ready,
});
obs.rung_capacity[TierId::L2.as_usize()] = Some(10);
obs.stats.by_tier[TierId::L2.as_usize()] = 9;
let mut heat = HashMap::new();
heat.insert(hot_id, 20_u64);
let settings = GovernorSettings {
max_moves_per_tick: 8,
hot_heat: 2,
..GovernorSettings::default()
};
let plan = plan_tick(&obs, &heat, &settings);
assert_eq!(plan.promote, vec![0], "the hot key is promoted");
assert_eq!(
plan.demote,
vec![1],
"the cold key is demoted: rung pressures"
);
}
#[test]
fn the_move_budget_saturates() {
testkit::proves!("autonomy.movement.move_budget_is_enforced");
let mut obs = observation_of(&[]);
let mut heat = HashMap::new();
for i in 0..10_u64 {
let id = KeyIdentity::of(format!("hot-{i}").as_bytes());
heat.insert(id, 50_u64);
obs.census.push(CensusEntryView {
index: i as usize,
identity: id,
tier: TierId::L2,
state: EntryState::Ready,
});
}
let settings = GovernorSettings::default();
let plan = plan_tick(&obs, &heat, &settings);
assert!(plan.promote.len() <= settings.max_moves_per_tick);
assert!(plan.demote.len() + plan.promote.len() <= settings.max_moves_per_tick);
}
#[test]
fn an_entry_sitting_on_the_authority_is_never_planned() {
let mut obs = observation_of(&[]);
let mut heat = HashMap::new();
let id = KeyIdentity::of(b"authority-entry");
heat.insert(id, 100_u64);
obs.census.push(CensusEntryView {
index: 0,
identity: id,
tier: TierId::L6,
state: EntryState::Ready,
});
let settings = GovernorSettings::default();
let plan = plan_tick(&obs, &heat, &settings);
assert_eq!(plan.promote, Vec::<usize>::new());
assert_eq!(plan.demote, Vec::<usize>::new());
}
}