diff options
Diffstat (limited to 'src/remake/mod.rs')
| -rw-r--r-- | src/remake/mod.rs | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/remake/mod.rs b/src/remake/mod.rs new file mode 100644 index 0000000..d971fcf --- /dev/null +++ b/src/remake/mod.rs @@ -0,0 +1,150 @@ +//! Remakes: coordinated moves of the whole cluster to a new document, a new epoch. +//! Compaction is one: the new document has the old one's content without its history. +//! +//! * `snapshot`: deterministic build of the next epoch's initial document. +//! * `records`: the replicated remake records under `_continuum/remakes/<id>`. +//! * `coordinator`: proposal, settle period and decision on the node that was asked. +//! * `switch`: switching this node to a committed epoch (worker). +//! * `catchup`: rejoining a newer epoch from peers (late, new or failed nodes), then +//! replaying or quarantining this node's own missing changes. +//! * `quarantine`: inspecting, replaying or discarding quarantined changes. +//! * `delta`: replaying this node's changes since the base into the new epoch. +//! * `local`: per-node in-memory state (prebuilt snapshots, switch status, archive). +//! * `api`: `/admin/remakes` handlers. +//! +//! The rest of the node is reached exclusively through [`Host`], so this module never +//! depends on `AppState`, peers or sync internals. + +mod api; +mod catchup; +mod coordinator; +mod delta; +mod local; +mod quarantine; +mod records; +pub(crate) mod snapshot; +mod switch; + +pub(crate) use local::RemakeState; +pub(crate) use switch::run_worker; + +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::document::replica::Epoch; +use crate::swarm::sync::ChangeSet; + +/// What remake 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 in-memory remake state. + fn remake(&self) -> &RemakeState; + + /// 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<Output = bool> + Send; + + /// Known (not forgotten) peers. + fn peer_summaries(&self) -> impl Future<Output = Vec<PeerSummary>> + Send; + + /// Stops serving requests (back to `Joining`, keeping the cluster ID), as while a + /// rejoin syncs a fresh document. Returns whether the node was serving. + fn suspend(&self) -> impl Future<Output = bool> + Send; + + /// Serves requests again after [`Host::suspend`]. + fn resume(&self) -> impl Future<Output = ()> + Send; + + /// Serves reads from this document (read-only, marked stale) while the node is not + /// ready; `None` ends it. + fn set_stale_view(&self, view: Option<(Epoch, AutoCommit)>); + + /// Whether the document caught up with a peer since the last reset. + fn initial_sync_done(&self) -> bool; + + /// Makes the next sync count as initial again. + fn reset_initial_sync(&self); + + /// Store paths touched by each published document change. + fn subscribe_changes(&self) -> broadcast::Receiver<Arc<ChangeSet>>; + + /// Reads the document on the blocking pool. + fn read_doc<T, F>(&self, f: F) -> impl Future<Output = T> + Send + where + F: FnOnce(&AutoCommit) -> T + Send + 'static, + T: Send + 'static; + + /// One committed change (rolled back on error), published and replicated. + fn commit_doc<T, E, F>(&self, f: F) -> impl Future<Output = Result<T, E>> + Send + where + F: FnOnce(&mut AutoCommit) -> Result<T, E> + Send + 'static, + T: Send + 'static, + E: Send + 'static; + + /// Replaces the document and its epoch atomically. Under the write lock, `f` gets + /// the live document and returns the new one. Afterwards the node resets every + /// peer's sync state and publishes the change. Returns the old document. + fn switch_epoch<E, F>( + &self, + epoch: Epoch, + f: F, + ) -> impl Future<Output = Result<AutoCommit, E>> + Send + where + F: FnOnce(&mut AutoCommit) -> Result<AutoCommit, E> + Send + 'static, + E: Send + 'static; +} + +#[derive(Clone, Debug)] +pub(crate) struct PeerSummary { + pub(crate) id: String, + /// Not dormant. + pub(crate) active: bool, + /// The epoch the peer last advertised. + pub(crate) epoch: Option<Epoch>, +} + +/// Admin routes. Merged into the authenticated part of the node's router. +pub(crate) fn router<H: Host>() -> Router<H> { + Router::new() + .route( + "/admin/remakes", + get(api::list::<H>) + .post(api::start::<H>) + .delete(api::abort_current::<H>), + ) + // The original endpoint, kept as a shortcut for a compacting remake. + .route("/admin/compaction", post(api::start::<H>)) + .route( + "/admin/remakes/{id}", + get(api::get::<H>).delete(api::abort::<H>), + ) + .route("/admin/epochs", get(api::epochs::<H>)) + .route("/admin/quarantine", get(quarantine::list::<H>)) + .route( + "/admin/quarantine/{id}", + get(quarantine::get::<H>).delete(quarantine::discard::<H>), + ) + .route( + "/admin/quarantine/{id}/document", + get(quarantine::document::<H>), + ) + .route( + "/admin/quarantine/{id}/replay", + post(quarantine::replay::<H>), + ) +} |
