use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
};
use tokio::{io::AsyncReadExt, select, task::JoinHandle};
use tokio_util::sync::CancellationToken;
use crate::{
buffers::{Bytes, Chunk},
pipeline::{
self,
rx::{PipelineRxIn, PipelineRxOut, PullConfig, PushConfig, RecvError, SendError},
},
protocol::{BatchSize, core::QoS},
sync::AtomicDuration,
};
#[cfg(feature = "stats")]
struct ReceiverTaskStats {
batches_count: AtomicUsize,
bytes_count: AtomicUsize,
dropped: AtomicUsize,
}
#[cfg(feature = "stats")]
impl ReceiverTaskStats {
fn new() -> Self {
Self {
batches_count: AtomicUsize::new(0),
bytes_count: AtomicUsize::new(0),
dropped: AtomicUsize::new(0),
}
}
}
struct ReceiverTaskInner {
timeout_read: AtomicDuration,
timeout_drop: AtomicDuration,
timeout_block: AtomicDuration,
max_frag_size: AtomicUsize,
#[cfg(feature = "stats")]
stats: ReceiverTaskStats,
}
impl ReceiverTaskInner {
fn push_config(&self) -> PushConfig {
PushConfig {
frag_max_size: self.max_frag_size.load(Ordering::Relaxed),
timeout_drop: self.timeout_drop.load(Ordering::Relaxed),
timeout_block: self.timeout_block.load(Ordering::Relaxed),
}
}
}
#[cfg(feature = "stats")]
#[non_exhaustive]
pub struct ReceiverStats {
pub batches: usize,
pub bytes: usize,
pub dropped: usize,
}
pub struct ReceiverTask<R> {
handle: JoinHandle<R>,
token: CancellationToken,
inner: Arc<ReceiverTaskInner>,
}
impl<R> ReceiverTask<R> {
pub fn set_read_timeout(&self, timeout: Duration) {
self.inner.timeout_read.store(timeout, Ordering::Relaxed);
}
#[cfg(feature = "stats")]
pub fn get_stats(&self) -> ReceiverStats {
ReceiverStats {
batches: self.inner.stats.batches_count.load(Ordering::Relaxed),
bytes: self.inner.stats.bytes_count.load(Ordering::Relaxed),
dropped: self.inner.stats.dropped.load(Ordering::Relaxed),
}
}
pub fn stop(self) -> JoinHandle<R> {
let Self { handle, token, inner } = self;
token.cancel();
inner.timeout_read.store(Duration::from_secs(0), Ordering::Relaxed);
handle
}
}
pub struct Receiver {
inner: PipelineRxOut,
config: PullConfig,
token: CancellationToken,
}
impl Receiver {
pub async fn recv(&mut self) -> Result<(Bytes, QoS), RecvError> {
self.inner.pull(&self.config).await
}
pub async fn try_recv(&mut self) -> Option<(Bytes, QoS)> {
self.inner.try_pull()
}
pub async fn stop(self) {
self.token.cancel();
}
pub fn is_closed(&self) -> bool {
self.token.is_cancelled()
}
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
self.config.timeout = timeout;
self
}
}
pub struct ReceiverBuilder<R>
where
R: AsyncReadExt + Send + Sync + Unpin + 'static,
{
capacity: usize,
timeout: Duration,
max_frag_size: usize,
reader: R,
}
impl<R> ReceiverBuilder<R>
where
R: AsyncReadExt + Send + Sync + Unpin + 'static,
{
#[must_use]
pub fn capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
#[must_use]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn max_frag_size(mut self, max_frag_size: usize) -> Self {
self.max_frag_size = max_frag_size;
self
}
#[must_use]
pub fn build(self) -> (Receiver, ReceiverTask<R>) {
let Self {
capacity,
max_frag_size,
timeout,
reader,
} = self;
let token = CancellationToken::new();
let c_token = token.clone();
let inner = Arc::new(ReceiverTaskInner {
timeout_read: AtomicDuration::new(Duration::from_secs(10)),
timeout_drop: AtomicDuration::new(Duration::from_millis(500)),
timeout_block: AtomicDuration::new(Duration::from_secs(60)),
max_frag_size: AtomicUsize::new(max_frag_size),
#[cfg(feature = "stats")]
stats: ReceiverTaskStats::new(),
});
let c_inner = inner.clone();
let (sender, receiver) = pipeline::rx::pipeline_rx(capacity);
let handle = tokio::spawn(read_task(sender, reader, c_inner, c_token));
let receiver = Receiver {
inner: receiver,
config: PullConfig { timeout },
token: token.clone(),
};
let reader = ReceiverTask { handle, token, inner };
(receiver, reader)
}
}
async fn read_task<R>(
mut sender: PipelineRxIn,
mut reader: R,
inner: Arc<ReceiverTaskInner>,
token: CancellationToken,
) -> R
where
R: AsyncReadExt + Send + Sync + Unpin + 'static,
{
loop {
let read_timeout = inner.timeout_read.load(Ordering::Relaxed);
macro_rules! read_all {
($buff:expr) => {{
let res = select! {
res = tokio::time::timeout(read_timeout, reader.read_exact($buff)) => res,
_ = token.cancelled() => break,
};
match res {
Ok(Ok(read)) if read == 0 => break, Ok(Err(_)) | Err(_) => break, _ => {}
}
}};
}
let mut len = BatchSize::MIN.to_le_bytes();
read_all!(&mut len);
let len = BatchSize::from_le_bytes(len) as usize;
if len == 0 {
continue; }
let mut buf = vec![0u8; len.next_power_of_two()];
read_all!(&mut buf[..len]);
#[cfg(feature = "stats")]
{
inner.stats.batches_count.fetch_add(1, Ordering::Relaxed);
inner.stats.bytes_count.fetch_add(len, Ordering::Relaxed);
}
let res = sender
.push(
Bytes::single(unsafe {
Chunk::new_unchecked(Arc::new(buf), 0, len)
}),
&inner.push_config(),
)
.await;
match res {
Ok(true) => {
}
Ok(false) => {
#[cfg(feature = "stats")]
inner.stats.dropped.fetch_add(1, Ordering::Relaxed);
}
Err(e) => match e {
SendError::InvalidSeqNum | SendError::InvalidFragId | SendError::CapacityLimit | SendError::Timeout => {
#[cfg(feature = "stats")]
inner.stats.dropped.fetch_add(1, Ordering::Relaxed);
}
SendError::DecodingFailed | SendError::ChannelClosed | SendError::InternalError => break,
},
}
}
reader
}
#[must_use]
pub fn receiver<R>(reader: R) -> ReceiverBuilder<R>
where
R: AsyncReadExt + Send + Sync + Unpin + 'static,
{
ReceiverBuilder {
capacity: 16,
max_frag_size: 1 << 30, timeout: Duration::MAX,
reader,
}
}