Skip to content

Advance the trace once in ArenaStartCollect before releasing the arena - #322

Open
defmacro-jam wants to merge 1 commit into
Ravenbrook:masterfrom
defmacro-jam:branch/2026-10-25/start-collect-band
Open

defmacro-jam wants to merge 1 commit into
Ravenbrook:masterfrom
defmacro-jam:branch/2026-10-25/start-collect-band

Conversation

@defmacro-jam

Copy link
Copy Markdown

Fixes #321.

ArenaStartCollect (code/traceanc.c) starts the trace with TraceStartCollectAll, which flips it, and then calls ArenaRelease, which advances the trace only if a poll is due. Usually none is, so mps_arena_start_collect returned with a flipped trace whose band was still RankAMBIG. A read barrier hit before the next poll then failed the NOTREACHED in TraceRankForAccess at trace.c:989. ArenaStartCollect now calls TraceAdvance once before ArenaRelease, as TracePoll and ArenaStep do after starting a trace.

Why one advance is enough:

  • The trace is TraceFLIPPED when TraceStartCollectAll succeeds, so TraceAdvance calls traceFindGrey.
  • In band RankAMBIG, traceFindGrey searches nothing and calls traceBandAdvance, which moves the band to RankEXACT.
  • From there it either returns a grey segment in band RankEXACT or later, which TraceAdvance scans without changing the band, or it runs out of bands. In that case no segment is grey for the trace, traceBandAdvance resets the band to RankMIN, and TraceAdvance moves the trace to TraceRECLAIM.
  • So afterwards either the band is past RankAMBIG, or nothing is grey for the trace and the read barrier cannot be hit. ArenaStep can already return with a trace in that TraceRECLAIM state, so the arena is left in no state it could not reach before.

awlSegCanTrySingleAccess (poolawl.c:345) calls TraceRankForAccess on any barrier hit on a weak AWL segment while a trace is flipped, grey or not. After this change it can see RankAMBIG only in the TraceRECLAIM state above. I believe a write hit there would need a reachable object in a condemned weak AWL segment that is not grey, and the flip greys any such segment when it marks the object from a root, but I have not tested that path, and this change does not alter it.

A single step from TraceFLIPPED cannot finish the trace (TraceAdvance moves it at most to TraceRECLAIM), so unlike TracePoll there is no finished trace to destroy.

ArenaCollect, the only other caller of ArenaStartCollect, parks the arena straight afterwards, so the extra step makes no difference there.

Tested on macOS arm64 (16 KiB pages) with Apple clang:

Other platforms, the hot variety of the test suite, and the multi-threaded case we first saw it in have not been run against this change.

ArenaStartCollect starts the trace with TraceStartCollectAll, which
flips it, and then calls ArenaRelease, which advances the trace only if
a poll is due. Usually none is, so mps_arena_start_collect returned with
a flipped trace whose band was still RankAMBIG, and a read barrier hit
before the next poll failed the NOTREACHED in TraceRankForAccess at
trace.c:989. ArenaStartCollect now advances the trace once before
releasing the arena, as TracePoll and ArenaStep do after starting a
trace. That leaves the band at RankEXACT or later, or leaves no segment
grey for the trace.

Fixes GitHub issue Ravenbrook#321 <Ravenbrook#321>.
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.

traceanc.c: mps_arena_start_collect returns with the trace in band RankAMBIG, so a read barrier hit asserts at trace.c:989

1 participant