diff --git a/packages/cultmesh-rs/src/rudp_document_server.rs b/packages/cultmesh-rs/src/rudp_document_server.rs index 6c2bf639..afdfeea6 100644 --- a/packages/cultmesh-rs/src/rudp_document_server.rs +++ b/packages/cultmesh-rs/src/rudp_document_server.rs @@ -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)); diff --git a/packages/cultmesh-rs/src/rudp_document_server/send_failure_tests.rs b/packages/cultmesh-rs/src/rudp_document_server/send_failure_tests.rs index 8c48a092..dd6b0069 100644 --- a/packages/cultmesh-rs/src/rudp_document_server/send_failure_tests.rs +++ b/packages/cultmesh-rs/src/rudp_document_server/send_failure_tests.rs @@ -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"); +}