#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::sync::Arc;
#[cfg(feature = "std")]
use std::sync::Arc;
use core::future::Future;
use core::pin::Pin;
#[cfg(feature = "derive")]
use crate::Errorizable;
use crate::policy::{ReceptorPolicy, TransitStatus};
use crate::trace::TraceCollector;
use crate::Message;
pub mod macros;
pub mod worker_runtime {
pub mod rt {
pub use crate::runtime::rt::*;
pub type QueueSender<T> = crate::runtime::rt::Sender<T>;
pub type QueueReceiver<T> = crate::runtime::rt::Receiver<T>;
}
}
pub struct WorkerRequest<I: Send, O> {
pub message: Arc<I>,
pub respond_to: worker_runtime::rt::OneshotSender<Result<O, TransitStatus>>,
pub trace: Arc<TraceCollector>,
}
#[cfg_attr(feature = "derive", derive(Errorizable))]
#[derive(Debug)]
pub enum WorkerRelayError {
#[cfg_attr(feature = "derive", error("Worker queue closed"))]
QueueClosed,
#[cfg_attr(feature = "derive", error("Worker response channel dropped"))]
ResponseDropped,
#[cfg_attr(feature = "derive", error("Message rejected with status {:?}"))]
#[cfg_attr(feature = "derive", from)]
Rejected(TransitStatus),
}
#[cfg(not(feature = "derive"))]
impl core::fmt::Display for WorkerRelayError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::QueueClosed => f.write_str("worker queue closed"),
Self::ResponseDropped => f.write_str("worker response channel dropped"),
Self::Rejected(status) => write!(f, "message rejected with status {:?}", status),
}
}
}
#[cfg(not(feature = "derive"))]
impl std::error::Error for WorkerRelayError {}
pub type WorkerRelayFuture<O> = Pin<Box<dyn Future<Output = Result<O, WorkerRelayError>> + Send + 'static>>;
pub type WorkerStartFuture<W> = Pin<Box<dyn Future<Output = Result<W, crate::error::TightBeamError>> + Send>>;
pub type WorkerKillFuture = Pin<Box<dyn Future<Output = Result<(), crate::error::TightBeamError>> + Send + 'static>>;
pub fn relay_to_worker<I, O>(
sender: Option<worker_runtime::rt::QueueSender<WorkerRequest<I, O>>>,
trace: Arc<TraceCollector>,
message: Arc<I>,
) -> WorkerRelayFuture<O>
where
I: Send + Sync + 'static,
O: Send + 'static,
{
Box::pin(async move {
let sender = sender.ok_or(WorkerRelayError::QueueClosed)?;
let (tx, rx) = worker_runtime::rt::oneshot();
let request = WorkerRequest { message, respond_to: tx, trace };
worker_runtime::rt::send(&sender, request)
.await
.map_err(|_| WorkerRelayError::QueueClosed)?;
match worker_runtime::rt::wait_response(rx).await {
Ok(Ok(output)) => Ok(output),
Ok(Err(status)) => Err(WorkerRelayError::Rejected(status)),
Err(()) => Err(WorkerRelayError::ResponseDropped),
}
})
}
pub fn kill_worker<I, O>(
sender: Option<worker_runtime::rt::QueueSender<WorkerRequest<I, O>>>,
join: Option<worker_runtime::rt::JoinHandle>,
) -> WorkerKillFuture
where
I: Send + Sync + 'static,
O: Send + 'static,
{
Box::pin(async move {
drop(sender);
if let Some(handle) = join {
worker_runtime::rt::join(handle)
.await
.map_err(|_| crate::error::TightBeamError::JoinError)?;
}
Ok(())
})
}
pub trait Worker: Send + Sync + Sized {
type Input: Send + Sync + 'static;
type Output: Send + 'static;
type Config: Send + Sync + 'static;
fn new(config: Self::Config) -> Self;
fn start(self, trace: Arc<TraceCollector>) -> WorkerStartFuture<Self>;
fn kill(self) -> WorkerKillFuture;
fn relay(&self, message: Arc<Self::Input>) -> WorkerRelayFuture<Self::Output>;
fn queue_capacity(&self) -> usize;
}
pub trait WorkerMetadata {
fn name() -> &'static str;
}
pub struct WorkerPolicies<I: Send> {
#[allow(dead_code)]
pub(crate) receptor_gates: Vec<Arc<dyn ReceptorPolicy<I> + Send + Sync>>,
}
impl<I: Send> WorkerPolicies<I> {
pub fn receptor_gates(&self) -> &[Arc<dyn ReceptorPolicy<I> + Send + Sync>] {
&self.receptor_gates
}
}
impl<I: Send> Default for WorkerPolicies<I> {
fn default() -> Self {
Self { receptor_gates: Vec::new() }
}
}
pub struct WorkerPolicyBuilder<I: Send> {
receptor_gates: Vec<Arc<dyn ReceptorPolicy<I> + Send + Sync>>,
}
impl<I: Send> Default for WorkerPolicyBuilder<I> {
fn default() -> Self {
Self { receptor_gates: Vec::new() }
}
}
impl<I: Message + Send> WorkerPolicyBuilder<I> {
pub fn build(self) -> WorkerPolicies<I> {
WorkerPolicies { receptor_gates: self.receptor_gates }
}
pub fn with_receptor_gate<R, const N: usize>(mut self, gates: [R; N]) -> Self
where
R: ReceptorPolicy<I> + Send + Sync + 'static,
{
self.receptor_gates.extend(
gates
.into_iter()
.map(|gate| Arc::new(gate) as Arc<dyn ReceptorPolicy<I> + Send + Sync>),
);
self
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::WorkerRelayError;
use crate::der::Sequence;
use crate::policy::{ReceptorPolicy, TransitStatus};
use crate::worker;
use crate::Beamable;
#[derive(Beamable, Clone, Debug, PartialEq, Sequence)]
pub struct RequestMessage {
content: String,
lucky_number: u32,
}
#[derive(Sequence, Beamable, Clone, Debug, PartialEq)]
pub struct PongMessage {
result: String,
}
#[derive(Default)]
struct PingGate;
impl ReceptorPolicy<RequestMessage> for PingGate {
fn evaluate(&self, maybe_ping: &RequestMessage) -> TransitStatus {
if maybe_ping.content == "PING" {
TransitStatus::Accepted
} else {
TransitStatus::Forbidden
}
}
}
worker! {
name: LuckyNumberDeterminer<RequestMessage, bool>,
config: {
lotto_number: u32,
},
handle: |message, _trace, config| async move {
message.lucky_number == config.lotto_number
}
}
worker! {
name: PingPongWorker<RequestMessage, PongMessage>,
policies: {
with_receptor_gate: [PingGate]
},
handle: |_message, _trace| async move {
PongMessage {
result: "PONG".to_string(),
}
}
}
#[cfg(feature = "std")]
crate::test_worker! {
name: lucky_number_worker_checks_winner,
setup: || {
LuckyNumberDeterminer::new(LuckyNumberDeterminerConf { lotto_number: 42 })
},
assertions: |worker| async move {
assert_eq!(worker.queue_capacity(), 64);
let winner = worker.relay(Arc::new(RequestMessage {
content: "PING".to_string(),
lucky_number: 42,
})).await?;
assert!(winner);
let loser = worker.relay(Arc::new(RequestMessage {
content: "PING".to_string(),
lucky_number: 7,
})).await?;
assert!(!loser);
Ok(())
}
}
#[cfg(feature = "std")]
crate::test_worker! {
name: test_ping_pong_worker,
setup: || {
PingPongWorker::new(())
},
assertions: |worker| async move {
let ping_msg = RequestMessage {
content: "PING".to_string(),
lucky_number: 42,
};
let response = worker.relay(Arc::new(ping_msg)).await?;
assert_eq!(response, PongMessage { result: "PONG".to_string() });
let pong_msg = RequestMessage {
content: "PONG".to_string(),
lucky_number: 42,
};
let result = worker.relay(Arc::new(pong_msg)).await;
assert!(matches!(result, Err(WorkerRelayError::Rejected(_))));
Ok(())
}
}
}