diff --git a/soulseek-rs-lib/src/actor/peer_actor.rs b/soulseek-rs-lib/src/actor/peer_actor.rs index 147bc1f..bf63e3a 100644 --- a/soulseek-rs-lib/src/actor/peer_actor.rs +++ b/soulseek-rs-lib/src/actor/peer_actor.rs @@ -556,17 +556,18 @@ impl PeerActor { /// Read what the socket holds, up to `READS_PER_TICK` buffers, so a big /// listing lands in a few ticks rather than a few minutes; the cap keeps - /// a peer that never stops sending from starving the mailbox. + /// a peer that never stops sending from starving the mailbox. Only the + /// first read reports an error: a peer that sends its reply and hangs up + /// has still sent the reply, which must be parsed before the close is + /// acted on, and the socket says so again on the next call. fn drain( reader: &mut MessageReader, stream: &mut TcpStream, ) -> io::Result<()> { reader.read_from_socket(stream)?; for _ in 1..READS_PER_TICK { - match reader.read_from_socket(stream) { - Ok(()) => {} - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, - Err(e) => return Err(e), + if reader.read_from_socket(stream).is_err() { + break; } } Ok(()) diff --git a/soulseek-rs-lib/src/actor/peer_actor/tests.rs b/soulseek-rs-lib/src/actor/peer_actor/tests.rs index ff0c9f2..be62ec0 100644 --- a/soulseek-rs-lib/src/actor/peer_actor/tests.rs +++ b/soulseek-rs-lib/src/actor/peer_actor/tests.rs @@ -166,6 +166,32 @@ fn a_retired_actor_delivers_what_is_on_the_wire_then_stops_itself() { ); } +// The peer that replaced the connection considers the old one done too: it +// writes its reply and hangs up in the same breath. The reply still counts. +#[test] +fn a_reply_sent_right_before_the_peer_hangs_up_is_still_delivered() { + use std::io::Write; + let (mut actor, rx, mut far_end) = connected_actor(); + + actor.handle_message(PeerMessage::Retire); + far_end.write_all(&big_listing()).unwrap(); + drop(far_end); + std::thread::sleep(Duration::from_millis(50)); + actor.tick(); + actor.tick(); + + match rx.try_recv() { + Ok(ClientOperation::BrowseResult { username, .. }) => { + assert_eq!(username, "bob"); + } + other => panic!("expected the listing before the close, got {other:?}"), + } + match rx.try_recv() { + Ok(ClientOperation::PeerDisconnected(7, _, None)) => {} + other => panic!("expected a clean PeerDisconnected, got {other:?}"), + } +} + #[test] fn a_retired_actor_reporting_an_error_reports_a_clean_close() { let (mut actor, rx, _far_end) = connected_actor();