aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/mod.rs')
-rw-r--r--src/remake/mod.rs115
1 files changed, 90 insertions, 25 deletions
diff --git a/src/remake/mod.rs b/src/remake/mod.rs
index ca7f5a0..9351cc5 100644
--- a/src/remake/mod.rs
+++ b/src/remake/mod.rs
@@ -5,7 +5,9 @@
//! where writes made after the base go): `compact`, `replace` and `import`.
//! * `snapshot`: deterministic build of the next epoch's initial document.
//! * `records`: the replicated remake records under `_continuum/remakes/<id>`.
-//! * `coordinator`: proposal, vote count and decision on the node that was asked.
+//! * `proposal`: writing a proposal (its checks, superseding expired remakes), and when
+//! a node other than the coordinator may abort an expired one.
+//! * `coordinator`: vote count and decision on the node that was asked.
//! * `vote`: who votes, for which remake, and when a remake has its majority.
//! * `content`: the new document of a `replace`/`import` remake, fetched by every voter.
//! * `switch`: switching this node to a committed epoch (worker).
@@ -20,12 +22,15 @@
//! depends on `AppState`, peers or sync internals.
mod api;
+#[cfg(test)]
+mod bench;
mod catchup;
mod content;
mod coordinator;
mod delta;
mod kind;
mod local;
+mod proposal;
mod quarantine;
mod records;
pub(crate) mod snapshot;
@@ -34,6 +39,7 @@ mod vote;
#[cfg(test)]
pub(crate) use api::testing;
+pub(crate) use delta::Superseded;
pub(crate) use kind::remake_genesis;
pub(crate) use local::RemakeState;
pub(crate) use switch::run_worker;
@@ -42,7 +48,6 @@ use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
-use automerge::AutoCommit;
use axum::{
Router,
extract::DefaultBodyLimit,
@@ -51,7 +56,8 @@ use axum::{
use tokio::sync::broadcast;
use crate::document::replica::Epoch;
-use crate::swarm::sync::ChangeSet;
+use crate::document::store::{ChangeSet, StoreError};
+use crate::document::{Document, Transaction};
/// Remakes and their aftermath on this node, in numbers (`/admin/stats`).
#[derive(Debug, Clone, Copy, Default)]
@@ -71,7 +77,7 @@ pub(crate) struct Summary {
}
/// The `Summary` of `remake` with the records of `doc`, whose epoch is `epoch`.
-pub(crate) fn summary(remake: &RemakeState, doc: &AutoCommit, epoch: Epoch) -> Summary {
+pub(crate) fn summary(remake: &RemakeState, doc: &Document, epoch: Epoch) -> Summary {
let local = records::EpochRecord::from(epoch);
let mut summary = Summary {
rejoining: remake.is_rejoining(),
@@ -120,16 +126,24 @@ pub(crate) trait Host: Clone + Send + Sync + 'static {
/// Default basis for the voting period, and the grace margin after it.
fn dormant_after(&self) -> Duration;
+ /// This node's clock against its peers' (`swarm::skew`): a coordinator judges the
+ /// remake's deadlines by it.
+ fn clock_skew(&self) -> impl Future<Output = crate::swarm::skew::Skew> + Send;
+
/// The electorate of a remake proposed now (`vote`): the members that said they are
/// alive recently enough, read from `doc`.
- fn electorate(&self, doc: &AutoCommit, now: crate::clock::UnixMillis) -> Vec<String>;
+ fn electorate(
+ &self,
+ doc: &impl crate::document::ReadDoc,
+ now: crate::clock::UnixMillis,
+ ) -> Vec<String>;
/// Downloads a change from peers on the local epoch, in chunks from several at once,
/// checked against its hash.
fn fetch_change(
&self,
- hash: automerge::ChangeHash,
- ) -> impl Future<Output = anyhow::Result<automerge::Change>> + Send;
+ hash: crate::document::ChangeHash,
+ ) -> impl Future<Output = anyhow::Result<crate::document::Change>> + Send;
/// Downloads content of `size` bytes by its BLAKE3 hash from peers on the local
/// epoch, checked against it.
@@ -147,16 +161,44 @@ pub(crate) trait Host: Clone + Send + Sync + 'static {
/// Known (not forgotten) peers.
fn peer_summaries(&self) -> impl Future<Output = Vec<PeerSummary>> + Send;
- /// Stops serving requests (back to `Joining`, keeping the swarm ID), as while a
- /// rejoin syncs a fresh document. Returns whether the node was serving.
- fn suspend(&self) -> impl Future<Output = bool> + Send;
+ /// Starts a rejoin of `target`, under the document's write lock: the live document
+ /// becomes an empty one of `target`, written by `actor`, that takes no writes until
+ /// the rejoin ends. A ready node stops being ready (back to `Joining`, keeping the
+ /// swarm ID), and its document so far answers reads (marked stale) and takes its
+ /// writes meanwhile; a node that wasn't ready gets its document back. Afterwards
+ /// the node resets every peer's sync state and publishes the change.
+ fn begin_rejoin(
+ &self,
+ target: Epoch,
+ actor: crate::document::ActorId,
+ ) -> impl Future<Output = BegunRejoin> + Send;
+
+ /// A copy of the document before the rejoin, with the writes it took since, if the
+ /// node keeps it ([`Host::begin_rejoin`]: it was ready).
+ fn rejoin_document(&self) -> impl Future<Output = Option<Document>> + Send;
- /// Serves requests again after [`Host::suspend`].
- fn resume(&self) -> impl Future<Output = ()> + Send;
+ /// Ends a rejoin on its target, under the write lock: `f` writes into the live
+ /// document from the one before the rejoin (the node's, or `old` where it keeps
+ /// none), as many changes as it likes; then writes go to the live document again,
+ /// and a node that was ready is again. Returns what `f` returned and the document
+ /// before the rejoin.
+ fn finish_rejoin<T, F>(
+ &self,
+ old: Option<Document>,
+ f: F,
+ ) -> impl Future<Output = (T, Document)> + Send
+ where
+ F: FnOnce(&mut Document, &Document) -> T + Send + 'static,
+ T: Send + 'static;
- /// 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)>);
+ /// Ends a rejoin by going back, under the write lock: the document before it (the
+ /// node's, with the writes it took meanwhile, or `old`) and `previous` are live
+ /// again, and a node that was ready is again. Afterwards as [`Host::begin_rejoin`].
+ fn abandon_rejoin(
+ &self,
+ previous: Epoch,
+ old: Option<Document>,
+ ) -> impl Future<Output = ()> + Send;
/// Whether the document caught up with a peer since the last reset.
fn initial_sync_done(&self) -> bool;
@@ -170,15 +212,27 @@ pub(crate) trait Host: Clone + Send + Sync + 'static {
/// 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,
+ F: FnOnce(&Document) -> T + Send + 'static,
T: Send + 'static;
- /// One committed change (rolled back on error), published and replicated.
+ /// One committed change (rolled back on error), published and replicated. While a
+ /// rejoin runs, into the document before it, or refused (`StoreError::Rejoining`)
+ /// by a node that keeps none (`AppState::mutate_doc`).
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,
+ F: FnOnce(&mut Transaction<'_>) -> Result<T, E> + Send + 'static,
T: Send + 'static,
- E: Send + 'static;
+ E: From<StoreError> + Send + 'static;
+
+ /// What a replay does with writes a newer one superseded (`--superseded-writes`).
+ fn superseded(&self) -> Superseded;
+
+ /// Applies a change downloaded from peers (`fetch_change`), then publishes it like
+ /// a change received by sync.
+ fn apply_change(
+ &self,
+ change: crate::document::Change,
+ ) -> impl Future<Output = Result<(), String>> + Send;
/// 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
@@ -187,12 +241,23 @@ pub(crate) trait Host: Clone + Send + Sync + 'static {
&self,
epoch: Epoch,
f: F,
- ) -> impl Future<Output = Result<AutoCommit, E>> + Send
+ ) -> impl Future<Output = Result<Document, E>> + Send
where
- F: FnOnce(&mut AutoCommit) -> Result<AutoCommit, E> + Send + 'static,
+ F: FnOnce(&Document) -> Result<Document, E> + Send + 'static,
E: Send + 'static;
}
+/// What [`Host::begin_rejoin`] left the rejoin with.
+pub(crate) struct BegunRejoin {
+ /// The node was ready, and is again once the rejoin ends.
+ pub(crate) was_ready: bool,
+ /// The heads of the document before the rejoin as it began: what changes after
+ /// them, it took during the rejoin.
+ pub(crate) heads: Vec<crate::document::ChangeHash>,
+ /// The document before the rejoin, where the node doesn't keep it (it wasn't ready).
+ pub(crate) old: Option<Document>,
+}
+
#[derive(Clone, Debug)]
pub(crate) struct PeerSummary {
pub(crate) id: String,
@@ -222,12 +287,12 @@ impl PeerSummary {
}
}
-/// Admin routes. Merged into the authenticated part of the node's router.
-/// Largest remake request (bytes). The proposal carries the new document to every
-/// node in one sync message, which the transport must deliver within its delivery
-/// limit, so documents near this size are already a stretch.
+/// Largest remake request (bytes). The coordinator holds the new document in memory
+/// and every member downloads it in chunks before voting (`content`), so documents near
+/// this size cost every node that much memory, and the swarm that many bytes per node.
const REMAKE_REQUEST_LIMIT: usize = 64 * 1024 * 1024;
+/// Admin routes. Merged into the authenticated part of the node's router.
pub(crate) fn router<H: Host>() -> Router<H> {
Router::new()
.route(