aboutsummaryrefslogtreecommitdiffstats
path: root/src/config
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-06 19:54:09 +0100
committertv <tv@krebsco.de>2026-11-06 19:54:09 +0100
commit5994b49412c058079639585501a78e6458fdbb6f (patch)
treeca78aa3cd7d0d6d2a48bfa42d61afcddb8efd6df /src/config
parent7c0c318dca3054be6c7bacf5a4b3f5a4e47cc949 (diff)
HyParView (slim)
Diffstat (limited to 'src/config')
-rw-r--r--src/config/mod.rs77
1 files changed, 75 insertions, 2 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 5f6634f..b7c7ff2 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -275,7 +275,7 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
Kind::Integer,
"COUNT",
Some("12"),
- "Neighbours each member keeps (at least 2), on a genesis node: recorded in the document as the swarm's, which every node uses (a protocol writer may change it there). Elsewhere it counts only in a swarm that recorded none (one from before), where it must be the same on every node. Swarms of up to this many + 1 nodes are a full mesh; larger ones sync over several hops.",
+ "Neighbours each member keeps (at least 2), on a genesis node: recorded in the document as the swarm's, which every node uses (a protocol writer may change it there). Elsewhere it counts only in a swarm that recorded none (one from before), where it must be the same on every node. Swarms of up to this many + 1 nodes are a full mesh; larger ones sync over several hops. With topology hyparview, the active view's size, 5 unless set (docs/topology.md).",
),
option(
"electorate-window",
@@ -285,11 +285,18 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
"Members that renewed their alive stamp within this time vote on remakes (a majority of them must agree). At least 7h. Members renew their stamps every seventh of the shortest window any of them publishes, hourly at most, so nodes may differ. Matters on the node that coordinates a remake.",
),
option(
+ "topology",
+ Kind::Text,
+ "ring-and-pairs|hyparview",
+ Some("ring-and-pairs"),
+ "How a node's neighbours are chosen: by the graph every node computes alike from the member list, or by HyParView (an active view of neighbours each agreed to, the others asked from the member list as neighbours fail). A genesis node records its own in the document; every node follows what the swarm records, which a protocol writer may change (_continuum/topology), and uses its own only where the swarm records none it knows. With hyparview, what needs the shared graph does nothing (writers' trees: use plumtree).",
+ ),
+ option(
"dissemination",
Kind::Text,
"writers-trees|plumtree",
Some("writers-trees"),
- "How a change reaches every member: along writers' trees computed alike on every node from the member list, or by PlumTree (eager links pruned where they deliver twice, grafted where announcements had to bring a change). For experiments: every node of a swarm must use the same one.",
+ "How a change reaches every member: along writers' trees computed alike on every node from the member list, or by PlumTree (eager links pruned where they deliver twice, grafted where announcements had to bring a change). A genesis node records its own in the document; every node follows what the swarm records, which a protocol writer may change (_continuum/dissemination), and uses its own only where the swarm records none it knows. Default: plumtree with topology hyparview, else writers-trees (docs/topology.md).",
),
option(
"superseded-writes",
@@ -583,6 +590,8 @@ pub(crate) struct Settings {
)]
pub(crate) electorate_window: Duration,
#[serde(default)]
+ pub(crate) topology: crate::swarm::topology::Kind,
+ #[serde(default)]
pub(crate) dissemination: crate::swarm::dissemination::Kind,
#[serde(default)]
pub(crate) superseded_writes: crate::remake::Superseded,
@@ -1538,6 +1547,67 @@ pub(crate) fn load() -> Result<Loaded> {
Ok(loaded)
}
+/// HyParView's active view, where `neighbours` isn't set: the paper's, for swarms of up
+/// to about 10 000 members (its fanout of 4, plus one).
+const HYPARVIEW_NEIGHBOURS: usize = 5;
+
+/// Defaults that depend on other options, for those not `set`: with `topology
+/// hyparview`, `dissemination plumtree` (writers' trees need the graph every node shares,
+/// which HyParView has none of) and `neighbours` `HYPARVIEW_NEIGHBOURS`.
+fn derive_defaults(settings: &mut Settings, set: &dyn Fn(&str) -> bool) {
+ use crate::swarm::dissemination::Kind as Dissemination;
+ use crate::swarm::topology::Kind as Topology;
+ if settings.topology == Topology::Hyparview {
+ if !set("dissemination") {
+ settings.dissemination = Dissemination::Plumtree;
+ info!("topology hyparview: dissemination plumtree, as none is set");
+ }
+ if !set("neighbours") {
+ settings.neighbours = HYPARVIEW_NEIGHBOURS;
+ info!(
+ neighbours = HYPARVIEW_NEIGHBOURS,
+ "topology hyparview: an active view of HyParView's size, as neighbours isn't set"
+ );
+ }
+ }
+}
+
+/// Combinations of options that work, but not as well as others (`docs/topology.md`):
+/// warned of at start.
+fn warn_combinations(settings: &Settings, set: &dyn Fn(&str) -> bool) {
+ use crate::swarm::dissemination::Kind as Dissemination;
+ use crate::swarm::topology::Kind as Topology;
+ match (settings.topology, settings.dissemination) {
+ (Topology::Hyparview, Dissemination::WritersTrees) => warn!(
+ "topology hyparview with dissemination writers-trees: writers' trees need the graph every node shares, which HyParView has none of, so changes pass on by announcements alone, a round and a pull per hop; dissemination plumtree is what goes with it"
+ ),
+ (Topology::RingAndPairs, Dissemination::Plumtree) => warn!(
+ "dissemination plumtree with topology ring-and-pairs: an experiment; writers' trees, on the graph every node shares, were faster in every scenario benchmarked (docs/topology.md)"
+ ),
+ _ => {}
+ }
+ if settings.topology == Topology::RingAndPairs && settings.neighbours == 2 {
+ warn!(
+ "neighbours 2: the member list's graph is its ring alone, and a change takes about half as many hops as there are members"
+ );
+ }
+ if settings.topology == Topology::Hyparview && settings.neighbours == 2 {
+ warn!(
+ "neighbours 2 with topology hyparview: a member whose two neighbours fail at once is cut off until it asks others"
+ );
+ }
+ if !settings.genesis {
+ for name in ["neighbours", "topology", "dissemination"] {
+ if set(name) {
+ info!(
+ option = name,
+ "Set on a node that isn't the genesis node: the swarm's own (recorded by its genesis node) counts, this only where it records none"
+ );
+ }
+ }
+ }
+}
+
fn resolve(merged: Layer, files: Vec<PathBuf>) -> Result<Loaded> {
// An override is for the start it was given for; in a file it would silently apply
// to every start, e.g. replacing the document file with a backup each time.
@@ -1568,6 +1638,9 @@ fn resolve(merged: Layer, files: Vec<PathBuf>) -> Result<Loaded> {
settings.alias = settings.alias.map(|alias| alias.trim().to_owned());
settings.transports = settings.ordered_transports();
settings.check().context("invalid configuration")?;
+ let set = |name: &str| merged.get(name).is_some_and(|entry| !entry.value.is_null());
+ derive_defaults(&mut settings, &set);
+ warn_combinations(&settings, &set);
// No dashboard to be reached there: none is published (and one published before is
// removed, `swarm::members`).
if !cfg!(feature = "dashboard")