AppKit label retention across list rebuilds: measure it in CI (#139) - #165
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Towards #139.
The issue reports every label a list rebuild retires staying alive in the window's layout engine, measured once, by hand, with
heapon 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.aerebuilds a 400-rowlistboxon 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 fromAETHER_PROBE_PERIOD_MS/AETHER_PROBE_ROUNDS.ci.sh Phase 1f (macOS only; SKIP elsewhere and when
heapis missing) builds it and runs it twice, counting liveNSTextFieldwithheap: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.