radicle-node: Prevent infinite connection loop on instant dial failure
When a connection drops and the node attempts to reconnect within the same second, the `last_attempt` timestamp written to the database is identitcal to the `last_success` timestamp due to the Unix time (seconds) precision loss. The `maintain_connections` logic filters eligible peers using the condition `last_success >= last_attempt`. Because the truncated timestamps are equal, this evaluates to true, causing the node to bypass the `CONNECTION_RETRY_DELTA` backoff. If the OS instantly rejects the dial attempt (e.g. `EHOSTUNREACH`), the node synchronously queues another connection attempt. This traps the reactor in an infinite `while` loop. Changing the condition to `last_success > last_attempt` ensures that a failed attempt in the same second correctly evaluates to false, forcing the node to respect the backoff timer and breaking the loop.
This commit is contained in:
parent
e4a16dc40a
commit
a294e05730
|
|
@ -1401,6 +1401,48 @@ fn test_maintain_connections_failed_attempt() {
|
||||||
assert!(!alice.outbox().any(|o| matches!(o, Io::Connect(_, _))));
|
assert!(!alice.outbox().any(|o| matches!(o, Io::Connect(_, _))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_maintain_connections_same_second_loop() {
|
||||||
|
use std::io;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
let eve = Peer::new("eve", [9, 9, 9, 9]);
|
||||||
|
let mut alice = Peer::new("alice", [7, 7, 7, 7]);
|
||||||
|
let reason = DisconnectReason::Dial(Arc::new(io::Error::from(io::ErrorKind::HostUnreachable)));
|
||||||
|
|
||||||
|
alice.connect_to(&eve);
|
||||||
|
|
||||||
|
// Advance clock to make the connection stable.
|
||||||
|
// This triggers `idle_connections` which sets `last_success` in the DB to the current time (T).
|
||||||
|
alice.elapse(session::CONNECTION_STABLE_THRESHOLD);
|
||||||
|
|
||||||
|
// Eve disconnects.
|
||||||
|
// This triggers `maintain_connections`.
|
||||||
|
alice.disconnected(eve.id(), Link::Outbound, &reason);
|
||||||
|
|
||||||
|
let connects = alice
|
||||||
|
.outbox()
|
||||||
|
.filter(|o| matches!(o, Io::Connect(id, _) if id == &eve.id))
|
||||||
|
.count();
|
||||||
|
assert_eq!(connects, 1, "Alice should attempt to reconnect once");
|
||||||
|
|
||||||
|
// Simulate the dial failing instantly.
|
||||||
|
// We DO NOT advance the clock. We just call disconnected again.
|
||||||
|
// This triggers `maintain_connections` again.
|
||||||
|
// Now `last_success` is T, and `last_attempt` is T.
|
||||||
|
alice.disconnected(eve.id(), Link::Outbound, &reason);
|
||||||
|
|
||||||
|
// Check if Alice tries to connect again in the exact same second.
|
||||||
|
let immediate_retry = alice
|
||||||
|
.outbox()
|
||||||
|
.any(|o| matches!(o, Io::Connect(id, _) if id == eve.id));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!immediate_retry,
|
||||||
|
"Alice immediately retried a connection when last_success == last_attempt"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_seed_repo_subscribe() {
|
fn test_seed_repo_subscribe() {
|
||||||
let mut alice = Peer::new("alice", [7, 7, 7, 7]);
|
let mut alice = Peer::new("alice", [7, 7, 7, 7]);
|
||||||
|
|
|
||||||
|
|
@ -2579,7 +2579,7 @@ where
|
||||||
// If we succeeded the last time we tried, this is a good address.
|
// If we succeeded the last time we tried, this is a good address.
|
||||||
// If it's been long enough that we failed to connect, we also try again.
|
// If it's been long enough that we failed to connect, we also try again.
|
||||||
(Some(success), Some(attempt)) => {
|
(Some(success), Some(attempt)) => {
|
||||||
success >= attempt || now - attempt >= CONNECTION_RETRY_DELTA
|
success > attempt || now - attempt >= CONNECTION_RETRY_DELTA
|
||||||
}
|
}
|
||||||
// If we haven't succeeded yet, and we waited long enough, we can try this address.
|
// If we haven't succeeded yet, and we waited long enough, we can try this address.
|
||||||
(None, Some(attempt)) => now - attempt >= CONNECTION_RETRY_DELTA,
|
(None, Some(attempt)) => now - attempt >= CONNECTION_RETRY_DELTA,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue