processNextTick leaks failure state between callbacks queued on the same promise in one tick
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 85/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- clojure, javascript
Direzione di ricerca
Inizia in src/promesa/impl/promise.js, in corrispondenza di processNextTick, e confronta la gestione della coda descritta per le versioni 11.0.678 e 12.0.1. Mantieni indipendente lo stato dell’esito di ogni attività accodata, quindi esegui o aggiungi un caso di regressione basato sulla riproduzione: un callback che genera un’eccezione, seguito da un callback riuscito sulla stessa promise, dovrebbe lasciare risolta la seconda chain.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
In promesa.impl.promise/processNextTick, the variable holding a task's outcome is declared outside the while loop and never reset between iterations. When one task throws, tasks after it in the same queue are completed as though they had failed too, even though their callbacks returned normally.
Callbacks attached to the same promise are independent of each other. One throwing should not affect the others.
Reproduction
(require '[promesa.core :as p])
(let [pr (p/resolved 1)]
;; both handlers are attached before the tick runs, so they share one queue
(p/catch (p/then pr (fn [_] (throw (ex-info "boom" {}))))
(fn [e] (println "first rejected:" (ex-message e))))
(p/catch (p/then pr (fn [v] (println "second resolved:" v)))
(fn [e] (println "second rejected:" (ex-message e)))))
Expected:
first rejected: boom
second resolved: 1
Actual, 11.0.678:
first rejected: boom
second resolved: 1
second rejected: boom
The second callback runs, but the promise it returns is rejected with the first callback's exception.
Actual, 12.0.1:
first rejected: boom
second rejected: nil
The second callback does not run at all, and its catch receives the promise's resolved value 1 as the cause, hence the nil from ex-message.
(Both built with ClojureScript 1.11.132, :target :nodejs, :optimizations :none, Node 22.)
Order matters: attach the throwing handler second and both behave correctly, which makes this easy to miss.
Cause
Same defect in both versions, different variable.
11.0.678, src/promesa/impl/promise.js#L269: let task, rvalue, rcause; is outside the loop. The catch sets rcause = e, and the next iteration reaches resolveTask(task, rvalue, rcause) with that error still in rcause, so if (cause) rejects a task that succeeded.
master / 12.0.1: rcause is gone, but let state = p[STATE] is now hoisted the same way and set to REJECTED in the catch. Later iterations then take the state === REJECTED branch and call task.reject(value) with the promise's resolved value, and resolveTask completes them as rejected.
Impact
Callbacks on a shared promise cannot be treated as independent, and the failure is misattributed: the error surfaced on the second chain was raised by unrelated code, or on 12.0.1 is not an error object at all. We hit this in Logseq where a batch of asset downloads chained off one promise, and a single bad item failed the whole batch with that item's error. The workaround was to make sure no callback ever throws, returning p/rejected instead.
Suggested fix
Keep the per-task state local to the iteration, leaving p[STATE] read-only:
while (p[QUEUE].length) {
const task = p[QUEUE].shift();
let state = p[STATE];
let rvalue;
...
}
- Lingua principale
- Clojure
- Stelle
- 547
- Fork
- 66
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
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 funcool/promesa
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 54/100
-
A throwing task corrupts other tasks in the same processNextTick drain (wrong value in 12.0.1, wrong rejection in 11.0.678)Forse già presa @yqrashawn l’ha presa 12 giorni fa. Aperta
Difficoltà 3/5 Mezza giornata Idoneità per principianti 70/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 76/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 52/100
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 25/100
Tutte le issue di funcool/promesa
Issue simili
-
bug triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
.Team/UXWest Misc/Accessibility Priority:P3 Reporting/Dashboards Type:Bug Visualizer
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
metabase/metabase#86780 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
nextjournal/markdown#77 ·
-
Expose the ECA version to hook payloadsForse già presa @zikajk l’ha presa 1 giorno fa. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
editor-code-assistant/eca#697 · 1 assegnatario ·
I maintainer di solito rispondono entro 2 giorni
-
enhancement good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
itsOmidKarami/kraft#1346 ·
I maintainer di solito rispondono entro 1 giorno