fix: survive busy sessions without exhausting file descriptors
The peer listener died with "Too many open files (os error 24)" on any long-lived non-daemon session: only the daemon raised RLIMIT_NOFILE, and peer sockets were unbounded — no idle reaping, no deadlines on accepted transfer streams, and one peer could hold two connections under split registry identities. - raise the open-file limit at the top of run(), for every entry point, and warn from Client::connect when the soft limit is below 1024 - reap peer control connections idle for five minutes; a clean disconnect no longer discards the peer's queued uploads - give listener-supplied download streams and upload serving the same read/write deadlines dialled sockets already had - register inbound peers under their plain username so the registry holds one connection per peer, and announce them like pierced ones - hold the registry lock across actor spawn + insert and surface thread-spawn failure, so an instantly-dead peer cannot leave a zombie entry
This commit is contained in:
@@ -231,6 +231,19 @@ impl DownloadPeer {
|
||||
)
|
||||
.map_err(DownloadError::ConnectionFailed)?;
|
||||
|
||||
Self::apply_transfer_deadlines(&stream)?;
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
/// Bound every stall on a transfer socket. This must cover streams the
|
||||
/// listener accepted as well as ones we dialled: an accepted F connection
|
||||
/// used to arrive here with no read timeout at all, so a peer that sent a
|
||||
/// token and went silent parked the transfer thread — holding the socket
|
||||
/// and the open `.part` file — for the rest of the session.
|
||||
fn apply_transfer_deadlines(
|
||||
stream: &TcpStream,
|
||||
) -> Result<(), DownloadError> {
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(30)))
|
||||
.map_err(DownloadError::ConnectionFailed)?;
|
||||
@@ -240,8 +253,7 @@ impl DownloadPeer {
|
||||
stream
|
||||
.set_nodelay(true)
|
||||
.map_err(DownloadError::ConnectionFailed)?;
|
||||
|
||||
Ok(stream)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn perform_handshake(
|
||||
@@ -481,7 +493,10 @@ impl DownloadPeer {
|
||||
}
|
||||
|
||||
let mut stream = match stream {
|
||||
Some(s) => s,
|
||||
Some(s) => {
|
||||
Self::apply_transfer_deadlines(&s)?;
|
||||
s
|
||||
}
|
||||
None => self.establish_connection()?,
|
||||
};
|
||||
|
||||
@@ -622,6 +637,23 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
// Streams the listener accepted arrive with no deadlines; without these a
|
||||
// peer that sends a token and goes silent parks the transfer thread —
|
||||
// socket and open .part file included — for the rest of the session.
|
||||
#[test]
|
||||
fn supplied_streams_get_transfer_deadlines() {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let stream =
|
||||
std::net::TcpStream::connect(listener.local_addr().unwrap())
|
||||
.unwrap();
|
||||
|
||||
DownloadPeer::apply_transfer_deadlines(&stream).unwrap();
|
||||
|
||||
let secs = std::time::Duration::from_secs;
|
||||
assert_eq!(stream.read_timeout().unwrap(), Some(secs(30)));
|
||||
assert_eq!(stream.write_timeout().unwrap(), Some(secs(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_establish_connection_invalid_address() {
|
||||
let download_peer = DownloadPeer::new(
|
||||
|
||||
Reference in New Issue
Block a user