Ack Cut 3: a server's new generation does not inherit acknowledged bytes
A server accepting a fresh Connect can send before its Accept is acknowledged, so no ack of the new generation prunes the acknowledged sizes; only `end_session` clearing them keeps the ended generation's bytes from counting against the new window. Rust, TypeScript, Python and C# each pin it: 4 MiB acknowledged above a lost packet, a fresh Connect, and the server's first 1 MiB send of the new generation goes out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1833df58d4
commit
b4cee00af2
4 changed files with 91 additions and 0 deletions
|
|
@ -1017,6 +1017,25 @@ class CultNetTests(unittest.TestCase):
|
|||
promoted = sender.receive(self._flow_ack(h.sequence), 2).ready_to_send
|
||||
self.assertEqual([p.sequence for p in promoted], [g.sequence + 6])
|
||||
|
||||
def test_cultnet_rudp_a_new_generation_on_the_server_does_not_inherit_acknowledged_bytes(self) -> None:
|
||||
# A server that accepts a fresh Connect may send before its Accept is acknowledged, so no acknowledgement of
|
||||
# the new generation has moved the floor yet. Bytes the ended generation had acknowledged above its own lost
|
||||
# packet belong to no receiver's hold any more and must not count.
|
||||
server = self._flow_session(500)
|
||||
accept = server.accept_connect(self._flow_session(1).create_connect(0), 0)
|
||||
server.receive(self._flow_ack(accept.sequence), 0)
|
||||
g = self._flow_send(server, 1)[0]
|
||||
for offset in range(1, 5):
|
||||
self.assertEqual([p.sequence for p in self._flow_send(server, self.MIB)], [g.sequence + offset])
|
||||
server.receive(self._flow_ack(g.sequence + offset), 1)
|
||||
self.assertEqual(len(self._flow_send(server, self.MIB)), 0)
|
||||
|
||||
next_accept = server.accept_connect(self._flow_session(200_000).create_connect(2), 2)
|
||||
self.assertEqual(next_accept.packet_type, CultNetRudpPacketType.ACCEPT)
|
||||
# The Accept is unacknowledged and the only thing pending: 1 MiB fits above it.
|
||||
sent = server.send("state", bytes(self.MIB), CultNetRudpSendOptions(reliable=True))
|
||||
self.assertEqual(sent.sequence, next_accept.sequence + 1)
|
||||
|
||||
def test_cultnet_rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames(self) -> None:
|
||||
server_socket = bind_udp_socket()
|
||||
client_socket = bind_udp_socket()
|
||||
|
|
|
|||
|
|
@ -280,3 +280,31 @@ fn a_direct_send_is_not_refused_for_bytes_whose_lowest_packet_has_expired() -> R
|
|||
assert_eq!(sender.outstanding_reliable_packet_count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A server that accepts a fresh Connect may send before its Accept is
|
||||
/// acknowledged, so no acknowledgement of the new generation has moved the
|
||||
/// floor yet. Bytes the ended generation had acknowledged above its own lost
|
||||
/// packet belong to no receiver's hold any more and must not count.
|
||||
#[test]
|
||||
fn a_new_generation_on_the_server_does_not_inherit_acknowledged_bytes() -> Result<()> {
|
||||
let mut server = session(500);
|
||||
let mut first_client = session(1);
|
||||
let connect = first_client.create_connect(0, Vec::new())?;
|
||||
let accept = server.accept_connect(&connect, 0, Vec::new())?;
|
||||
server.receive(&ack_for(accept.sequence), 0)?;
|
||||
let g = send(&mut server, 1)?.remove(0);
|
||||
for offset in 1..=4u32 {
|
||||
assert_eq!(sequences(&send(&mut server, MIB)?), vec![g.sequence + offset]);
|
||||
server.receive(&ack_for(g.sequence + offset), 1)?;
|
||||
}
|
||||
assert!(send(&mut server, MIB)?.is_empty());
|
||||
|
||||
let mut second_client = session(200_000);
|
||||
let connect = second_client.create_connect(2, Vec::new())?;
|
||||
let accept = server.accept_connect(&connect, 2, Vec::new())?;
|
||||
assert_eq!(accept.packet_type, CultNetRudpPacketType::Accept);
|
||||
// The Accept is unacknowledged and the only thing pending: 1 MiB fits above it.
|
||||
let sent = server.send("state", vec![7; MIB], reliable())?;
|
||||
assert_eq!(sent.sequence, accept.sequence + 1);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1558,6 +1558,26 @@ test("acknowledged bytes count against the lowest unacked sequence as it advance
|
|||
assert.deepEqual(sequencesOf(sender.receive(flowAck(h.sequence), 2).readyToSend ?? []), [g.sequence + 6]);
|
||||
});
|
||||
|
||||
// A server that accepts a fresh Connect may send before its Accept is acknowledged, so no acknowledgement of the new
|
||||
// generation has moved the floor yet. Bytes the ended generation had acknowledged above its own lost packet belong to
|
||||
// no receiver's hold any more and must not count.
|
||||
test("a new generation on the server does not inherit acknowledged bytes", () => {
|
||||
const server = flowSession(500);
|
||||
const accept = server.acceptConnect(flowSession(1).createConnect(0), 0);
|
||||
server.receive(flowAck(accept.sequence), 0);
|
||||
const g = flowSend(server, 1)[0]!;
|
||||
for (let offset = 1; offset <= 4; offset++) {
|
||||
assert.deepEqual(sequencesOf(flowSend(server, MIB)), [g.sequence + offset]);
|
||||
server.receive(flowAck(g.sequence + offset), 1);
|
||||
}
|
||||
assert.equal(flowSend(server, MIB).length, 0);
|
||||
|
||||
const nextAccept = server.acceptConnect(flowSession(200_000).createConnect(2), 2);
|
||||
assert.equal(nextAccept.packetType, "accept");
|
||||
// The Accept is unacknowledged and the only thing pending: 1 MiB fits above it.
|
||||
assert.equal(server.send("state", new Uint8Array(MIB), { reliable: true }).sequence, nextAccept.sequence + 1);
|
||||
});
|
||||
|
||||
test("CultNet contracts encode legacy bytes without Node Buffer authority", () => {
|
||||
const originalBuffer = globalThis.Buffer;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1307,6 +1307,30 @@ namespace GameCult.Networking.Tests
|
|||
Assert.That(promoted.Select(packet => packet.Sequence), Is.EqualTo(new[] { g.Sequence + 6 }));
|
||||
}
|
||||
|
||||
// A server that accepts a fresh Connect may send before its Accept is acknowledged, so no acknowledgement of
|
||||
// the new generation has moved the floor yet. Bytes the ended generation had acknowledged above its own lost
|
||||
// packet belong to no receiver's hold any more and must not count.
|
||||
[Test]
|
||||
public void RudpSession_ANewGenerationOnTheServerDoesNotInheritAcknowledgedBytes()
|
||||
{
|
||||
var server = FlowSession(500);
|
||||
var accept = server.AcceptConnect(FlowSession(1).CreateConnect(0), 0);
|
||||
server.Receive(FlowAck(accept.Sequence), 0);
|
||||
var g = FlowSend(server, 1).Single();
|
||||
for (uint offset = 1; offset <= 4; offset++)
|
||||
{
|
||||
Assert.That(FlowSend(server, Mib).Select(packet => packet.Sequence), Is.EqualTo(new[] { g.Sequence + offset }));
|
||||
server.Receive(FlowAck(g.Sequence + offset), 1);
|
||||
}
|
||||
Assert.That(FlowSend(server, Mib), Is.Empty);
|
||||
|
||||
var nextAccept = server.AcceptConnect(FlowSession(200_000).CreateConnect(2), 2);
|
||||
Assert.That(nextAccept.PacketType, Is.EqualTo(CultNetRudpPacketType.Accept));
|
||||
// The Accept is unacknowledged and the only thing pending: 1 MiB fits above it.
|
||||
var sent = server.Send("state", new byte[Mib], new CultNetRudpSendOptions { Reliable = true });
|
||||
Assert.That(sent.Sequence, Is.EqualTo(nextAccept.Sequence + 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RudpSocketTransport_HandshakesAndCarriesReliableOrderedSchemaFrames()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue