use crate::{
client::asynchronous::FalkorAsyncClientInner, connection::map_redis_err,
parser::parse_redis_info, FalkorDBError, FalkorResult,
};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::mpsc;
pub(crate) enum FalkorAsyncConnection {
Redis(redis::aio::MultiplexedConnection),
Managed(redis::aio::ConnectionManager),
}
impl FalkorAsyncConnection {
pub(crate) fn clone_handle(&self) -> Self {
match self {
FalkorAsyncConnection::Redis(conn) => FalkorAsyncConnection::Redis(conn.clone()),
FalkorAsyncConnection::Managed(conn) => FalkorAsyncConnection::Managed(conn.clone()),
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "Connection Inner Execute Command", skip_all, level = "debug")
)]
pub(crate) async fn execute_command(
&mut self,
graph_name: Option<&str>,
command: &str,
subcommand: Option<&str>,
params: Option<&[&str]>,
) -> FalkorResult<redis::Value> {
let mut cmd = redis::cmd(command);
cmd.arg(subcommand);
cmd.arg(graph_name);
if let Some(params) = params {
for param in params {
cmd.arg(*param);
}
}
match self {
FalkorAsyncConnection::Redis(redis_conn) => redis_conn
.send_packed_command(&cmd)
.await
.map_err(map_redis_err),
FalkorAsyncConnection::Managed(redis_conn) => {
use redis::aio::ConnectionLike as _;
redis_conn
.req_packed_command(&cmd)
.await
.map_err(map_redis_err)
}
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "Connection Inner Execute Pipeline", skip_all, level = "debug")
)]
pub(crate) async fn execute_pipeline(
&mut self,
pipeline: &redis::Pipeline,
) -> FalkorResult<Vec<redis::Value>> {
use redis::aio::ConnectionLike as _;
let count = pipeline.len();
match self {
FalkorAsyncConnection::Redis(redis_conn) => redis_conn
.req_packed_commands(pipeline, 0, count)
.await
.map_err(map_redis_err),
FalkorAsyncConnection::Managed(redis_conn) => redis_conn
.req_packed_commands(pipeline, 0, count)
.await
.map_err(map_redis_err),
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "Connection Get Redis Info", skip_all, level = "info")
)]
pub(crate) async fn get_redis_info(
&mut self,
section: Option<&str>,
) -> FalkorResult<HashMap<String, String>> {
self.execute_command(None, "INFO", section, None)
.await
.and_then(parse_redis_info)
}
pub(crate) async fn check_is_redis_sentinel(&mut self) -> FalkorResult<bool> {
let info_map = self.get_redis_info(Some("server")).await?;
Ok(info_map
.get("redis_mode")
.map(|redis_mode| redis_mode == "sentinel")
.unwrap_or_default())
}
}
enum ConnReturn {
Pool(mpsc::Sender<FalkorAsyncConnection>),
Discard,
}
pub struct BorrowedAsyncConnection {
conn: Option<FalkorAsyncConnection>,
return_to: ConnReturn,
client: Arc<FalkorAsyncClientInner>,
readonly: bool,
}
impl BorrowedAsyncConnection {
pub(crate) fn new(
conn: FalkorAsyncConnection,
return_tx: mpsc::Sender<FalkorAsyncConnection>,
client: Arc<FalkorAsyncClientInner>,
readonly: bool,
) -> Self {
#[cfg(feature = "metrics")]
crate::observability::connection_borrow_started(readonly);
Self {
conn: Some(conn),
return_to: ConnReturn::Pool(return_tx),
client,
readonly,
}
}
pub(crate) fn new_multiplexed(
conn: FalkorAsyncConnection,
client: Arc<FalkorAsyncClientInner>,
readonly: bool,
) -> Self {
#[cfg(feature = "metrics")]
crate::observability::connection_borrow_started(readonly);
Self {
conn: Some(conn),
return_to: ConnReturn::Discard,
client,
readonly,
}
}
pub(crate) fn as_inner(&mut self) -> FalkorResult<&mut FalkorAsyncConnection> {
self.conn.as_mut().ok_or(FalkorDBError::EmptyConnection)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "Borrowed Connection Execute Command",
skip_all,
level = "trace"
)
)]
pub(crate) async fn execute_command(
mut self,
graph_name: Option<&str>,
command: &str,
subcommand: Option<&str>,
params: Option<&[&str]>,
) -> FalkorResult<redis::Value> {
let result = self
.as_inner()?
.execute_command(graph_name, command, subcommand, params)
.await;
self.recover_on_connection_down(result).await
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "Borrowed Connection Execute Pipeline",
skip_all,
level = "trace"
)
)]
pub(crate) async fn execute_pipeline(
mut self,
pipeline: &redis::Pipeline,
) -> FalkorResult<Vec<redis::Value>> {
let result = self.as_inner()?.execute_pipeline(pipeline).await;
self.recover_on_connection_down(result).await
}
async fn recover_on_connection_down<T>(
&mut self,
result: FalkorResult<T>,
) -> FalkorResult<T> {
match result {
Err(FalkorDBError::ConnectionDown) => {
if let Ok(new_conn) = self.client.fresh_connection(self.readonly).await {
self.conn = Some(new_conn);
return Err(FalkorDBError::ConnectionDown);
}
Err(FalkorDBError::NoConnection)
}
res => res,
}
}
}
impl Drop for BorrowedAsyncConnection {
fn drop(&mut self) {
#[cfg(feature = "metrics")]
crate::observability::connection_borrow_finished(self.readonly);
if let (Some(conn), ConnReturn::Pool(return_tx)) = (self.conn.take(), &self.return_to) {
match return_tx.try_send(conn) {
Ok(()) => {}
Err(mpsc::error::TrySendError::Closed(_)) => {}
Err(mpsc::error::TrySendError::Full(_)) => {
debug_assert!(
false,
"pool channel unexpectedly full while returning a borrowed connection"
);
}
}
}
}
}