fix(cluster): prevent spurious heartbeat elections (#4194)
diff --git a/core/configs/src/server_config/message_bus.rs b/core/configs/src/server_config/message_bus.rs
index b37c17d..8ce3ea5 100644
--- a/core/configs/src/server_config/message_bus.rs
+++ b/core/configs/src/server_config/message_bus.rs
@@ -99,7 +99,8 @@
pub client_queue_capacity: usize,
/// Interval between outbound reconnect attempts to peers with
- /// `peer_id > self_id`.
+ /// `peer_id > self_id`. Also bounds each dial, so a peer that drops
+ /// SYNs cannot stall the sweep.
#[config_env(leaf)]
#[serde_as(as = "DisplayFromStr")]
pub reconnect_period: IggyDuration,
diff --git a/core/consensus/src/impls.rs b/core/consensus/src/impls.rs
index 64e2882..2f2fa03 100644
--- a/core/consensus/src/impls.rs
+++ b/core/consensus/src/impls.rs
@@ -1198,9 +1198,9 @@
timeouts: RefCell<TimeoutManager>,
- /// Monotonic timestamp from the most recent accepted commit heartbeat.
- /// Old/replayed commit messages with a lower timestamp are ignored.
- heartbeat_timestamp: Cell<u64>,
+ /// View and counter of the last emitted or accepted heartbeat. A new
+ /// primary's counter can be lower, so freshness is scoped to its view.
+ heartbeat_watermark: Cell<(u32, u64)>,
/// Time source for [`Self::next_monotonic_timestamp`]; see
/// [`ConsensusClock`].
@@ -1489,7 +1489,7 @@
sent_own_start_view_change: Cell::new(false),
sent_own_do_view_change: Cell::new(false),
timeouts: RefCell::new(TimeoutManager::new(timeout_seed)),
- heartbeat_timestamp: Cell::new(0),
+ heartbeat_watermark: Cell::new((0, 0)),
clock,
}
}
@@ -2837,8 +2837,8 @@
// After view change the new primary may have commit_min < commit_max
// until commit_journal catches up. Send commit_min (what we've
// actually applied) so backups don't advance past us.
- let ts = self.heartbeat_timestamp.get() + 1;
- self.heartbeat_timestamp.set(ts);
+ let ts = self.heartbeat_watermark.get().1 + 1;
+ self.heartbeat_watermark.set((self.view.get(), ts));
vec![VsrAction::SendCommit {
view: self.view.get(),
@@ -3495,8 +3495,8 @@
/// so it doesn't start a spurious view change. Returns `true` if
/// `commit_max` advanced, signalling the caller to run `commit_journal`.
///
- /// Only accepts heartbeats with a strictly newer monotonic timestamp
- /// to prevent old/replayed messages from suppressing view changes.
+ /// Only refreshes liveness for a newer heartbeat within its view, so
+ /// old/replayed messages cannot suppress view changes.
///
/// # Panics
/// If `header.group` does not match this replica's namespace.
@@ -3550,10 +3550,9 @@
return CommitOutcome::Accepted;
}
- // Only accept heartbeats with a strictly newer timestamp to prevent
- // old/replayed commit messages from resetting the timeout.
- if self.heartbeat_timestamp.get() < header.timestamp_monotonic {
- self.heartbeat_timestamp.set(header.timestamp_monotonic);
+ let heartbeat = (header.view, header.timestamp_monotonic);
+ if self.heartbeat_watermark.get() < heartbeat {
+ self.heartbeat_watermark.set(heartbeat);
self.timeouts
.borrow_mut()
.reset(TimeoutKind::NormalHeartbeat);
@@ -4593,6 +4592,7 @@
#[cfg(test)]
pub mod test_bus {
use super::{Command, METADATA_GROUP, Message, StartViewHeader};
+ use iggy_binary_protocol::{CommitHeader, ConsensusHeader};
use message_bus::{BusMessage, MessageBus};
use server_common::MESSAGE_ALIGN;
use server_common::iobuf::Frozen;
@@ -4629,6 +4629,26 @@
msg
}
+ /// A `Commit` heartbeat from `replica` in `view` stamped with `counter`.
+ ///
+ /// # Panics
+ /// Never: the header size fits in `u32`.
+ #[must_use]
+ pub fn make_commit(view: u32, replica: u8, counter: u64) -> Message<CommitHeader> {
+ Message::<CommitHeader>::new(std::mem::size_of::<CommitHeader>()).transmute_header(
+ |_, header: &mut CommitHeader| {
+ header.command = Command::Commit;
+ header.cluster = 1;
+ header.replica = replica;
+ header.view = view;
+ header.group = METADATA_GROUP;
+ header.timestamp_monotonic = counter;
+ header.size = u32::try_from(std::mem::size_of::<CommitHeader>()).unwrap();
+ header.seal();
+ },
+ )
+ }
+
/// A [`MessageBus`] that accepts everything and remembers nothing.
pub struct NoopBus;
@@ -5254,6 +5274,93 @@
);
}
+ #[test]
+ fn given_an_old_primary_when_the_new_primary_heartbeats_should_remain_in_the_new_view() {
+ for enters_election in [false, true] {
+ let consensus =
+ VsrConsensus::new(1, 0, 3, METADATA_GROUP, StageNoopBus, LocalPipeline::new());
+ consensus.init();
+ for _ in 0..TimeoutManager::NORMAL_HEARTBEAT_TICKS {
+ let _ = consensus.tick(PlaneKind::Metadata);
+ }
+ assert_eq!(
+ consensus.heartbeat_watermark.get(),
+ (
+ 0,
+ TimeoutManager::NORMAL_HEARTBEAT_TICKS / TimeoutManager::COMMIT_MESSAGE_TICKS
+ ),
+ "the old primary must have emitted a full timeout's worth of heartbeats"
+ );
+
+ if enters_election {
+ let _ = consensus.start_election(
+ PlaneKind::Metadata,
+ ViewChangeReason::NormalHeartbeatTimeout,
+ );
+ }
+ let start_view = test_bus::make_start_view(1, 0, 0, 1, 0);
+ let _ = consensus.handle_start_view(PlaneKind::Metadata, start_view.header(), &[]);
+
+ for tick in 1..=TimeoutManager::NORMAL_HEARTBEAT_TICKS * 2 {
+ if tick % TimeoutManager::COMMIT_MESSAGE_TICKS == 0 {
+ let heartbeat =
+ test_bus::make_commit(1, 1, tick / TimeoutManager::COMMIT_MESSAGE_TICKS);
+ consensus.handle_commit(heartbeat.header());
+ }
+ let _ = consensus.tick(PlaneKind::Metadata);
+ assert_eq!(
+ consensus.view(),
+ 1,
+ "fresh heartbeats must keep the adopted view alive at tick {tick}, \
+ enters_election={enters_election}"
+ );
+ }
+
+ for _ in 0..TimeoutManager::NORMAL_HEARTBEAT_TICKS {
+ let _ = consensus.tick(PlaneKind::Metadata);
+ }
+ assert_eq!(
+ consensus.view(),
+ 2,
+ "losing the new primary must still trigger an election"
+ );
+ }
+ }
+
+ #[test]
+ fn given_an_adopted_view_when_heartbeats_are_stale_or_foreign_should_time_out() {
+ let consensus =
+ VsrConsensus::new(1, 0, 3, METADATA_GROUP, StageNoopBus, LocalPipeline::new());
+ consensus.init();
+ let start_view = test_bus::make_start_view(1, 0, 0, 1, 0);
+ let _ = consensus.handle_start_view(PlaneKind::Metadata, start_view.header(), &[]);
+ let heartbeat = test_bus::make_commit(1, 1, 1);
+ consensus.handle_commit(heartbeat.header());
+ // Re-adopting the same view restarts the timer but must keep the
+ // watermark; the replayed heartbeat below proves it.
+ let _ = consensus.handle_start_view(PlaneKind::Metadata, start_view.header(), &[]);
+
+ let invalid_heartbeats = [
+ heartbeat,
+ test_bus::make_commit(1, 1, 0),
+ test_bus::make_commit(0, 0, u64::MAX),
+ test_bus::make_commit(1, 2, u64::MAX),
+ ];
+ for tick in 1..=TimeoutManager::NORMAL_HEARTBEAT_TICKS {
+ if tick % TimeoutManager::COMMIT_MESSAGE_TICKS == 0 {
+ for heartbeat in &invalid_heartbeats {
+ consensus.handle_commit(heartbeat.header());
+ }
+ }
+ let _ = consensus.tick(PlaneKind::Metadata);
+ }
+ assert_eq!(
+ consensus.view(),
+ 2,
+ "replayed, older-view, and non-primary heartbeats must not suppress an election"
+ );
+ }
+
// `observe_newer_view` is monotone: an older stamp cannot lower it.
#[test]
fn observe_newer_view_keeps_the_max() {
diff --git a/core/message_bus/src/connector.rs b/core/message_bus/src/connector.rs
index e1771f4..b7b4d60 100644
--- a/core/message_bus/src/connector.rs
+++ b/core/message_bus/src/connector.rs
@@ -44,15 +44,6 @@
use std::time::Duration;
use tracing::{debug, info};
-/// Default reconnect sweep period.
-///
-/// Equivalent to `MessageBusConfig::default().reconnect_period`; exposed
-/// as a named const for test / bench ergonomics. Kept in sync with the
-/// `MessageBusConfig::default` impl. Remove once the configs-crate
-/// migration lands and bootstrap always reads the period from
-/// `ServerConfig`.
-pub const DEFAULT_RECONNECT_PERIOD: Duration = Duration::from_secs(5);
-
/// Dial every peer with `peer_id > self_id` once, then launch a periodic
/// sweep in the background. The periodic task handle is tracked on the bus
/// so graceful shutdown can await it.
@@ -64,7 +55,7 @@
on_dialed: DialedReplicaFn,
reconnect_period: Duration,
) {
- connect_all(bus, self_id, &peers, &on_dialed).await;
+ connect_all(bus, self_id, &peers, &on_dialed, reconnect_period).await;
let handler = on_dialed.clone();
let token = bus.token();
@@ -89,6 +80,7 @@
self_id: u8,
peers: &[(u8, SocketAddr)],
on_dialed: &DialedReplicaFn,
+ connect_timeout: Duration,
) {
let dials = peers.iter().filter_map(|&(peer_id, addr)| {
if peer_id <= self_id {
@@ -116,7 +108,7 @@
);
return None;
}
- Some(connect_one(peer_id, addr, on_dialed))
+ Some(connect_one(peer_id, addr, on_dialed, connect_timeout))
});
// Dial concurrently so one unreachable peer's connect latency does not
// stall the rest. The futures share one task, so the `on_dialed`
@@ -136,7 +128,7 @@
token: ShutdownToken,
) {
while token.sleep_or_shutdown(period).await {
- connect_all(bus, self_id, &peers, &on_dialed).await;
+ connect_all(bus, self_id, &peers, &on_dialed, period).await;
}
debug!("replica reconnect periodic task exiting");
}
@@ -146,14 +138,28 @@
/// Connect failures are logged and swallowed; VSR tolerates missing
/// peers and the periodic sweep retries. The handshake (and its
/// `handshake_grace` bound) runs on the owning shard after delegation.
+///
+/// The dial is bounded by `connect_timeout`: the sweep joins every dial,
+/// so a peer that drops SYNs would otherwise hold the whole sweep for the
+/// kernel connect timeout (about two minutes on Linux) and starve the
+/// retries the heartbeat timeout depends on.
#[allow(clippy::future_not_send)]
-async fn connect_one(peer_id: u8, addr: SocketAddr, on_dialed: &DialedReplicaFn) {
- let stream = match TcpStream::connect(addr).await {
- Ok(s) => s,
- Err(e) => {
+async fn connect_one(
+ peer_id: u8,
+ addr: SocketAddr,
+ on_dialed: &DialedReplicaFn,
+ connect_timeout: Duration,
+) {
+ let stream = match compio::time::timeout(connect_timeout, TcpStream::connect(addr)).await {
+ Ok(Ok(s)) => s,
+ Ok(Err(e)) => {
debug!(replica = peer_id, %addr, "connect failed: {e}");
return;
}
+ Err(_) => {
+ debug!(replica = peer_id, %addr, ?connect_timeout, "connect timed out");
+ return;
+ }
};
if let Err(e) = stream.set_nodelay(true) {
debug!(replica = peer_id, %addr, "set_nodelay failed: {e}");
diff --git a/core/message_bus/tests/backpressure.rs b/core/message_bus/tests/backpressure.rs
index a329d50..d109c6b 100644
--- a/core/message_bus/tests/backpressure.rs
+++ b/core/message_bus/tests/backpressure.rs
@@ -29,7 +29,7 @@
set_replica_ctx,
};
use iggy_binary_protocol::Command;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::{IggyMessageBus, MessageBus, SendError};
use std::rc::Rc;
@@ -66,7 +66,14 @@
// bus0 dials bus1.
let on_message0: MessageHandler = Rc::new(|_, _| {});
let dial_0 = install_dialed_replicas_locally(bus0.clone(), on_message0);
- start_connector(&bus0, 0, vec![(1, addr1)], dial_0, DEFAULT_RECONNECT_PERIOD).await;
+ start_connector(
+ &bus0,
+ 0,
+ vec![(1, addr1)],
+ dial_0,
+ bus0.config().reconnect_period,
+ )
+ .await;
// Wait for the connection to register.
let deadline = std::time::Instant::now() + Duration::from_secs(2);
diff --git a/core/message_bus/tests/connection_lost_notify.rs b/core/message_bus/tests/connection_lost_notify.rs
index e3203dc..bc430e9 100644
--- a/core/message_bus/tests/connection_lost_notify.rs
+++ b/core/message_bus/tests/connection_lost_notify.rs
@@ -26,7 +26,7 @@
install_dialed_replicas_locally, install_replicas_locally, loopback, set_replica_ctx,
};
use message_bus::IggyMessageBus;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use std::cell::Cell;
use std::rc::Rc;
@@ -70,7 +70,7 @@
0,
vec![(1u8, addr1)],
dial_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
diff --git a/core/message_bus/tests/directional_connection.rs b/core/message_bus/tests/directional_connection.rs
index 4825f0f..a999cf4 100644
--- a/core/message_bus/tests/directional_connection.rs
+++ b/core/message_bus/tests/directional_connection.rs
@@ -26,7 +26,7 @@
install_dialed_replicas_locally, install_replicas_locally, loopback, set_replica_ctx,
};
use message_bus::IggyMessageBus;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use std::rc::Rc;
use std::time::Duration;
@@ -64,9 +64,16 @@
let peers = vec![(0u8, addr0), (1u8, addr1)];
let dial_0 = install_dialed_replicas_locally(bus0.clone(), on_message.clone());
- start_connector(&bus0, 0, peers.clone(), dial_0, DEFAULT_RECONNECT_PERIOD).await;
+ start_connector(
+ &bus0,
+ 0,
+ peers.clone(),
+ dial_0,
+ bus0.config().reconnect_period,
+ )
+ .await;
let dial_1 = install_dialed_replicas_locally(bus1.clone(), on_message.clone());
- start_connector(&bus1, 1, peers, dial_1, DEFAULT_RECONNECT_PERIOD).await;
+ start_connector(&bus1, 1, peers, dial_1, bus1.config().reconnect_period).await;
// Wait for the directional connection to settle.
let deadline = std::time::Instant::now() + Duration::from_secs(2);
diff --git a/core/message_bus/tests/head_of_line.rs b/core/message_bus/tests/head_of_line.rs
index d632f6b..e5bec8b 100644
--- a/core/message_bus/tests/head_of_line.rs
+++ b/core/message_bus/tests/head_of_line.rs
@@ -31,7 +31,7 @@
};
use compio::net::TcpListener;
use iggy_binary_protocol::{Command, HEADER_SIZE};
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::{IggyMessageBus, MessageBus, SendError};
use std::cell::Cell;
@@ -96,7 +96,7 @@
0,
vec![(1, addr_a), (2, addr_b)],
dial_delegate,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
diff --git a/core/message_bus/tests/replica_roundtrip.rs b/core/message_bus/tests/replica_roundtrip.rs
index 0b2ed65..3c7b491 100644
--- a/core/message_bus/tests/replica_roundtrip.rs
+++ b/core/message_bus/tests/replica_roundtrip.rs
@@ -28,7 +28,7 @@
set_replica_ctx,
};
use iggy_binary_protocol::Command;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::{IggyMessageBus, MessageBus};
use std::rc::Rc;
@@ -70,7 +70,7 @@
0,
vec![(1, addr1)],
dial_delegate_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
diff --git a/core/message_bus/tests/replica_tls_bench.rs b/core/message_bus/tests/replica_tls_bench.rs
index f3f9f30..31ca8fa 100644
--- a/core/message_bus/tests/replica_tls_bench.rs
+++ b/core/message_bus/tests/replica_tls_bench.rs
@@ -38,7 +38,7 @@
self_signed_replica_tls_ctx, set_replica_ctx, set_replica_ctx_with_tls,
};
use iggy_binary_protocol::{Command, GenericHeader};
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::{IggyMessageBus, MessageBus, SendError};
use server_common::Message;
@@ -108,7 +108,7 @@
0,
vec![(1, addr1)],
dial_delegate_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
wait_until(|| bus0.replicas().contains(1), Duration::from_secs(5)).await;
diff --git a/core/message_bus/tests/replica_tls_mitm.rs b/core/message_bus/tests/replica_tls_mitm.rs
index f3bfc29..ba0714a 100644
--- a/core/message_bus/tests/replica_tls_mitm.rs
+++ b/core/message_bus/tests/replica_tls_mitm.rs
@@ -36,7 +36,7 @@
use compio::net::{TcpListener, TcpStream};
use futures::AsyncReadExt;
use message_bus::IggyMessageBus;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::auth::ReplicaAuth;
use message_bus::replica::handshake::ReplicaTlsCtx;
use message_bus::replica::listener::{MessageHandler, bind, run};
@@ -100,7 +100,7 @@
0,
vec![(1, relay_addr)],
dial_delegate_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
diff --git a/core/message_bus/tests/replica_tls_roundtrip.rs b/core/message_bus/tests/replica_tls_roundtrip.rs
index 8cf1c0b..cb8aece 100644
--- a/core/message_bus/tests/replica_tls_roundtrip.rs
+++ b/core/message_bus/tests/replica_tls_roundtrip.rs
@@ -29,7 +29,7 @@
self_signed_replica_tls_ctx, set_replica_ctx, set_replica_ctx_with_tls,
};
use iggy_binary_protocol::Command;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::auth::ReplicaAuth;
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::{IggyMessageBus, MessageBus};
@@ -89,7 +89,7 @@
0,
vec![(1, addr1)],
dial_delegate_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
@@ -143,7 +143,7 @@
0,
vec![(1, addr1)],
dial_delegate_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
@@ -196,7 +196,7 @@
0,
vec![(1, addr1)],
dial_delegate_0,
- DEFAULT_RECONNECT_PERIOD,
+ bus0.config().reconnect_period,
)
.await;
diff --git a/core/message_bus/tests/shard_zero_gating.rs b/core/message_bus/tests/shard_zero_gating.rs
index 34108d5..fbb703a 100644
--- a/core/message_bus/tests/shard_zero_gating.rs
+++ b/core/message_bus/tests/shard_zero_gating.rs
@@ -28,7 +28,6 @@
};
use iggy_common::IggyError;
use message_bus::client_listener::RequestHandler;
-use message_bus::connector::DEFAULT_RECONNECT_PERIOD;
use message_bus::replica::io::{QuicServerCredentials, start_on_shard_zero};
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::transports::tls::self_signed_for_loopback;
@@ -86,7 +85,7 @@
None,
None,
None,
- DEFAULT_RECONNECT_PERIOD,
+ bus_zero.config().reconnect_period,
)
.await
.expect("start_on_shard_zero must succeed on shard 0");
@@ -149,7 +148,7 @@
None,
None,
None,
- DEFAULT_RECONNECT_PERIOD,
+ bus_one.config().reconnect_period,
)
.await
.expect("start_on_shard_zero must succeed on non-zero shard (no-op)");
@@ -227,7 +226,7 @@
Some(accepted_quic),
Some(accepted_tls),
Some(accepted_wss),
- DEFAULT_RECONNECT_PERIOD,
+ bus_zero.config().reconnect_period,
)
.await
.expect("start_on_shard_zero must succeed");
@@ -297,7 +296,7 @@
None,
Some(accepted_tls),
None,
- DEFAULT_RECONNECT_PERIOD,
+ bus_zero.config().reconnect_period,
)
.await
.expect_err("partial TCP-TLS trio must reject");
@@ -339,7 +338,7 @@
None,
None,
Some(accepted_wss),
- DEFAULT_RECONNECT_PERIOD,
+ bus_zero.config().reconnect_period,
)
.await
.expect_err("partial WSS trio must reject");
diff --git a/core/message_bus/tests/tcp_tls_client_roundtrip.rs b/core/message_bus/tests/tcp_tls_client_roundtrip.rs
index b3154f9..8bfcefa 100644
--- a/core/message_bus/tests/tcp_tls_client_roundtrip.rs
+++ b/core/message_bus/tests/tcp_tls_client_roundtrip.rs
@@ -27,7 +27,6 @@
use iggy_binary_protocol::GenericHeader;
use message_bus::BusMessage;
use message_bus::client_listener::RequestHandler;
-use message_bus::connector::DEFAULT_RECONNECT_PERIOD;
use message_bus::replica::io::start_on_shard_zero;
use message_bus::replica::listener::MessageHandler;
use message_bus::transports::tcp_tls::TcpTlsTransportConn;
@@ -92,7 +91,7 @@
None,
Some(accepted_tls),
None,
- DEFAULT_RECONNECT_PERIOD,
+ bus.config().reconnect_period,
)
.await
.expect("start_on_shard_zero must succeed")
diff --git a/core/message_bus/tests/vectored_batch.rs b/core/message_bus/tests/vectored_batch.rs
index fc692c3..c271297 100644
--- a/core/message_bus/tests/vectored_batch.rs
+++ b/core/message_bus/tests/vectored_batch.rs
@@ -25,7 +25,7 @@
set_replica_ctx,
};
use iggy_binary_protocol::Command;
-use message_bus::connector::{DEFAULT_RECONNECT_PERIOD, start as start_connector};
+use message_bus::connector::start as start_connector;
use message_bus::replica::listener::{MessageHandler, bind, run};
use message_bus::{IggyMessageBus, MessageBus};
use std::cell::Cell;
@@ -61,7 +61,14 @@
set_replica_ctx(&bus0, CLUSTER, 0, 2, None);
let on_reply: MessageHandler = Rc::new(|_, _| {});
let dial_0 = install_dialed_replicas_locally(bus0.clone(), on_reply);
- start_connector(&bus0, 0, vec![(1, addr1)], dial_0, DEFAULT_RECONNECT_PERIOD).await;
+ start_connector(
+ &bus0,
+ 0,
+ vec![(1, addr1)],
+ dial_0,
+ bus0.config().reconnect_period,
+ )
+ .await;
// Wait for connect.
let deadline = std::time::Instant::now() + Duration::from_secs(2);
diff --git a/core/message_bus/tests/wss_client_roundtrip.rs b/core/message_bus/tests/wss_client_roundtrip.rs
index a1e2950..b3d3f78 100644
--- a/core/message_bus/tests/wss_client_roundtrip.rs
+++ b/core/message_bus/tests/wss_client_roundtrip.rs
@@ -27,7 +27,6 @@
use iggy_binary_protocol::GenericHeader;
use message_bus::BusMessage;
use message_bus::client_listener::RequestHandler;
-use message_bus::connector::DEFAULT_RECONNECT_PERIOD;
use message_bus::replica::io::start_on_shard_zero;
use message_bus::replica::listener::MessageHandler;
use message_bus::transports::tls::{install_default_crypto_provider, self_signed_for_loopback};
@@ -92,7 +91,7 @@
None,
None,
Some(accepted_wss),
- DEFAULT_RECONNECT_PERIOD,
+ bus.config().reconnect_period,
)
.await
.expect("start_on_shard_zero must succeed")
diff --git a/core/server/config.toml b/core/server/config.toml
index 2143beb..b32988b 100644
--- a/core/server/config.toml
+++ b/core/server/config.toml
@@ -1066,7 +1066,8 @@
client_queue_capacity = 256
# Interval between outbound reconnect attempts to peers with peer_id > self_id.
-reconnect_period = "5 s"
+# Also bounds each dial, so a peer that drops SYNs cannot stall the sweep.
+reconnect_period = "1 s"
# Timeout for per-peer close drain (flush writer, tear down reader)
# before force-cancellation.