(redis: Redis)
| 86 | } |
| 87 | |
| 88 | function startPingHealthCheck(redis: Redis): void { |
| 89 | if (state.pingInterval) return |
| 90 | |
| 91 | state.pingInterval = setInterval(async () => { |
| 92 | if (state.pingInFlight) return |
| 93 | state.pingInFlight = true |
| 94 | try { |
| 95 | await redis.ping() |
| 96 | state.pingFailures = 0 |
| 97 | } catch (error) { |
| 98 | state.pingFailures++ |
| 99 | logger.warn('Redis PING failed', { |
| 100 | consecutiveFailures: state.pingFailures, |
| 101 | error: toError(error).message, |
| 102 | }) |
| 103 | |
| 104 | if (state.pingFailures >= MAX_PING_FAILURES) { |
| 105 | logger.error('Redis PING failed consecutive times — forcing reconnect', { |
| 106 | consecutiveFailures: state.pingFailures, |
| 107 | }) |
| 108 | state.pingFailures = 0 |
| 109 | // Clear before notifying listeners — they may call getRedisClient() and must see the reset state. |
| 110 | state.client = null |
| 111 | if (state.pingInterval) { |
| 112 | clearInterval(state.pingInterval) |
| 113 | state.pingInterval = null |
| 114 | } |
| 115 | for (const cb of state.reconnectListeners) { |
| 116 | try { |
| 117 | cb() |
| 118 | } catch (cbError) { |
| 119 | logger.error('Redis reconnect listener error', { error: cbError }) |
| 120 | } |
| 121 | } |
| 122 | try { |
| 123 | redis.disconnect(true) |
| 124 | } catch (disconnectError) { |
| 125 | logger.error('Error during forced Redis disconnect', { error: disconnectError }) |
| 126 | } |
| 127 | } |
| 128 | } finally { |
| 129 | state.pingInFlight = false |
| 130 | } |
| 131 | }, PING_INTERVAL_MS) |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Get a Redis client instance. |
no test coverage detected