use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use futures::{FutureExt, Stream, StreamExt};
use tokio::sync::Semaphore;
use crate::{
client::Client,
queue::{FlushHandle, MessageQueueItem},
};
pub struct SenderTaskBuilder {
max_concurrency: usize,
initial_retry_delay: Duration,
max_retry_delay: Duration,
max_retries: Option<u32>,
}
impl SenderTaskBuilder {
fn new() -> Self {
Self {
max_concurrency: 16,
initial_retry_delay: Duration::from_secs(1),
max_retry_delay: Duration::from_secs(60),
max_retries: None,
}
}
pub fn max_concurrency(mut self, concurrency: usize) -> Self {
assert!(concurrency > 0);
self.max_concurrency = concurrency;
self
}
pub fn initial_retry_delay(mut self, delay: Duration) -> Self {
self.initial_retry_delay = delay;
self
}
pub fn max_retry_delay(mut self, delay: Duration) -> Self {
self.max_retry_delay = delay;
self
}
pub fn max_retries(mut self, retries: Option<u32>) -> Self {
self.max_retries = retries;
self
}
pub fn build<C, S, M>(self, client: C, messages: S) -> SenderTask
where
C: Client<Message = M> + Send + Sync + 'static,
S: Stream<Item = MessageQueueItem<M>> + Send + 'static,
M: Clone + Send + 'static,
{
let sender = Sender {
client: Arc::new(client),
semaphore: Arc::new(Semaphore::new(self.max_concurrency)),
max_concurrency: self.max_concurrency,
initial_retry_delay: self.initial_retry_delay,
max_retry_delay: self.max_retry_delay,
max_retries: self.max_retries,
};
SenderTask {
inner: Box::pin(sender.send_all(messages)),
}
}
}
pub struct SenderTask {
inner: Pin<Box<dyn Future<Output = ()> + Send>>,
}
impl SenderTask {
pub fn builder() -> SenderTaskBuilder {
SenderTaskBuilder::new()
}
}
impl Future for SenderTask {
type Output = ();
#[inline]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.poll_unpin(cx)
}
}
struct Sender<C> {
client: Arc<C>,
semaphore: Arc<Semaphore>,
max_concurrency: usize,
initial_retry_delay: Duration,
max_retry_delay: Duration,
max_retries: Option<u32>,
}
impl<C, M> Sender<C>
where
C: Client<Message = M> + Send + Sync + 'static,
M: Clone + Send + 'static,
{
async fn send_all<S>(mut self, messages: S)
where
S: Stream<Item = MessageQueueItem<M>>,
{
futures::pin_mut!(messages);
while let Some(item) = messages.next().await {
match item {
MessageQueueItem::Message(msg) => self.send(msg).await,
MessageQueueItem::FlushHandle(handle) => self.flush(handle),
}
}
for _ in 0..self.max_concurrency {
self.semaphore
.acquire()
.await
.expect("closed semaphore")
.forget();
}
}
async fn send(&self, msg: M) {
let permit = self
.semaphore
.clone()
.acquire_owned()
.await
.expect("closed semaphore");
let client = self.client.clone();
let initial_retry_delay = self.initial_retry_delay;
let max_retry_delay = self.max_retry_delay;
let max_retries = self.max_retries;
tokio::spawn(async move {
let mut attempts = u32::saturating_add(1, max_retries.unwrap_or(0));
let mut retry_delay = initial_retry_delay;
loop {
attempts -= u32::from(max_retries.is_some());
let send = client.send(msg.clone());
match send.await {
Ok(_) => break,
Err(err) if err.can_retry() && attempts > 0 => (),
Err(_) => break,
}
tokio::time::sleep(retry_delay).await;
retry_delay = max_retry_delay.min(retry_delay.saturating_mul(2));
}
std::mem::drop(permit);
});
}
fn flush(&mut self, handle: FlushHandle) {
let new_semaphore = Arc::new(Semaphore::new(0));
let old_semaphore = std::mem::replace(&mut self.semaphore, new_semaphore.clone());
let max_concurrency = self.max_concurrency;
tokio::spawn(async move {
for _ in 0..max_concurrency {
old_semaphore
.acquire()
.await
.expect("closed semaphore")
.forget();
new_semaphore.add_permits(1);
}
handle.resolve();
});
}
}