Describe the bug
On 2.0.0-rc.4, the dev build throws Potential Infinite Loop Detected. from the flush() guard while a browser sits on one screen of our app. The page keeps working. The error reaches window.onerror, which is how our end-to-end suite catches it.
The guard is flush() counting its own iterations, in @solidjs/signals dist/dev.js around line 1882:
let count = 0;
while (scheduled$1 || activeTransition) {
if (++count === 1e5) throw new Error("Potential Infinite Loop Detected.");
globalQueue.flush();
}
So something reschedules work 100,000 times inside one flush.
Every occurrence carries the same three frames, and none of ours:
at flush (@solidjs/signals dev, the guard above)
at asyncWrite (@solidjs/signals dev)
at result.then.syncError (@solidjs/signals dev)
asyncWrite and the syncError continuation put it in the write-back of a settled async computation.
I am not claiming this is a framework defect. The guard lives in the framework, so it throws from there whoever caused the runaway. #2843 is the same message and its cause was application code. I am filing because I cannot attribute it after several days of work, and because of the production half below.
In production the guard is not there. dist/prod/core/scheduler.js has the same loop with no counter:
while (scheduled || activeTransition) {
globalQueue.flush();
}
Whatever the cause, in a production build this does not throw. It spins. Pegged CPU, frozen tab, no error.
What I am asking for: which application patterns can drive asyncWrite to reschedule like this, and whether the guard could name the node it kept rescheduling instead of only reporting that it happened. The second one would let anyone hitting this attribute it themselves.
Your Example Website or App
https://github.com/PT-Perkasa-Pilar-Utama/testate
The app is public, but I have no minimal reproduction and I want to be honest about that up front rather than waste your time. The screen is a database grid at /projects/:slug/adapters/:id/tables/:table. It holds four async computations built on createMemo(async ...): a page of rows whose query a person controls through filters, sort, cursor and page size, plus the adapter, its schema, and a write-session presenter that refreshes the first one. It is the only screen in the product with that many, and the only one whose async memo takes a query that changes as fast as someone can click.
Relevant source: apps/web/src/lib/async.ts, apps/web/src/features/data/grid.presenter.ts, and the write-up in docs/upstream-solid-flush-loop.md.
Steps to Reproduce the Bug or Issue
I cannot give you a reliable recipe, and this is the honest state of it:
- Run the full browser suite:
bun run e2e. The suite drives a seeded instance through snapshots, checkouts, fixture imports and a job stream, then a crawler visits about thirty screens and clicks every control on each one.
- About one run in four turns red on the grid screen with this error.
Counting the message in a run log instead of waiting for a test to fail: 4, 0 and 0 occurrences across three identical runs. So it fires more often than the red runs suggest, and single-run counts prove nothing about whether a change helped.
What does not reproduce it:
- A dedicated stress spec that drives the same screen's sort, filter, paging and read/write switch twenty times over with no wait between clicks, with and without a query the API rejects. Eight clean runs across three shapes, including one that walks a live server-sent-event stream first.
- The crawler run on its own, with no other project before it. Three clean runs.
The difference between a clean crawler run and a red one is only what ran before it in the same browser context. That points at accumulated state across thirty screens rather than anything the grid does by itself.
Expected behavior
I expected the flush to drain. Failing that, I expected the thrown error to tell me which computation kept rescheduling, so I could decide whether the fault was mine. Today the message says a loop happened and nothing about what looped, and the only frames are internal, so an application author has nowhere to start.
Screenshots or Videos
None that would help. The stack above is the whole observable signal.
Platform
- OS: macOS 26.6.2
- Browser: Chromium via Playwright 1.58.2 (browser build 1208)
- Version:
solid-js and @solidjs/web 2.0.0-rc.4, @solidjs/signals 2.0.0-rc.4 transitively. Bun 1.4.0, Vite dev server.
Additional context
Prior art. #2843 is the same message on 2.0.0-beta.15, caused by isPending(() => latest(asyncMemo)) read in a user effect with no render effect subscribed to that memo. We use neither isPending nor latest, and that one was fixed in #2838 well before rc.4, so I do not think it is our case. It is the reason I am not calling this a framework bug.
Version history here. It happened on rc.3 as well. rc.4's note about a flush loop spinning forever on a pending store read (#3068) did not end it.
What I ruled out. The grid's only pure helper on that path. The grid's table-change effect, whose handler writes nothing its own source reads. Two server-sent-event effects elsewhere in the app, whose source value does not change when their list refreshes.
What I tried that did not fix it. I rewrote the data layer onto core primitives: refresh() on a memo instead of a version counter bumped to force re-runs, and a createMemo for a derived list that had been rebuilding a new array on every read. Both changes are right on their own merits and both are kept. Neither ended the loop.
Diagnostics. The reactivity-diagnostics skill shipped with rc.4 lists UNSTABLE_MEMO_OUTPUT as a pattern that makes the scheduler re-run more than it should. That is the closest description of anything we do, since an async memo returns a fresh promise per run by construction. Our suite dropped every console warning until yesterday, so no run has ever reported one. It keeps them now, and I will add a comment here if a run produces a diagnostic code.
Happy to run instrumented builds, patch a local copy of dist/dev.js with whatever logging you want, or try a branch. Tell me what to capture and I will capture it.
Describe the bug
On
2.0.0-rc.4, the dev build throwsPotential Infinite Loop Detected.from theflush()guard while a browser sits on one screen of our app. The page keeps working. The error reacheswindow.onerror, which is how our end-to-end suite catches it.The guard is
flush()counting its own iterations, in@solidjs/signalsdist/dev.jsaround line 1882:So something reschedules work 100,000 times inside one flush.
Every occurrence carries the same three frames, and none of ours:
asyncWriteand thesyncErrorcontinuation put it in the write-back of a settled async computation.I am not claiming this is a framework defect. The guard lives in the framework, so it throws from there whoever caused the runaway. #2843 is the same message and its cause was application code. I am filing because I cannot attribute it after several days of work, and because of the production half below.
In production the guard is not there.
dist/prod/core/scheduler.jshas the same loop with no counter:Whatever the cause, in a production build this does not throw. It spins. Pegged CPU, frozen tab, no error.
What I am asking for: which application patterns can drive
asyncWriteto reschedule like this, and whether the guard could name the node it kept rescheduling instead of only reporting that it happened. The second one would let anyone hitting this attribute it themselves.Your Example Website or App
https://github.com/PT-Perkasa-Pilar-Utama/testate
The app is public, but I have no minimal reproduction and I want to be honest about that up front rather than waste your time. The screen is a database grid at
/projects/:slug/adapters/:id/tables/:table. It holds four async computations built oncreateMemo(async ...): a page of rows whose query a person controls through filters, sort, cursor and page size, plus the adapter, its schema, and a write-session presenter that refreshes the first one. It is the only screen in the product with that many, and the only one whose async memo takes a query that changes as fast as someone can click.Relevant source:
apps/web/src/lib/async.ts,apps/web/src/features/data/grid.presenter.ts, and the write-up indocs/upstream-solid-flush-loop.md.Steps to Reproduce the Bug or Issue
I cannot give you a reliable recipe, and this is the honest state of it:
bun run e2e. The suite drives a seeded instance through snapshots, checkouts, fixture imports and a job stream, then a crawler visits about thirty screens and clicks every control on each one.Counting the message in a run log instead of waiting for a test to fail: 4, 0 and 0 occurrences across three identical runs. So it fires more often than the red runs suggest, and single-run counts prove nothing about whether a change helped.
What does not reproduce it:
The difference between a clean crawler run and a red one is only what ran before it in the same browser context. That points at accumulated state across thirty screens rather than anything the grid does by itself.
Expected behavior
I expected the flush to drain. Failing that, I expected the thrown error to tell me which computation kept rescheduling, so I could decide whether the fault was mine. Today the message says a loop happened and nothing about what looped, and the only frames are internal, so an application author has nowhere to start.
Screenshots or Videos
None that would help. The stack above is the whole observable signal.
Platform
solid-jsand@solidjs/web2.0.0-rc.4,@solidjs/signals2.0.0-rc.4transitively. Bun 1.4.0, Vite dev server.Additional context
Prior art. #2843 is the same message on
2.0.0-beta.15, caused byisPending(() => latest(asyncMemo))read in a user effect with no render effect subscribed to that memo. We use neitherisPendingnorlatest, and that one was fixed in #2838 well before rc.4, so I do not think it is our case. It is the reason I am not calling this a framework bug.Version history here. It happened on rc.3 as well. rc.4's note about a flush loop spinning forever on a pending store read (#3068) did not end it.
What I ruled out. The grid's only pure helper on that path. The grid's table-change effect, whose handler writes nothing its own source reads. Two server-sent-event effects elsewhere in the app, whose source value does not change when their list refreshes.
What I tried that did not fix it. I rewrote the data layer onto core primitives:
refresh()on a memo instead of a version counter bumped to force re-runs, and acreateMemofor a derived list that had been rebuilding a new array on every read. Both changes are right on their own merits and both are kept. Neither ended the loop.Diagnostics. The
reactivity-diagnosticsskill shipped with rc.4 listsUNSTABLE_MEMO_OUTPUTas a pattern that makes the scheduler re-run more than it should. That is the closest description of anything we do, since an async memo returns a fresh promise per run by construction. Our suite dropped every console warning until yesterday, so no run has ever reported one. It keeps them now, and I will add a comment here if a run produces a diagnostic code.Happy to run instrumented builds, patch a local copy of
dist/dev.jswith whatever logging you want, or try a branch. Tell me what to capture and I will capture it.