use super::connection_mode::{ConnectionMode, ReplyRoute};
use super::message_queue::{MessageQueue, MessageToReceive, ReplyMatch};
use super::pub_sub_push::PubSubPush;
use super::reply_mode::ReplyMode;
use super::retry_policy::RetryPolicy;
use super::router::{
Delivery, PendingSubscription, Router, SubscriptionConfirmed, UnsubscriptionConfirmed,
};
#[cfg(test)]
use super::test_hooks::{QueueMetricsTestHook, SendBatchTestHook};
use crate::{
ClientError, Connection, ConnectionState, Error, ErrorKind, JoinHandle, MasterWatch,
ReconnectionState, RedisError, RedisErrorKind, Result, RetryReason,
client::{Config, Message, MessageKind, PreparedCommand, ServerConfig, StatsRecorder},
commands::InternalPubSubCommands,
resp::{
ClientReplyMode, CommandKind, RespResponse, RespView, StateSlot, SubscriptionType, cmd,
},
sleep, spawn, timeout_future,
};
use bytes::Bytes;
use futures_util::{FutureExt, select};
use smallvec::SmallVec;
use std::borrow::Cow;
use std::{future::poll_fn, sync::Arc, task::Poll, time::Duration};
use tokio::{sync::broadcast, time::Instant};
use tracing::{Instrument, debug, error, info, info_span, trace, warn};
pub(crate) type MsgSender = tokio::sync::mpsc::UnboundedSender<Message>;
pub(crate) type MsgReceiver = tokio::sync::mpsc::UnboundedReceiver<Message>;
type WeakMsgSender = tokio::sync::mpsc::WeakUnboundedSender<Message>;
pub(crate) type ResultSender = tokio::sync::oneshot::Sender<Result<RespResponse>>;
pub(crate) type ResultReceiver = tokio::sync::oneshot::Receiver<Result<RespResponse>>;
pub(crate) type ResultsSender = tokio::sync::oneshot::Sender<Result<Vec<RespResponse>>>;
pub(crate) type ResultsReceiver = tokio::sync::oneshot::Receiver<Result<Vec<RespResponse>>>;
pub(crate) type PubSubSender = crate::client::BoundedSender;
pub(crate) type PubSubReceiver = crate::client::BoundedReceiver;
pub(crate) type PushSender = crate::client::BoundedSender;
pub(crate) type PushReceiver = crate::client::BoundedReceiver;
pub(crate) type ReconnectSender = broadcast::Sender<()>;
pub(crate) type ReconnectReceiver = broadcast::Receiver<()>;
pub(crate) struct NetworkHandler {
mode: ConnectionMode,
connection: Connection,
msg_sender: WeakMsgSender,
msg_receiver: MsgReceiver,
queue: MessageQueue,
router: Router,
reply_mode: ReplyMode,
connection_state: ConnectionState,
reconnect_sender: ReconnectSender,
auto_resubscribe: bool,
auto_remonitor: bool,
reconnection_state: ReconnectionState,
retry_policy: RetryPolicy,
max_messages_per_wave: usize,
master_watch: Option<MasterWatch>,
stats: Arc<StatsRecorder>,
#[cfg(test)]
send_batch_test_hook: Option<SendBatchTestHook>,
#[cfg(test)]
queue_metrics_test_hook: Option<QueueMetricsTestHook>,
}
impl NetworkHandler {
pub(crate) async fn connect(
config: Config,
) -> Result<(
MsgSender,
JoinHandle<()>,
ReconnectSender,
Arc<str>,
Arc<StatsRecorder>,
)> {
config.validate()?;
let auto_resubscribe = config.auto_resubscribe;
let auto_remonitor = config.auto_remonitor;
let max_command_attempts = config.max_command_attempts;
let max_messages_per_wave = config.max_messages_per_wave;
let max_queued_bytes = config.backpressure.max_queued_bytes;
let reconnection_config = config.reconnection.clone();
#[cfg(test)]
let send_batch_test_hook = config.send_batch_test_hook.clone();
#[cfg(test)]
let queue_metrics_test_hook = config.queue_metrics_test_hook.clone();
let mut connection_state = ConnectionState::default();
let master_watch = match &config.server {
ServerConfig::Sentinel(sentinel_config) => {
Some(MasterWatch::new(sentinel_config, &config))
}
_ => None,
};
let connection = Connection::connect(config, &mut connection_state).await?;
let (msg_sender, msg_receiver): (MsgSender, MsgReceiver) =
tokio::sync::mpsc::unbounded_channel();
let (reconnect_sender, _): (ReconnectSender, ReconnectReceiver) = broadcast::channel(32);
let tag = connection.tag().to_owned();
let stats = StatsRecorder::new();
stats.set_connected(true);
stats.set_server_version(connection.server_version());
let mut network_handler = NetworkHandler {
mode: ConnectionMode::Connected,
connection,
msg_sender: msg_sender.downgrade(),
msg_receiver,
queue: MessageQueue::new(max_queued_bytes, Arc::clone(&stats)),
router: Router::new(),
reply_mode: ReplyMode::new(),
connection_state,
reconnect_sender: reconnect_sender.clone(),
auto_resubscribe,
auto_remonitor,
reconnection_state: ReconnectionState::new(reconnection_config),
retry_policy: RetryPolicy::new(max_command_attempts),
max_messages_per_wave,
master_watch,
stats: Arc::clone(&stats),
#[cfg(test)]
send_batch_test_hook,
#[cfg(test)]
queue_metrics_test_hook,
};
let span = info_span!("connection", tag = %tag);
let join_handle = spawn(
async move {
if let Err(e) = network_handler.network_loop().await {
error!("network loop ended in error: {e}");
}
}
.instrument(span),
);
Ok((msg_sender, join_handle, reconnect_sender, tag, stats))
}
async fn network_loop(&mut self) -> Result<()> {
loop {
let until_maintenance = self
.connection
.next_maintenance()
.map_or(NO_MAINTENANCE_DELAY, |due| {
due.saturating_duration_since(Instant::now())
});
select! {
msg = poll_fn(|cx| self.msg_receiver.poll_recv(cx)).fuse() => {
if !self.try_handle_message(msg).await { break; }
},
result = self.connection.read().fuse() => {
if !self.try_handle_result(result).await { break; }
},
() = sleep(until_maintenance).fuse() => {
if self.connection.run_maintenance().await {
info!("The Sentinels announce another master, rediscovering it");
if !self.reconnect().await { break; }
}
},
() = watch_switch(&mut self.master_watch).fuse() => {
info!("A Sentinel announced a failover, rediscovering the master");
if !self.reconnect().await { break; }
}
}
self.queue.publish();
}
debug!("end of network loop");
Ok(())
}
#[expect(
clippy::arithmetic_side_effects,
reason = "one increment per message taken from the channel in this wave, \
and the wave is cut at `max_messages_per_wave` below."
)]
async fn try_handle_message(&mut self, mut msg: Option<Message>) -> bool {
let mut is_channel_closed = false;
let mut queued: usize = 0;
let mut handled: usize = 0;
loop {
if let Some(msg) = msg {
self.handle_message(msg);
queued += 1;
handled += 1;
} else {
is_channel_closed = true;
break;
}
if queued >= self.max_messages_per_wave {
if !self.mode.is_disconnected() {
self.send_messages().await;
}
queued = 0;
}
if handled >= self.max_messages_per_wave {
break;
}
match self.msg_receiver.try_recv() {
Ok(m) => msg = Some(m),
Err(_) => {
break;
}
}
}
if !self.mode.is_disconnected() {
self.send_messages().await
}
#[cfg(test)]
if let Some(hook) = &self.queue_metrics_test_hook {
hook.record_write_wave(handled);
}
!is_channel_closed
}
#[cfg(test)]
fn record_queue_depths(&self) {
if let Some(hook) = &self.queue_metrics_test_hook {
hook.record_queue_depths(
self.queue.to_send_len(),
self.queue.to_receive_len(),
self.queue.queued_commands(),
);
}
}
fn handle_message(&mut self, mut msg: Message) {
trace!("[{:?}] Will handle message: {msg:?}", self.mode);
let will_be_queued = !self.mode.is_disconnected() || msg.retry_on_error;
if will_be_queued
&& msg.attempts == 0
&& !matches!(msg.kind, MessageKind::Invalidation { .. })
&& self.queue.would_exceed_budget(msg.queued_bytes())
{
debug!(
"send queue is full ({} bytes), shedding command: {:?}",
self.queue.queued_bytes(),
msg.commands()
);
self.stats.record_shed();
msg.send_error(Error::from(ClientError::SendQueueFull));
return;
}
let mut collision_error = None;
match &self.mode {
ConnectionMode::Connected => {
match &mut msg.kind {
MessageKind::PubSub {
subscription_type,
subscriptions,
..
} => {
for (channel_or_pattern, _sender) in subscriptions.iter() {
if self.router.is_subscribed(channel_or_pattern) {
debug!(
"[{:?}] There is already a subscription on channel `{}`",
self.mode,
String::from_utf8_lossy(channel_or_pattern)
);
collision_error = Some(Error::from(ClientError::AlreadySubscribed));
break;
}
}
if collision_error.is_none() {
let subscriptions = std::mem::take(subscriptions);
let pending_subscriptions =
subscriptions
.into_iter()
.map(|(channel_or_pattern, sender)| PendingSubscription {
channel_or_pattern,
subscription_type: *subscription_type,
sender,
});
self.router.expect_subscriptions(pending_subscriptions);
}
}
MessageKind::Monitor { push_sender, .. } => {
self.mode.enter_monitor();
let push_sender = push_sender.take();
if let Some(push_sender) = push_sender {
debug!("Registering MONITOR push_sender");
self.router.set_monitor_sink(push_sender);
}
}
MessageKind::Invalidation { push_sender } => {
let push_sender = push_sender.take();
if let Some(push_sender) = push_sender {
debug!("Registering Invalidation push_sender");
self.router.set_invalidation_sink(push_sender);
}
return; }
MessageKind::Single { command, .. } => {
if let CommandKind::Unsbuscribe(subscription_type) = command.kind() {
let channels = if command.num_args() > 0 {
command.args().map(|a| (a, *subscription_type)).collect()
} else {
self.router.subscriptions_of(*subscription_type)
};
self.router.expect_unsubscriptions(channels);
}
}
_ => (),
}
if let Some(err) = collision_error {
msg.send_error(err);
} else {
self.queue.push_to_send(msg);
}
}
ConnectionMode::Disconnected => {
if msg.retry_on_error {
debug!(
"network disconnected, queuing command: {:?}",
msg.commands()
);
self.queue.push_to_send(msg);
} else {
debug!(
"network disconnected, sending command in error: {:?}",
msg.commands()
);
msg.send_error(Error::from(ErrorKind::DisconnectedByPeer));
}
}
_ => {
for command in msg.commands() {
self.mode.observe_queued(*command.kind());
}
self.queue.push_to_send(msg);
}
}
#[cfg(test)]
self.record_queue_depths();
}
#[expect(
clippy::arithmetic_side_effects,
reason = "both counters add one per command in the send queue: the queue is \
bounded by `max_messages_per_wave` and each command owns an \
allocated buffer, so neither total can approach `usize::MAX`."
)]
async fn send_messages(&mut self) {
#[cfg(test)]
self.record_queue_depths();
if self.queue.queued_commands() > 1 {
debug!("sending batch of {} commands", self.queue.queued_commands());
}
#[cfg(test)]
if let Some(hook) = &self.send_batch_test_hook
&& !self.queue.to_send_is_empty()
&& let Some(reasons) = hook.take_injection()
&& let Some(front) = self.queue.front_to_send_mut()
{
front.message.retry_reasons = Some(reasons);
}
let start_len = self.queue.to_receive_len();
while let Some((mut msg, cost)) = self.queue.pop_to_send() {
let mut retry_reasons = SmallVec::<[RetryReason; 10]>::new();
let reasons = msg.retry_reasons.take();
if let Some(reasons) = reasons {
retry_reasons.extend(reasons);
}
let mut num_commands_to_receive: usize = 0;
for command in msg.commands_mut() {
let kind = *command.kind();
match kind {
CommandKind::ClientReply(ClientReplyMode::On | ClientReplyMode::Off) => {
self.connection_state.record(StateSlot::ReplyMode, command);
}
CommandKind::ConnectionState(slot) => {
self.connection_state.record(slot, command);
}
CommandKind::Reset => {
self.connection_state.clear();
self.router.clear_subscriptions();
}
_ => (),
}
if matches!(
kind,
CommandKind::ConnectionState(_)
| CommandKind::ClientReply(_)
| CommandKind::Reset
) {
self.connection
.sync_connection_state(&self.connection_state);
}
if self.reply_mode.admit(kind) {
num_commands_to_receive += 1;
}
#[cfg(test)]
if let Some(hook) = &self.send_batch_test_hook {
let command_name = String::from_utf8_lossy(command.name()).into_owned();
hook.record_fed(command_name.clone(), retry_reasons.len());
if let Some(num_reads) = hook.take_kill_on_read_for(&command_name) {
command
.kill_connection_on_read
.store(num_reads, std::sync::atomic::Ordering::SeqCst);
}
}
if let Err(e) = self.connection.feed(command, &retry_reasons).await {
error!("Feed error: {e}");
msg.send_error(e);
return;
}
}
if num_commands_to_receive > 0 {
self.queue.await_reply(msg, num_commands_to_receive, cost);
} else {
self.queue.release(cost);
}
}
if let Err(e) = self.connection.flush().await {
error!("Flush error: {e}");
for msg_to_receive in self.queue.rollback_awaiting(start_len) {
msg_to_receive.message.send_error(e.clone());
}
}
#[cfg(test)]
self.record_queue_depths();
}
#[expect(
clippy::arithmetic_side_effects,
reason = "the drain loop runs only while `handled` is below \
`max_messages_per_wave`, so the counter is bounded by it."
)]
async fn try_handle_result(&mut self, result: Option<Result<RespResponse>>) -> bool {
let Some(result) = result else {
return self.reconnect().await;
};
if let Err(e) = &result
&& is_connection_level_error(e)
{
debug!("Connection-level read error, reconnecting: {e}");
return self.reconnect().await;
}
let mut master_demoted = self.master_demoted(&result);
self.handle_result(result);
let mut handled: usize = 1;
while handled < self.max_messages_per_wave
&& let Poll::Ready(result) = self.connection.try_read()
{
handled += 1;
let Some(result) = result else {
return self.reconnect().await;
};
if let Err(e) = &result
&& is_connection_level_error(e)
{
debug!("Connection-level read error, reconnecting: {e}");
return self.reconnect().await;
}
master_demoted |= self.master_demoted(&result);
self.handle_result(result);
}
#[cfg(test)]
if let Some(hook) = &self.queue_metrics_test_hook {
hook.record_read_wave(handled);
}
if master_demoted {
debug!("The master was demoted to replica, rediscovering it");
return self.reconnect().await;
}
self.unsubscribe_orphaned_subscriptions().await;
true
}
fn master_demoted(&self, result: &Result<RespResponse>) -> bool {
indicates_demoted_master(result) && self.connection.rediscovers_master_on_reconnect()
}
fn dispatch_result<T>(
&self,
sender: tokio::sync::oneshot::Sender<T>,
value: T,
command: Option<&Bytes>,
) {
if sender.send(value).is_err() {
let command = command.map_or_else(
|| Cow::Borrowed("<none>"),
|name| String::from_utf8_lossy(name),
);
debug!("Dropping the reply to {command}: its receiver is gone");
}
}
fn handle_result(&mut self, result: Result<RespResponse>) {
let is_monitor_line = matches!(&result, Ok(response) if response.is_monitor());
match self.mode.route_reply(is_monitor_line) {
ReplyRoute::Dropped => (),
ReplyRoute::MonitorSink => self.deliver_monitor_result(result),
ReplyRoute::ToCaller => self.receive_result(result),
ReplyRoute::Routed => match &result {
Ok(response) if response.is_push() => {
if let Some(response) = self.try_match_pubsub_message(result) {
if response.is_err() {
self.receive_result(response);
} else {
match self.router.invalidation_sink_mut() {
Some(push_sender) => {
#[cfg(test)]
let delivered_bytes = response
.as_ref()
.map(|response| response.retained_bytes())
.unwrap_or(0);
let sent = push_sender.send(response);
#[cfg(test)]
if let Some(hook) = &self.queue_metrics_test_hook {
if sent.is_ok() {
hook.record_push_delivered(delivered_bytes);
} else {
hook.record_push_delivery_failed();
}
}
if let Err(e) = sent {
warn!("Cannot send push message result to caller: {e}");
}
}
None => {
warn!(
"Received a push message with no sender configured: {response:?}"
)
}
}
}
}
}
_ => {
self.receive_result(result);
}
},
}
}
fn deliver_monitor_result(&self, result: Result<RespResponse>) {
#[cfg(test)]
let delivered_bytes = result
.as_ref()
.map(|response| response.retained_bytes())
.unwrap_or(0);
let Some(push_sender) = self.router.monitor_sink() else {
return;
};
let sent = push_sender.send(result);
#[cfg(test)]
if let Some(hook) = &self.queue_metrics_test_hook {
if sent.is_ok() {
hook.record_push_delivered(delivered_bytes);
} else {
hook.record_push_delivery_failed();
}
}
if let Err(e) = sent {
warn!("Cannot send monitor result to caller: {e}");
}
}
fn receive_result(&mut self, result: Result<RespResponse>) {
match self.queue.match_reply(result) {
ReplyMatch::Discarded(result) => {
debug!("discarding response of an already resolved message: {result:?}");
}
ReplyMatch::Absorbed => (),
ReplyMatch::Completed(message_to_receive, result) => {
trace!("message_to_receive: {message_to_receive:?}");
self.resolve(message_to_receive, result);
}
ReplyMatch::Unmatched(result) => {
if result.is_ok() {
warn!(
"Dropping an unexpected response with no message awaiting it: {result:?}"
);
}
}
}
}
fn resolve(&mut self, mut message_to_receive: MessageToReceive, result: Result<RespResponse>) {
if self
.retry_policy
.asks_for_retry(&result, &message_to_receive.message)
{
self.retry_policy
.absorb_reasons(&mut message_to_receive.message, result);
if !self
.retry_policy
.charge_attempt(&mut message_to_receive.message)
{
debug!("Message reached the maximum number of attempts, failing it");
message_to_receive
.message
.send_error(Error::from(ClientError::MaxCommandAttemptsReached));
}
else if let Some(msg_sender) = self.msg_sender.upgrade() {
if let Err(e) = msg_sender.send(message_to_receive.message) {
error!("Cannot retry message: {e}");
}
} else {
debug!("Cannot retry message: channel closed");
}
return;
}
trace!("Will respond to: {:?}", message_to_receive.message);
let command_name = message_to_receive.message.command_name();
let result = match (result, &command_name) {
(Err(e), Some(command)) => Err(e.with_command(command.clone())),
(result, _) => result,
};
match message_to_receive.message.kind {
MessageKind::Single {
result_sender: Some(result_sender),
..
}
| MessageKind::PubSub { result_sender, .. }
| MessageKind::Monitor { result_sender, .. } => {
self.dispatch_result(result_sender, result, command_name.as_ref());
}
MessageKind::Batch { results_sender, .. } => match result {
Ok(resp_buf) => {
message_to_receive.pending_responses.push(resp_buf);
self.dispatch_result(
results_sender,
Ok(message_to_receive.pending_responses),
command_name.as_ref(),
);
}
Err(e) => {
self.dispatch_result(results_sender, Err(e), command_name.as_ref());
}
},
MessageKind::Invalidation { .. }
| MessageKind::Single {
result_sender: None,
..
} => {
debug!("forget value {result:?}")
}
}
}
#[cfg_attr(
not(test),
expect(unused_variables, reason = "no hook outside a test build")
)]
fn record_delivery(&self, delivery: &Delivery) {
#[cfg(test)]
if let Some(hook) = &self.queue_metrics_test_hook {
match delivery {
Delivery::Delivered { retained_bytes } => {
hook.record_pub_sub_delivered(*retained_bytes);
}
Delivery::SubscriberGone => hook.record_pub_sub_delivery_failed(),
Delivery::NoSubscriber => (),
}
}
}
async fn unsubscribe_orphaned_subscriptions(&mut self) {
if !self.router.has_orphaned() {
return;
}
for (channel_or_pattern, subscription_type) in self.router.take_orphaned() {
let PreparedCommand { mut command, .. } = match subscription_type {
SubscriptionType::Channel => self.connection.unsubscribe(channel_or_pattern),
SubscriptionType::Pattern => self.connection.punsubscribe(channel_or_pattern),
SubscriptionType::ShardChannel => self.connection.sunsubscribe(channel_or_pattern),
};
if self.connection.is_cluster() {
command.compute_slots();
}
self.handle_message(Message::single_forget(command, true));
}
if !self.mode.is_disconnected() {
self.send_messages().await;
}
}
fn try_match_pubsub_message(
&mut self,
value: Result<RespResponse>,
) -> Option<Result<RespResponse>> {
if let Ok(ref_value) = &value {
if is_empty_unsubscribe_confirmation(ref_value) {
return None;
}
if let Ok(pub_sub_message) = PubSubPush::try_from(ref_value) {
match pub_sub_message {
PubSubPush::Message(channel_or_pattern, _)
| PubSubPush::SMessage(channel_or_pattern, _) => {
let named = Bytes::copy_from_slice(channel_or_pattern);
let delivery = self.router.deliver(&named, value);
self.record_delivery(&delivery);
match delivery {
Delivery::Delivered { .. } => (),
Delivery::SubscriberGone => warn!(
"Cannot send pub/sub message to caller from channel `{}`: the receiver is gone",
String::from_utf8_lossy(&named)
),
Delivery::NoSubscriber => error!(
"Unexpected message on channel `{}`",
String::from_utf8_lossy(&named)
),
}
None
}
PubSubPush::Subscribe(channel_or_pattern)
| PubSubPush::PSubscribe(channel_or_pattern)
| PubSubPush::SSubscribe(channel_or_pattern) => {
let named = Bytes::copy_from_slice(channel_or_pattern);
match self.router.confirm_subscription(&named) {
SubscriptionConfirmed::AlreadySubscribed => {
return Some(Err(Error::from(ClientError::AlreadySubscribed)));
}
SubscriptionConfirmed::Registered { more_to_come: true } => {
return None;
}
SubscriptionConfirmed::Registered {
more_to_come: false,
} => self.receive_result(Ok(RespResponse::ok())),
SubscriptionConfirmed::Unexpected => {
error!(
"Unexpected subscription confirmation on channel `{}`",
String::from_utf8_lossy(&named)
);
self.receive_result(Err(Error::from(
ClientError::UnexpectedSubscriptionConfirmation,
)));
}
}
None
}
PubSubPush::Unsubscribe(channel_or_pattern)
| PubSubPush::PUnsubscribe(channel_or_pattern)
| PubSubPush::SUnsubscribe(channel_or_pattern) => {
let named = Bytes::copy_from_slice(channel_or_pattern);
match self.router.confirm_unsubscription(&named) {
UnsubscriptionConfirmed::More => None,
UnsubscriptionConfirmed::Complete => {
self.receive_result(Ok(RespResponse::ok()));
None
}
UnsubscriptionConfirmed::Unsolicited => Some(value),
}
}
PubSubPush::PMessage(pattern, channel, _) => {
let named_pattern = Bytes::copy_from_slice(pattern);
let named_channel = Bytes::copy_from_slice(channel);
let delivery = self.router.deliver(&named_pattern, value);
self.record_delivery(&delivery);
match delivery {
Delivery::Delivered { .. } => (),
Delivery::SubscriberGone => warn!(
"Cannot send pub/sub message to caller for pattern `{}`: the receiver is gone",
String::from_utf8_lossy(&named_pattern)
),
Delivery::NoSubscriber => error!(
"Unexpected message on channel `{}` for pattern `{}`",
String::from_utf8_lossy(&named_channel),
String::from_utf8_lossy(&named_pattern)
),
}
None
}
}
} else {
Some(value)
}
} else {
Some(value)
}
}
#[tracing::instrument(name = "reconnect", skip_all)]
async fn reconnect(&mut self) -> bool {
debug!("reconnecting...");
let was_monitoring = self.abandon_connection();
loop {
let Some(delay) = self.reconnection_state.next_delay() else {
self.abandon_client();
return false;
};
debug!("Waiting {delay} ms before reconnection");
if !self
.serve_until(backoff_deadline(Instant::now(), delay))
.await
{
return false;
}
if let Err(e) = self.connection.reconnect(&mut self.connection_state).await {
error!("Failed to reconnect: {e:?}");
continue;
}
if let Err(e) = self.restore_connection(was_monitoring).await {
error!("Failed to reconnect: {e:?}");
continue;
}
return true;
}
}
fn abandon_connection(&mut self) -> bool {
let was_monitoring = self.mode.disconnect().is_monitoring();
self.stats.set_connected(false);
self.reply_mode.forget_pending_skip();
self.router.clear_orphaned();
#[cfg(test)]
self.record_queue_depths();
self.queue.purge_for_replay(&self.retry_policy);
was_monitoring
}
fn abandon_client(&mut self) {
error!("Max reconnection attempts reached: the client is finished");
for message in self.queue.take_all() {
message.send_error(Error::from(ErrorKind::DisconnectedByPeer));
}
self.queue.publish();
}
async fn serve_until(&mut self, deadline: Instant) -> bool {
loop {
let delay = deadline.duration_since(Instant::now());
match timeout_future(delay, poll_fn(|cx| self.msg_receiver.poll_recv(cx))).await {
Ok(msg) => {
if !self.try_handle_message(msg).await {
return false;
}
}
Err(_) => return true,
}
}
}
async fn restore_connection(&mut self, was_monitoring: bool) -> Result<()> {
self.reply_mode.restore(self.connection_state.is_reply_on());
if self.auto_resubscribe {
self.auto_resubscribe().await?;
}
if self.auto_remonitor {
self.auto_remonitor(was_monitoring).await?;
}
if let Err(e) = self.reconnect_sender.send(()) {
debug!("Cannot send reconnect notification to clients: {e}");
}
self.mode
.restore(was_monitoring, self.router.has_monitor_sink());
for message in self.queue.take_all() {
self.handle_message(message);
}
self.send_messages().await;
info!("reconnected!");
self.stats.set_connected(true);
self.stats
.set_server_version(self.connection.server_version());
self.stats.record_reconnection();
self.reconnection_state.reset_attempts();
Ok(())
}
async fn auto_resubscribe(&mut self) -> Result<()> {
for (channel_or_pattern, subscription_type) in self.router.take_resubscriptions() {
match subscription_type {
SubscriptionType::Channel => {
self.connection.subscribe(channel_or_pattern).await?;
}
SubscriptionType::Pattern => {
self.connection.psubscribe(channel_or_pattern).await?;
}
SubscriptionType::ShardChannel => {
self.connection.ssubscribe(channel_or_pattern).await?;
}
}
}
Ok(())
}
async fn auto_remonitor(&mut self, was_monitoring: bool) -> Result<()> {
if was_monitoring {
self.connection.send(&cmd("MONITOR").into()).await?;
}
Ok(())
}
}
#[inline]
fn is_connection_level_error(error: &Error) -> bool {
match error.kind() {
ErrorKind::IO(_) | ErrorKind::EOF => true,
ErrorKind::Client(client_error) => client_error.is_framing_error(),
_ => false,
}
}
#[inline]
fn indicates_demoted_master(result: &Result<RespResponse>) -> bool {
match result {
Ok(response) => {
response.is_error()
&& matches!(response.view(), Ok(RespView::Error(message))
if matches!(RedisError::try_from(message),
Ok(error) if error.kind == RedisErrorKind::Readonly))
}
Err(e) => {
matches!(e.kind(), ErrorKind::Redis(error) if error.kind == RedisErrorKind::Readonly)
}
}
}
fn is_empty_unsubscribe_confirmation(response: &RespResponse) -> bool {
let Ok(RespView::Push(push)) = response.view() else {
return false;
};
let mut fields = push.into_iter();
let Some(Ok(RespView::BulkString(kind @ (b"unsubscribe" | b"punsubscribe" | b"sunsubscribe")))) =
fields.next()
else {
return false;
};
let _ = kind;
matches!(fields.next(), Some(Ok(RespView::Null)))
}
async fn watch_switch(master_watch: &mut Option<MasterWatch>) {
match master_watch {
Some(master_watch) => master_watch.switched().await,
None => std::future::pending().await,
}
}
const NO_MAINTENANCE_DELAY: Duration = Duration::from_secs(3600);
const UNREPRESENTABLE_BACKOFF: Duration = Duration::from_secs(3600);
fn backoff_deadline(start: Instant, delay: u64) -> Instant {
start
.checked_add(Duration::from_millis(delay))
.or_else(|| start.checked_add(UNREPRESENTABLE_BACKOFF))
.unwrap_or(start)
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable,
clippy::indexing_slicing,
reason = "test code: a panic is how a test reports failure"
)]
use super::{
Duration, Instant, backoff_deadline, indicates_demoted_master, is_connection_level_error,
};
use crate::{ClientError, Error, ErrorKind, RedisError, RedisErrorKind};
#[test]
fn a_backoff_ends_where_its_delay_puts_it() {
let start = Instant::now();
assert_eq!(
start + Duration::from_millis(250),
backoff_deadline(start, 250)
);
}
#[test]
fn a_zero_delay_is_over_at_once() {
let start = Instant::now();
assert_eq!(start, backoff_deadline(start, 0));
}
#[test]
fn the_largest_delay_a_policy_can_return_yields_a_deadline_instead_of_a_panic() {
let start = Instant::now();
assert!(backoff_deadline(start, u64::MAX) > start);
}
#[test]
fn per_message_errors_are_not_connection_level() {
assert!(!is_connection_level_error(&Error::from(ErrorKind::Retry(
Default::default()
))));
assert!(!is_connection_level_error(&Error::from(ErrorKind::Redis(
RedisError {
kind: RedisErrorKind::NoPerm,
description: bytes::Bytes::from_static(b"no permission"),
}
))));
assert!(!is_connection_level_error(&Error::from(
ClientError::CrossSlot
)));
}
#[test]
fn decode_and_transport_errors_are_connection_level() {
assert!(is_connection_level_error(&Error::from(
ClientError::CannotParseInteger
)));
assert!(is_connection_level_error(&Error::from(
ClientError::UnknownRespTag('?')
)));
assert!(is_connection_level_error(&Error::from(
ClientError::MaxNestingDepthExceeded
)));
assert!(is_connection_level_error(&Error::from(ErrorKind::EOF)));
}
#[test]
fn the_public_predicate_agrees_on_every_connection_level_error() {
for error in [
Error::from(ClientError::CannotParseInteger),
Error::from(ClientError::UnknownRespTag('?')),
Error::from(ClientError::MaxNestingDepthExceeded),
Error::from(ClientError::VerbatimStringTooShort),
Error::from(ClientError::BulkLengthTooLarge),
Error::from(ErrorKind::EOF),
Error::from(ErrorKind::IO(std::sync::Arc::new(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"reset",
)))),
] {
assert!(is_connection_level_error(&error));
assert!(
error.is_connection_error(),
"the handler reconnects on {error:?} but the user is told the connection is fine"
);
}
}
#[test]
fn readonly_is_the_only_demotion_signal() {
assert!(indicates_demoted_master(&Ok(decode_one(
"-READONLY You can't write against a read only replica.\r\n"
))));
assert!(!indicates_demoted_master(&Ok(decode_one(
"-NOPERM no permission\r\n"
))));
assert!(!indicates_demoted_master(&Ok(decode_one("+OK\r\n"))));
assert!(!indicates_demoted_master(&Ok(decode_one(":12\r\n"))));
assert!(indicates_demoted_master(&Err(Error::from(
ErrorKind::Redis(RedisError {
kind: RedisErrorKind::Readonly,
description: bytes::Bytes::from_static(
b"You can't write against a read only replica."
),
})
))));
assert!(!indicates_demoted_master(&Err(Error::from(
ErrorKind::Retry(Default::default())
))));
assert!(!indicates_demoted_master(&Err(Error::from(ErrorKind::EOF))));
}
fn decode_one(str: &str) -> crate::resp::RespResponse {
use tokio_util::codec::Decoder;
let mut buf: bytes::BytesMut = str.into();
crate::resp::BufferDecoder::new()
.decode(&mut buf)
.unwrap()
.expect("one complete frame")
}
#[test]
fn readonly_stays_a_per_message_error() {
assert!(!is_connection_level_error(&Error::from(ErrorKind::Redis(
RedisError {
kind: RedisErrorKind::Readonly,
description: bytes::Bytes::from_static(
b"You can't write against a read only replica."
),
}
))));
}
}