Skip to content

fix(runtime): skip the behavior scan while the context is quiescent - #536

Merged
HuiJun merged 3 commits into
developfrom
fix/quiescent-behavior-scan
Sep 23, 2026
Merged

HuiJun merged 3 commits into
developfrom
fix/quiescent-behavior-scan

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

What and why

Since the dynamic object lifecycle work (#468), every outermost start (holdDrivenWork) and every nextRunnableBehavior call walks all of ctx.objectBehaviors calling hasPendingWork(). A context holding many instantiated objects with idle exhibited machines therefore pays O(#behaviors) per CheckConstraintOn / feature write, making batches quadratic. Profiling BenchmarkBatchConstraints on develop showed 34% of CPU in hasPendingWork and 7 ms → 260 ms versus v0.8.1.

The fix is a dependency-aware quiescence memo on Context:

work      uint64      // bumped by anything that can give a behavior pending work
quiescent quiescence  // {at, writes, readsData}: the marks an unfiltered scan found nothing under

func (q quiescence) holds(ctx *Context) bool {
    return q.at != 0 && q.at == ctx.work && (!q.readsData || q.writes == ctx.writes)
}

work is bumped by postFrom, every clock move (now routed through setClock), every StateExecutor event enqueue, beginExecutorRun/previewExecutorRun on entry and leave, objectBehaviors appends/removals, held-image materialization, and by rollbackJournal/runCapture.restore, so a snapshot, probe or transaction restore drops the memo. Each full scan installs a pendingMemo as ctx.polling, so when any pending check read object data (notePollReadsData, the existing via mechanism) the recorded quiescence also keys on ctx.writes; a held per-machine pendingSignal memo that read data reports that to the enclosing scan. Data-free scans stay immune to feature writes, which is what keeps a plain write cheap. Filtered scans (run boundaries, held behaviors) never record quiescence.

The run bookkeeping a store sets up is also allocated only when there is something to run: holdDrivenWork allocates its held map on the first pending behavior (a holdingDriven flag now carries the outer-start sentinel), nextRunnableBehavior installs a poll memo only when its scan range is non-empty, and runState.extentCandidates is created at its write site.

How it was verified

  • quiescence_test.go: a posted signal, a clock advance past an after trigger and a direct executor run each break quiescence; a snapshot restore after an idle scan re-delivers the restored message; a held memo that read data reports the read to an enclosing poll and no longer covers a write; an idle store on a behavior-free object stays within its allocation budget (AllocsPerRun).
  • go test -count=1 ./internal/exec/runtime, go test -race ./... (full suite), ./tests/grpc ./tests/corpus ./tests/identity with all four corpora required — all pass, no baseline moved. go vet, gofmt -l, changelog.py check clean.
  • Benchmarks vs v0.8.1: BatchConstraints 245 ms → 9.0 ms (v0.8.1 7.4 ms); SetFeatureValueNoDependents 1.5 µs / 28 allocs → 386 ns / 9 allocs (v0.8.1 143 ns / 1). The remaining allocations are the eager runState/scheduler and the run-bracket closures every top-level store still creates.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@HuiJun
HuiJun marked this pull request as ready for review September 22, 2026 20:39
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 22, 2026 20:55
…rs to run

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit 4f2427a into develop Sep 23, 2026
15 checks passed
@HuiJun
HuiJun deleted the fix/quiescent-behavior-scan branch September 23, 2026 01:44
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