Skip to content

[Bug]: Unbounded Message Listener Duplication on Reconnect #1106

@deepak0x

Description

@deepak0x

The EmbeddedChatApi class fails to clear existing message event listeners when the connect() method is called. The issue occurs because rcClient.onMessage(...) is additive: every invocation registers a new callback without removing previously registered ones.

The close() method currently calls unsubscribeAll() on the SDK client, but this only unsubscribes from server-side DDP streams. It does not clear the internal JavaScript callbacks registered via onMessage on the client instance.

As a result, each reconnect accumulates additional message listeners.


UI Masking

This issue is largely hidden in the UI due to message deduplication logic:

  • The React frontend uses a store that deduplicates messages by ID.
  • useMessageStore relies on an upsertMessage helper that updates existing messages if the ID already exists.

Effect:

  • Duplicate message events are processed internally.
  • The UI shows only a single message update, masking the underlying duplication.

Memory Leak (Linear Growth)

  • Each reconnection adds a new closure retaining references to the message handler context.
  • Long-lived sessions or unstable network conditions cause unbounded listener growth

Performance Degradation

  • CPU Overhead: Each incoming message triggers the processing logic N times.
  • Render Thrashing: Even with deduplication, repeated state updates may still trigger reconciliation cycles depending on equality checks.

Side-Effect Duplication

Any non-idempotent side effects tied to message receipt will execute multiple times:

  • Sound notifications
  • Read receipts
  • Analytics or logging
  • Desktop notifications

Example: After 5 reconnects, a single incoming message may trigger 5 notification sounds.


Reproduction

  1. Login and connect.
  2. Send Message 1 → Received once.
  3. Reconnect (simulate network flap).
  4. Send Message 2 → Received twice (proof of leak).

Expected behaviour

Ensure message listeners are registered exactly once, or explicitly removed before adding new ones during reconnection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions