From 9d95e7516d230241d9b3b5ed986e17b2dcb3e4cd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 1 Oct 2026 00:39:52 +0200 Subject: [PATCH] cultmesh-rs: pin that a release inside a poll sends exactly one ack A sink can answer an earlier put while it is being offered a later one, in the same poll. If that poll's packet is unreliable it has no ack of its own, so the release must send one. If it is reliable, its own end-of-poll ack is the only one. A sink that accepts every held reply when it is offered the put "last" drives both cases. Co-Authored-By: Claude Opus 5.5 --- .../cultmesh-rs/tests/rudp_document_server.rs | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index 63a9080f..340d0abb 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -1487,10 +1487,14 @@ impl RawPeer { /// Sends `message` reliably and in order; returns its sequences. fn send(&mut self, message: &CultNetMessage) -> Result> { + self.send_with(message, true) + } + + fn send_with(&mut self, message: &CultNetMessage, reliable: bool) -> Result> { let payload = encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?; let options = cultnet_rs::CultNetRudpSendOptions { - reliable: true, - ordered: true, + reliable, + ordered: reliable, sequenced: false, now_ms: 2, reliable_expire_after_ms: None, @@ -2372,3 +2376,59 @@ fn an_unservable_put_is_never_offered_to_the_sink() -> Result<()> { assert_eq!(sink.offered(), Vec::::new()); Ok(()) } + +/// A sink that holds each put's reply until it is offered the put `last`, +/// then accepts every held reply and that one, inside the same poll. +#[derive(Default)] +struct AnswersOnLast(Vec); + +impl CultMeshRudpRawDocumentSink for AnswersOnLast { + fn accept_raw_document( + &mut self, + receipt: CultMeshRudpRawDocumentReceipt, + reply: CultMeshRudpPutReply, + ) { + self.0.push(reply); + if receipt.message_id == "last" { + self.0.drain(..).for_each(CultMeshRudpPutReply::accept); + } + } +} + +/// A poll whose packet releases a withheld session sends exactly one +/// acknowledgement. A reliable packet's own acknowledgement is that one; an +/// unreliable packet has none, so the release sends it. +#[test] +fn a_release_inside_a_poll_sends_exactly_one_acknowledgement() -> Result<()> { + for reliable in [false, true] { + let mut server = CultMeshRudpDocumentServer::new( + UdpSocket::bind("127.0.0.1:0")?, + AnswersOnLast::default(), + Source::default(), + Clock::new(89_000), + Default::default(), + )?; + let mut peer = RawPeer::connect(&mut server, 102)?; + let first = peer.send(&put("first"))?; + poll_until_idle(&mut server)?; + assert_eq!(peer.drain(), Vec::new(), "fixture: the first put is held"); + + let last = peer.send_with(&put("last"), reliable)?; + assert_eq!(server.poll_once()?, CultMeshRudpPollOutcome::Handled); + let sent = peer.drain(); + assert_eq!(sent.len(), 1, "reliable {reliable}: {sent:?}"); + assert_eq!(sent[0].packet_type, cultnet_rs::CultNetRudpPacketType::Ack); + assert!( + first + .iter() + .all(|sequence| acknowledges(&sent[0], *sequence)) + ); + if reliable { + assert!( + last.iter() + .all(|sequence| acknowledges(&sent[0], *sequence)) + ); + } + } + Ok(()) +}