use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use futures_util::stream::{FuturesUnordered, StreamExt};
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::actor::{Actor, AsyncHandler, Handler, RemoteMessage};
use crate::endpoint::Endpoint;
use crate::listing::{ListingEvent, ReceptionKey, WatchedListing};
use crate::receptionist::Receptionist;
use crate::runtime::{Runtime, TokioRuntime};
use crate::wire::SendError;
#[derive(Debug, Clone, Default)]
pub enum RoutingStrategy {
#[default]
RoundRobin,
Random,
Broadcast,
}
pub struct Router<A: Actor> {
endpoints: Vec<Endpoint<A>>,
strategy: RoutingStrategy,
counter: AtomicUsize,
runtime: Arc<dyn Runtime>,
}
impl<A: Actor + 'static> Router<A> {
pub fn new(endpoints: Vec<Endpoint<A>>, strategy: RoutingStrategy) -> Self {
Self::with_runtime(endpoints, strategy, Arc::new(TokioRuntime))
}
pub fn with_runtime(
endpoints: Vec<Endpoint<A>>,
strategy: RoutingStrategy,
runtime: Arc<dyn Runtime>,
) -> Self {
Self {
endpoints,
strategy,
counter: AtomicUsize::new(0),
runtime,
}
}
fn pick_index(&self, len: usize) -> usize {
match self.strategy {
RoutingStrategy::RoundRobin => self.counter.fetch_add(1, Ordering::Relaxed) % len,
RoutingStrategy::Random => (self.runtime.rng_u64() % len as u64) as usize,
RoutingStrategy::Broadcast => 0,
}
}
pub async fn send<M>(&self, msg: M) -> Result<M::Result, SendError>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
if self.endpoints.is_empty() {
return Err(SendError::MailboxClosed);
}
let idx = self.pick_index(self.endpoints.len());
self.endpoints[idx].send(msg).await
}
pub async fn send_async<M>(&self, msg: M) -> Result<M::Result, SendError>
where
A: AsyncHandler<M>,
M: RemoteMessage,
M::Result: Serialize + DeserializeOwned,
{
if self.endpoints.is_empty() {
return Err(SendError::MailboxClosed);
}
let idx = self.pick_index(self.endpoints.len());
self.endpoints[idx].send_async(msg).await
}
pub async fn broadcast<M>(&self, msg: M) -> Vec<Result<M::Result, SendError>>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
let mut results = Vec::with_capacity(self.endpoints.len());
for ep in &self.endpoints {
results.push(ep.send(msg.clone()).await);
}
results
}
pub async fn send_quorum<M>(&self, msg: M, k: usize) -> Vec<Result<M::Result, SendError>>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
if self.endpoints.is_empty() {
return vec![Err(SendError::MailboxClosed)];
}
let mut inflight = FuturesUnordered::new();
for ep in &self.endpoints {
let ep = ep.clone();
let msg = msg.clone();
inflight.push(async move { ep.send(msg).await });
}
let target = k.min(self.endpoints.len());
let mut results = Vec::with_capacity(target);
let mut successes = 0;
while let Some(result) = inflight.next().await {
if result.is_ok() {
successes += 1;
}
results.push(result);
if successes >= target {
break;
}
}
results
}
pub async fn scatter_gather<M>(
&self,
msg: M,
timeout: std::time::Duration,
) -> Vec<Result<M::Result, SendError>>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
if self.endpoints.is_empty() {
return vec![Err(SendError::MailboxClosed)];
}
let mut inflight = FuturesUnordered::new();
for ep in &self.endpoints {
let ep = ep.clone();
let msg = msg.clone();
inflight.push(async move { ep.send(msg).await });
}
let mut results = Vec::with_capacity(self.endpoints.len());
let mut timeout_fut = self.runtime.sleep(timeout);
loop {
tokio::select! {
biased;
Some(result) = inflight.next() => {
results.push(result);
if results.len() == self.endpoints.len() {
break;
}
}
_ = &mut timeout_fut => {
break; }
}
}
results
}
pub async fn first_response<M>(&self, msg: M) -> Result<M::Result, SendError>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
if self.endpoints.is_empty() {
return Err(SendError::MailboxClosed);
}
let mut inflight = FuturesUnordered::new();
for ep in &self.endpoints {
let ep = ep.clone();
let msg = msg.clone();
inflight.push(async move { ep.send(msg).await });
}
let mut last_error = SendError::MailboxClosed;
while let Some(result) = inflight.next().await {
match result {
Ok(result) => {
return Ok(result);
}
Err(e) => last_error = e,
}
}
Err(last_error)
}
pub fn fan_out<M>(&self, msg: M)
where
A: Handler<M>,
M: RemoteMessage + Clone + 'static,
M::Result: Serialize + DeserializeOwned,
{
for ep in &self.endpoints {
let ep = ep.clone();
let msg = msg.clone();
self.runtime.spawn(Box::pin(async move {
let _ = ep.send(msg).await;
}));
}
}
pub fn add(&mut self, endpoint: Endpoint<A>) {
self.endpoints.push(endpoint);
}
pub fn remove(&mut self, index: usize) {
if index < self.endpoints.len() {
self.endpoints.remove(index);
}
}
pub fn len(&self) -> usize {
self.endpoints.len()
}
pub fn is_empty(&self) -> bool {
self.endpoints.is_empty()
}
}
type Pool<A> = Arc<RwLock<Vec<(String, Endpoint<A>)>>>;
pub struct PoolRouter<A: Actor> {
pool: Pool<A>,
strategy: RoutingStrategy,
counter: AtomicUsize,
runtime: Arc<dyn Runtime>,
}
impl<A: Actor + 'static> PoolRouter<A> {
pub fn new(
receptionist: &Receptionist,
key: ReceptionKey<A>,
strategy: RoutingStrategy,
) -> Self {
let watched = receptionist.watched_listing(key);
Self::build(watched, strategy, receptionist.runtime().clone())
}
pub fn from_watched_listing(watched: WatchedListing<A>, strategy: RoutingStrategy) -> Self {
Self::build(watched, strategy, Arc::new(TokioRuntime))
}
fn build(
watched: WatchedListing<A>,
strategy: RoutingStrategy,
runtime: Arc<dyn Runtime>,
) -> Self {
let pool: Pool<A> = Arc::new(RwLock::new(Vec::new()));
let pool_clone = Arc::clone(&pool);
runtime.spawn(Box::pin(Self::run_watcher(pool_clone, watched)));
Self {
pool,
strategy,
counter: AtomicUsize::new(0),
runtime,
}
}
async fn run_watcher(pool: Pool<A>, mut watched: WatchedListing<A>) {
while let Some(event) = watched.next().await {
match event {
ListingEvent::Added { label, endpoint } => {
let mut pool = pool.write().unwrap();
if !pool.iter().any(|(l, _)| l == &label) {
pool.push((label, endpoint));
}
}
ListingEvent::Removed { label } => {
let mut pool = pool.write().unwrap();
pool.retain(|(l, _)| l != &label);
}
}
}
}
fn pick_index(&self, len: usize) -> usize {
match self.strategy {
RoutingStrategy::RoundRobin => self.counter.fetch_add(1, Ordering::Relaxed) % len,
RoutingStrategy::Random => (self.runtime.rng_u64() % len as u64) as usize,
RoutingStrategy::Broadcast => 0,
}
}
pub async fn send<M>(&self, msg: M) -> Result<M::Result, SendError>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
let endpoint = {
let pool = self.pool.read().unwrap();
if pool.is_empty() {
return Err(SendError::MailboxClosed);
}
let idx = self.pick_index(pool.len());
pool[idx].1.clone()
};
endpoint.send(msg).await
}
pub async fn send_async<M>(&self, msg: M) -> Result<M::Result, SendError>
where
A: AsyncHandler<M>,
M: RemoteMessage,
M::Result: Serialize + DeserializeOwned,
{
let endpoint = {
let pool = self.pool.read().unwrap();
if pool.is_empty() {
return Err(SendError::MailboxClosed);
}
let idx = self.pick_index(pool.len());
pool[idx].1.clone()
};
endpoint.send_async(msg).await
}
pub async fn broadcast<M>(&self, msg: M) -> Vec<Result<M::Result, SendError>>
where
A: Handler<M>,
M: RemoteMessage + Clone,
M::Result: Serialize + DeserializeOwned,
{
let endpoints: Vec<_> = {
let pool = self.pool.read().unwrap();
pool.iter().map(|(_, ep)| ep.clone()).collect()
};
let mut inflight = FuturesUnordered::new();
for ep in endpoints {
let msg = msg.clone();
inflight.push(async move { ep.send(msg).await });
}
let mut results = Vec::new();
while let Some(result) = inflight.next().await {
results.push(result);
}
results
}
pub fn len(&self) -> usize {
self.pool.read().unwrap().len()
}
pub fn is_empty(&self) -> bool {
self.pool.read().unwrap().is_empty()
}
pub fn labels(&self) -> Vec<String> {
self.pool
.read()
.unwrap()
.iter()
.map(|(l, _)| l.clone())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::actor::ActorContext;
use crate::prelude::*;
use crate::receptionist::Receptionist;
use serde::{Deserialize, Serialize};
struct AsyncWorker;
#[derive(Default)]
struct WorkerState {
seen: i64,
}
impl Actor for AsyncWorker {
type State = WorkerState;
}
#[derive(Debug, Clone, Serialize, Deserialize, Message)]
#[message(result = i64, remote = "router::AsyncAdd")]
struct AsyncAdd {
amount: i64,
}
#[handlers]
impl AsyncWorker {
#[handler]
async fn async_add(
&mut self,
_ctx: &ActorContext<Self>,
state: &mut WorkerState,
msg: AsyncAdd,
) -> i64 {
state.seen += msg.amount;
state.seen
}
}
#[tokio::test]
async fn router_send_async_round_robins_to_async_handlers() {
let recep = Receptionist::new();
let ep1 = recep.start("w/1", AsyncWorker, WorkerState::default());
let ep2 = recep.start("w/2", AsyncWorker, WorkerState::default());
let router = Router::new(vec![ep1, ep2], RoutingStrategy::RoundRobin);
let mut last = 0;
for _ in 0..4 {
last = router
.send_async(AsyncAdd { amount: 1 })
.await
.expect("send_async routes to the AsyncHandler");
}
assert_eq!(
last, 2,
"round-robin spread the 4 sends evenly across 2 async actors"
);
}
#[tokio::test]
async fn router_send_async_empty_pool_errors() {
let router: Router<AsyncWorker> = Router::new(vec![], RoutingStrategy::RoundRobin);
assert!(matches!(
router.send_async(AsyncAdd { amount: 1 }).await,
Err(SendError::MailboxClosed)
));
}
}