/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * * Modified by Coffey Labs in 2026 for INBUXA. */ use super::{RedisPool, RedisStore, into_error}; use crate::{Deserialize, write::now}; use deadpool::managed::{Manager, Object, Pool}; use redis::{AsyncCommands, RedisError, RedisResult, RetryMethod}; impl RedisStore { pub async fn key_set(&self, key: &[u8], value: &[u8], expires: Option) -> trc::Result<()> { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| { Self::key_set_(conn, key, value, expires).await }) .await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| { Self::key_set_(conn, key, value, expires).await }) .await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| { Self::key_set_(conn, key, value, expires).await }) .await } } } pub async fn key_incr(&self, key: &[u8], value: i64, expires: Option) -> trc::Result { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| { self.key_incr_(conn, key, value, expires).await }) .await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| { self.key_incr_(conn, key, value, expires).await }) .await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| { self.key_incr_(conn, key, value, expires).await }) .await } } } pub async fn try_lock(&self, key: &[u8], expires: u64) -> trc::Result { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| Self::try_lock_(conn, key, expires).await).await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| Self::try_lock_(conn, key, expires).await).await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| Self::try_lock_(conn, key, expires).await).await } } } // inbuxa: see InMemoryStore::renew_lock pub async fn renew_lock(&self, key: &[u8], expires: u64) -> trc::Result { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| { Self::renew_lock_(conn, key, expires).await }) .await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| { Self::renew_lock_(conn, key, expires).await }) .await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| { Self::renew_lock_(conn, key, expires).await }) .await } } } pub async fn key_delete(&self, key: &[u8]) -> trc::Result<()> { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| Self::key_delete_(conn, key).await).await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| Self::key_delete_(conn, key).await).await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| Self::key_delete_(conn, key).await).await } } } pub async fn key_delete_prefix(&self, prefix: &[u8]) -> trc::Result<()> { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| { Self::key_delete_prefix_(conn, prefix).await }) .await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| { Self::key_delete_prefix_(conn, prefix).await }) .await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| { Self::key_delete_prefix_(conn, prefix).await }) .await } } } pub async fn key_get( &self, key: &[u8], ) -> trc::Result> { let value = match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| Self::key_get_(conn, key).await).await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| Self::key_get_(conn, key).await).await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| Self::key_get_(conn, key).await).await } }?; value.map(T::deserialize_owned).transpose() } pub async fn counter_get(&self, key: &[u8]) -> trc::Result { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| Self::counter_get_(conn, key).await).await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| Self::counter_get_(conn, key).await).await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| Self::counter_get_(conn, key).await).await } } } pub async fn key_exists(&self, key: &[u8]) -> trc::Result { match &self.pool { RedisPool::Single(pool) => { with_conn(pool, async |conn| Self::key_exists_(conn, key).await).await } RedisPool::Cluster(pool) => { with_conn(pool, async |conn| Self::key_exists_(conn, key).await).await } RedisPool::Sentinel(pool) => { with_conn(pool, async |conn| Self::key_exists_(conn, key).await).await } } } async fn key_get_(conn: &mut impl AsyncCommands, key: &[u8]) -> RedisResult>> { redis::cmd("GET").arg(key).query_async(conn).await } async fn counter_get_(conn: &mut impl AsyncCommands, key: &[u8]) -> RedisResult { redis::cmd("GET") .arg(key) .query_async::>(conn) .await .map(|value| value.unwrap_or(0)) } async fn key_exists_(conn: &mut impl AsyncCommands, key: &[u8]) -> RedisResult { conn.exists(key).await } async fn key_set_( conn: &mut impl AsyncCommands, key: &[u8], value: &[u8], expires: Option, ) -> RedisResult<()> { if let Some(expires) = expires { conn.set_ex(key, value, expires).await } else { conn.set(key, value).await } } async fn key_incr_( &self, conn: &mut impl AsyncCommands, key: &[u8], value: i64, expires: Option, ) -> RedisResult { if let Some(expires) = expires { self.incr_expire .key(key) .arg(value) .arg(expires as i64) .invoke_async(conn) .await } else { conn.incr(key, value).await } } async fn try_lock_( conn: &mut impl AsyncCommands, key: &[u8], expires: u64, ) -> RedisResult { redis::cmd("SET") .arg(key) .arg(now() + expires) .arg("NX") .arg("EX") .arg(expires as i64) .query_async::>(conn) .await .map(|reply| reply.is_some()) } async fn renew_lock_( conn: &mut impl AsyncCommands, key: &[u8], expires: u64, ) -> RedisResult { redis::cmd("SET") .arg(key) .arg(now() + expires) .arg("XX") .arg("EX") .arg(expires as i64) .query_async::>(conn) .await .map(|reply| reply.is_some()) } async fn key_delete_(conn: &mut impl AsyncCommands, key: &[u8]) -> RedisResult<()> { conn.del(key).await } async fn key_delete_prefix_(conn: &mut impl AsyncCommands, prefix: &[u8]) -> RedisResult<()> { let mut pattern = Vec::with_capacity(prefix.len() + 1); pattern.extend_from_slice(prefix); pattern.push(b'*'); let mut cursor = 0; loop { let (new_cursor, keys): (u64, Vec>) = redis::cmd("SCAN") .cursor_arg(cursor) .arg("MATCH") .arg(&pattern) .arg("COUNT") .arg(100) .query_async(conn) .await?; if !keys.is_empty() { conn.del::<_, ()>(&keys).await?; } if new_cursor != 0 { cursor = new_cursor; } else { return Ok(()); } } } } async fn with_conn( pool: &Pool, operation: impl AsyncFnOnce(&mut M::Type) -> RedisResult, ) -> trc::Result where M: Manager, { let mut conn = pool.get().await.map_err(into_error)?; match operation(conn.as_mut()).await { Ok(value) => Ok(value), Err(err) => { if is_stale_connection(&err) { drop(Object::take(conn)); } Err(into_error(err)) } } } fn is_stale_connection(err: &RedisError) -> bool { matches!( err.retry_method(), RetryMethod::Reconnect | RetryMethod::ReconnectFromInitialConnections | RetryMethod::RefreshSlotsAndRetry | RetryMethod::MovedRedirect | RetryMethod::AskRedirect ) }