Skip to content

perf(runtime): read state synchronously - #64

Merged
kyscott18 merged 1 commit into
mainfrom
perf/sync-state-reads
Oct 14, 2026
Merged

kyscott18 merged 1 commit into
mainfrom
perf/sync-state-reads

Conversation

@kyscott18

@kyscott18 kyscott18 commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The native EVM call is synchronous, but the runtime gave createStorageView an async getter. Each cache miss ran Effect.runPromise, and each leaf read in a route was an await. The getter is now sync and runs Effect.runSync(evm.readStorage(...)). Thus app.state and app.accounts reads return values, not promises.

  • Consistent reads. A route now reads state with no yield point between reads, so it sees one snapshot. Before, the batch loop could execute a mutation between two awaits. A route could then read an array length from one state and the elements from the next.
  • No semaphore in createEVM. Each native call blocks the thread, so calls cannot overlap without a lock. With the semaphore, a fiber could yield to the scheduler (every 2048 ops by default) while it held the permit. runSync on a storage read then failed with AsyncFiberError. Keep native methods synchronous: an async native method needs calls to be serialized again.
  • Routes. The order-book and token routes no longer await storage reads. The order-book helpers are sync, and the Promise.all calls around storage reads are gone.

Breaking change

app.state and app.accounts leaf reads and dynamic array length change type from Promise<T> to T. Code that only uses await on them still works. Code that calls .then() on a read, or gives a read to Effect.promise, fails.

Test plan

  • bun run typecheck, bun run test (all six packages pass)
  • biome check on the changed files. bun run lint at the root stops on a nested biome.json in an untracked local worktree under .claude/, which is not in this PR.
  • New test runSync completes while another call is paused mid-call. It fails with AsyncFiberError against the semaphore version of createEVM and passes on this branch.
  • Latency and throughput bench (not run)

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge.

Summary

This PR makes local state reads synchronous.

  • Typewriter state and account reads return values directly.
  • Native EVM calls run directly without a semaphore.
  • App routes read Typewriter state without awaiting each value.

No actionable issues were found. Tests were inspected but not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Route[Route reads a leaf] --> View[Storage view]
  View --> Cache{Slot cached?}
  Cache -->|Yes| Decode[Decode value]
  Cache -->|No| Sync[Effect.runSync]
  Sync --> Native[Synchronous native call]
  Native --> Store[Cache returned slots]
  Store --> Decode
  Decode --> Value[Return value without await]
Loading

Reviews (1) · Last reviewed commit: "perf(runtime): read state synchronously" · Reviewed by Greptile

The native EVM call is synchronous, but the runtime gave createStorageView
an async getter. Each cache miss ran Effect.runPromise, and each leaf read
in a route was an await. The getter is now sync and runs
Effect.runSync(evm.readStorage(...)), so app.state and app.accounts reads
return values, not promises.

A route now reads state with no yield point between reads, so it sees one
snapshot. Before, the batch loop could execute a mutation between two
awaits, and a route could read a length from one state and the elements
from the next.

Remove the semaphore in createEVM. Each native call blocks the thread, so
calls cannot overlap without it. With the semaphore, a fiber could yield to
the scheduler while it held the permit, and runSync on a storage read then
failed with AsyncFiberError. A new test pauses a call at each op and makes
sure runSync still completes.

The order-book and token routes no longer await storage reads.
@kyscott18
kyscott18 merged commit ae907b0 into main Oct 14, 2026
8 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