Skip to content

Commit fe2b124

Browse files
committed
Pin the stall test's TCP window instead of relying on buffer autotuning
The stall test flooded 10000 x 4000B (~40MB) to force the writer to block on a full TCP receive window, a size tuned to fit between "fits entirely" and "nothing arrives in 5s" on one machine's loopback buffer autotuning. A CI runner with larger tcp_rmem/tcp_wmem maxima could absorb the flood and spuriously pass with zero drops. Give RtmpPlayer::connect_and_play an optional rcvbuf parameter (0 by default, preserving the delivery test's behavior unchanged) that pins SO_RCVBUF before connect so the advertised window is capped at handshake. The stall test now pins it to 4096 bytes and shrinks the flood to 1500 x 4000B frames, making the writer block -- and the resulting frames_dropped() > 0 -- deterministic regardless of host buffer tuning, and the test runs in under a second.
1 parent d57bfc5 commit fe2b124

1 file changed

Lines changed: 21 additions & 9 deletions

File tree

tests/integration/egress_test.cpp

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@ struct RtmpPlayer {
3232
roqr::rtmp::ChunkReader reader;
3333
roqr::rtmp::ChunkWriter writer;
3434

35-
bool connect_and_play(uint16_t port, const std::string& name) {
35+
// rcvbuf: 0 = don't set (delivery test's default, kernel autotuning
36+
// applies); > 0 pins SO_RCVBUF before connect so the advertised TCP
37+
// window is capped at handshake, making a stalled reader's window-full
38+
// point deterministic regardless of host tcp_rmem/tcp_wmem tuning.
39+
bool connect_and_play(uint16_t port, const std::string& name,
40+
int rcvbuf = 0) {
3641
fd = ::socket(AF_INET, SOCK_STREAM, 0);
42+
if (rcvbuf > 0)
43+
::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
3744
sockaddr_in addr{};
3845
addr.sin_family = AF_INET;
3946
addr.sin_port = htons(port);
@@ -172,20 +179,25 @@ TEST_CASE("a stalled player does not wedge the egress QUIC thread") {
172179
REQUIRE(egress.start(eo));
173180
REQUIRE(egress.wait_playing(5s));
174181

175-
// A player that plays then stops reading (stalls its TCP receive).
182+
// A player that plays then stops reading (stalls its TCP receive). Pin
183+
// its receive buffer small (rcvbuf=4096; the kernel doubles the request
184+
// and enforces a ~2304-byte floor on Linux) so the writer blocks after a
185+
// few KB regardless of the host's tcp_rmem/tcp_wmem autotuning -- a CI
186+
// runner with larger maxima would otherwise absorb the flood entirely
187+
// and the assertion below would spuriously fail.
176188
RtmpPlayer player;
177-
REQUIRE(player.connect_and_play(45587, "cam"));
189+
REQUIRE(player.connect_and_play(45587, "cam", /*rcvbuf=*/4096));
178190
std::this_thread::sleep_for(200ms);
179191

180192
// Seq header + a flood of large keyframe-ish frames. The player never
181-
// reads, so its TCP window fills and the writer thread blocks; the queue
182-
// fills and starts dropping — but the QUIC network thread (on_frame)
183-
// keeps accepting, so this loop completes without hanging. (10000 x
184-
// 4000-byte frames: smaller floods can fit entirely within the OS
185-
// socket buffers on loopback without ever blocking the writer.)
193+
// reads, so its (now tiny) TCP window fills and the writer thread
194+
// blocks; the queue fills and starts dropping — but the QUIC network
195+
// thread (on_frame) keeps accepting, so this loop completes without
196+
// hanging. With the window pinned small, 1500 x 4000-byte frames is
197+
// plenty to force drops deterministically and keeps the test fast.
186198
pub.send(to_frame(vid(0, {0x17, 0x00, 0x11}), 0),
187199
roqr::quic::DeliveryMode::Stream);
188-
for (int i = 1; i <= 10000; ++i) {
200+
for (int i = 1; i <= 1500; ++i) {
189201
pub.send(to_frame(vid(static_cast<uint32_t>(i * 10),
190202
std::vector<uint8_t>(4000, 0x27)),
191203
0),

0 commit comments

Comments
 (0)