From 39127d30f318c9399799d817a05cbadc9ef6a4f0 Mon Sep 17 00:00:00 2001 From: ospab Date: Sun, 28 Jun 2026 01:44:37 +0300 Subject: [PATCH] =?UTF-8?q?=C2=A7B:=20fix=20Closing-state=20teardown=20dat?= =?UTF-8?q?a=20loss=20(from=2047d44fa)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the Closing state the old code force-transitioned to Closed after a SINGLE inbound packet, so any data/ACKs the peer still had in flight when we initiated Close were dropped (Closed returns Noop for everything). Stay in Closing and process inbound normally; handle_inbound already owns the Close->Closed transition when it actually receives the peer's Close frame. Also handle Tick in Closing so our own Close frame is retransmitted until acknowledged. Ported surgically from 47d44fa — only the Closing-state correctness fix, NOT that commit's bundled RFC-6298 RTO / congestion rewrite (a behavioural change to the working base) or the sent_history BTreeMap perf swap (broad hot-path change for a perf-only gain). cargo test -p ostp-core: 36/36 incl. test_close_sequence. Co-Authored-By: Claude Opus 4.8 --- ostp-core/src/protocol.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ostp-core/src/protocol.rs b/ostp-core/src/protocol.rs index a2dd6bb..c0874d1 100644 --- a/ostp-core/src/protocol.rs +++ b/ostp-core/src/protocol.rs @@ -207,13 +207,16 @@ impl ProtocolMachine { .map(ProtocolAction::SendDatagram) } (OstpState::Closing, OstpEvent::Inbound(raw)) => { - // Process final in-flight packets to prevent data loss during teardown. - // The remote may still have data or ACKs in transit when we initiated Close. - let result = self.handle_inbound(raw); - self.state = OstpState::Closed; - result + // The remote may still have data or ACKs in transit when we initiated + // Close. Stay in Closing and process them; handle_inbound transitions to + // Closed only when it actually receives the peer's Close frame — the old + // code force-closed after a single inbound packet, losing in-flight data. + // (Ported from 0.3.x 47d44fa.) + self.handle_inbound(raw) } (OstpState::Established, OstpEvent::Tick) => self.handle_tick(), + // Retransmit our Close frame (and drain pending) while waiting for teardown. + (OstpState::Closing, OstpEvent::Tick) => self.handle_tick(), (OstpState::Closed, _) => Ok(ProtocolAction::Noop), (_, OstpEvent::Close) => { self.state = OstpState::Closed;