Skip to content

fix(tests): the RSS gate judged the one batch an arena step can land in (#768) - #769

Merged
InauguralPhysicist merged 1 commit into
mainfrom
fix-768-rss-median
Jul 29, 2026
Merged

fix(tests): the RSS gate judged the one batch an arena step can land in (#768)#769
InauguralPhysicist merged 1 commit into
mainfrom
fix-768-rss-median

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Closes #768

Main went red in run 30439602640 with RSS1 shared_incr leaked 2876 kB over 2000 reqs (1472 B/req). Attempt 2 of the same commit passed, and the PR branch had passed on the same tree 40 minutes earlier.

It was not a leak. A 24,000-request soak grows 0 kB after warmup, bounding any real per-request leak at <3 B/req against the 1472 B/req reported.

Cause. Every connection is served by a fresh EigsState on its own thread (ext_http.c:~1400). The first time two workers' lifetimes overlap, glibc allocates a second per-thread malloc arena and RSS steps by ~one worker's footprint — once, then arenas are recycled and RSS is flat. The overlap is scheduled by wall clock, not request count: the step lands at request 400 / 500 / 600 / 700 across four identical dev-box runs (size 2668–2856 kB, matching CI's 2876 kB), and on a slower runner past request 3000 — inside measured batch 2, which #765 had just made the sole verdict.

#765 fixed the front-loaded warmup shape. A late one-shot step wears the same "did not decay" signature while being equally one-time. The discriminating evidence was already in the failure text: first batch 0 kB — a leak of 1472 B/req cannot be absent from the immediately preceding identical batch.

Fix. Three measured batches, verdict = their median. One step, wherever it lands, moves at most one of three; a real leak moves all three. Threshold unchanged at 64 kB. Cost is one extra 2000-request batch per check (~4s locally).

Validation

One note worth carrying forward: the first fault planted to validate this gate — dropping a val_decref in shared_incr — leaked nothing measurable, because make_num serves request-path Values from the arena and they die at arena reset regardless of refcount. A green planted fault is evidence about the fault first.

🤖 Generated with Claude Code

…in (#768)

#765 made the per-request leak gate judge the SECOND of two measured
batches, on the premise that the process's one-time memory cost is
front-loaded and so already spent by then. It is not.

Every connection is served by a fresh EigsState on its own thread
(ext_http.c:~1400), so the first time two workers' lifetimes overlap,
glibc allocates a second per-thread malloc arena and RSS steps by about
one worker's footprint -- ~2.7 MB, once, after which arenas are recycled
and RSS is flat. That overlap is scheduled by wall clock, not by request
count: the step lands at request 400, 500, 600 and 700 across four
identical dev-box runs, and on a slower CI runner past 3000 -- inside the
batch #765 had just made the verdict. Run 30439602640 failed that way
(2876 kB, "first batch 0 kB") while attempt 2 of the same commit passed.

It was never a leak: a 24,000-request soak grows 0 kB after warmup,
bounding any real per-request leak at <3 B/req against the 1472 B/req
reported. The tell was already in the failure text -- a leak of
1472 B/req cannot be absent from the immediately preceding identical
batch.

Three measured batches now, verdict = their median: one step, wherever
it lands, moves at most one of the three; a real leak moves all three.
The threshold is unchanged at 64 kB.

The verdict rule is now a pure function of the three numbers, self-tested
on every run against ten planted faults -- both historical leak rates,
the step in each of the three positions, and a leak with a step on top.
It was also validated end-to-end against a real 256 B/req heap leak
planted in shared_incr, which it reports as 272 B/req with all three
batches grown.

That end-to-end check earned its place: the first fault planted to
validate this gate -- dropping a val_decref in shared_incr -- leaked
nothing measurable, because make_num serves request-path Values from the
arena and they die at arena reset regardless of refcount. A gate this
class depends on has to be shown turning red by something that leaks.

Closes #768

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 11:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes flakiness in the HTTP per-request RSS growth leak gate by making it robust against one-time allocator “steps” that can land in any measured window depending on scheduler timing, while keeping the leak threshold unchanged.

Changes:

  • Switch the RSS-growth verdict to three consecutive measured batches and judge the median (instead of judging only batch 2).
  • Factor the verdict logic into a pure leak_verdict function and add an always-on self-test against planted scenarios.
  • Update the test-suite runner label and document the incident and fix in CHANGELOG.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/test_http_rss_growth.sh Implements 3-batch median verdict, extracts the decision rule into a function, and adds self-tests.
tests/run_all_tests.sh Updates the suite log line to reflect the added verdict self-test.
CHANGELOG.md Records the intermittent CI failure cause and the new median-based verdict rule.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# class goes unwatched.

median3() { printf '%s\n%s\n%s\n' "$1" "$2" "$3" | sort -n | sed -n 2p; }
max3() { printf '%s\n%s\n%s\n' "$1" "$2" "$3" | sort -n | tail -1; }
# `reason` and `rc` are declared here but assigned separately: `local x=$(f)`
# would make $? the exit status of `local`, not of f, and the verdict would
# always read as clean.
local warm m1 m2 m3 d reason rc
Comment on lines +238 to +242
REASON=$(leak_verdict "$((B - A))" "$((C - B))" "$((D - C))"); RC=$?
if [ "$RC" -eq 0 ]; then
ok "RSS2 authed route $REASON"
else
ok "RSS2 authed route steady-state growth ${GROWTH} kB over $MEASURE reqs (<= ${THRESHOLD_KB} kB)"
fail "RSS2 authed route $REASON"
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.

The RSS leak gate's warmup discriminator picks the one batch a per-connection arena step can land in (#765 follow-up)

2 participants