Repository navigation
2.0.0-rc.13: refresh() racing an unrelated async write overflows the stack (recompute → runInTransition → recompute) #3738
Description
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: 0andb: 0. - Click Refresh a, then write b. It calls
refresh(a)and writesb's signal 10 ms later. - The devtools console shows
RangeError: Maximum call stack size exceededtwice, with therecompute→runInTransition→recomputestack, after whichaandbshow1. - Click the button again.
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 moves a on (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 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/signals2.0.0-rc.13 (dev build)
Activity
ryansolid commentedon Oct 5, 2026