Skip to content

fix(pstack): check statechart rules with every CPU again - #1179

Open
pr656d wants to merge 1 commit into
cursor:mainfrom
pr656d:statechart-every-core
Open

pr656d wants to merge 1 commit into
cursor:mainfrom
pr656d:statechart-every-core

Conversation

@pr656d

@pr656d pr656d commented Nov 5, 2026

Copy link
Copy Markdown

#776 had TLC check each rule with one worker per CPU, then rerun a broken rule with one worker so the shortest path is the same on every run. #977 pinned -workers 1 -fp 0 -lncheck final for every TLC run with no heap flag, so Java's default of 25% of RAM applies. I found no stated reason for this, so I assume it was lost in the port.

On a 12-core Mac mini, a 31-million-state model ran for about 50 minutes on one core, then hit OutOfMemoryError: Java heap space in the final liveness pass. On a 4-core server with a 1.4 GB heap, it slowed from about 1.3 million to about 20,000 new states per minute once the heap filled.

  1. A config's first, unbounded verdict run uses one worker per CPU (os.availableParallelism(), or STATECHART_WORKERS).
  2. When that run reaches 100,000 states or fewer (RERUN_STATES), or an invariant or deadlock breaks, it runs again with one worker (PINNED_FLAGS unchanged) and uses that result. Shortest paths and small-model output stay identical, since multi-worker BFS does not guarantee the shortest counterexample ( Ignore flaky DumpLoadTraceTest AliasSub auto-worker tests; add AliasSub2 tests. tlaplus/tlaplus#1326).
  3. Bounded reruns (ChartBound) and the chart run stay at one worker.
  4. Above 100,000 states, a broken PROPERTY keeps the multi-worker trace, which can differ between runs. SKILL.md's "with one worker" sentence now describes all this.
  5. Every run gets -XX:MaxRAMPercentage=50. I left out -XX:+UseParallelGC because SKILL.md's note on tlc.out relies on the first-warning behaviour. Happy to add it if you prefer.

A 1,283,467-state model with a liveness property went from 68.6 s to 25-27 s on 12 cores and from 2 min 20 s to 57 s on 4, with the same verdicts and state count.

The new test/workers.test.ts runs two models above 100,000 states with 2 workers and with 1. When the rules hold, both give the same outcomes and state count. When an invariant breaks, both give the same shortest path. Full suite on macOS: 266 pass, 69 skipped, 1 fail. The failure is the symlinked-launcher test in test/cli.test.ts, which fails the same way on main.

We vendor pstack in our own plugin and carry this patch there in the meantime.

🤖 Generated with Claude Code

cursor#776 checked each rule with one worker per CPU and reran a broken rule
with one worker for the shortest path. The compiler port (cursor#977) pinned
one worker and Java's default heap for every TLC run. Restore the
all-CPU first run, keep one worker where the output depends on it, and
let each TLC run's heap grow to half the memory Java sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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