-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
Fix events remove listener emission #60137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #60137 +/- ##
==========================================
+ Coverage 88.55% 89.77% +1.22%
==========================================
Files 704 672 -32
Lines 208087 203908 -4179
Branches 40019 39202 -817
==========================================
- Hits 184266 183058 -1208
+ Misses 15818 13169 -2649
+ Partials 8003 7681 -322
🚀 New features to boost your workflow:
|
Adds test coverage for the removeListener event being emitted when a once() listener is automatically removed after execution. This verifies that streams and other EventEmitters correctly emit removeListener events when once() wrappers clean up.
074f626 to
d322e4d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could delete 2 more lines:
del 690-691, 715-716
+718 if (events.removeListener !== undefined)
+719 this.emit('removeListener', type, listener);
Delete lines 690-691, 715-716, and add consolidated emit at 718-719. The Problem I Found When I tried this, the test failed because:
Current State I kept both emit calls inside their respective branches:
Your consolidation proposal cannot work due to the early return at line |
|
Sorry if I am wrong. And I think is same as |
@simonkcleung Thank you for the suggestion! I explored consolidating the emit calls, but found a issue: Edge Case Problem: Test case (line 42-48): ee.on('hello', listener1);
ee.on('removeListener', common.mustNotCall());
ee.removeListener('hello', listener2); // listener2 never added - should NOT emit
Performance:
Consolidation would require a tracking variable (let removed or let removedListener), adding overhead to every removeListener() call.
The current approach with duplicate emit calls is actually optimal:
- Zero overhead (no extra variables)
- Handles edge cases correctly
- Properly handles wrapped listeners (list.listener || listener vs listener)
I've kept the duplicate emits while fixing the original bug. Thanks for the detailed review! |
You are right. |
Commit Queue failed- Loading data for nodejs/node/pull/60137 ✔ Done loading data for nodejs/node/pull/60137 ----------------------------------- PR info ------------------------------------ Title Fix events remove listener emission (#60137) Author sangwook <rewq5991@gmail.com> (@Han5991) Branch Han5991:fix-events-remove-listener-emission -> nodejs:main Labels events, author ready, needs-ci, commit-queue-rebase Commits 2 - test: ensure removeListener event fires for once() listeners - fix: emit removeListener event when last listener is removed Committers 1 - sangwook <bnbt3@naver.com> PR-URL: https://github.com/nodejs/node/pull/60137 Fixes: https://github.com/nodejs/node/issues/59977 Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> ------------------------------ Generated metadata ------------------------------ PR-URL: https://github.com/nodejs/node/pull/60137 Fixes: https://github.com/nodejs/node/issues/59977 Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> -------------------------------------------------------------------------------- ℹ This PR was created on Tue, 07 Oct 2025 05:54:03 GMT ✔ Approvals: 2 ✔ - Aviv Keller (@avivkeller): https://github.com/nodejs/node/pull/60137#pullrequestreview-3668276886 ✔ - Chemi Atlow (@atlowChemi): https://github.com/nodejs/node/pull/60137#pullrequestreview-3702227533 ✔ Last GitHub CI successful ℹ Last Full PR CI on 2026-01-16T01:17:25Z: https://ci.nodejs.org/job/node-test-pull-request/70824/ - Querying data for job/node-test-pull-request/70824/ ✔ Build data downloaded ✔ Last Jenkins CI successful -------------------------------------------------------------------------------- ✔ No git cherry-pick in progress ✔ No git am in progress ✔ No git rebase in progress -------------------------------------------------------------------------------- - Bringing origin/main up to date... From https://github.com/nodejs/node * branch main -> FETCH_HEAD ✔ origin/main is now up-to-date - Downloading patch for 60137 From https://github.com/nodejs/node * branch refs/pull/60137/merge -> FETCH_HEAD ✔ Fetched commits as 4bc42c0ac85c..d322e4de739c -------------------------------------------------------------------------------- [main 6b05ddbc1c] test: ensure removeListener event fires for once() listeners Author: sangwook <bnbt3@naver.com> Date: Tue Oct 7 12:38:02 2025 +0900 1 file changed, 16 insertions(+) Auto-merging lib/events.js [main 43a9d905a5] fix: emit removeListener event when last listener is removed Author: sangwook <bnbt3@naver.com> Date: Tue Oct 7 13:08:22 2025 +0900 1 file changed, 3 insertions(+), 2 deletions(-) ✔ Patches applied There are 2 commits in the PR. Attempting autorebase. (node:2355) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated. (Use `node --trace-deprecation ...` to show where the warning was created) Rebasing (2/4) Executing: git node land --amend --yes --------------------------------- New Message ---------------------------------- test: ensure removeListener event fires for once() listenershttps://github.com/nodejs/node/actions/runs/21318975549 |
mertcanaltin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
When the last listener is removed and _eventsCount becomes 0, the removeListener event was not being emitted because the check was inside the else block. This moves the removeListener emission outside the conditional to ensure it always fires when a listener is removed.
d322e4d to
598fbb4
Compare
Description
This PR fixes a bug where the
removeListenerevent was not being emitted when the last listener was removed from an EventEmitter.Background
When
removeListener()is called and the last listener is removed (_eventsCount === 0), the code path that emits theremoveListenereventwas being skipped. This was because the emission logic was inside the
elseblock of the event count check.This issue particularly affects
once()listeners, which automatically callremoveListener()after execution. If the once listener was thelast listener, the
removeListenerevent would not fire.Changes
removeListenerevent emission outside the conditional block to ensure it always executes when a listener isremoved
once()listeners withremoveListenerevent monitoringTest Plan
Related Issues
fixes: #59977