Skip to content

docs(gc)+cli: cwist_gc_scope_flush contract and hazard warnings (#347) - #348

Merged
gg582 merged 2 commits into
devfrom
docs/347-gc-flush-contract
Oct 10, 2026
Merged

gg582 merged 2 commits into
devfrom
docs/347-gc-flush-contract

Conversation

@gg582

@gg582 gg582 commented Oct 10, 2026

Copy link
Copy Markdown
Member

Addresses the documentation and tooling items of #347.

Why

The cwist_gc_scope_flush() comment in gc.h told callers to flush "at the end of each unit of work … (a request, a queued job)". fly.board followed that advice from a request middleware. That is unsafe for two reasons:

  1. A flush retires every block the thread still tracks, live or not. That includes the response being built and, on a connection thread, h2 session state.
  2. Retired blocks are freed only when something drives reclaim. Request-serving threads never enter an epoch, so once reclaim runs it frees retired blocks even while they are in use. In fly.board nothing ever drove reclaim, so the flush freed nothing and leaked one retire node per block. It would have turned into a use-after-free the moment anything started reclaiming.

The middleware has since been removed from fly.board (gg582/fly.board@db27855) and redeployed.

Docs (aab01576)

  • gc.h:
  • docs/GC.md and docs/ko/GC.md: new section 7, Explicit flushes and reclamation, with an OK / not-OK table and the ack marker. The summary table's teardown-safety row is corrected: epoch deferral protects only readers inside ttak_epoch_enter().

CLI (8c9f3374)

cwist audit and cwist watcher warn on application calls to:

  • cwist_gc_scope_flush
  • cwist_ebr_free
  • cwist_gc_scope_disown / cwist_gc_scope_untrack
  • cwist_conn_registry_flush
  • cwist_conn_registry_sweep_all
WARNING: cwist_gc_scope_flush() retires EVERY block the calling thread still tracks, live or not.
  Use it only if you know its contract: call only where the thread holds no live tracked block (end of a self-contained job); never from a handler, middleware, or a thread that is serving connections.
  Read CWIST's docs/GC.md ("Explicit flushes and reclamation") before using it,
  then mark the call with a comment: /* cwist-ack: cwist_gc_scope_flush */
    src/engine/async_route.c:111
    ...
  • Adding cwist-ack: <api> on the same line or the line above silences the warning. A single marker can list several APIs.
  • CWIST's own tree is not scanned, so cwist audit src in this repo shows only the multiport warning it already showed.
  • watcher reports once at startup, then again for each changed .c/.h file.
  • audit --json writes the warnings to stderr, so stdout stays valid JSON.

Tests

  • scripts/ci/test_cli_hazards.py, 9 cases: detection in .c/.h, ack on the same line or the line above, a wrong-API ack, a multi-API ack, comment and prefix false positives, skipping CWIST's own tree, and the report text. It is picked up by the existing unittest discover -s scripts/ci -p 'test_*.py' CI step.
  • test_malloc_gate.py still passes.
  • Checked by hand against the fly.board tree: 7 flush call sites and 2 disown call sites are flagged. watcher --once warns once, and the warning goes away after acking.

Not in this PR: the third #347 item, deciding whether full GC should drive reclaim itself.

gg582 added 2 commits October 11, 2026 00:02
…ependency (#347)

The gc.h comment told callers to flush at the end of each request. That
is unsafe: a flush retires every block the thread still tracks, live or
not, and request-serving threads never enter an epoch, so reclaim frees
retired blocks even while they are in use. It also only frees anything
when reclamation is driven, which a full-GC process does not do on its
own. fly.board followed the old advice from a middleware.

- gc.h: cwist_gc_scope_flush() now states the precondition (no live
  tracked block on the thread), where it is and is not OK, and the
  reclaim dependency; cwist_conn_registry_flush() warns that it closes
  connections other threads are serving; cwist_conn_registry_untrack()
  no longer claims it only searches the caller's list (#344 changed that).
- docs/GC.md and docs/ko/GC.md: new section 7, "Explicit flushes and
  reclamation", and a corrected teardown-safety row in the summary.
…ed (#347)

cwist audit and cwist watcher now flag application calls to
cwist_gc_scope_flush, cwist_ebr_free, cwist_gc_scope_disown/untrack,
cwist_conn_registry_flush and cwist_conn_registry_sweep_all. Each
warning states the API's contract and points at docs/GC.md section 7;
marking the call site 'cwist-ack: <api>' (same line or the line above)
silences it once someone has checked the call against the contract.
CWIST's own tree is not scanned. watcher reports at startup and on
every changed .c/.h file; audit --json sends the warnings to stderr so
its stdout stays valid JSON.

scripts/ci/test_cli_hazards.py covers detection, the ack marker, comment
and prefix false positives, and skipping CWIST's own sources.
@gg582
gg582 merged commit f3e7a44 into dev Oct 10, 2026
16 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