Skip to content

reactor: wake for a due QUIC deadline instead of waiting for the 250 ms tick - #271

Closed
MDA2AV wants to merge 1 commit into
mainfrom
fix/quic-timed-wait
Closed

MDA2AV wants to merge 1 commit into
mainfrom
fix/quic-timed-wait

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

An idle reactor sleeps in io_uring_enter until a completion arrives, and the only one it can count on is the 250 ms ticker. So a QUIC retransmit, ACK or pacing step due in a few ms waited for the next tick. #267 made the deadline correct; nothing woke the loop for it.

This bounds the loop's wait by the earliest engine deadline (IORING_ENTER_EXT_ARG with a timeout). The 250 ms timer and the sweeps are untouched, and a reactor with no pending QUIC deadline (every TCP-only reactor) makes the same unbounded wait as before.

One of two alternatives - the other is #272, which also replaces the timer. Merge one, close the other.

Tests

  • quic/timer: an idle reactor fires an engine deadline when it is due, not at the next tick: the answer is lost and the peer stays silent, so the retransmit timer fires again and again; each firing is timed against the engine's own ns deadline.
    • main: up to 226-237 ms late ([146, 233, 22, 44, 89], [144, 226, 13, 26, 52] ms)
    • this PR: 0.0-1.1 ms
  • quic/timer: a reactor whose last connection is gone does not keep waking for it: nothing resets the tracked deadline when the last connection leaves, so the wait ignores it then. Without that guard the empty reactor woke 996 times/s.
  • quic/timer: a deadline that is already due is reported as due… needed the loop to notice a deadline 2 ms late, which it no longer does; any amount past now counts (the clock only moves forward).

All suites pass, pending lists unchanged from main: E2E 233, Unit 60, Chaos 47, Http 44, Tls 151 (the 7 kTLS tests skip without sudo), File 4.

Bench

Interleaved: 5 rounds of 10 s per sample in rotating order, 2 reactors pinned to the P-cores. control is a second build of main, so its delta is the noise floor. Medians against main, req/s · CPU per request:

sample main this PR #272 control (main again)
Tcp/Raw (h1) 883,722/s · 2.285 µs +1.3% · −1.3% +4.3% · −4.0% +3.7% · −3.5%
Http3/Nghttp3Buffered (h3, 16 conns) 782,765/s · 2.552 µs +0.9% · −0.8% +1.3% · −1.3% −0.7% · +0.7%
Tls/OpenSsl (h1s) 365,622/s · 5.533 µs +0.1% · −0.1% +0.7% · −0.7% +2.3% · −2.2%

Every delta is inside the control's own spread, so neither design costs anything measurable under load. Idle reactors wake 4.0-4.2 times/s on main and both PRs alike (the 250 ms tick), for TCP and for QUIC after a 1.35M-request h3 burst has ended.

Size: src +62 −17 in 9 files.

…ms tick

The loop's wait is bounded by the earliest engine deadline, so an idle reactor fires a
retransmit, ACK or pacing step when it is due. The 250 ms timer and the sweeps are
unchanged, and a reactor with no QUIC deadline makes the same unbounded wait as before.
@MDA2AV

MDA2AV commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Closing in favour of #272.

@MDA2AV MDA2AV closed this Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant