Skip to content

fix: remove stale listeners in setReconnect() before adding new ones (OPTI-2441)#518

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
bugfix/OPTI-2441-fix-setreconnect-listener-accumulation
Open

fix: remove stale listeners in setReconnect() before adding new ones (OPTI-2441)#518
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
bugfix/OPTI-2441-fix-setreconnect-listener-accumulation

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented Apr 24, 2026

Summary

setReconnect() in BaseWebRTC.js adds event listeners via .on() to this.signaling and this.webRTCPeer but never removes old listeners. This PR stores handler references and removes them before adding new ones, and also cleans up listeners in stop().

Changes:

  • BaseWebRTC.js: New removeReconnectListeners() method that removes stored handler references (_migrateHandler, _signalingErrorHandler, _peerStateHandler). Called at the top of setReconnect() and in stop().
  • View.js / Publish.js: removeReconnectListeners() is called before the instance swap (this.signaling = signalingInstance / this.webRTCPeer = webRTCPeerInstance) so that .off() targets the old instances, not the new ones.
  • View.js / Publish.js: Migrate cleanup handlers use .on() with explicit self-removal (.off()) once a terminal state is reached. This replaces the original anonymous .on() listeners and avoids the pitfall of .once() being consumed by non-terminal states like 'connecting' (Chrome) or 'checking' (Firefox).

Tracked in OPTI-2441.

Review & Testing Checklist for Human

  • Verify migrate cleanup handlers survive non-terminal states. The self-removing .on() pattern should ignore states like 'connecting'/'checking' and only fire cleanup + self-remove on terminal states (connected, disconnected, failed, closed). Confirm this in a real browser migration scenario.
  • Verify old-instance listener removal during migrate. removeReconnectListeners() is called before instance swap in initConnection() (View.js:384, Publish.js:250), so .off() should target the old signaling/peer. Note that setReconnect() also calls removeReconnectListeners() internally, but at that point it operates on the new instances (effectively a no-op for old listeners). The pre-swap call is what provides the actual cleanup.
  • Test normal reconnect flow — verify that stop()connect() still works correctly (non-migrate path) and that listeners don't accumulate across reconnects.
  • No unit tests were added. Consider adding test coverage for listener cleanup, especially for the migrate path.

Notes

  • The re-emitter cleanup pattern (stopReemitingWebRTCPeerInstanceEvents) already exists for re-emitted events but was not extended to setReconnect listeners. This PR takes a parallel approach by storing and removing individual handler references.
  • removeReconnectListeners() does not null out handler references after removal — they are overwritten by the subsequent setReconnect() call. This is intentional to keep the method simple and idempotent.

Link to Devin session: https://dolby.devinenterprise.com/sessions/4d843ed3136b4e299f5bf3e30f6ff944
Requested by: @craig-johnston


Open in Devin Review

OPTI-2441: setReconnect() adds event listeners via .on() but never
removes old ones. During migrate paths, stale listeners on old
signaling/peer instances can fire and operate on the current connection
via this closure, potentially triggering unwanted reconnects.

Changes:
- Store listener references and remove them at the start of
  setReconnect() before adding new ones (Option A)
- Add removeReconnectListeners() and call it from stop() as well
- Use .once() for migrate cleanup listeners in View.js and Publish.js
  since they only need to fire once

Co-Authored-By: craig.johnston <cjohn@dolby.com>
@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

⚠️ No Changeset found

Latest commit: 7a19784

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Call removeReconnectListeners() before swapping this.signaling and
this.webRTCPeer to new instances in initConnection(), ensuring stale
listeners are removed from the OLD instances (not the new ones).

Co-Authored-By: craig.johnston <cjohn@dolby.com>
devin-ai-integration[bot]

This comment was marked as resolved.

The .once() handler gets consumed by non-terminal states like
'connecting' (Chrome) or 'checking' (Firefox) before reaching a
terminal state, causing old resources to leak. Use .on() with
explicit .off() self-removal when a terminal state is reached.

Co-Authored-By: craig.johnston <cjohn@dolby.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants