The one cell that fails the soak leaks live memory, and it is the only one with streaming routes
The 24-hour soak (probatorium run 34368601903, pin c9f7dad) put 48 cells through an hour each. Forty-six report zero property violations. The two that do not are the same cell on both architectures — auth_session_ratelimit on io_uring — and they fail only I-MEM-1, the heap-growth oracle. The same reference app on epoll and on std is clean on both hosts, and every other io_uring cell is clean.
| host |
cell |
heap_inuse over the hour |
violations |
| msa2 (amd64) |
auth_session_ratelimit / io_uring |
11.2 → 40.9 MB |
2550 |
| msr1 (arm64) |
auth_session_ratelimit / io_uring |
9.0 → 29.1 MB |
2476 |
| msa2 |
auth_session_ratelimit / epoll |
3.1 → 11.8 MB |
0 |
| msa2 |
auth_session_ratelimit / std |
1.7 → 6.8 MB |
0 |
Magnitude is not what separates them. observability on io_uring grew further in its own hour, 17.9 → 53.5 MB, and passed. The oracle fits post-warm-up per-150-second minima, so a workload that ramps and then plateaus scores flat however high the plateau sits. What fails is a series whose post-collection floor keeps rising.
It is live memory, not span fragmentation
I reproduced the cell locally under Docker against the same pin and sampled exact runtime.MemStats from the reference app once a second, then re-derived the oracle's own statistic from the series. At eighteen minutes:
| series |
post-collection trough slope |
HeapInuse |
1,987 B/s — over the 1,024 B/s budget, same verdict as the cluster |
HeapAlloc |
1,334 B/s |
HeapObjects |
5.7 /s |
Live object count rising is the part that matters. Fragmentation moves HeapInuse without moving HeapAlloc or HeapObjects; all three move here. Roughly 234 bytes per retained object, against about 71 accepted connections per second.
What is distinctive about this cell
auth_session_ratelimit is the only one of the nine reference apps that routes /ws and /events. The other eight have no WebSocket and no server-sent events at all, and the validator probes for those routes and skips the corresponding walker slices when they are absent. All three engines take the identical streaming load in this cell — 47,598 WebSocket upgrades and 9,185 event streams killed mid-stream per hour, matched to within five requests across engines — and only io_uring retains.
So the intersection is: io_uring, plus connections that detach.
What this is not
- Not goroutines. The failing cell has the smallest goroutine growth of any io_uring cell in the run (+15 over the hour), and I-MEM-3 is zero everywhere. The incident's goroutine profile holds 101 goroutines, two of them in the event-stream handler and none in WebSocket.
- Not connection accumulation. Active connections sit at 44 at the incident, with accepted minus closed accounted for.
- Not the session store. Sessions expired exceeds sessions created over the window, and the same middleware runs on the engines that stay clean.
- Not the reference app's own bookkeeping. Its WebSocket handler is a bare echo loop and its event handler a ticker; neither registers the connection anywhere.
Still open
Which allocation site. The default heap profiler samples one allocation per 512 KB, which cannot resolve this rate over any window shorter than the cell itself — the soak's own incident dossier ships two profiles whose difference is smaller than the profiler's sampling error, which is why it names nothing (probatorium#319). Two controls and a finer-grained profiling run are in flight:
- epoll on the identical local cell, to confirm the asymmetry off-cluster
- the same reference app with
/ws and /events renamed away, so the streaming walkers are skipped and everything else is byte-identical
- a repeat io_uring cell with the profiler sampling every 4 KiB, which resolves the growth well enough for a profile diff to name the site
The one cell that fails the soak leaks live memory, and it is the only one with streaming routes
The 24-hour soak (probatorium run 34368601903, pin
c9f7dad) put 48 cells through an hour each. Forty-six report zero property violations. The two that do not are the same cell on both architectures —auth_session_ratelimiton io_uring — and they fail only I-MEM-1, the heap-growth oracle. The same reference app on epoll and on std is clean on both hosts, and every other io_uring cell is clean.Magnitude is not what separates them.
observabilityon io_uring grew further in its own hour, 17.9 → 53.5 MB, and passed. The oracle fits post-warm-up per-150-second minima, so a workload that ramps and then plateaus scores flat however high the plateau sits. What fails is a series whose post-collection floor keeps rising.It is live memory, not span fragmentation
I reproduced the cell locally under Docker against the same pin and sampled exact
runtime.MemStatsfrom the reference app once a second, then re-derived the oracle's own statistic from the series. At eighteen minutes:HeapInuseHeapAllocHeapObjectsLive object count rising is the part that matters. Fragmentation moves
HeapInusewithout movingHeapAllocorHeapObjects; all three move here. Roughly 234 bytes per retained object, against about 71 accepted connections per second.What is distinctive about this cell
auth_session_ratelimitis the only one of the nine reference apps that routes/wsand/events. The other eight have no WebSocket and no server-sent events at all, and the validator probes for those routes and skips the corresponding walker slices when they are absent. All three engines take the identical streaming load in this cell — 47,598 WebSocket upgrades and 9,185 event streams killed mid-stream per hour, matched to within five requests across engines — and only io_uring retains.So the intersection is: io_uring, plus connections that detach.
What this is not
Still open
Which allocation site. The default heap profiler samples one allocation per 512 KB, which cannot resolve this rate over any window shorter than the cell itself — the soak's own incident dossier ships two profiles whose difference is smaller than the profiler's sampling error, which is why it names nothing (probatorium#319). Two controls and a finer-grained profiling run are in flight:
/wsand/eventsrenamed away, so the streaming walkers are skipped and everything else is byte-identical