Skip to content

AppKit label retention across list rebuilds: measure it in CI (#139) - #165

Merged
nicolas-maman merged 4 commits into
mainfrom
fix/139-appkit-label-retention
Sep 18, 2026
Merged

nicolas-maman merged 4 commits into
mainfrom
fix/139-appkit-label-retention

Conversation

@nicolas-maman

Copy link
Copy Markdown
Contributor

Towards #139.

The issue reports every label a list rebuild retires staying alive in the window's layout engine, measured once, by hand, with heap on a headless probe that rebuilt 400 rows every 60ms (faster than a rebuild takes, ~270ms). Nothing in the tree could repeat that measurement, and nothing here can run AppKit but the macOS leg, so the first step is the measurement in CI.

tests/appkit_retention/retention_probe.ae rebuilds a 400-row listbox on a timer (two alternating 400-row lists, so every rebuild renders text the previous one did not), prints a marker line after N rebuilds and then sits still to be inspected. Period and rounds come from AETHER_PROBE_PERIOD_MS / AETHER_PROBE_ROUNDS.

ci.sh Phase 1f (macOS only; SKIP elsewhere and when heap is missing) builds it and runs it twice, counting live NSTextField with heap:

Whichever way the assertion goes, the result is the next step: if the idle run is bounded, #139's count was the layout pass never getting to run in a saturated loop (the engine drops a retired view's variables on its next pass, and a loop that never waits never runs one), and this phase becomes the guard that says so; if it is not, the fix goes on this branch with the phase to prove it.

No behaviour change to any backend in this commit.

tests/appkit_retention/retention_probe.ae rebuilds a 400-row listbox on a
timer and then sits still; ci.sh's Phase 1f runs it on the macOS leg and
counts the live NSTextField instances with heap(1). Two runs: the run loop
idle between rebuilds, the shape of an app refreshing a list, asserted
against a bound of 1200 (rows on screen, a rebuild in flight, the window's
other labels); and the loop saturated by a period shorter than a rebuild,
the shape #139 was measured in, reported and not asserted.

#139 has no measurement anyone but its author can repeat; this is the
measurement, in the one place with an AppKit runtime.
…evel

The first run counted 1 275 NSTextField after 20 rebuilds of 400 rows,
idle and saturated alike: a steady state, not the 8 000 twenty rebuilds
that freed nothing would leave, and 75 over a bound guessed at 1 200. A
level needs a baseline nobody has; growth does not. The same probe after
10 and after 20 rebuilds may differ by one generation of rows (the 400
the last rebuild retired). Headless is added as a reported run, since
#139 was measured headless and a window nobody lays out is where a
layout engine would keep what it was never asked to drop.
1 181 after 10 and 1 275 after 20 is either one generation's slack or a
drift of nine labels a rebuild; a count after 40 tells the two apart.
10 -> 20 -> 40 rebuilds counted 1 181, 1 275, 1 465: a drift of nine and
a half labels a rebuild, far from the 400 #139 describes but not zero.
For the 40-rebuild run the phase now prints the live view classes and
`leaks --trace` root paths to six labels spread through the address list,
which is what says who keeps a retired label alive.
@nicolas-maman
nicolas-maman merged commit 43307b1 into main Sep 18, 2026
3 checks passed
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