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:
@@ -3,6 +3,7 @@ pub mod logger;
|
||||
pub mod lock;
|
||||
pub mod md5;
|
||||
pub mod path;
|
||||
pub(crate) mod semaphore;
|
||||
pub mod zlib;
|
||||
|
||||
// Re-export commonly used items
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::sync::{Arc, Condvar, Mutex, PoisonError};
|
||||
|
||||
pub struct Semaphore {
|
||||
permits: Mutex<usize>,
|
||||
available: Condvar,
|
||||
}
|
||||
|
||||
pub struct Permit(Arc<Semaphore>);
|
||||
|
||||
impl Semaphore {
|
||||
#[must_use]
|
||||
pub const fn new(permits: usize) -> Self {
|
||||
Self {
|
||||
permits: Mutex::new(permits),
|
||||
available: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn acquire(self: &Arc<Self>) -> Permit {
|
||||
let mut permits =
|
||||
self.permits.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
while *permits == 0 {
|
||||
permits = self
|
||||
.available
|
||||
.wait(permits)
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
}
|
||||
*permits -= 1;
|
||||
Permit(Arc::clone(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Permit {
|
||||
fn drop(&mut self) {
|
||||
let mut permits = self
|
||||
.0
|
||||
.permits
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
*permits += 1;
|
||||
self.0.available.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn permits_bound_concurrent_holders() {
|
||||
let semaphore = Arc::new(Semaphore::new(2));
|
||||
let first = semaphore.acquire();
|
||||
let _second = semaphore.acquire();
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let _third = semaphore.acquire();
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
assert!(
|
||||
rx.recv_timeout(Duration::from_millis(100)).is_err(),
|
||||
"a third acquire must block while both permits are held"
|
||||
);
|
||||
drop(first);
|
||||
assert!(
|
||||
rx.recv_timeout(Duration::from_secs(2)).is_ok(),
|
||||
"releasing a permit must wake the waiter"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user