aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-06 07:04:33 +0100
committertv <tv@krebsco.de>2026-11-06 07:04:33 +0100
commit43305f5a0e2a87c68405989600b33e33c1ab70df (patch)
treee77cd4b848e21fcb2e32b21b82bf56c118dbbec4
parent9914413ce61aaa6f6c64d138dd1ae5576d948758 (diff)
partition registrations tested
-rw-r--r--src/simtest/remakes.rs64
1 files changed, 64 insertions, 0 deletions
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.