fix: add reconnection policy to Redis subscriber to prevent ghost state (#708)

* fix: add reconnection policy to Redis subscriber to prevent ghost state

- Add ReconnectPolicy::new_exponential(0, 100, 30_000, 2) to the subscriber builder, unlimited retries with exponential backoff (100ms min, 30s max)
- Add on_reconnect handler that signals the listener loop to force a subscription reset, re-subscribing to all topics on the new connection
- Add warn-level logging to on_error for all Redis subscriber errors (previously only Canceled was handled, others were silently ignored)

Signed-off-by: Infiland <ljubica.citydesign@gmail.com>

* Update websocket.rs

Signed-off-by: Infiland <88491175+Infiland@users.noreply.github.com>

* Auto-manage subscriptions on reconnect

Call subscriber.manage_subscriptions() so the subscriber will automatically re-subscribe tracked channels after a Redis reconnect. Remove the manual reconnect channel and on_reconnect handler along with the select branch that forced SubscriptionStateChange::Reset.

Signed-off-by: Infiland <88491175+Infiland@users.noreply.github.com>

---------

Signed-off-by: Infiland <ljubica.citydesign@gmail.com>
Signed-off-by: Infiland <88491175+Infiland@users.noreply.github.com>
This commit is contained in:
Infiland
2026-04-18 05:15:54 +02:00
committed by GitHub
parent 3675ff1a1f
commit 057f2bb8b3

View File

@@ -5,7 +5,7 @@ use authifier::AuthifierEvent;
use fred::{
error::RedisErrorKind,
interfaces::{ClientLike, EventInterface, PubsubInterface},
types::RedisConfig,
types::{ReconnectPolicy, RedisConfig},
};
use futures::{
channel::oneshot,
@@ -225,9 +225,9 @@ async fn listener(
.unwrap_or(REDIS_URI.to_string());
let redis_config = RedisConfig::from_url(&url).unwrap();
let subscriber = match report_internal_error!(
fred::types::Builder::from_config(redis_config).build_subscriber_client()
) {
let mut builder = fred::types::Builder::from_config(redis_config);
builder.set_policy(ReconnectPolicy::new_exponential(8, 100, 30_000, 2));
let subscriber = match report_internal_error!(builder.build_subscriber_client()) {
Ok(subscriber) => subscriber,
Err(_) => return,
};
@@ -236,16 +236,21 @@ async fn listener(
return;
}
// Let Fred automatically re-subscribe to tracked channels on reconnect.
subscriber.manage_subscriptions();
// Handle Redis connection dropping
let (clean_up_s, clean_up_r) = async_channel::bounded(1);
let clean_up_s = Arc::new(Mutex::new(clean_up_s));
subscriber.on_error(move |err| {
warn!("Redis subscriber error: {:?}", err);
if let RedisErrorKind::Canceled = err.kind() {
let clean_up_s = clean_up_s.clone();
spawn(async move {
clean_up_s.lock().await.send(()).await.ok();
});
}
// Transient errors (IO, timeout) are handled by the reconnect policy.
Ok(())
});
@@ -522,4 +527,4 @@ async fn worker(
}
}
}
}
}