mirror of https://github.com/ospab/ostp.git
The freeze users hit every few minutes: traffic drops to 0 B/s, the RTT readout sticks at its last value, and only a manual reconnect clears it. Delivery is gated on expected_recv_nonce, so one missing frame holds back every frame behind it. That is correct only while the sender can still retransmit — but the sender drops a frame from sent_history once it passes max_retries + 2 attempts (zombie eviction in handle_tick). Past that point the frame no longer exists anywhere and both sides deadlock: the receiver buffers indefinitely and NACKs a nonce nobody can resend. The watchdog could not save it, which is why it froze rather than reconnecting. Retransmits, ACKs and NACKs keep arriving throughout, so the client's last_valid_recv keeps refreshing and its 25s stall detector never fires. The frozen RTT has the same cause: Pong travels in a Data frame, stuck behind the very gap it would have reported. The machinery for this was half-built: last_recv_advance was declared, initialised and written on every advance, and its doc comment describes exactly this recovery — but nothing ever read it, and a warning elsewhere already referred to "gap recovery" that did not exist. So implement it. Once the sequence has been stuck longer than the sender's retransmit budget could plausibly last (8x the live RTO, clamped to 2..10s so fast links do not discard merely-late frames and slow ones still unblock), skip to the lowest buffered nonce, drain, and mark an ACK pending so the peer stops retransmitting into a void. This runs on the inbound path, not on Tick, for two reasons: both tick handlers discard DeliverApp actions (client bridge.rs and server dispatcher.rs match only SendDatagram/Multiple), and inbound frames keep flowing all through the stall, so the path is reliably reached. Skipping the hole drops one frame's payload — one RelayMessage, a chunk of a single stream. That is a real cost, paid only when the data was already lost for good, against a tunnel that otherwise stays dead until the user intervenes. Both tests were confirmed to fail without the fix (0 frames released instead of 2), so they pin the deadlock rather than just the happy path. |
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.toml | ||