events: addAbortListener ignores disposal and passes undefined event when signal is already aborted
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Facilidade para iniciantes
- 75/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Stack de tecnologia
- javascript
- Domínio
- backend
Direção de pesquisa
Comece reproduzindo o script fornecido e, em seguida, leia lib/internal/events/abort_listener.js, concentrando-se no branch signal.aborted e no tratamento de SymbolDispose. Adicione cobertura de regressão para o descarte antes do microtask enfileirado e para o argumento do callback; depois, verifique se o listener é suprimido após o descarte e se, caso contrário, recebe um abort Event.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
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 {
// ...
- Linguagem predominante
- JavaScript
- Estrelas
- 122k
- Forks
- 37.4k
- Merge médio
- 4d 3h
- PRs com merge (30d)
- 279
Guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de nodejs/node
-
doc
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 65/100
-
build
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 88/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 90/100
-
feature request
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Todas as issues de nodejs/node
Issues semelhantes
-
awaiting triage bug Causes friction Hop Gui P1 P2 Transforms
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
-
Improve Title Support Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 70/100
georgestephanis/p2026#40 ·
-
Enatega Customer and Rider app: Add-ons price is not visible to customer after order is placed. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
Margaret-Petersen/food-delivery-app-clone-react-native#1981 ·