use std::sync::Arc;
use futures::StreamExt;
use lapin::message::Delivery;
use lapin::{Channel, ConnectionState, Consumer, ErrorKind};
use tokio::sync::watch;
use crate::broker::AmqpConnection;
use crate::error::AmqpError;
pub(crate) struct RecoveringConsumer {
pub(crate) connection: Arc<AmqpConnection>,
channel: Channel,
consumer: Consumer,
recovery: watch::Receiver<bool>,
ended: bool,
}
impl Drop for RecoveringConsumer {
fn drop(&mut self) {
if self.ended && !self.channel.status().connected() {
self.consumer.set_delegate(async move |_| {});
}
}
}
impl RecoveringConsumer {
pub(crate) fn new(
connection: Arc<AmqpConnection>,
channel: Channel,
consumer: Consumer,
) -> Self {
let recovery = connection.recovery();
Self {
connection,
channel,
consumer,
recovery,
ended: false,
}
}
async fn recover(&mut self, error: lapin::Error) -> Result<(), AmqpError> {
let recovery = self.channel.wait_for_recovery(error.clone());
tokio::pin!(recovery);
loop {
if self.connection.is_closed() || self.connection.failed() {
return Err(AmqpError::consume(error));
}
tokio::select! {
biased;
changed = self.recovery.changed() => {
if changed.is_err() { return Err(AmqpError::consume(error)); }
}
result = &mut recovery => return result.map_err(AmqpError::consume),
}
}
}
pub(crate) async fn next(&mut self) -> Option<Result<Delivery, AmqpError>> {
loop {
if self.ended || self.connection.is_closed() {
return None;
}
if *self.recovery.borrow() {
self.ended = true;
return Some(Err(AmqpError::consume(
ErrorKind::InvalidConnectionState(ConnectionState::Error).into(),
)));
}
let item = tokio::select! {
biased;
changed = self.recovery.changed() => {
if changed.is_err() { self.ended = true; return None; }
continue;
}
item = self.consumer.next() => item,
};
match item {
Some(Err(error)) => {
if let Err(error) = self.recover(error).await {
self.ended = true;
return Some(Err(error));
}
}
None => {
self.ended = true;
return None;
}
Some(Ok(delivery)) => return Some(Ok(delivery)),
}
}
}
}