Ack Cut 1 tests: three held frames, the Rust byte bound, reset reseeds
A gap now holds three ordered frames so a watermark that advances one step per call fails; the reset test follows a second handshake at a different sequence so a watermark that survives the reset fails; Rust gains the 4 MiB hold bound in bytes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ebc7080165
commit
43d7e53efd
4 changed files with 95 additions and 23 deletions
|
|
@ -10,7 +10,6 @@ import unittest
|
|||
|
||||
from cultnet_py.transport import (
|
||||
CultNetRudpPacket,
|
||||
CultNetRudpPacketType,
|
||||
CultNetRudpSendOptions,
|
||||
CultNetRudpSession,
|
||||
CultNetRudpSessionOptions,
|
||||
|
|
@ -40,11 +39,13 @@ class CultNetRudpOrderedDeliveryTests(unittest.TestCase):
|
|||
u = send(sender, "rel", "u", False)
|
||||
o2 = send(sender, "schema", "o2", True)
|
||||
o3 = send(sender, "schema", "o3", True)
|
||||
o4 = send(sender, "schema", "o4", True)
|
||||
|
||||
self.assertEqual(names(receiver.receive(o1, 1)), ["o1"])
|
||||
self.assertEqual(names(receiver.receive(o2, 2)), [], "u is missing")
|
||||
self.assertEqual(names(receiver.receive(o3, 3)), [], "u is missing")
|
||||
self.assertEqual(names(receiver.receive(u, 4)), ["u", "o2", "o3"])
|
||||
self.assertEqual(names(receiver.receive(o4, 4)), [], "u is missing")
|
||||
self.assertEqual(names(receiver.receive(u, 5)), ["u", "o2", "o3", "o4"])
|
||||
|
||||
def test_two_ordered_channels_release_each_other_in_sequence_order(self) -> None:
|
||||
sender, receiver = handshake()
|
||||
|
|
@ -93,16 +94,21 @@ class CultNetRudpOrderedDeliveryTests(unittest.TestCase):
|
|||
self.assertEqual(names(receiver.receive(s1, 3)), ["s1", "s2"])
|
||||
self.assertEqual(names(receiver.receive(s2, 4)), [])
|
||||
|
||||
def test_reset_peer_state_forgets_held_frames_and_the_watermark(self) -> None:
|
||||
def test_reset_peer_state_forgets_held_frames_and_reseeds_the_watermark_from_the_next_handshake(self) -> None:
|
||||
sender, receiver = handshake()
|
||||
s1 = send(sender, "schema", "s1", True)
|
||||
send(sender, "schema", "s1", True)
|
||||
s2 = send(sender, "schema", "s2", True)
|
||||
self.assertEqual(names(receiver.receive(s2, 1)), [])
|
||||
|
||||
receiver.reset_peer_state()
|
||||
receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 410, 1, 0, 0, "control"), 2)
|
||||
following = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=410, initial_sequence=100))
|
||||
following.receive(receiver.accept_connect(following.create_connect(0), 2), 2)
|
||||
d1 = send(following, "schema", "d1", True)
|
||||
d2 = send(following, "schema", "d2", True)
|
||||
|
||||
self.assertEqual(names(receiver.receive(s1, 3)), ["s1"], "the held s2 died with the reset")
|
||||
# The held s2 died with the reset, and the watermark follows the new peer.
|
||||
self.assertEqual(names(receiver.receive(d2, 3)), [])
|
||||
self.assertEqual(names(receiver.receive(d1, 4)), ["d1", "d2"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -2762,16 +2762,18 @@ fn rudp_ordered_frame_waits_for_a_gap_filled_by_another_channel() -> Result<()>
|
|||
let u = sender.send("media", b"u".to_vec(), rudp_send_options(false))?;
|
||||
let o2 = sender.send("schema", b"o2".to_vec(), rudp_send_options(true))?;
|
||||
let o3 = sender.send("schema", b"o3".to_vec(), rudp_send_options(true))?;
|
||||
let o4 = sender.send("schema", b"o4".to_vec(), rudp_send_options(true))?;
|
||||
|
||||
assert_eq!(rudp_delivered_names(&receiver.receive(&o1, 1)?.delivered), ["o1"]);
|
||||
// `u` is lost, so nothing behind it may be delivered.
|
||||
assert!(receiver.receive(&o2, 2)?.delivered.is_empty());
|
||||
assert!(receiver.receive(&o3, 3)?.delivered.is_empty());
|
||||
// The other channel's packet fills the gap and releases the ordered frames
|
||||
// in the same call.
|
||||
assert!(receiver.receive(&o4, 4)?.delivered.is_empty());
|
||||
// The other channel's packet fills the gap and releases every ordered
|
||||
// frame behind it in the same call.
|
||||
assert_eq!(
|
||||
rudp_delivered_names(&receiver.receive(&u, 4)?.delivered),
|
||||
["u", "o2", "o3"]
|
||||
rudp_delivered_names(&receiver.receive(&u, 5)?.delivered),
|
||||
["u", "o2", "o3", "o4"]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2826,6 +2828,30 @@ fn rudp_accept_seeds_the_watermark_of_the_connecting_side() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudp_reset_forgets_held_frames_and_reseeds_the_watermark_from_the_next_handshake() -> Result<()> {
|
||||
let (mut sender, mut receiver) = rudp_ordered_pair()?;
|
||||
sender.send("schema", b"s1".to_vec(), rudp_send_options(true))?;
|
||||
let s2 = sender.send("schema", b"s2".to_vec(), rudp_send_options(true))?;
|
||||
assert!(receiver.receive(&s2, 1)?.delivered.is_empty());
|
||||
|
||||
receiver.reset_peer_state();
|
||||
let mut next = CultNetRudpSession::new(CultNetRudpSessionOptions {
|
||||
connection_id: 410,
|
||||
initial_sequence: 100,
|
||||
..CultNetRudpSessionOptions::default()
|
||||
});
|
||||
let accept = receiver.accept_connect(&next.create_connect(0, Vec::new())?, 2, Vec::new())?;
|
||||
next.receive(&accept, 2)?;
|
||||
let d1 = next.send("schema", b"d1".to_vec(), rudp_send_options(true))?;
|
||||
let d2 = next.send("schema", b"d2".to_vec(), rudp_send_options(true))?;
|
||||
|
||||
// The held s2 died with the reset, and the watermark follows the new peer.
|
||||
assert!(receiver.receive(&d2, 3)?.delivered.is_empty());
|
||||
assert_eq!(rudp_delivered_names(&receiver.receive(&d1, 4)?.delivered), ["d1", "d2"]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudp_large_ordered_frame_without_a_gap_is_never_held_or_refused() -> Result<()> {
|
||||
let (mut sender, mut receiver) = rudp_ordered_pair()?;
|
||||
|
|
@ -2851,6 +2877,32 @@ fn rudp_large_ordered_frame_without_a_gap_is_never_held_or_refused() -> Result<(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudp_ordered_hold_buffer_is_bounded_in_bytes() -> Result<()> {
|
||||
let (mut sender, mut receiver) = rudp_ordered_pair()?;
|
||||
let withheld = sender.send("schema", b"first".to_vec(), rudp_send_options(true))?;
|
||||
sender.receive(&receiver.create_ack_for(withheld.sequence), 1)?;
|
||||
|
||||
// Four 1 MiB frames fit the 4 MiB hold; the fifth does not.
|
||||
let mut refused_at = None;
|
||||
for step in 0..8u32 {
|
||||
let packet = sender.send("schema", vec![7u8; 1024 * 1024], rudp_send_options(true))?;
|
||||
match receiver.receive(&packet, 2) {
|
||||
Ok(result) => {
|
||||
assert!(result.delivered.is_empty(), "the gap holds every frame");
|
||||
sender.receive(&receiver.create_ack_for(packet.sequence), 2)?;
|
||||
}
|
||||
Err(refusal) => {
|
||||
assert!(refusal.to_string().contains("ordered hold buffer is full"), "{refusal}");
|
||||
refused_at = Some(step);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(refused_at, Some(4));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rudp_far_ahead_sequence_cannot_starve_the_session() -> Result<()> {
|
||||
let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions {
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ test("an ordered frame waits for a gap filled by another channel", () => {
|
|||
const u = send(sender, "rel", "u", false);
|
||||
const o2 = send(sender, "schema", "o2", true);
|
||||
const o3 = send(sender, "schema", "o3", true);
|
||||
const o4 = send(sender, "schema", "o4", true);
|
||||
|
||||
assert.deepEqual(names(receiver.receive(o1, 1)), ["o1"]);
|
||||
assert.deepEqual(names(receiver.receive(o2, 2)), [], "u is missing");
|
||||
assert.deepEqual(names(receiver.receive(o3, 3)), [], "u is missing");
|
||||
assert.deepEqual(names(receiver.receive(u, 4)), ["u", "o2", "o3"]);
|
||||
assert.deepEqual(names(receiver.receive(o4, 4)), [], "u is missing");
|
||||
assert.deepEqual(names(receiver.receive(u, 5)), ["u", "o2", "o3", "o4"]);
|
||||
});
|
||||
|
||||
test("two ordered channels release each other in sequence order", () => {
|
||||
|
|
@ -90,14 +92,19 @@ test("a duplicate of a held frame is not delivered twice", () => {
|
|||
assert.deepEqual(names(receiver.receive(s2, 4)), []);
|
||||
});
|
||||
|
||||
test("resetting peer state forgets held frames and the watermark", () => {
|
||||
test("resetting peer state forgets held frames and reseeds the watermark from the next handshake", () => {
|
||||
const { client: sender, server: receiver } = handshake();
|
||||
const s1 = send(sender, "schema", "s1", true);
|
||||
send(sender, "schema", "s1", true);
|
||||
const s2 = send(sender, "schema", "s2", true);
|
||||
assert.deepEqual(names(receiver.receive(s2, 1)), []);
|
||||
|
||||
receiver.resetPeerState();
|
||||
receiver.receive({ packetType: "accept", connectionId: 410, sequence: 1, ack: 0, ackMask: 0, channelId: "control" }, 2);
|
||||
const next = new CultNetRudpSession({ connectionId: 410, initialSequence: 100 });
|
||||
next.receive(receiver.acceptConnect(next.createConnect(0), 2), 2);
|
||||
const d1 = send(next, "schema", "d1", true);
|
||||
const d2 = send(next, "schema", "d2", true);
|
||||
|
||||
assert.deepEqual(names(receiver.receive(s1, 3)), ["s1"], "the held s2 died with the reset");
|
||||
// The held s2 died with the reset, and the watermark follows the new peer.
|
||||
assert.deepEqual(names(receiver.receive(d2, 3)), []);
|
||||
assert.deepEqual(names(receiver.receive(d1, 4)), ["d1", "d2"]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,11 +38,13 @@ namespace GameCult.Networking.Tests
|
|||
var u = Send(sender, "rel", "u", false);
|
||||
var o2 = Send(sender, "schema", "o2", true);
|
||||
var o3 = Send(sender, "schema", "o3", true);
|
||||
var o4 = Send(sender, "schema", "o4", true);
|
||||
|
||||
Assert.That(Names(receiver.Receive(o1, 1)), Is.EqualTo(new[] { "o1" }));
|
||||
Assert.That(Names(receiver.Receive(o2, 2)), Is.Empty, "u is missing");
|
||||
Assert.That(Names(receiver.Receive(o3, 3)), Is.Empty, "u is missing");
|
||||
Assert.That(Names(receiver.Receive(u, 4)), Is.EqualTo(new[] { "u", "o2", "o3" }));
|
||||
Assert.That(Names(receiver.Receive(o4, 4)), Is.Empty, "u is missing");
|
||||
Assert.That(Names(receiver.Receive(u, 5)), Is.EqualTo(new[] { "u", "o2", "o3", "o4" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
@ -111,17 +113,22 @@ namespace GameCult.Networking.Tests
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void ResetPeerStateForgetsHeldFramesAndTheWatermark()
|
||||
public void ResetPeerStateForgetsHeldFramesAndReseedsTheWatermarkFromTheNextHandshake()
|
||||
{
|
||||
var (sender, receiver) = Handshake();
|
||||
var s1 = Send(sender, "schema", "s1", true);
|
||||
Send(sender, "schema", "s1", true);
|
||||
var s2 = Send(sender, "schema", "s2", true);
|
||||
Assert.That(Names(receiver.Receive(s2, 1)), Is.Empty);
|
||||
|
||||
receiver.ResetPeerState();
|
||||
receiver.Receive(new CultNetRudpPacket { PacketType = CultNetRudpPacketType.Accept, ConnectionId = 410, Sequence = 1, ChannelId = "control" }, 2);
|
||||
var next = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 410, InitialSequence = 100 });
|
||||
next.Receive(receiver.AcceptConnect(next.CreateConnect(0), 2), 2);
|
||||
var d1 = Send(next, "schema", "d1", true);
|
||||
var d2 = Send(next, "schema", "d2", true);
|
||||
|
||||
Assert.That(Names(receiver.Receive(s1, 3)), Is.EqualTo(new[] { "s1" }), "the held s2 died with the reset");
|
||||
// The held s2 died with the reset, and the watermark follows the new peer.
|
||||
Assert.That(Names(receiver.Receive(d2, 3)), Is.Empty);
|
||||
Assert.That(Names(receiver.Receive(d1, 4)), Is.EqualTo(new[] { "d1", "d2" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue