Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[2.0 / next] `probing` leaks into a memo's pass pulled inside `isPending` — a nested `latest()` joins the pass to the hold

Chiusa
#4,429 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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
35/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
typescript
Ambito
frontend

Direzione di ricerca

Start with the reader-seat rebuild in issue #4395 and the S1 PR, which the issue identifies as deciding a pulled pass’s world from its seat and the read’s posture. Reproduce with async-gen seed 7, case #1691, in packages/web/test/async-gen. Trace how verdict, probing, and probeFound are handled around a pulled memo’s pass; done means the pass’s world and probe answer reflect its own reads, not the outer isPending window.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

isPending(fn) sets the module flag probing for the duration of fn. When a read inside fn pulls a dirty memo, verdictValue closes the window for the memo's pass (setVerdict(null) — A31: "the pulled memo's pass is its own, not the probe's") but leaves probing set. If that pass calls latest() itself, latest re-opens verdictValue with probing still true, and the pulled memo's read is treated as the outer probe's:

  • a held node born into the future (CONFIG_HELD, uninitialized, staged) read through latest() takes the probing && owned arm and joinPasses the pulled memo to the hold (REACTIVE_JOINED) — the memo's pass joins a world its own read (latest, a verdict read) does not derive from;
  • probeFound is set from inside the pulled pass (heldNotFinal, flying, a live affects() mark), so the outer isPending answers for reads the pulled memo made, not for the memo it read.

The same memo pulled by a plain read (or already clean) does neither. The pass's world depends on who woke it.

Reproduction

async-gen seed 7 case #1691 (packages/web/test/async-gen):

const sc = fc.sample(scenarioArb(), { seed: 7, numRuns: 1692 })[1691];
await runScenario(sc);
[s0=0 m0=memo(s0) m1=memo(latest(m0)+m0 !%4=1) m2=memo(s0 !%4=2)]
<Show when=latest(m2) fb>
  <span split[isPending(m0),isPending(s0)]>m0()</span>
  <span split[latest(s0)]>isPending(m1)</span>
</Show>
:: t A(s0=1) s0=2 s0=3 f s0=0 t s0=1 s0=3 m t m
scenario JSON
{"signals":[0],"memos":[{"async":false,"deps":[{"src":{"k":"s","i":0},"how":"plain"}],"poison":null},{"async":false,"deps":[{"src":{"k":"m","i":0},"how":"latest"},{"src":{"k":"m","i":0},"how":"plain"}],"poison":1},{"async":false,"deps":[{"src":{"k":"s","i":0},"how":"plain"}],"poison":2}],"tree":{"t":"show","when":{"how":"latest","src":{"k":"m","i":2}},"child":{"t":"group","kids":[{"t":"leaf","text":[{"how":"plain","src":{"k":"m","i":0}}],"attrs":[{"how":"pending","src":{"k":"m","i":0}},{"how":"pending","src":{"k":"s","i":0}}],"grouped":false},{"t":"leaf","text":[{"how":"pending","src":{"k":"m","i":1}}],"attrs":[{"how":"latest","src":{"k":"s","i":0}}],"grouped":false}]},"fallback":true},"steps":[{"t":"task"},{"t":"action","s":0,"v":1},{"t":"write","s":0,"v":2},{"t":"write","s":0,"v":3},{"t":"flush"},{"t":"write","s":0,"v":0},{"t":"task"},{"t":"write","s":0,"v":1},{"t":"write","s":0,"v":3},{"t":"micro"},{"t":"task"},{"t":"micro"}]}

isPending(m1) in a render effect pulls m1; m1's pass calls latest(m0) with m0 held and uninitialized in the flush's own transaction (born into it). Stack: joinPass < verdictValue (the uninitialized-held arm, probing) < read < latest < m1's fn < recompute < pullComputed < verdictValue < read < isPending.

Found by the S1 reader-seat differential (SEAT_SHADOW_REPORT): class Vl4 P/P — world after the read (old 2, new 0; Vl posture; s h u · vseat ahead oldVlane flushTx flushIsElTx), 1 instance at 2k cases × seed 7; none in the signals, solid or web suites. The slot agrees (the staging, P); the pass's world does not. No end-screen difference in this case.

Why it is a bug

  • A31: a memo's pass pulled inside a window is its own; the window answers for what the pass produced. verdictValue applies this to the dispatch (setVerdict(null)) and not to probing/probeFound, so a nested latest() inside the pulled pass re-enters the window with the outer probe's state.
  • F-vworld (2026-10-29): a verdict read leaves marks and does not raise the pass's world; latest() of a born-into-the-future node is a verdict read whichever pass makes it.

Intended fix

The reader-seat rebuild S1 (#4395 → S1 PR) decides the pass's world from its seat and the read's posture (row Vl4), not from a flag the puller left set. Until the swap lands, the shadow carries this as a known class (shadowKnown, this issue). (probeFound's leak is the same bug's other half; the differential compares worlds and slots, not probe answers — the swap should save/clear probing and probeFound around the pulled pass as it does verdict.)

Lingua principale
TypeScript
Stelle
36.2k
Fork
1.1k
Merge medio
11h 48m
PR unite (30g)
441

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di solidjs/solid

Tutte le issue di solidjs/solid

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.