fix(client): bound the threads a peer flood can create
One popular search produced a ConnectToPeer for every responder, and each one cost a 2 MiB wrapper thread, an unbounded 256 KiB actor, a listener thread per inbound socket, and on failure a 2 MiB thread that slept for 20 seconds. A container with a pids limit died on one album search (issues #32, #33). Four bounds, no behavior change for a quiet session: - P-type registrations run inline on the ops loop; only F transfers keep a worker thread. The dial already ran on the actor thread. - The broker timeout is a deadline swept by the ops loop instead of a sleeping thread per failed dial. - The registry caps live actors (default 512, Client::set_max_peers). At capacity the oldest peer outside the caller's protected set (live downloads, uploads, pending serves, queued messages, browses in flight) is evicted; if none qualify the registration is refused before any thread is spawned. - Concurrent listener handshakes are bounded by a 128-permit semaphore released once the peer-init arrives, so slow handshakes cannot multiply threads while transfers stay unthrottled. A replayed TransferResponse for an InProgress or Completed token is dropped instead of starting a second transfer thread over the same .part file. Fixes #32 Fixes #33
This commit is contained in:
@@ -395,3 +395,51 @@ fn a_clean_disconnect_keeps_queued_uploads_an_error_drops_them() {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pending_connect_expires_after_the_broker_timeout() {
|
||||
let mut context = ClientContext::new();
|
||||
context.add_pending_connect(7, "ghost".to_string());
|
||||
|
||||
assert!(context.take_expired_connects(Instant::now()).is_empty());
|
||||
|
||||
let after_deadline =
|
||||
Instant::now() + BROKER_CONNECT_TIMEOUT + Duration::from_secs(1);
|
||||
assert_eq!(
|
||||
context.take_expired_connects(after_deadline),
|
||||
vec!["ghost".to_string()]
|
||||
);
|
||||
assert!(context.take_pending_connect(7).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protected_peers_covers_downloads_uploads_and_pending_serves() {
|
||||
let mut context = ClientContext::new();
|
||||
context.add_download(download(
|
||||
"downloader",
|
||||
"song.mp3",
|
||||
1,
|
||||
DownloadStatus::Queued,
|
||||
mpsc::channel().0,
|
||||
));
|
||||
context.add_download(download(
|
||||
"done",
|
||||
"old.mp3",
|
||||
2,
|
||||
DownloadStatus::Completed,
|
||||
mpsc::channel().0,
|
||||
));
|
||||
context
|
||||
.pending_serves
|
||||
.insert("waiting".to_string(), vec![9]);
|
||||
context.mark_browse_pending("browsed");
|
||||
|
||||
let protected = context.protected_peers();
|
||||
assert!(protected.contains("downloader"));
|
||||
assert!(protected.contains("waiting"));
|
||||
assert!(protected.contains("browsed"));
|
||||
assert!(!protected.contains("done"));
|
||||
|
||||
context.store_browse_result("browsed".to_string(), Vec::new());
|
||||
assert!(!context.protected_peers().contains("browsed"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user