[2.0 / next] Optimistic todo list with an "N left" memo loops forever when a toggle's save lands
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 15/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- javascript, typescript
- Ambito
- frontend
Direzione di ricerca
The reproduction is a vitest spec in packages/web/test/ that drives createOptimisticStore, createMemo, refresh and flush through a fake server. Start by running npx vitest run in packages/web with the spec, then find where the "Potential Infinite Loop Detected" error is thrown in the scheduler. Done means the count=true case settles without that error while count=false still passes. This touches the reactive core, so expect to read it closely before changing anything.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Describe the bug
The todo list from the 2.0 cheatsheet, with toggle, remove and add actions that each write a guess, yield the save, then call refresh(todos), plus a remaining memo for an "N left" count. Against a slow server:
- The list loads.
- Toggle 1, toggle 2, delete 2, add a todo.
- The delete's save lands, so its refresh of the list goes out.
- Add another todo.
- The list refetch lands.
- Toggle 1's save lands.
At step 6 the flush never settles. The dev build throws Potential Infinite Loop Detected. Kept alive by scheduled work; last staged node: computed, so a production build would presumably spin and freeze the tab. Without the remaining memo, the same sequence settles normally.
A second shape loops the same way: an inbox with a polled createOptimisticStore list, a memo that filters it by read state, and optimistic deletes. The sequence is filter to "read", poll, delete 1, delete 3, poll, the server declines the delete of 1, a list answer lands, filter back to "all". Then the remaining answers loop, with last staged node: signal. That spec is longer and can be attached if useful.
Found by fuzzing random sequences of these actions against a fake server.
Your Example Website or App
The vitest spec below (in packages/web/test/), on next @ 42350195f.
Steps to Reproduce the Bug or Issue
import { render } from "@solidjs/web";
import { action, createMemo, createOptimisticStore, flush, For, Loading, refresh } from "solid-js";
import { expect, it } from "vitest";
const tick = async () => { for (let i = 0; i < 2; i++) { await new Promise(r => setTimeout(r, 0)); flush(); } };
type Todo = { id: number; done: boolean };
// The cheatsheet's todo list with an "N left" count, against a slow server.
for (const withCount of [true, false])
it(`toggle, toggle, remove, add, add: the toggle's save lands without a loop (count=${withCount})`, async () => {
let server: Todo[] = [{ id: 1, done: false }, { id: 2, done: false }];
const out: { key: string; go: () => void }[] = [];
const call = (key: string, apply?: () => void) =>
new Promise<any>(resolve => out.push({ key, go: () => { apply?.(); resolve(key === "list" ? server.map(t => ({ ...t })) : true); } }));
const land = (key: string) => out.splice(out.findIndex(f => f.key === key), 1)[0].go();
const el = document.createElement("div");
let toggle!: (id: number) => Promise<unknown>, remove!: (id: number) => Promise<unknown>, add!: (id: number) => Promise<unknown>;
const dispose = render(() => {
const [todos, setTodos] = createOptimisticStore<Todo[]>(() => call("list"), []);
const remaining = createMemo(() => todos.filter(t => !t.done).length);
toggle = action(function* (id: number) {
setTodos(s => { const t = s.find(x => x.id === id); if (t) t.done = !t.done; });
yield call("toggle:" + id, () => { server = server.map(t => t.id === id ? { ...t, done: !t.done } : t); });
refresh(todos);
});
remove = action(function* (id: number) {
setTodos(s => s.filter(x => x.id !== id));
yield call("remove:" + id, () => { server = server.filter(t => t.id !== id); });
refresh(todos);
});
add = action(function* (id: number) {
setTodos(s => { s.push({ id, done: false }); });
yield call("add:" + id, () => { server = [...server, { id, done: false }]; });
refresh(todos);
});
return (
<Loading fallback={<p>loading</p>}>
<ul><For each={todos}>{t => <li>[{t.id}{t.done ? "x" : "o"}]</li>}</For></ul>
{withCount ? <p>{remaining()} left</p> : null}
</Loading>
);
}, el);
const step = async (f: () => void) => { f(); flush(); await tick(); };
let error: string | null = null;
try {
await step(() => land("list"));
await step(() => { toggle(1); });
await step(() => { toggle(2); });
await step(() => { remove(2); });
await step(() => { add(3); });
await step(() => land("remove:2")); // the remove's refresh goes out
await step(() => { add(4); });
await step(() => land("list"));
await step(() => land("toggle:1")); // Potential Infinite Loop Detected (dev)
} catch (e) { error = (e as Error).message.slice(0, 60); }
dispose();
expect(error).toBeNull();
});
npx vitest run in packages/web. count=true fails with Potential Infinite Loop Detected, and count=false passes.
Expected behavior
Each landing settles in a bounded number of passes, with or without a memo over the list.
Platform
- OS: macOS
- Browser: n/a (jsdom)
- Version: solid-js 2.0.0-rc.20,
next@ 42350195f
- Lingua principale
- TypeScript
- Stelle
- 36.2k
- Fork
- 1.1k
- Merge medio
- 10h 10m
- PR unite (30g)
- 505
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di solidjs/solid
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 22/100
I maintainer di solito rispondono entro 1 giorno
-
What gets passed to the `when` prop of a `Show` still gets executed twice on SSRForse già presa @brenelz l’ha presa 4 giorni fa. Aperta1.x
Difficoltà 3/5 1-2 giorni Idoneità per principianti 52/100
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
solidjs/solid#4447 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di solidjs/solid
Issue simili
-
bug module:nodes tests
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
rocketride-org/rocketride-server#3020 ·
I maintainer di solito rispondono entro 2 giorni
-
area: dashboard area: tests good first issue size: XS tests
Difficoltà 1/5 1-3 ore Idoneità per principianti 88/100
I maintainer di solito rispondono entro 1 giorno
-
Read canvas commentsApertaenhancement needs-community-review
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
shaharia-lab/slackcli#501 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug] Pod detail shows CPU usage ten times too high, and throws on decimal memory suffixesForse già presa @RaphaelFakhri l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 11 giorni
-
`package.json`: build tools listed as runtime dependencies, exact pins causing duplicate installsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
themesberg/flowbite-vue#471 ·