Ack Cut D D2: a refused send withdraws the fragments the flow window queued behind the failed one

Rust hub and C# listener: 1,100 one-byte fragments, the first can never be
sent, and nothing of the send stays queued.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 12:49:46 +02:00
commit aab72f3d47
2 changed files with 39 additions and 0 deletions

View file

@ -414,3 +414,25 @@ fn a_peer_ended_in_the_resend_loop_is_sent_nothing_more_from_it() {
assert_eq!(hub.stats().send_failures, 3);
assert_eq!(hub.sessions().len(), 1);
}
#[test]
fn a_refused_send_withdraws_the_fragments_the_window_queued_as_well() {
let (mut hub, _peer, session) = one_peer(Some(1));
let outstanding = hub.peers[&session.remote_addr]
.session
.outstanding_reliable_packet_count();
hub.unsendable_after.insert(session.remote_addr, 0);
// 1,100 one-byte fragments: the flow window admits the first ones and
// queues the rest, and the first datagram can never be sent.
hub.send(&session, "schema", vec![7; 1_100])
.expect_err("the first fragment can never be sent");
assert_eq!(
hub.peers[&session.remote_addr]
.session
.outstanding_reliable_packet_count(),
outstanding,
"nothing of the refused send stays queued"
);
}

View file

@ -176,6 +176,23 @@ namespace GameCult.Networking.Tests
}
}
[Test]
public void ARefusedSendWithdrawsTheFragmentsTheWindowQueuedAsWell()
{
var (server, _, x, _) = TwoPeers(maxFragmentBytes: 1);
using (server)
{
var outstanding = x.Server.Session.OutstandingReliablePacketCount;
server.UnsendableAfter[x.EndPoint] = 0;
// 1,100 one-byte fragments: the flow window admits the first ones and queues the rest,
// and the first datagram can never be sent.
Assert.Throws<SocketException>(() => server.SendSchema(x.Server, new byte[1_100]));
Assert.That(x.Server.Session.OutstandingReliablePacketCount, Is.EqualTo(outstanding), "nothing of the refused send stays queued");
}
}
[Test]
public void ASendThatFailsPermanentlyAfterAFragmentLeftEndsTheSession()
{