Skip to content

Adopt synchronous machine transitions - #41

Merged
SandroMaglione merged 2 commits into
mainfrom
codex/synchronous-transition-core
Aug 8, 2026
Merged

Adopt synchronous machine transitions#41
SandroMaglione merged 2 commits into
mainfrom
codex/synchronous-transition-core

Conversation

@SandroMaglione

@SandroMaglione SandroMaglione commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

  • replace Effectful transition, lifecycle, initial, choice, and history callbacks with a synchronous transition core
  • add a closed, protocol-typed enqueue API for raise, emit, sendTo, and stop
  • keep asynchronous work and Effect service/error inference at invoke, actor, and child-machine boundaries
  • migrate runtime execution, Cluster support, testing utilities, examples, documentation, consumer fixtures, and type-performance fixtures
  • add differential coverage for planning/runtime/resume ordering and large raised-event drainage
  • add memory-attribution profiles for Effect primitives, managed processes, statecharts, parent-child ownership, and observation

Changeset

  • Added or updated for a library or package-metadata change
  • Not required because this PR does not change src/ or package.json

Validation

  • pnpm check
  • Relevant example checks, when examples changed
  • Reviewed the automated type-performance report, when the public TypeScript API or inference changed
  • Reviewed the automated runtime-performance report, when runtime behavior changed

Local runtime comparison used three paired full runs on Apple M1 with Node 26.5.0. Median gains over the exact parent commit were 3.42x for pure planning, 2.25x for runtime bursts, 2.07x for observed bursts, and 2.30x for child sends. Idle memory was effectively unchanged.

The full memory ladder measured 0.7 KiB per suspended Effect fiber, 2.8 KiB per Effect queue/waiting-fiber mailbox, 3.2 KiB per minimal Effect actor shell, 18.6 KiB per raw managed process, 23.5 KiB per idle statechart, 46.1 KiB per two independent statecharts, 74.1 KiB per parent-child pair, and 82.9 KiB with child observation active. These attribution profiles are now part of the local and pull-request benchmark reports.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Type performance

Measured with TypeScript 6.0.3 and skipLibCheck=true.

Scenario Base PR Difference
Effect only 55 55 0 (0.0%)
Import effect-machine 55 55 0 (0.0%)
Machine.defineStates (3 states) 2,965 2,965 0 (0.0%)
Machine.make (3 states, 2 events) 8,731 8,715 -16 (-0.2%)
machine.handle (3 states, 2 transitions) 23,634 23,492 -142 (-0.6%)
machine.handle (depth 8) 113,023 117,075 +4,052 (+3.6%)
machine.handle (depth 12) 125,661 129,713 +4,052 (+3.2%)
machine.handle (depth 16) 141,003 145,055 +4,052 (+2.9%)
machine.handle (depth 24) 179,799 183,851 +4,052 (+2.3%)
machine.handle (wide depth 16) 210,137 214,189 +4,052 (+1.9%)
machine.handle (parallel/history/choice) 128,880 132,667 +3,787 (+2.9%)
machine.handle (4 successive calls) 129,498 128,674 -824 (-0.6%)
machine exact input/output/error/services 107,375 110,584 +3,209 (+3.0%)
execution adapter readiness 122,890 127,854 +4,964 (+4.0%)

Marginal instantiations are measured against the matching setup without that API call:

Scenario Base PR Difference
Import effect-machine 0 0 0
Machine.defineStates (3 states) 2,910 2,910 0 (0.0%)
Machine.make (3 states, 2 events) 5,758 5,742 -16 (-0.3%)
machine.handle (3 states, 2 transitions) 14,903 14,777 -126 (-0.8%)
machine.handle (depth 8) 105,546 109,601 +4,055 (+3.8%)
machine.handle (depth 12) 116,984 121,039 +4,055 (+3.5%)
machine.handle (depth 16) 131,126 135,181 +4,055 (+3.1%)
machine.handle (depth 24) 167,522 171,577 +4,055 (+2.4%)
machine.handle (wide depth 16) 198,169 202,224 +4,055 (+2.0%)
machine.handle (parallel/history/choice) 110,514 114,331 +3,817 (+3.5%)
machine.handle (4 successive calls) 114,497 113,703 -794 (-0.7%)
machine exact input/output/error/services 95,819 100,670 +4,851 (+5.1%)
execution adapter readiness 96,968 102,092 +5,124 (+5.3%)
Check times (informational)
Scenario Base PR
Effect only 0.03s 0.03s
Import effect-machine 0.03s 0.03s
Machine.defineStates (3 states) 0.10s 0.10s
Machine.make (3 states, 2 events) 0.16s 0.16s
machine.handle (3 states, 2 transitions) 0.24s 0.24s
machine.handle (depth 8) 0.53s 0.51s
machine.handle (depth 12) 0.56s 0.56s
machine.handle (depth 16) 0.62s 0.60s
machine.handle (depth 24) 0.70s 0.68s
machine.handle (wide depth 16) 0.76s 0.77s
machine.handle (parallel/history/choice) 0.63s 0.63s
machine.handle (4 successive calls) 0.60s 0.59s
machine exact input/output/error/services 0.55s 0.52s
execution adapter readiness 0.56s 0.59s

Type instantiations are the comparison metric. Check time varies with runner load and is informational only.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Runtime performance

Median of 5 independent benchmark processes on AMD EPYC 9V74 80-Core Processor with Node v24.18.0.

Pull request baseline

Scenario Effect Machine XState 5 XState 6 alpha
Plan counter transitions 108,366 transitions/s 138,975 transitions/s 115,785 transitions/s
Drain burst with terminal fence 65,283 increments/s 388,989 increments/s 107,642 increments/s
Drain burst with a change observer 55,099 increments/s 384,952 increments/s 107,375 increments/s
Lookup and send to one child 58,366 increments/s 384,667 increments/s 104,826 increments/s
Start and stop a machine 15,240 machines/s 179,565 machines/s 73,692 machines/s
Start and stop a parent with one child 4,679 families/s 71,526 families/s 30,574 families/s
Memory profile Effect Machine XState 5 XState 6 alpha
Idle machine 23.4 KiB 3.8 KiB 6.1 KiB
Raw managed process 18.4 KiB
Two independent idle machines 46.0 KiB 7.3 KiB 11.8 KiB
Idle parent with one child 73.7 KiB 5.5 KiB 8.8 KiB
Parent with observed child registry 82.6 KiB

Effect Machine change from base

Metric Base Base variability PR PR variability Difference
Plan counter transitions 38,977 transitions/s 0.5% MAD 108,366 transitions/s 0.4% MAD +178.0%
Drain burst with terminal fence 33,562 increments/s 0.6% MAD 65,283 increments/s 0.9% MAD +94.5%
Drain burst with a change observer 30,603 increments/s 0.4% MAD 55,099 increments/s 1.8% MAD +80.0%
Lookup and send to one child 28,837 increments/s 0.3% MAD 58,366 increments/s 0.5% MAD +102.4%
Start and stop a machine 10,186 machines/s 1.9% MAD 15,240 machines/s 1.4% MAD +49.6%
Start and stop a parent with one child 3,920 families/s 1.6% MAD 4,679 families/s 1.0% MAD +19.4%
Idle machine heap per unit 23.6 KiB 0.0% MAD 23.4 KiB 0.0% MAD -1.1%
Raw managed process heap per unit 18.4 KiB 0.0% MAD 18.4 KiB 0.0% MAD -0.1%
Two independent idle machines heap per unit 46.3 KiB 0.0% MAD 46.0 KiB 0.0% MAD -0.7%
Idle parent with one child heap per unit 73.9 KiB 0.0% MAD 73.7 KiB 0.0% MAD -0.2%
Parent with observed child registry heap per unit 82.8 KiB 0.0% MAD 82.6 KiB 0.0% MAD -0.2%
Versions and interpretation
  • Effect Machine: 0.3.0
  • XState 5: 5.32.5
  • XState 6 alpha: 6.0.0-alpha.27

Higher throughput is better; lower heap is better. Variability is the median absolute deviation across independent processes, relative to their median. Runtime measurements on shared GitHub-hosted hardware remain informational, so small differences should be confirmed across multiple workflow runs.

@SandroMaglione
SandroMaglione merged commit 4d6e7b0 into main Aug 8, 2026
9 checks passed
@SandroMaglione
SandroMaglione deleted the codex/synchronous-transition-core branch August 8, 2026 12:31
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