Describe the bug
If refresh() re-runs an async memo while an unrelated signal write has another async memo in flight, the dev build overflows the stack when the refreshed memo lands:
RangeError: Maximum call stack size exceeded
at runDisposal (@solidjs/signals/dist/dev-shared.js:3251:21)
at disposeChildren (@solidjs/signals/dist/dev-shared.js:3233:3)
at recompute (@solidjs/signals/dist/dev-shared.js:4821:7)
at @solidjs/signals/dist/dev-shared.js:5399:43
at runInTransition (@solidjs/signals/dist/dev-shared.js:2928:12)
at recompute (@solidjs/signals/dist/dev-shared.js:5399:5)
at @solidjs/signals/dist/dev-shared.js:5399:43
at runInTransition ...
The innermost frame varies (notifyStatus, runDisposal, recompute), but the recompute → runInTransition → recompute cycle is always the same. Sometimes the browser tab then spins at full CPU.
We hit it in an app where a navbar polls an unread-notifications count with refresh() every 10 s, while a list's filters change 20–150 ms apart. Rapid filter clicks there throw this in about half of the runs. The repro reduces it to two async memos and three calls, and fails every time.
Your Example Website or App
Solid 2.0 playground (rc.13, dev build)
Steps to Reproduce the Bug or Issue
- Open the playground link above (pinned to Solid 2.0.0-rc.13, dev build) and wait for
a: 0 and b: 0.
- Click Refresh a, then write b. It calls
refresh(a) and writes b's signal 10 ms later.
- The devtools console shows
RangeError: Maximum call stack size exceeded twice, with the recompute → runInTransition → recompute stack, after which a and b show 1.
- Click the button again.
a and b stay at 1 and the console gets three more of the same RangeError: after the first overflow, the graph no longer updates.
Without the write to b's signal, every click moves a on (1, 2, …) with no errors. In the Node version below, each of these makes it pass, so they are all needed:
a returning the same value on every run
- dropping
setX(1)
- dropping the render effect on
b
Same repro in Node
With solid-js@2.0.0-rc.13 as the only dependency, node --conditions=browser --conditions=development repro.mjs throws the RangeError right after the refresh() promise resolves. The production build (--conditions=browser only) prints done without error.
// node --conditions=browser --conditions=development repro.mjs
// -> RangeError: Maximum call stack size exceeded (recompute -> runInTransition -> recompute)
import {createRoot, createSignal, createMemo, createRenderEffect, refresh} from "solid-js";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let a, setX;
createRoot(() => {
// An async memo that settles to a new value on every run.
let run = 0;
a = createMemo(async () => {
const n = run++;
await sleep(n === 0 ? 100 : 50);
return n;
});
createRenderEffect(a, () => {});
// An unrelated async memo behind a plain signal.
const [x, sx] = createSignal(0);
setX = sx;
const b = createMemo(async () => {
const v = x();
await sleep(50);
return v;
});
createRenderEffect(b, () => {});
});
await sleep(150); // a's first value has landed
refresh(a); // a re-runs, lands at ~200 ms
await sleep(10);
setX(1); // b re-runs, lands at ~210 ms
await sleep(300);
console.log("done without error");
Expected behavior
Each click moves a and b on to their next value with no errors.
Screenshots or Videos
N/A — the stack trace above is the whole symptom.
Platform
- OS: Linux (Arch, kernel 7.2.4)
- Browser: Chromium 153 (the playground link, and our app under Vite 8 dev); the Node repro on Node 24.14.1
- Version:
solid-js / @solidjs/signals 2.0.0-rc.13 (dev build)
Describe the bug
If
refresh()re-runs an async memo while an unrelated signal write has another async memo in flight, the dev build overflows the stack when the refreshed memo lands:The innermost frame varies (
notifyStatus,runDisposal,recompute), but therecompute→runInTransition→recomputecycle is always the same. Sometimes the browser tab then spins at full CPU.We hit it in an app where a navbar polls an unread-notifications count with
refresh()every 10 s, while a list's filters change 20–150 ms apart. Rapid filter clicks there throw this in about half of the runs. The repro reduces it to two async memos and three calls, and fails every time.Your Example Website or App
Solid 2.0 playground (rc.13, dev build)
Steps to Reproduce the Bug or Issue
a: 0andb: 0.refresh(a)and writesb's signal 10 ms later.RangeError: Maximum call stack size exceededtwice, with therecompute→runInTransition→recomputestack, after whichaandbshow1.aandbstay at1and the console gets three more of the same RangeError: after the first overflow, the graph no longer updates.Without the write to
b's signal, every click movesaon (1,2, …) with no errors. In the Node version below, each of these makes it pass, so they are all needed:areturning the same value on every runsetX(1)bSame repro in Node
With
solid-js@2.0.0-rc.13as the only dependency,node --conditions=browser --conditions=development repro.mjsthrows the RangeError right after therefresh()promise resolves. The production build (--conditions=browseronly) printsdone without error.Expected behavior
Each click moves
aandbon to their next value with no errors.Screenshots or Videos
N/A — the stack trace above is the whole symptom.
Platform
solid-js/@solidjs/signals2.0.0-rc.13 (dev build)