events: addAbortListener ignores disposal and passes undefined event when signal is already aborted
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 75/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- javascript
- Ambito
- backend
Direzione di ricerca
Inizia riproducendo lo script fornito, quindi leggi lib/internal/events/abort_listener.js, concentrandoti sul ramo signal.aborted e sulla gestione di SymbolDispose. Aggiungi una copertura di regressione per il rilascio prima del microtask accodato e per l’argomento del callback, quindi verifica che il listener venga soppresso dopo il rilascio e che altrimenti riceva un abort Event.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Version
Tested on v24.7.0, reproduces on main (commit a48e33fb6f).
Platform
All platforms (pure JS logic in `lib/internal/events/abort_listener.js`).
Subsystem
events
What steps will reproduce the bug?
'use strict';
const { addAbortListener } = require('node:events');
// --- Bug 1: Disposing the listener does not cancel it if the signal was already aborted ---
const ac = new AbortController();
ac.abort();
let listenerCalledAfterDispose = false;
{
using _ = addAbortListener(ac.signal, (event) => {
listenerCalledAfterDispose = true;
console.log('Event argument received:', event); // Bug 2: logs 'undefined' instead of an Event object
});
// Scope exits here -> disposable[Symbol.dispose]() is invoked synchronously.
}
queueMicrotask(() => {
console.log('Listener called after disposal:', listenerCalledAfterDispose);
// Prints: true (Expected: false)
});
How often does it reproduce? Is there a required condition?
100% reproducible whenever addAbortListener() is called on an AbortSignal where signal.aborted === true.
What is the expected behavior? Why is that the expected behavior?
- Calling
disposable[Symbol.dispose]()(or exiting ausingscope) before the microtask executes should disarm/cancel the scheduled listener. The main motivation foraddAbortListener()is TC39 explicit resource management, so disposal should always prevent the callback from firing. - The listener callback should receive an
Eventobject (type === 'abort'), consistent with when the listener is triggered by an active signal.
What do you see instead?
- The listener runs unconditionally in the microtask, ignoring
Symbol.dispose(). - The listener receives
undefinedinstead of anEventinstance. Code following the documented(e) => { ... }signature that accesses properties likee.typecrashes with aTypeErrorinside the microtask.
Additional information
In lib/internal/events/abort_listener.js:
let removeEventListener;
if (signal.aborted) {
queueMicrotask ??= require('internal/process/task_queues').queueMicrotask;
queueMicrotask(() => listener()); // <-- 1. Listener called with no argument
} else {
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
abortListenerOptions ??= ObjectFreeze({ __proto__: null, once: true, [kResistStopPropagation]: true });
signal.addEventListener('abort', listener, abortListenerOptions);
removeEventListener = () => {
signal.removeEventListener('abort', listener);
};
}
return {
__proto__: null,
[SymbolDispose]() {
removeEventListener?.(); // <-- 2. removeEventListener is undefined on the aborted branch
},
};
When signal.aborted is true:
removeEventListeneris leftundefined, makingSymbolDisposea no-op.queueMicrotask(() => listener())invokeslistenerwithout passing anEventobject.
A possible fix is tracking a disposed flag for the microtask and creating an Event('abort') instance:
let removeEventListener;
if (signal.aborted) {
queueMicrotask ??= require('internal/process/task_queues').queueMicrotask;
let disposed = false;
queueMicrotask(() => {
if (!disposed) {
const { Event } = require('internal/event_target');
listener(new Event('abort', { cancelable: false, bubbles: false }));
}
});
removeEventListener = () => {
disposed = true;
};
} else {
// ...
- Lingua principale
- JavaScript
- Stelle
- 122k
- Fork
- 37.4k
- Merge medio
- 4g 3h
- PR unite (30g)
- 273
Guida per i contributori
Apri 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 nodejs/node
-
doc
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
-
build
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
-
feature request
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
Issue simili
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
avniproject/avni-client#2135 ·
-
automated broken-link
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
-
agent/security hive/hosted-available-lke648397-260827-5n31 security
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
babalae/bettergi-scripts-list#3674 ·
-
A-Release-Notes C-Editing D-Modest S-Ready-For-Implementation
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
bevyengine/bevy-website#2595 ·