Skip to content

fix(sim): bound the instance count the engine will act on - #58

Merged
xevrion merged 1 commit into
xevrion:mainfrom
kevin9327:fix/instances-bounds
Sep 6, 2026
Merged

fix(sim): bound the instance count the engine will act on#58
xevrion merged 1 commit into
xevrion:mainfrom
kevin9327:fix/instances-bounds

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

What

A design carrying a large instances takes the tab down on the first snapshot, and one carrying a
non-numeric instances throws.

effectiveInstances promises ">= 1" in its own doc comment, and cannot keep it:

effectiveInstances(state: NodeStateLike): number {
  const raw = state.config.instances;
  return raw === undefined ? 1 : Math.max(1, Math.floor(raw));
}

Math.floor(NaN) is NaN and Math.max(1, NaN) is NaN. The one caller that matters then does

units.length = instances;   // engine.ts, fillSlotInstances

which throws RangeError: Invalid array length for NaN and for Infinity, and allocates that many
elements for a large finite number. snapshot() runs at 10Hz, so it is the first frame that dies.

Where it comes from

Not from the inspector: its slider stops at 512. It comes from every other way a design arrives.
isTopology checks nine core config numbers are finite — capacity, serviceMs, serviceCv,
queueLimit, hitRate, errorRate, timeoutMs, retries, rps — and says nothing about
instances, so a shared link, a .breakscale file and a restored session all carry it straight
through.

That makes it a link anyone can send. decodeTopology says of itself:

Never throws and never returns something the engine cannot run

It defends against a payload that inflates without bound, and then hands over a number that makes
the engine allocate without bound.

Measured, running the decoded topology through the engine:

instances       what happens on the first snapshot
1e9             the process is killed
NaN             RangeError: Invalid array length
Infinity        RangeError: Invalid array length

The 1e9 case is not a slow frame. Running the new test file against main, the vitest worker
does not report a failure, it exits:

Caused by: Error: Worker exited unexpectedly
 Test Files   (1)
      Tests   (5)
     Errors  1 error

Fix

effectiveInstances keeps its promise. Anything that is not a finite number is one instance, and
the count is capped at MAX_INSTANCES = 512, which is the ceiling the inspector already offers, so
nothing a reader can build changes.

Bounding it in the engine rather than in isTopology puts the guard on the seam every path shares.
A design reaches the engine from the editor, a link, a file and a restored session, and only one of
those goes through the validator.

Tests

src/sim/engine.instances.test.ts, new, on the published instances count:

  • absent is one instance, which is the additive default the field is documented to have
  • NaN and Infinity are one instance rather than a throw
  • 0 and -4 are one instance
  • 2.7 is two, since a fractional machine is not a machine
  • 1e9 is 512, and the snapshot returns in well under a second

Against main the file cannot finish: the last of those kills the worker.

How I tested

Windows 11, Bun 1.3.14. bun run test is 916 passed, up from 911 by the five new tests, with
nothing else moving. bun run typecheck and bun run format:check are clean, and bun run lint
reports the same one pre-existing warning in Metrics.tsx as it does on main.

I have not touched the inspector or the validator. If you would rather isTopology also refuse a
design like this, that is a separate change and I am happy to write it; this one is the guard that
holds wherever the design came from.

@kevin9327
kevin9327 requested a review from xevrion as a code owner September 5, 2026 22:51
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

@kevin9327 is attempting to deploy a commit to the whoarrryou's projects Team on Vercel.

A member of the Team first needs to authorize it.

@xevrion

xevrion commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Reproduced all three on main, the 1e9 one really does take the process down. Good first contribution, thanks!

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.

2 participants