use redis::aio::{ConnectionLike, MultiplexedConnection};
use redis::{Cmd, Pipeline, RedisError, RedisFuture, RedisResult, Value};
use std::sync::{Mutex, MutexGuard, PoisonError};
#[cfg(test)]
#[path = "redis_connection_tests.rs"]
mod tests;
pub(crate) struct SharedRedisConnection {
client: redis::Client,
cached: Mutex<CachedConnection>,
}
#[derive(Default)]
struct CachedConnection {
generation: u64,
connection: Option<MultiplexedConnection>,
}
impl SharedRedisConnection {
pub(crate) fn new(client: redis::Client) -> Self {
Self {
client,
cached: Mutex::new(CachedConnection::default()),
}
}
pub(crate) async fn connection(&self) -> RedisResult<RedisConnection<'_>> {
if let Some(handle) = self.cached_handle() {
return Ok(handle);
}
let connection = self.client.get_multiplexed_async_connection().await?;
let mut cached = self.lock();
let (generation, inner) = match &cached.connection {
Some(existing) => (cached.generation, existing.clone()),
None => {
cached.generation = cached.generation.wrapping_add(1);
cached.connection = Some(connection.clone());
(cached.generation, connection)
}
};
Ok(RedisConnection {
owner: self,
generation,
inner,
})
}
fn cached_handle(&self) -> Option<RedisConnection<'_>> {
let cached = self.lock();
let inner = cached.connection.clone()?;
Some(RedisConnection {
owner: self,
generation: cached.generation,
inner,
})
}
fn discard_if_broken(&self, generation: u64, error: &RedisError) {
if !(error.is_unrecoverable_error() || error.is_connection_dropped()) {
return;
}
let mut cached = self.lock();
if cached.generation == generation {
cached.connection = None;
}
}
fn lock(&self) -> MutexGuard<'_, CachedConnection> {
self.cached.lock().unwrap_or_else(PoisonError::into_inner)
}
}
pub(crate) struct RedisConnection<'a> {
owner: &'a SharedRedisConnection,
generation: u64,
inner: MultiplexedConnection,
}
impl ConnectionLike for RedisConnection<'_> {
fn req_packed_command<'a>(&'a mut self, cmd: &'a Cmd) -> RedisFuture<'a, Value> {
Box::pin(async move {
let result = self.inner.req_packed_command(cmd).await;
if let Err(error) = &result {
self.owner.discard_if_broken(self.generation, error);
}
result
})
}
fn req_packed_commands<'a>(
&'a mut self,
pipeline: &'a Pipeline,
offset: usize,
count: usize,
) -> RedisFuture<'a, Vec<Value>> {
Box::pin(async move {
let result = self
.inner
.req_packed_commands(pipeline, offset, count)
.await;
if let Err(error) = &result {
self.owner.discard_if_broken(self.generation, error);
}
result
})
}
fn get_db(&self) -> i64 {
self.inner.get_db()
}
}