Skip to content

fix(#201): pace the event stream test on its own events - #202

Merged
octalide merged 1 commit into
devfrom
fix/201
Sep 28, 2026
Merged

octalide merged 1 commit into
devfrom
fix/201

Conversation

@octalide

Copy link
Copy Markdown
Contributor

Closes #201

What

Cause. event_scenario published one event per 100ms os.sleep from the client thread under a 300ms response_timeout_ns, which http re-arms on each write the client accepts. A stall of 200ms or more between two writes, on either thread, is a legitimate timeout: the server closes the connection and the client's read sees end of stream, which is exit 13. Setting the pause past the timeout reproduces exit 13 exactly. The failing leg ran slow overall (laurel.serve 2.6s against 1.8s and 2.0s on the passing darwin legs). The wake path (context.wake to hs.wake to io_runtime.wake) latches correctly, and http 0.25's lingering close is not involved, because the timeout closes the connection before any close the client could race. No library bug, so the fix is in the test.

Fix. The test now synchronizes on the stream itself:

  • The first event is published before the request, so the stream opens with it. Its arrival on the wire shows that the handler published the waker, which replaces the 10ms stream_ready poll.
  • Each later event is published only once the one before it has been read off the wire, so the client never runs ahead of the stream.
  • The stream ends when the client marks it done (stream_done) after it has lived EVENT_LIFETIMES (3) response timeouts by the client's monotonic clock. The fixed EVENTS count is gone.
  • A 10ms pause stays before each event. Without it, write-then-read ping-pong makes hundreds of events per millisecond and overruns the stream's 4096-byte cap within milliseconds. The pause is the only gap between two writes that is not scheduling. A stall has to eat 290ms of the 300ms timeout, where it had to eat 200ms before. The timeout is unchanged.

Nothing that paces a producer against a wall-clock server deadline can be fully deterministic without an injectable server clock, which http does not offer.

Ran locally: mach test . --filter serve (4 passed) and mach fmt --check ..

Checklist

  • Linked to an issue
  • Conventional Commit messages
  • Build and tests pass

The test published an event every 100ms from a client thread under a 300ms
response timeout, so any 200ms stall between two writes on a loaded runner
timed the stream out and the client read end of stream (exit 13). A pause past
the timeout reproduces that exit exactly. Nothing in the wake path or http
0.25's lingering close is involved: the timeout closes the connection before
any close the client could race.

The client now publishes the first event before the request, so the stream
opens with it and its arrival shows the waker is published, replacing the
stream_ready poll. Each later event is published once the one before it is on
the wire, after a 10ms pause, until the stream has lived three timeouts, and the
stream ends when the client marks it done. A stall now has to eat 290ms of the
300ms timeout, not 200ms.
@octalide
octalide marked this pull request as ready for review September 28, 2026 05:46
@octalide
octalide merged commit 4b01257 into dev Sep 28, 2026
10 checks passed
@octalide octalide mentioned this pull request Sep 28, 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.

serve: event stream test failed once on aarch64-darwin in the v0.22.0 release run

1 participant