From 43305f5a0e2a87c68405989600b33e33c1ab70df Mon Sep 17 00:00:00 2001 From: tv Date: Fri, 6 Nov 2026 07:04:33 +0100 Subject: partition registrations tested --- src/simtest/remakes.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/simtest/remakes.rs b/src/simtest/remakes.rs index 4a193aa..ec83c40 100644 --- a/src/simtest/remakes.rs +++ b/src/simtest/remakes.rs @@ -479,6 +479,70 @@ async fn both_sides_of_an_old_partition_commit_and_converge() { assert_eq!(read(&e, "data").await, Some(json!(1))); } +/// Electorates widened on one side: with three members and c cut off, d and e join +/// through c and register there, so c's side counts 5 members (3 of them on its side) +/// and a's side 3 (2 on its side). Each side's remake gets a majority of the electorate +/// it sees, and both commit (`remakes.md`, Limits). Once the partition heals, the swarm +/// ends on one epoch: the one more nodes are on. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn members_registered_during_a_partition_commit_on_their_side_and_converge() { + use crate::remake::Host; + + let sim = Sim::new(40); + let (a, b, c) = three(&sim).await; + write(&a, "data", json!(1)).await; + sim.wait_converged(&[&a, &b, &c], SOON).await; + + let cut = LinkParams { + loss: 1.0, + ..LinkParams::default() + }; + let link = |params: &LinkParams| { + for one in ["a", "b"] { + for other in ["c", "d", "e"] { + sim.net.set_link(one, other, params.clone()); + sim.net.set_link(other, one, params.clone()); + } + } + }; + link(&cut); + let [d, e] = ["d", "e"].map(|id| sim.start(id, &["c"], Start::Join)); + sim.wait_ready(&[&d, &e], SOON).await; + let (a_ref, c_ref) = (&a, &c); + sim.wait_until("c's electorate is all five", SOON, move || async move { + let doc = c_ref.doc.read().await; + c_ref.electorate(&doc.doc, c_ref.now_millis()).len() == 5 + }) + .await; + { + let doc = a_ref.doc.read().await; + assert_eq!(a_ref.electorate(&doc.doc, a_ref.now_millis()).len(), 3); + } + + propose(&a, json!({ "kind": "compact" })).await; + propose(&c, json!({ "kind": "compact" })).await; + on_generation(&sim, &[&a, &b], 1).await; + on_generation(&sim, &[&c, &d, &e], 1).await; + let larger = same_epoch(&[&c, &d, &e]); + assert_ne!(same_epoch(&[&a, &b]), larger, "two epochs of generation 1"); + + link(&LinkParams::default()); + let all = [&a, &b, &c, &d, &e]; + let all_ref = &all; + sim.wait_until("every node is on c's epoch", REMAKE, move || async move { + for node in all_ref { + if node.local_epoch() != larger || !node.is_ready().await { + return false; + } + } + true + }) + .await; + for node in all { + assert_eq!(read(node, "data").await, Some(json!(1))); + } +} + /// A node whose clock is ahead sees a remake expire early: aborting it from there is /// refused while the node knows its clock is off (`?force=clock` overrides), as its /// coordinator, alive, may still commit it. -- cgit v1.3.1