What happened
extensions on main failed in run 30439602640 (commit 9caff0c):
FAIL: RSS1 shared_incr leaked 2876 kB over 2000 reqs
(1472 B/req; threshold 64 kB; first batch 0 kB
— growth did NOT decay, so this is a leak, not warmup)
Attempt 2 of the same commit passed. The PR branch had passed 40 minutes earlier on the same tree.
It is not a leak
A 24,000-request soak against /sinc grows 0 kB after warmup:
| requests |
2000 |
4000 |
… |
24000 |
| RSS delta |
+4244 kB |
0 |
0 |
0 |
That bounds any real per-request leak at <3 B/req, against the 1472 B/req the gate reported.
At 100-request granularity the warmup is two discrete steps, and the second one moves:
| run |
step 1 |
step 2 |
| 1 |
req 100, +1576 kB |
req 400, +2668 kB |
| 2 |
req 100, +1576 kB |
req 500, +2668 kB |
| 3 |
req 100, +1388 kB |
req 600, +2856 kB |
| 4 |
req 100, +1388 kB |
req 700, +2856 kB |
Step 2's size (2668–2856 kB) matches CI's reported 2876 kB.
Cause
Every connection is served by a fresh EigsState on its own detached thread (src/ext_http.c:~1400). The first time two workers' lifetimes overlap, glibc allocates a second per-thread malloc arena, and RSS steps by roughly one worker's footprint — once. Arenas are recycled afterwards, which is why RSS is then flat forever.
That overlap is governed by wall-clock scheduling, not request count. On a slower or contended CI runner it lands past request 3000 — i.e. inside measured batch 2, which #765 had just made the sole verdict.
Why #765's rule cannot see this
#765 fixed the front-loaded warmup shape ("one-time warmup decays, a per-request leak does not") by judging the second batch. A late one-shot step has the same "did not decay" signature while being just as 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.
The verdict rule becomes a pure function of the three numbers, self-tested on every run against ten planted faults (both historical leak rates, the step in each position, a leak with a step on top), plus an end-to-end validation against a real 256 B/req heap leak planted in shared_incr — reported as 272 B/req with all three batches grown.
Note for anyone validating this gate in future: the first fault planted here, dropping a val_decref in shared_incr, leaked nothing measurable — make_num serves request-path Values from the arena, and they die at arena reset regardless of refcount. Validate with something that allocates on the heap.
What happened
extensionson main failed in run 30439602640 (commit 9caff0c):Attempt 2 of the same commit passed. The PR branch had passed 40 minutes earlier on the same tree.
It is not a leak
A 24,000-request soak against
/sincgrows 0 kB after warmup:That bounds any real per-request leak at <3 B/req, against the 1472 B/req the gate reported.
At 100-request granularity the warmup is two discrete steps, and the second one moves:
Step 2's size (2668–2856 kB) matches CI's reported 2876 kB.
Cause
Every connection is served by a fresh
EigsStateon its own detached thread (src/ext_http.c:~1400). The first time two workers' lifetimes overlap, glibc allocates a second per-thread malloc arena, and RSS steps by roughly one worker's footprint — once. Arenas are recycled afterwards, which is why RSS is then flat forever.That overlap is governed by wall-clock scheduling, not request count. On a slower or contended CI runner it lands past request 3000 — i.e. inside measured batch 2, which #765 had just made the sole verdict.
Why #765's rule cannot see this
#765 fixed the front-loaded warmup shape ("one-time warmup decays, a per-request leak does not") by judging the second batch. A late one-shot step has the same "did not decay" signature while being just as 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.
The verdict rule becomes a pure function of the three numbers, self-tested on every run against ten planted faults (both historical leak rates, the step in each position, a leak with a step on top), plus an end-to-end validation against a real 256 B/req heap leak planted in
shared_incr— reported as 272 B/req with all three batches grown.Note for anyone validating this gate in future: the first fault planted here, dropping a
val_decrefinshared_incr, leaked nothing measurable —make_numserves request-path Values from the arena, and they die at arena reset regardless of refcount. Validate with something that allocates on the heap.