Fix Redis crash loop from unhandled socket errors - #18
Conversation
node-redis session and cache clients crash the process when Memorystore drops idle TLS connections. Add error handlers, pingInterval, TCP keepalive, and reconnect strategy to both the session-storage and cache-storage Redis clients.
089c3b9 to
8645ab6
Compare
| keepAlive: true, | ||
| keepAliveInitialDelay: 30_000, |
There was a problem hiding this comment.
[💡 Suggestion]: keepAlive: true isn't the right type for node-redis v4, and keepAliveInitialDelay is ignored
In @redis/client 1.6 (redis 4.7), socket.keepAlive is typed number | false, and the client applies it as socket.setKeepAlive(keepAlive !== false, keepAlive || 0) — it never reads keepAliveInitialDelay. So keepAlive: true fails tsc (TS2322), and at runtime the intended 30s delay is dropped (falls back to the OS keepalive default, ~2h). The SWC image build strips types so it won't break the Docker build, but the typecheck target will. Collapse to a single numeric value:
socket: {
keepAlive: 30_000,
reconnectStrategy: (retries: number) => Math.min(retries * 200, 5_000),
},Not blocking the crash fix, pingInterval: 30_000 is what actually keeps the Memorystore connection warm, so the core fix holds regardless.
| keepAlive: true, | ||
| keepAliveInitialDelay: 30_000, |
There was a problem hiding this comment.
[💡 Suggestion]: Same keepAlive type/runtime issue here as in the cache factory
socket.keepAlive is number | false in @redis/client 1.6, and node-redis ignores keepAliveInitialDelay (it uses the numeric keepAlive as the delay). keepAlive: true fails tsc and doesn't apply the intended 30s delay at runtime. Use:
socket: {
keepAlive: 30_000,
reconnectStrategy: (retries: number) => Math.min(retries * 200, 5_000),
},| ...cacheModuleOptions, | ||
| store: redisStore, | ||
| url: redisUrl, | ||
| store: redisInsStore(redisClient, { |
There was a problem hiding this comment.
[💡 Suggestion]: redisInsStore(redisClient, …) needs a RedisClientType cast to pass tsc
createClient() without functions/scripts infers generics that don't match redisInsStore's RedisClientType parameter (RedisFunctions vs Record<string, never>), so this line fails tsc with TS2345 (reproduced against the pinned redis 4.7 / cache-manager-redis-yet 4.1.2, and it persists even with a single @redis/client copy). SWC strips types so the Docker image build is unaffected, but the typecheck target will fail. Add a cast:
import { createClient, type RedisClientType } from 'redis';
// ...
store: redisInsStore(redisClient as RedisClientType, { ttl: cacheStorageTtl * 1000 }),
NIXKnight
left a comment
There was a problem hiding this comment.
The fix correctly resolves the crash loop: both redis clients now register an error listener before connect, and dropping the session re-throw restores auto-reconnect. Verified the node-redis v4.7 options and redisInsStore usage against the pinned versions. Approving.
- Use keepAlive: 30_000 (number) instead of keepAlive: true (boolean) since node-redis v4 types socket.keepAlive as number | false - Remove keepAliveInitialDelay which node-redis ignores - Cast redisClient as RedisClientType for redisInsStore compatibility
LogDetails |
Description
Twenty pods crash-loop on the FOSS sandbox because the node-redis clients (session storage and cache storage) lack error handlers. When Memorystore (Valkey) drops idle TLS connections, node-redis emits an
errorevent with no listener, which Node treats as fatal (exit 1). The.connect().catch()in session storage also re-throws, producing an unhandled rejection that breaks built-in auto-reconnect.This patch fixes both the session-storage (
connect-redis) and cache-storage (cache-manager-redis-yet) Redis clients:.on('error', ...)handler to both clients to prevent unhandled error crashes.catch()with log-only handler so auto-reconnect works (session client)redisStore(which creates its own client without error handling) toredisInsStorewith a pre-built client that has a proper error handler attachedpingInterval: 30sto keep idle connections alive and avoid Memorystore reapingsocket.keepAlive+keepAliveInitialDelayfor TCP-level keepalivesocket.reconnectStrategywith exponential backoff capped at 5sThe ioredis clients (BullMQ, pub/sub) are not affected -- they have built-in reconnect and error handling.
Testing
Session Redis client error:/Cache Redis client error:lines (expected, non-fatal) instead ofSocketClosedUnexpectedlyErrorstack traces