aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-01 13:40:38 +0100
committertv <tv@krebsco.de>2026-11-01 13:40:38 +0100
commit83ed4e966bf4b7a2d145264ccb086d3ce3e3dd36 (patch)
tree99752adb9d8d7597cdd0cab432ab452924ed22db /src
parentc426e22fc4d17585cf90101a5b5a60bbc58ca7a0 (diff)
add both partitions commit test
Diffstat (limited to 'src')
-rw-r--r--src/simtest/remakes.rs77
1 files changed, 76 insertions, 1 deletions
diff --git a/src/simtest/remakes.rs b/src/simtest/remakes.rs
index b109f0d..2a53c65 100644
--- a/src/simtest/remakes.rs
+++ b/src/simtest/remakes.rs
@@ -1,7 +1,8 @@
//! Remakes on whole nodes (`docs/remakes.md`): a compaction everywhere, competing
//! proposals across a partition, a node that misses the switch, a coordinator that is
//! gone, a skewed clock, writes while a node starts to rejoin, and two remakes committed
-//! for one epoch (a coordinator cut off after it committed).
+//! for one epoch (a coordinator cut off after it committed; electorates of a partition
+//! older than the electorate window).
//!
//! The simulated nodes' timings make a remake quick: settle 2 s (two `dormant_after`),
//! switch delay a keepalive, votes within half a keepalive.
@@ -370,3 +371,77 @@ async fn coordinator_cut_off_after_committing_rejoins_the_others() {
.await;
assert_eq!(read(&a, "data").await, Some(json!(1)));
}
+
+/// Electorates that don't overlap: on one side of a partition the other side's alive
+/// stamps look older than `--electorate-window` (here by the clocks of d and e, a month
+/// ahead, as a partition that long would make them), so each side's remake gets a
+/// majority of its own electorate and both commit. Once the partition heals, the two
+/// nodes of the smaller side rejoin the larger side's epoch.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn both_sides_of_an_old_partition_commit_and_converge() {
+ use crate::remake::Host;
+
+ let sim = Sim::new(38);
+ let a = sim.start("a", &[], Start::Genesis);
+ let [b, c, d, e] = ["b", "c", "d", "e"].map(|id| sim.start(id, &["a"], Start::Join));
+ let all = [&a, &b, &c, &d, &e];
+ sim.wait_ready(&all, SOON).await;
+ let nodes = [a.clone(), b.clone(), c.clone(), d.clone(), e.clone()];
+ sim.wait_until("every node is registered", SOON, || {
+ let nodes = nodes.clone();
+ async move {
+ for node in &nodes {
+ if crate::swarm::members::members(&node.doc.read().await.doc).len() < 5 {
+ return false;
+ }
+ }
+ true
+ }
+ })
+ .await;
+ write(&a, "data", json!(1)).await;
+ sim.wait_converged(&all, SOON).await;
+
+ let cut = LinkParams {
+ loss: 1.0,
+ ..LinkParams::default()
+ };
+ let link = |params: &LinkParams| {
+ for one in ["a", "b", "c"] {
+ for other in ["d", "e"] {
+ sim.net.set_link(one, other, params.clone());
+ sim.net.set_link(other, one, params.clone());
+ }
+ }
+ };
+ link(&cut);
+ let month = 31 * 24 * 3600 * 1000;
+ d.clock.set_offset_ms(month);
+ e.clock.set_offset_ms(month);
+ // d and e renew their stamps (by their clocks, theirs were a month old), and count
+ // only themselves.
+ let d_ref = &d;
+ sim.wait_until("d's electorate is d and e", SOON, move || async move {
+ let doc = d_ref.doc.read().await;
+ d_ref.electorate(&doc.doc, d_ref.now_millis()).len() == 2
+ })
+ .await;
+
+ propose(&a, json!({ "kind": "compact" })).await;
+ propose(&d, json!({ "kind": "compact" })).await;
+ on_generation(&sim, &[&a, &b, &c], 1).await;
+ on_generation(&sim, &[&d, &e], 1).await;
+ let larger = same_epoch(&[&a, &b, &c]);
+ assert_ne!(same_epoch(&[&d, &e]), larger, "two epochs of generation 1");
+
+ link(&LinkParams::default());
+ let (d_ref, e_ref) = (&d, &e);
+ sim.wait_until("d and e are on a's epoch", REMAKE, move || async move {
+ d_ref.local_epoch() == larger
+ && e_ref.local_epoch() == larger
+ && d_ref.is_ready().await
+ && e_ref.is_ready().await
+ })
+ .await;
+ assert_eq!(read(&e, "data").await, Some(json!(1)));
+}