Skip to content

Refactor redisReceiver to eliminate global state and prevent memory leaks #1893

@AbanoubGhadban

Description

@AbanoubGhadban

Problem

The redisReceiver.ts module in the dummy app currently uses global module-level state that causes several issues:

Global State Issues

  1. Shared Redis Client - Single sharedRedisClient connection shared across ALL concurrent requests
  2. Global pendingPromises Map - Promise tracking map shared across ALL listeners, regardless of requestId
  3. Global activeListeners Map - Listener cache that accumulates undefined entries (never cleaned up properly)

Memory Leak Risk

  • pendingPromises and activeListeners maps only set entries to undefined instead of deleting them
  • Over time in long-running processes, these maps accumulate undefined entries
  • Global state prevents proper garbage collection between requests

Request Isolation Issues

  • Multiple concurrent requests share the same Redis client connection
  • pendingPromises being global means different requestIds can interfere with each other's promise resolution
  • No proper isolation between independent stream listeners

Solution

Refactor to eliminate all global state by moving to request-scoped encapsulation:

Changes

  1. Remove all module-level state variables:

    • sharedRedisClient
    • isClientConnected
    • activeListeners
    • pendingPromises
  2. Create private state per listenToRequestData() call:

    • Each listener gets its own dedicated Redis client instance
    • Each listener has its own isolated pendingPromises map
    • Each listener manages its own connection lifecycle
  3. Proper cleanup:

    • Use delete instead of setting to undefined
    • Always close Redis client in close() method
    • Clear all Maps completely on cleanup

Benefits

No memory leaks - All state properly cleaned up per request
Request isolation - Each stream listener is completely independent
Simpler code - No global state management complexity
Better resource management - Each listener owns its connection lifecycle
Same API - No breaking changes to consumers

Scope

Files modified:

  • /react_on_rails_pro/spec/dummy/client/app/utils/redisReceiver.ts - Internal refactoring only

No changes needed:

  • Consumer components (RedisReceiver.jsx, RSCPostsPageOverRedis.jsx)
  • E2E tests
  • Backend Ruby controllers

Behavioral differences:

  • Removed listener caching by requestId (each call creates fresh listener)
  • Each listener has isolated state and own Redis connection
  • External API and behavior unchanged

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions