fix(client): survive the review findings on the thread-flood fix

Review of the capacity work surfaced three real defects and two gaps:

- A peer dribbling bytes re-armed the 10s handshake read timeout on
  every read and held its accept permit forever; 128 such sockets
  wedged the accept loop for good. The handshake now has one deadline
  across all reads, plus a 4 KiB cap on buffered handshake bytes.
- A capacity refusal was logged and swallowed, leaving the refused
  peer's queued downloads stuck until the caller's own timeout. Both
  registration paths now fail those downloads so the caller gets a
  verdict, and the listener only announces PeerConnected when the
  registration actually happened.
- The transfer-claim was undone by the worker thread writing Queued
  back over it; that write is gone, so a replayed TransferResponse
  can no longer start a second writer on the same .part file.
- Offered uploads and queue waiters joined the eviction-protected
  set, and the browse protection window matches the minutes a real
  share listing takes rather than one.

Regression tests: the broker-timeout sweep failing a queued download
end to end, the replay guard observed over a real socket, and the
handshake deadline against a dribbling and an oversized peer.
This commit is contained in:
Michel de Graaf
2026-08-13 14:10:20 +02:00
parent 9fe1c9984c
commit 492b961766
5 changed files with 271 additions and 81 deletions
+28 -20
View File
@@ -214,28 +214,36 @@ impl Client {
ConnectionType::P => {
let username = peer.username;
let context = match client_context.read_safe() {
Ok(c) => c,
Err(e) => {
error!("[client] connect_to_peer read: {}", e);
return;
let refused = {
let context = match client_context.read_safe() {
Ok(c) => c,
Err(e) => {
error!("[client] connect_to_peer read: {}", e);
return;
}
};
if let Some(ref registry) = context.peer_registry {
let protected = context.protected_peers();
match registry.register_peer_protected(
peer_clone, stream, None, &protected,
) {
Ok(_) => false,
Err(e) => {
warn!(
"[client] refusing peer connection for \
{:?}: {:?}",
username, e
);
true
}
}
} else {
trace!("PeerRegistry not initialized");
false
}
};
if let Some(ref registry) = context.peer_registry {
let protected = context.protected_peers();
match registry.register_peer_protected(
peer_clone, stream, None, &protected,
) {
Ok(_) => (),
Err(e) => {
trace!(
"Failed to spawn peer actor for {:?}: {:?}",
username, e
);
}
}
} else {
trace!("PeerRegistry not initialized");
if refused {
Self::fail_queued_downloads(&client_context, &username);
}
}