From fdb8823df7f01da9295c8f7fef8e5911c85b1e9e Mon Sep 17 00:00:00 2001 From: Boki Date: Tue, 10 Jun 2025 15:17:47 -0400 Subject: [PATCH] fixes for redis event memory leak --- libs/cache/src/index.ts | 35 +++++++++++++++++++++++------------ libs/cache/src/redis-cache.ts | 18 ++++++++++++++++-- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/libs/cache/src/index.ts b/libs/cache/src/index.ts index 71fb056..88bb6b8 100644 --- a/libs/cache/src/index.ts +++ b/libs/cache/src/index.ts @@ -2,6 +2,9 @@ import { RedisCache } from './redis-cache'; import { RedisConnectionManager } from './connection-manager'; import type { CacheProvider, CacheOptions } from './types'; +// Cache instances registry to prevent multiple instances with same prefix +const cacheInstances = new Map(); + /** * Create a Redis cache instance with trading-optimized defaults */ @@ -14,6 +17,20 @@ export function createCache(options: Partial = {}): CacheProvider ...options }; + // For shared connections, reuse cache instances with the same key prefix + if (defaultOptions.shared) { + const cacheKey = `${defaultOptions.keyPrefix}-${defaultOptions.ttl}`; + + if (cacheInstances.has(cacheKey)) { + return cacheInstances.get(cacheKey)!; + } + + const cache = new RedisCache(defaultOptions); + cacheInstances.set(cacheKey, cache); + return cache; + } + + // For non-shared connections, always create new instances return new RedisCache(defaultOptions); } @@ -21,45 +38,39 @@ export function createCache(options: Partial = {}): CacheProvider * Create a cache instance for trading data */ export function createTradingCache(options: Partial = {}): CacheProvider { - const defaultOptions: CacheOptions = { + return createCache({ keyPrefix: 'trading:', ttl: 3600, // 1 hour default enableMetrics: true, shared: true, ...options - }; - - return new RedisCache(defaultOptions); + }); } /** * Create a cache for market data with shorter TTL */ export function createMarketDataCache(options: Partial = {}): CacheProvider { - const defaultOptions: CacheOptions = { + return createCache({ keyPrefix: 'market:', ttl: 300, // 5 minutes for market data enableMetrics: true, shared: true, ...options - }; - - return new RedisCache(defaultOptions); + }); } /** * Create a cache for indicators with longer TTL */ export function createIndicatorCache(options: Partial = {}): CacheProvider { - const defaultOptions: CacheOptions = { + return createCache({ keyPrefix: 'indicators:', ttl: 1800, // 30 minutes for indicators enableMetrics: true, shared: true, ...options - }; - - return new RedisCache(defaultOptions); + }); } // Export types and classes diff --git a/libs/cache/src/redis-cache.ts b/libs/cache/src/redis-cache.ts index f8fcd8f..b4ed4c0 100644 --- a/libs/cache/src/redis-cache.ts +++ b/libs/cache/src/redis-cache.ts @@ -42,7 +42,13 @@ export class RedisCache implements CacheProvider { singleton: options.shared ?? true, // Default to shared connection for cache }); - this.setupEventHandlers(); + // Only setup event handlers for non-shared connections to avoid memory leaks + if (!(options.shared ?? true)) { + this.setupEventHandlers(); + } else { + // For shared connections, just monitor the connection status without adding handlers + this.isConnected = this.redis.status === 'ready'; + } } private setupEventHandlers(): void { @@ -235,6 +241,14 @@ export class RedisCache implements CacheProvider { } isReady(): boolean { - return this.redis.status === 'ready'; + // Always check the actual Redis connection status + const ready = this.redis.status === 'ready'; + + // Update local flag if we're not using shared connection + if (this.isConnected !== ready) { + this.isConnected = ready; + } + + return ready; } }