Skip to content

Conversation

@tanmaysharma2001
Copy link
Contributor

🔍 Description

Fixed a critical memory leak in RealtimeClient._teardownConnection() where WebSocket connections were not properly closed before being nullified, causing connections to remain open in memory indefinitely.

What changed?

Enhanced the _teardownConnection() method in packages/core/realtime-js/src/RealtimeClient.ts to:

  • Check WebSocket connection state before cleanup
  • Explicitly call conn.close() for OPEN or CONNECTING connections
  • Add error handling with try-catch to gracefully handle close failures
  • Log any errors during connection closure for debugging

Before:

private _teardownConnection(): void {
  if (this.conn) {
    this.conn.onopen = null
    this.conn.onerror = null
    this.conn.onmessage = null
    this.conn.onclose = null
    this.conn = null  // reference nullified without ensuring closure
  }
}

After:

private _teardownConnection(): void {
  if (this.conn) {
    // Ensure connection is properly closed first
    if (
      this.conn.readyState === SOCKET_STATES.open ||
      this.conn.readyState === SOCKET_STATES.connecting
    ) {
      try {
        this.conn.close()
      } catch (e) {
        this.log('error', 'Error closing connection', e)
      }
    }

    this.conn.onopen = null
    this.conn.onerror = null
    this.conn.onmessage = null
    this.conn.onclose = null
    this.conn = null
  }
  this._clearAllTimers()
  this.channels.forEach((channel) => channel.teardown())
}

Why was this change needed?

The original implementation would set WebSocket event handlers to null and nullify the connection reference without first ensuring the WebSocket was properly closed. This caused:

  • Memory Leaks: WebSocket connections remain open in memory in long-running applications
  • Resource Exhaustion: Accumulation of unclosed connections over time
  • Connection Limits: Could hit browser/system connection limits in high-traffic scenarios

Closes #1840

🔄 Breaking changes

  • [☑️] This PR contains no breaking changes

📋 Checklist

  • [☑️] I have read the Contributing Guidelines
  • [☑️] My PR title follows the conventional commit format: <type>(<scope>): <description>
  • [☑️] I have run npx nx format to ensure consistent code formatting
  • [☑️] I have added tests for new functionality (if applicable)
  • [☑️] I have updated documentation (if applicable)

📝 Additional notes

Technical details:

  • Uses existing SOCKET_STATES enum constants for readyState checks
  • Leverages existing this.log() method for error reporting
  • Maintains all existing cleanup logic (_clearAllTimers(), channel teardown)
  • No changes to public API or behavior

Testing:

  • Existing unit tests continue to pass
  • Integration tests validate proper connection lifecycle
  • Recommended manual testing: Long-running applications with frequent connection cycles
  • Affected packages:
    • @supabase/realtime-js (primary fix)
    • @supabase/supabase-js (indirect benefit)

@tanmaysharma2001 tanmaysharma2001 requested review from a team as code owners November 10, 2025 06:40
@mandarini mandarini changed the title fix(realtime-js): ensure WebSocket connections are properly closed in teardown fix(realtime): ensure WebSocket connections are properly closed in teardown Nov 10, 2025
@mandarini mandarini merged commit 5e6dda1 into supabase:master Nov 10, 2025
21 checks passed
@coveralls
Copy link

coveralls commented Nov 10, 2025

Coverage Status

coverage: 95.276% (+13.5%) from 81.737%
when pulling d4cb87e on tanmaysharma2001:feat/ws-mem-leak-fix
into d1ba7d9 on supabase:master.

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.

WebSocket Connection Memory Leak in RealtimeClient

4 participants