Skip to content

2.0.0-rc.13: refresh() racing an unrelated async write overflows the stack (recompute → runInTransition → recompute) #3738

Description

@Tonius

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

  1. Open the playground link above (pinned to Solid 2.0.0-rc.13, dev build) and wait for a: 0 and b: 0.
  2. Click Refresh a, then write b. It calls refresh(a) and writes b's signal 10 ms later.
  3. The devtools console shows RangeError: Maximum call stack size exceeded twice, with the recompute → runInTransition → recompute stack, after which a and b show 1.
  4. 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions