fix: review findings on the protocol work
Correctness: - A child that stopped reading could freeze the whole client: the relay writes under the context lock and the socket was blocking, so one stalled peer took every download, upload and API call with it. Child writes now have a deadline, and a child that hits it loses its slot. - Relaying searches to children was gated by the leaf's answer budget, so a parent forwarded a fraction of the stream while advertising itself as healthy. The budget bounds our own share scan, which is what it was for; the tree's stream goes down whole. - A dropped client kept its listener port for the life of the process, so a retried login advertised an ephemeral port nobody was told about. The listener is now stopped and the operations thread let go. - Excluded phrases were compared against a lowercased path without being lowercased themselves, so a capitalised phrase never matched. They are normalised where they arrive. - Losing our own operatorship wiped the room's whole operator roster, though we are still a member who can see it. - A brokered ConnectToPeer of type D was logged and dropped; the peer is now told, since we never dial out to adopt a child. - A peer request made with no server connection returned Ok and parked forever; it says NotConnected, as its docs always claimed. - An empty interest was refused locally but still sent, leaving the server's list and ours permanently apart. Simplification: - Deleted the excluded-phrase helper nothing called, and the field doc that promised behaviour it never had. - The four roster handlers and four standing handlers were eight constructors carrying constants their own message code already implies: one newtype each, 100 lines less. - One place registers a search; the phrase list is borrowed, not cloned per relayed search.
This commit is contained in:
@@ -5452,3 +5452,47 @@ fn privileges_can_be_handed_to_another_user() {
|
||||
"the gifted privileges should show up as time of our own"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_disconnected_client_gives_its_listener_port_back() {
|
||||
// A client that goes out of scope must release the port it bound: the
|
||||
// next one has to be able to advertise the port it was configured with,
|
||||
// not fall back to an ephemeral one nobody was told about. No server is
|
||||
// needed — binding the listener happens in connect(), before the dial.
|
||||
let port = free_port().expect("a port to hold");
|
||||
let settings = |port: u16| ClientSettings {
|
||||
username: "e2e_port_release".to_string(),
|
||||
password: "pw".to_string(),
|
||||
server_address: PeerAddress::new("127.0.0.1".to_string(), 1),
|
||||
enable_listen: true,
|
||||
listen_port: port,
|
||||
shared_directories: Vec::new(),
|
||||
accept_children: false,
|
||||
version: ClientVersion::default(),
|
||||
};
|
||||
|
||||
let mut first = Client::with_settings(settings(port));
|
||||
first.connect().expect("bind the listener");
|
||||
assert_eq!(first.listen_port(), Some(port));
|
||||
drop(first);
|
||||
|
||||
// The listener thread notices the stop the moment it is woken, which the
|
||||
// disconnect does by dialling it; give it a beat to unwind.
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
let mut second_port = None;
|
||||
while Instant::now() < deadline && second_port != Some(port) {
|
||||
let mut second = Client::with_settings(settings(port));
|
||||
second.connect().expect("bind again");
|
||||
second_port = second.listen_port();
|
||||
if second_port == Some(port) {
|
||||
break;
|
||||
}
|
||||
drop(second);
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
}
|
||||
assert_eq!(
|
||||
second_port,
|
||||
Some(port),
|
||||
"the port should be free again once the first client is dropped"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user