From ae34ebb3b70d529e93ea423f50b46a4ade15259b Mon Sep 17 00:00:00 2001 From: tv Date: Tue, 20 Oct 2026 00:20:01 +0200 Subject: add compaction coordinator --- src/compaction/mod.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) (limited to 'src/compaction/mod.rs') diff --git a/src/compaction/mod.rs b/src/compaction/mod.rs index 703764b..5608eae 100644 --- a/src/compaction/mod.rs +++ b/src/compaction/mod.rs @@ -1,6 +1,90 @@ //! Compaction: replacing the document with a snapshot in a new epoch. //! -//! Step 3 provides the deterministic snapshot build. The protocol (proposal, settle, -//! commit, switch) and the admin API follow in step 4. +//! * `snapshot`: deterministic snapshot build (step 3). +//! * `records`: the replicated compaction records under `_continuum/compactions/`. +//! * `coordinator`: proposal, settle period and decision on the node that was asked. +//! * `api`: `/admin/compaction(s)` handlers. +//! +//! Step 4 decides compactions but does not switch epochs yet; a committed record only +//! names the new epoch. +//! +//! The rest of the node is reached exclusively through [`Host`], so this module never +//! depends on `AppState`, peers or sync internals. +mod api; +mod coordinator; +mod records; pub(crate) mod snapshot; + +use std::future::Future; +use std::sync::Arc; +use std::time::Duration; + +use automerge::AutoCommit; +use axum::{ + routing::{get, post}, + Router, +}; +use tokio::sync::broadcast; + +use crate::replica::Epoch; +use crate::sync::ChangeSet; + +/// What compaction needs from the node. Implemented by `AppState`. +/// +/// Used generically (`H: Host`), never as `dyn Host`, so the generic methods are fine. +pub(crate) trait Host: Clone + Send + Sync + 'static { + /// This node's peer ID. + fn node_id(&self) -> String; + + /// The epoch of the local replica. + fn current_epoch(&self) -> Epoch; + + /// Lower bound and default basis for the settle period. + fn dormant_after(&self) -> Duration; + + fn is_node_ready(&self) -> impl Future + Send; + + /// Known (not forgotten) peers. + fn peer_summaries(&self) -> impl Future> + Send; + + /// Store paths touched by each published document change. + fn subscribe_changes(&self) -> broadcast::Receiver>; + + /// Reads the document on the blocking pool. + fn read_doc(&self, f: F) -> impl Future + Send + where + F: FnOnce(&AutoCommit) -> T + Send + 'static, + T: Send + 'static; + + /// Mutable access to the document without publishing a change (e.g. forks). + fn update_doc(&self, f: F) -> impl Future + Send + where + F: FnOnce(&mut AutoCommit) -> T + Send + 'static, + T: Send + 'static; + + /// One committed change (rolled back on error), published and replicated. + fn commit_doc(&self, f: F) -> impl Future> + Send + where + F: FnOnce(&mut AutoCommit) -> Result + Send + 'static, + T: Send + 'static, + E: Send + 'static; +} + +#[derive(Clone, Debug)] +pub(crate) struct PeerSummary { + pub(crate) id: String, + /// Not dormant. + pub(crate) active: bool, +} + +/// Admin routes. Merged into the authenticated part of the node's router. +pub(crate) fn router() -> Router { + Router::new() + .route("/admin/compaction", post(api::start::)) + .route("/admin/compactions", get(api::list::)) + .route( + "/admin/compactions/{id}", + get(api::get::).delete(api::abort::), + ) +} -- cgit v1.3.1