[2.0 / next] Optimistic todo list with an "N left" memo loops forever when a toggle's save lands
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 15/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- javascript, typescript
- Área
- frontend
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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
- Lenguaje dominante
- TypeScript
- Estrellas
- 36.2k
- Forks
- 1.1k
- Merge medio
- 10 h 10 min
- PR fusionados (30 d)
- 505
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de solidjs/solid
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 22/100
Los mantenedores suelen responder en 1 día
-
What gets passed to the `when` prop of a `Show` still gets executed twice on SSRPosiblemente ocupada @brenelz la tomó hace 4 días. Abierto1.x
Dificultad 3/5 1-2 días Aptitud para principiantes 52/100
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
solidjs/solid#4447 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
Todos los issues de solidjs/solid
Issues similares
-
apps/stage-web bug env/os-linux pending triage scope/audio-output
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
reproduced
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
anomalyco/opencode#61640 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
scope: data grid status: waiting for maintainer
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
mui/mui-x#24090 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
ai-driven-fuzzing bug Minor
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Los mantenedores suelen responder en 1 día
-
ai-driven-fuzzing bug claude Minor
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
linagora/twake-calendar-frontend#1944 ·
Los mantenedores suelen responder en 1 día