fix(lib): keep a replaced peer connection open for the reply in flight

Two connections to the same peer race whenever both sides dial at once:
we open one to send a browse, the peer opens one to deliver a search
reply. The registry keeps one actor per username, and until now stopped
the older one on the spot, which closed the socket carrying our browse
before the listing could arrive. Under load the stress gate lost up to
two thirds of its browses that way.

A replaced actor is now retired instead: it sends nothing new, keeps
reading for thirty seconds so a reply already on the wire still reaches
the client, and then stops itself. The peer closing a retired connection
is expected and reported as a clean close, so it cannot fail transfers
running on the connection that took over.
This commit is contained in:
Michel de Graaf
2026-09-08 00:35:11 +02:00
parent ee8d285505
commit ea168605b5
2 changed files with 91 additions and 6 deletions
+5 -5
View File
@@ -126,14 +126,14 @@ impl PeerRegistry {
actor.set_self_handle(handle);
})
.map_err(|e| format!("failed to spawn peer actor thread: {e}"))?;
// Stop any actor already registered under this username so it does not
// become an orphan pinning a pool worker forever. Eviction on the
// replaced actor's later shutdown is identity-aware (keyed on its id),
// so stopping it here cannot evict this new connection.
// Retire any actor already registered under this username rather than
// stop it: a reply to a request we sent on it may still be on the
// wire. It stops itself once that grace is over, and the eviction its
// shutdown triggers is keyed on its id, so it cannot evict this one.
if let Some((_, old_handle)) =
peers.insert(username.clone(), (id, handle.clone()))
{
let _ = old_handle.stop();
let _ = old_handle.send(PeerMessage::Retire);
debug!(
"[peer_registry] Replaced existing peer actor for {}",
username