Skip to content
This repository was archived by the owner on Apr 4, 2026. It is now read-only.

Commit d06d921

Browse files
fix: survive Redis restarts with infinite reconnection policy (#89)
The reconnect policy was configured with max_attempts=3, meaning Fred would give up reconnecting after 3 failed attempts. After a Redis restart, the pool would permanently die and all operations would timeout forever — requiring pod restarts to recover. Changes: - Set ReconnectPolicy max_attempts=0 (infinite retries with exp backoff) - Add 50ms jitter to reconnect delays to prevent thundering herd - Wire up Fred EventInterface (on_error, on_reconnect, on_unresponsive) for connection visibility - Health monitor now calls force_reconnection() when pool is disconnected instead of just logging a warning
1 parent 5a64428 commit d06d921

1 file changed

Lines changed: 28 additions & 11 deletions

File tree

src/redis/client.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use fred::types::{ConnectionConfig, Expiration, PerformanceConfig, ReconnectPoli
1212
use std::sync::Arc;
1313
use std::sync::atomic::{AtomicU64, Ordering};
1414
use std::time::Duration;
15-
use tracing::{info, warn};
15+
use tracing::{error, info, warn};
1616

1717
/// Internal metrics tracking for pool health
1818
struct PoolMetrics {
@@ -62,13 +62,14 @@ impl Redis {
6262
..Default::default()
6363
};
6464

65-
// Configure reconnection policy
66-
let reconnect_policy = ReconnectPolicy::new_exponential(
67-
3, // max retries
65+
// Configure reconnection: retry forever to survive Redis restarts
66+
let mut reconnect_policy = ReconnectPolicy::new_exponential(
67+
0, // max attempts (0 = infinite)
6868
100, // min delay ms
6969
30_000, // max delay ms
7070
2, // multiplier
7171
);
72+
reconnect_policy.set_jitter(50);
7273

7374
// Create the pool with proper configuration
7475
let pool = RedisPool::new(
@@ -117,22 +118,38 @@ impl Redis {
117118
circuit_breaker.is_some(),
118119
);
119120

120-
// Start pool health monitoring
121+
// Wire up event handlers for connection visibility.
122+
// RedisPool doesn't implement EventInterface directly, so we use .next()
123+
// to get a client handle -- these events fire for the entire pool.
124+
let client = pool.next();
125+
client.on_error(|err| {
126+
error!("Redis connection error: {:?}", err);
127+
Ok(())
128+
});
129+
client.on_reconnect(|server| {
130+
info!("Redis reconnected to {}", server);
131+
Ok(())
132+
});
133+
client.on_unresponsive(|server| {
134+
warn!("Redis connection unresponsive: {}", server);
135+
Ok(())
136+
});
137+
138+
// Start pool health monitoring -- force reconnection when disconnected
121139
let pool_monitor = pool.clone();
122-
let _max_pool_size = config.max_pool_size;
123140
let cb_monitor = circuit_breaker.clone();
124141
tokio::spawn(async move {
125142
let mut interval = tokio::time::interval(Duration::from_secs(30));
126143
loop {
127144
interval.tick().await;
128145

129-
// Fred handles pool metrics internally, but we can check connection state
130-
let state = pool_monitor.state();
131-
if matches!(state, fred::types::ClientState::Disconnected) {
132-
warn!("Redis pool disconnected, reconnecting...");
146+
if matches!(pool_monitor.state(), fred::types::ClientState::Disconnected) {
147+
warn!("Redis pool disconnected, forcing reconnection...");
148+
if let Err(e) = pool_monitor.force_reconnection().await {
149+
error!("Failed to force Redis reconnection: {:?}", e);
150+
}
133151
}
134152

135-
// Log circuit breaker metrics if enabled
136153
if let Some(cb) = &cb_monitor {
137154
let metrics = cb.get_metrics();
138155
let state = cb.get_state().await;

0 commit comments

Comments
 (0)