summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/simtest/timing.rs35
-rw-r--r--src/transport/sim.rs8
2 files changed, 40 insertions, 3 deletions
diff --git a/src/simtest/timing.rs b/src/simtest/timing.rs
index df7849f..7810f28 100644
--- a/src/simtest/timing.rs
+++ b/src/simtest/timing.rs
@@ -10,7 +10,7 @@ use crate::document::replica::Epoch;
use crate::state::{AppState, Liveness};
use crate::swarm::bootstrap::Start;
use crate::transport::sim::LinkParams;
-use crate::transport::{HeadsSummary, HelloRequest, Keepalive, ParsedMessage, RunId};
+use crate::transport::{HeadsSummary, HelloRequest, Keepalive, MessageKind, ParsedMessage, RunId};
/// Long enough for anything that should happen within a few keepalives.
const SOON: Duration = Duration::from_secs(10);
@@ -85,6 +85,7 @@ async fn converges_over_lossy_links() {
loss: 0.15,
duplicate: 0.05,
delay: (Duration::from_millis(1), Duration::from_millis(30)),
+ ..LinkParams::default()
});
let a = sim.start("a", &[], Start::Genesis);
let b = sim.start("b", &["a"], Start::Join);
@@ -107,6 +108,38 @@ async fn converges_over_lossy_links() {
assert!(sim.net.counts().lost > 0);
}
+/// R24: a catch-up source that is alive (it answers handshakes and keepalives) but whose
+/// sync messages never arrive is given up after three unanswered sync messages and
+/// `delivery_limit` without one, and another peer takes over.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn stalled_catch_up_source_is_replaced() {
+ let sim = Sim::new(6);
+ let a = sim.start("a", &[], Start::Genesis);
+ let b = sim.start("b", &["a"], Start::Join);
+ let (a, b) = (&a, &b);
+ sim.wait_ready(&[a, b], SOON).await;
+ write(a, "data", json!("x")).await;
+ sim.wait_converged(&[a, b], SOON).await;
+
+ // c joins through a, so a becomes its source (the first ready peer it hears
+ // from); b it learns of from a.
+ sim.net.set_link(
+ "a",
+ "c",
+ LinkParams {
+ drop: vec![MessageKind::Sync],
+ ..LinkParams::default()
+ },
+ );
+ let c = sim.start("c", &["a"], Start::Join);
+ let c = &c;
+ sim.wait_ready(&[c], LOSSY).await;
+
+ assert_eq!(read(c, "data").await, Some(json!("x")));
+ let sources = c.stats.snapshot().catch_up_sources;
+ assert!(sources >= 2, "{sources} catch-up sources");
+}
+
/// A peer that hears nothing for `dormant_after` is dormant; once messages flow again
/// it is active, re-handshakes, and syncs what it missed.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
diff --git a/src/transport/sim.rs b/src/transport/sim.rs
index 3e0163f..1556d69 100644
--- a/src/transport/sim.rs
+++ b/src/transport/sim.rs
@@ -25,7 +25,7 @@ use tokio::sync::{mpsc, oneshot};
use tracing::{debug, warn};
use super::mycelium::{decode_message, encode_message};
-use super::{EncodedMessage, MessageHandler, ParsedMessage, Transport};
+use super::{EncodedMessage, MessageHandler, MessageKind, ParsedMessage, Transport};
/// How one direction of a link behaves.
#[derive(Clone, Debug)]
@@ -36,6 +36,9 @@ pub(crate) struct LinkParams {
pub(crate) duplicate: f64,
/// Delay, uniform in this range. Overlapping delays reorder messages.
pub(crate) delay: (Duration, Duration),
+ /// Messages of these kinds are always lost: a peer that answers some messages but
+ /// not others (e.g. keepalives but not sync).
+ pub(crate) drop: Vec<MessageKind>,
}
impl Default for LinkParams {
@@ -45,6 +48,7 @@ impl Default for LinkParams {
loss: 0.0,
duplicate: 0.0,
delay: (Duration::from_millis(1), Duration::from_millis(5)),
+ drop: Vec::new(),
}
}
}
@@ -155,7 +159,7 @@ impl SimNet {
.get(&(from.to_owned(), message.peer.clone()))
.unwrap_or(&inner.default_link)
.clone();
- if inner.rng.chance(params.loss) {
+ if params.drop.contains(&message.kind) || inner.rng.chance(params.loss) {
inner.counts.lost += 1;
return;
}