Skip to content

fix: reuse exited mutator slots instead of growing the registry - #811

Merged
kacy merged 1 commit into
mainfrom
reuse-mutator-slots
Aug 19, 2026
Merged

fix: reuse exited mutator slots instead of growing the registry#811
kacy merged 1 commit into
mainfrom
reuse-mutator-slots

Conversation

@kacy

@kacy kacy commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Closes the last flag-quality issue from the #216 cycle-collector benchmark (task #231): the mutator registry was push-only — an exited thread's slot was marked MUTATOR_EXITED but never reclaimed — so on the os-thread backend, where every task is a fresh thread, the vec grew for the life of the process under PITH_CYCLE_GC.

register_new_slot now resurrects an exited slot (EXITED → RUNNING CAS under the registry lock) before allocating a new one, so the registry plateaus at the peak number of concurrently live mutators. A resurrected slot has exactly a fresh slot's contract: EXITED means the owning thread's teardown already ran (it can never gate again), and the adopting thread's first gate poll observes any stop already in progress before it mutates — the same reasoning that makes a fresh registration safe mid-rendezvous.

This also settles the sprint's cycle-collector either/or the way #216's measurements already pointed: weak-first stays the loud, documented default (docs/ownership.md has said so since the no-flip decision — cycles leak by default, weak is the first-class fix, the flag plus explicit gc_collect() is the escape hatch), and this fix makes that escape hatch non-self-defeating on the os-thread backend. #220 (threshold-mode churn) stays open as a documented limitation of a mode the recipe already tells users not to rely on.

what was tested

  • new unit test exited_mutator_slots_are_reused_not_accumulated: 64 sequential short-lived threads grow the registry by at most 2 slots
  • full runtime suite 196 passed, 0 failed; the serial world-stop suite (make test-cycle-gc) 23 passed, 0 failed
  • end-to-end probe: 20k sequential spawn/await tasks under PITH_GREEN=0 PITH_CYCLE_GC=1, peak RSS flat at 2.5 MB
  • make run-regressions-only against the rebuilt runtime: 369 passed, 0 failed

The cycle collector's mutator registry was push-only: an exited
thread's slot was marked MUTATOR_EXITED but never reclaimed, so on the
os-thread backend — where every task is a fresh thread — the vec grew
for the life of the process under PITH_CYCLE_GC.

register_new_slot now resurrects an exited slot (EXITED -> RUNNING CAS
under the registry lock) before allocating a new one, so the registry
plateaus at the peak number of concurrently live mutators. A
resurrected slot has exactly a fresh slot's contract: EXITED means the
owning thread's teardown already ran and it can never gate again, and
the adopting thread's first gate poll observes any stop already in
progress before it mutates — the same reasoning that makes a fresh
registration safe mid-rendezvous.

This closes the last flag-quality issue from the #216 benchmark. The
collector's contract is unchanged and stays as documented: off by
default, weak-first is the language's answer to cycles, and the flag
plus explicit gc_collect() is the escape hatch.

## what was tested

- new unit test exited_mutator_slots_are_reused_not_accumulated: 64
  sequential short-lived threads grow the registry by at most 2 slots
- full runtime suite 196 passed, 0 failed; the serial world-stop suite
  (make test-cycle-gc) 23 passed, 0 failed
- end-to-end probe: 20k sequential spawn/await tasks under PITH_GREEN=0
  PITH_CYCLE_GC=1, peak rss flat at 2.5mb
- make run-regressions-only against the rebuilt runtime: 369 passed
@kacy
kacy merged commit b9842f6 into main Aug 19, 2026
1 check passed
@kacy
kacy deleted the reuse-mutator-slots branch August 19, 2026 07:34
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