//! Session services
use super ::* ;
use tokio ::{
task ::JoinHandle ,
sync ::{
mpsc ,
broadcast ,
} ,
} ;
use std ::{ fmt , error } ;
use std ::marker ::{ Send , Sync } ;
///// A boxed message that can be downcasted.
//pub type BoxedMessage = Box<dyn std::any::Any + Send + Sync + 'static>;
/// A handle to a service.
pub trait Service < T = ExitStatus >
where T : Send + ' static
{
/// The message type to send to the service.
type Message : Send + Sync + ' static ;
/// The response to expect from the service.
type Response : Send + Sync + ' static ;
/// Return the wait handle.
///
/// This method should drop the message pipeline.
fn wait_on ( self ) -> JoinHandle < T > ;
/// An immutable reference to the pipe for sending message. Useful for service handle detaching (i.e. cloning the message input pipe).
fn message_in_ref ( & self ) -> & mpsc ::Sender < Self ::Message > ;
/// The message pipe for sending messages to the service.
fn message_in ( & mut self ) -> & mut mpsc ::Sender < Self ::Message > ;
/// The message pipe for receiving messages from the service.
fn message_out ( & mut self ) -> & mut broadcast ::Receiver < Self ::Response > ;
/// Is the service alive? A `None` value means 'maybe', and is the default return.
///
/// # Note
/// This should not be considered an infallible indicator of if the service has crashed or not. A better method is attempting to send or receive a message and the sender/receiver returning an error.
#[ inline ] fn is_alive ( & self ) -> Option < bool >
{
None
}
}
/// Error returned when subscribing to a service
#[ derive(Debug) ]
#[ non_exhaustive ]
pub enum SubscribeError
{
/// The service's receive has already been dropped.
SenderDropped ,
/// The service dropped the response oneshot channel.
NoResponse ,
}
impl error ::Error for SubscribeError { }
impl fmt ::Display for SubscribeError
{
fn fmt ( & self , f : & mut fmt ::Formatter < ' _ > ) -> fmt ::Result
{
match self {
Self ::SenderDropped = > write! ( f , "the service has already stopped" ) ,
Self ::NoResponse = > write! ( f , "the service declined to, or was unable to respond" ) ,
}
}
}
#[ macro_export ] macro_rules! join_service {
( $serv :expr ) = > ( $serv . await . map_err ( | _ | $crate ::service ::ExitStatus ::Abnormal ) ? )
}
/// How a service exited
#[ derive(Debug) ]
#[ non_exhaustive ]
pub enum ExitStatus < T = ( ) >
{
/// A graceful exit with value
Graceful ( T ) ,
/// An abnormal exit (counted as error)
///
/// # Usage
/// Usually for panicked services, otherwise use `Self::Error`.
/// The macro `join_service!()` can be used to convert handle join errors into this
Abnormal ,
/// Exit on an error report (counted as error)
Error ( eyre ::Report ) ,
}
impl < T , E : Into < eyre ::Report > > From < Result < T , E > > for ExitStatus < T >
{
fn from ( from : Result < T , E > ) -> Self
{
match from
{
Ok ( v ) = > Self ::Graceful ( v ) ,
Err ( e ) = > Self ::Error ( e . into ( ) )
}
}
}
#[ derive(Debug) ]
/// The error `ExitStatus::Abnormal` converts to.
pub struct AbnormalExitError ;
impl error ::Error for AbnormalExitError { }
impl fmt ::Display for AbnormalExitError
{
fn fmt ( & self , f : & mut fmt ::Formatter < ' _ > ) -> fmt ::Result
{
write! ( f , "service terminated in an abnormal way" )
}
}
/*
impl < U , T : error ::Error + Send + Sync + ' static > From < T > for ExitStatus < U >
{
fn from ( from : T ) -> Self
{
Self ::Error ( from . into ( ) )
}
} * /
impl < T : Default > Default for ExitStatus < T >
{
#[ inline ]
fn default ( ) -> Self
{
Self ::Graceful ( T ::default ( ) )
}
}
impl < T > From < ExitStatus < T > > for eyre ::Result < T >
{
fn from ( from : ExitStatus < T > ) -> Self
{
match from {
ExitStatus ::Abnormal = > Err ( AbnormalExitError ) . with_note ( | | "The background worker likely panicked" ) ,
ExitStatus ::Graceful ( t ) = > Ok ( t ) ,
ExitStatus ::Error ( rep ) = > Err ( rep ) . wrap_err ( AbnormalExitError ) ,
}
}
}