use std::future::{Future, poll_fn};
use std::pin::pin;
use std::sync::Arc;
use std::task::Poll;
use bytes::BytesMut;
use futures::Stream;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tracing::{debug, error};
use super::{
Delivery, DispatchFailure, Handler, Shutdown, Slot, Turn, Workers, dispatch, lane_of,
log_worker_exit,
};
use crate::{BuildContext, IncomingMessage, Subscriber};
pub(super) struct Shared<Body, State, Cx> {
pub(super) handler: Arc<Body>,
pub(super) name: Arc<str>,
pub(super) state: Arc<State>,
pub(super) delivery: Arc<Delivery<Cx>>,
pub(super) failure: DispatchFailure,
}
struct Idle {
index: usize,
loop_side: mpsc::Sender<usize>,
}
struct Farewell(Option<Idle>);
impl Drop for Farewell {
fn drop(&mut self) {
if let Some(idle) = &self.0 {
let _ = idle.loop_side.try_send(idle.index);
}
}
}
struct Crew(Vec<JoinHandle<()>>);
impl Crew {
async fn join(&mut self) {
for worker in &mut self.0 {
log_worker_exit(worker.await);
}
}
}
impl Drop for Crew {
fn drop(&mut self) {
for worker in &self.0 {
worker.abort();
}
}
}
async fn work<Message, Body, Cx, State>(
shared: Arc<Shared<Body, State, Cx>>,
mut inbox: mpsc::Receiver<Message>,
idle: Option<Idle>,
) where
Message: IncomingMessage,
Body: Handler<Message, Cx, State>,
Cx: BuildContext<Message> + Send + Sync + 'static,
State: Send + Sync,
{
let mut encode = BytesMut::new();
let mut farewell = Farewell(idle);
while let Some(msg) = inbox.recv().await {
let mut slot = Slot::new(msg);
dispatch(
&*shared.handler,
&mut slot,
&mut encode,
&shared.name,
&shared.state,
&shared.delivery,
&shared.failure,
)
.await;
if let Some(idle) = &farewell.0
&& idle.loop_side.send(idle.index).await.is_err()
{
break;
}
}
farewell.0 = None;
}
struct Started<Message> {
inboxes: Vec<mpsc::Sender<Message>>,
free: mpsc::Receiver<usize>,
crew: Crew,
}
fn start<Message, Body, Cx, State>(
shared: &Arc<Shared<Body, State, Cx>>,
workers: Workers,
) -> Started<Message>
where
Message: IncomingMessage + Send + 'static,
Body: Handler<Message, Cx, State> + 'static,
Cx: BuildContext<Message> + Send + Sync + 'static,
State: Send + Sync + 'static,
{
let count = workers.count;
let (free_side, free) = mpsc::channel(count);
let mut inboxes = Vec::with_capacity(count);
let mut crew = Crew(Vec::with_capacity(count));
for index in 0..count {
let (inbox_side, inbox) = mpsc::channel(1);
let idle = (!workers.by_key).then(|| Idle {
index,
loop_side: free_side.clone(),
});
let shared = Arc::clone(shared);
crew.0.push(tokio::spawn(work(shared, inbox, idle)));
inboxes.push(inbox_side);
if !workers.by_key {
let _ = free_side.try_send(index);
}
}
Started {
inboxes,
free,
crew,
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn spawn_dispatch_pool<Sub, Body, Cx, State>(
mut subscriber: Sub,
handler: Arc<Body>,
shutdown: Shutdown,
name: Arc<str>,
state: Arc<State>,
delivery: Arc<Delivery<Cx>>,
failure: DispatchFailure,
workers: Workers,
) -> JoinHandle<()>
where
Sub: Subscriber + Send + 'static,
Sub::Message: Send + Sync + 'static,
Body: Handler<Sub::Message, Cx, State> + 'static,
Cx: BuildContext<Sub::Message> + Send + Sync + 'static,
State: Send + Sync + 'static,
{
tokio::spawn(async move {
let count = workers.count;
let keyed = workers.by_key;
let shared = Arc::new(Shared {
handler,
name,
state,
delivery,
failure,
});
let Started {
inboxes,
mut free,
mut crew,
} = start(&shared, workers);
let name = &shared.name;
let mut stream = pin!(subscriber.stream());
let mut cancelled = pin!(shutdown.cancelled());
let mut rotation = 0usize;
let mut held = None;
loop {
if shutdown.is_cancelled() {
break;
}
if !keyed && held.is_none() {
let freed = poll_fn(|cx| match free.poll_recv(cx) {
Poll::Ready(index) => Poll::Ready(index.map_or(Turn::Ended, Turn::Delivery)),
Poll::Pending => cancelled.as_mut().poll(cx).map(|()| Turn::Shutdown),
})
.await;
match freed {
Turn::Delivery(index) => held = Some(index),
Turn::Ended | Turn::Shutdown => break,
}
}
let pulled = poll_fn(|cx| match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(item)) => Poll::Ready(Turn::Delivery(item)),
Poll::Ready(None) => Poll::Ready(Turn::Ended),
Poll::Pending => cancelled.as_mut().poll(cx).map(|()| Turn::Shutdown),
})
.await;
match pulled {
Turn::Delivery(Ok(msg)) => {
let worker = held.take().unwrap_or_else(|| {
msg.partition_key().map_or_else(
|| {
rotation = (rotation + 1) % count;
rotation
},
|key| lane_of(key, count),
)
});
if inboxes[worker].send(msg).await.is_err() {
error!(
target: "ruststream::dispatch",
subscription = %name,
worker,
"worker terminated; stopping dispatch",
);
break;
}
}
Turn::Delivery(Err(err)) => {
error!(
target: "ruststream::dispatch",
subscription = %name,
error = %err,
"subscriber stream error",
);
}
Turn::Ended => {
debug!(
target: "ruststream::dispatch",
subscription = %name,
"subscriber stream ended",
);
break;
}
Turn::Shutdown => break,
}
}
drop(inboxes);
crew.join().await;
drop(free);
})
}