diff options
| author | tv <tv@krebsco.de> | 2026-11-01 13:40:38 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-01 13:40:38 +0100 |
| commit | 3abb37431115f1402b54353ccd47a5b701b195e1 (patch) | |
| tree | 10534ed0bba7b8d90d586a47d2b6613e506f184f | |
| parent | 4bd32b46408690682c9c521b8744e8e02d261c04 (diff) | |
add both partitions commit test
| -rw-r--r-- | src/simtest/remakes.rs | 77 |
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))); +} |
