use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::task::JoinSet;
use tokio_util::sync::CancellationToken;
use crate::config::AppConfig;
use crate::core::Broker;
use crate::core::Context;
use crate::core::Handler;
use crate::handler_pool::HandlerPool;
use crate::handler_pool::HandlerTask;
use crate::runtime::backoff::Backoff;
pub(crate) type RouteMask = u128;
pub(crate) const MAX_HANDLERS: usize = RouteMask::BITS as usize;
pub(crate) type RoutingTable = HashMap<Arc<str>, RouteMask>;
mod sealed {
pub trait Sealed {}
impl Sealed for () {}
impl<Tail, H> Sealed for (Tail, H) {}
}
#[doc(hidden)]
pub struct PoolWiring<'a, B: Broker> {
pub(crate) context: Context<B>,
pub(crate) config: AppConfig,
pub(crate) backoff: Arc<Backoff>,
pub(crate) ack_retry_tx: mpsc::Sender<B::AckToken>,
pub(crate) cancellation: &'a CancellationToken,
pub(crate) handles: &'a mut JoinSet<()>,
pub(crate) senders: &'a mut Vec<mpsc::Sender<HandlerTask<B::AckToken>>>,
}
pub trait HandlerSet<B: Broker>: sealed::Sealed {
const LEN: usize;
const ASSERT_WITHIN_LIMIT: ();
#[doc(hidden)]
fn append_routes(routes: &mut RoutingTable);
#[doc(hidden)]
fn spawn_pools(self, wiring: &mut PoolWiring<'_, B>);
}
pub(crate) fn build_routing_table<B, HS>() -> RoutingTable
where
B: Broker,
HS: HandlerSet<B>,
{
let () = HS::ASSERT_WITHIN_LIMIT;
let mut routes = RoutingTable::new();
HS::append_routes(&mut routes);
routes
}
impl<B: Broker> HandlerSet<B> for () {
const LEN: usize = 0;
const ASSERT_WITHIN_LIMIT: () = ();
fn append_routes(_routes: &mut RoutingTable) {}
fn spawn_pools(self, _wiring: &mut PoolWiring<'_, B>) {}
}
impl<B, Tail, H> HandlerSet<B> for (Tail, H)
where
B: Broker,
Tail: HandlerSet<B>,
H: Handler<B>,
{
const LEN: usize = Tail::LEN + 1;
const ASSERT_WITHIN_LIMIT: () = {
let () = Tail::ASSERT_WITHIN_LIMIT;
assert!(Self::LEN <= MAX_HANDLERS, "handler count exceeds RouteMask capacity");
};
fn append_routes(routes: &mut RoutingTable) {
let () = Self::ASSERT_WITHIN_LIMIT;
Tail::append_routes(routes);
let handler_bit = 1_u128 << Tail::LEN;
for stream in H::STREAMS {
routes.entry(Arc::<str>::from(*stream)).and_modify(|mask| *mask |= handler_bit).or_insert(handler_bit);
}
}
fn spawn_pools(self, wiring: &mut PoolWiring<'_, B>) {
let (tail, handler) = self;
tail.spawn_pools(wiring);
let (sender, pool) = HandlerPool::new(
handler,
wiring.context.clone(),
&wiring.config,
Arc::clone(&wiring.backoff),
wiring.ack_retry_tx.clone(),
);
let cancellation = wiring.cancellation.clone();
wiring.handles.spawn(pool.run(cancellation));
wiring.senders.push(sender);
}
}
#[cfg(test)]
mod tests {
use super::build_routing_table;
use crate::core::Context;
use crate::core::Envelope;
use crate::core::Handler;
use crate::core::HandlerError;
use crate::core::Outcome;
use crate::test_support::MockBroker;
struct OrdersHandler;
struct SharedHandler;
impl Handler<MockBroker> for OrdersHandler {
const STREAMS: &'static [&'static str] = &["orders", "shared"];
async fn handle(&self, _ctx: &Context<MockBroker>, _msg: &Envelope) -> Result<Outcome, HandlerError> {
Ok(Outcome::Ack)
}
}
impl Handler<MockBroker> for SharedHandler {
const STREAMS: &'static [&'static str] = &["shared", "payments"];
async fn handle(&self, _ctx: &Context<MockBroker>, _msg: &Envelope) -> Result<Outcome, HandlerError> {
Ok(Outcome::Ack)
}
}
#[test]
fn routing_table_builds_expected_masks() {
type Handlers = (((), OrdersHandler), SharedHandler);
let routing = build_routing_table::<MockBroker, Handlers>();
assert_eq!(routing.get("orders"), Some(&(1_u128 << 0)));
assert_eq!(routing.get("shared"), Some(&((1_u128 << 0) | (1_u128 << 1))));
assert_eq!(routing.get("payments"), Some(&(1_u128 << 1)));
assert_eq!(routing.len(), 3);
}
}