cultmesh-rs: no permanent send failure is dropped when a rejection ends the session
A reply sent earlier in the same poll could fail permanently and then be dropped when a rejection returned first; and a refusal fragment that can never be sent must end the session naming its error. Both now reach end_session as the unsendable end. The test makes the refusal itself unsendable: the peer's goodbye names the error, nothing is counted as a lost datagram, and the other peer is served. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
62b34a7139
commit
2ee707d05e
2 changed files with 44 additions and 4 deletions
|
|
@ -546,11 +546,14 @@ where
|
|||
now_unix,
|
||||
now_monotonic,
|
||||
)? {
|
||||
let end = match rejection.reason {
|
||||
CultMeshRudpRejectionReason::ResponseSendFailed(kind) => {
|
||||
SessionEnd::Unsendable(std::io::Error::from(kind))
|
||||
// A reply that already failed permanently this poll is not dropped:
|
||||
// the session ends over it.
|
||||
let end = match (unsendable.take(), &rejection.reason) {
|
||||
(Some(error), _) => SessionEnd::Unsendable(error),
|
||||
(None, CultMeshRudpRejectionReason::ResponseSendFailed(kind)) => {
|
||||
SessionEnd::Unsendable(std::io::Error::from(*kind))
|
||||
}
|
||||
ref reason => SessionEnd::Rejected(reason.refusal_text()),
|
||||
(None, reason) => SessionEnd::Rejected(reason.refusal_text()),
|
||||
};
|
||||
self.end_session(key, end)?;
|
||||
return Ok(CultMeshRudpPollOutcome::ApplicationRejected(rejection));
|
||||
|
|
|
|||
|
|
@ -456,3 +456,40 @@ fn a_reset_reported_by_a_receive_is_idle_not_an_error() {
|
|||
}
|
||||
assert_eq!(received.lock().unwrap().as_slice(), ["y-put"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refusal_that_can_never_be_sent_ends_the_session_naming_the_error() {
|
||||
let (mut server, _clock, received, (mut x, x_addr), (mut y, _)) = two_peers();
|
||||
// Any document is now unservable, so X's put is refused; the refusal is
|
||||
// the first datagram X is owed, and it can never be sent as built.
|
||||
server.options.max_snapshot_response_bytes = 16;
|
||||
server.unsendable_after.insert(x_addr, 0);
|
||||
send(&mut x, &put("x-put"));
|
||||
let mut rejected = false;
|
||||
for _ in 0..10 {
|
||||
rejected |= matches!(
|
||||
server.poll_once().unwrap(),
|
||||
CultMeshRudpPollOutcome::ApplicationRejected(_)
|
||||
);
|
||||
}
|
||||
assert!(rejected);
|
||||
assert!(received.lock().unwrap().is_empty());
|
||||
assert_eq!(server.session_count(), 1);
|
||||
// The failure is not dropped: the goodbye names it, where a refusal's
|
||||
// goodbye would say the session refused a packet.
|
||||
assert!(is_unsendable_reason(&goodbye_reason(&mut x)));
|
||||
assert_eq!(server.send_failures(), 0);
|
||||
server.options.max_snapshot_response_bytes =
|
||||
CultMeshRudpDocumentServerOptions::default().max_snapshot_response_bytes;
|
||||
send(&mut y, &snapshot("y-snapshot"));
|
||||
let mut served = false;
|
||||
for _ in 0..50 {
|
||||
server.poll_once().unwrap();
|
||||
if y.receive_once().unwrap().is_some() {
|
||||
served = true;
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
assert!(served, "the other peer is still served");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue