bastion/src/bastion/src/child_ref.rs

318 lines
9.7 KiB
Rust

//!
//! Allows users to communicate with Child through the mailboxes.
use crate::broadcast::Sender;
use crate::context::BastionId;
use crate::envelope::{Envelope, RefAddr};
use crate::message::{Answer, BastionMessage, Message};
use crate::path::BastionPath;
use std::cmp::{Eq, PartialEq};
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
#[derive(Debug, Clone)]
/// A "reference" to an element of a children group, allowing to
/// communicate with it.
pub struct ChildRef {
id: BastionId,
sender: Sender,
name: String,
path: Arc<BastionPath>,
}
impl ChildRef {
pub(crate) fn new(
id: BastionId,
sender: Sender,
name: String,
path: Arc<BastionPath>,
) -> ChildRef {
ChildRef {
id,
sender,
name,
path,
}
}
/// Returns the identifier of the children group element this
/// `ChildRef` is referencing.
///
/// Note that the children group element's identifier is reset
/// when it is restarted.
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # fn main() {
/// # Bastion::init();
/// #
/// Bastion::children(|children| {
/// children.with_exec(|ctx| {
/// async move {
/// let child_id: &BastionId = ctx.current().id();
/// // ...
/// # Ok(())
/// }
/// })
/// }).expect("Couldn't create the children group.");
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn id(&self) -> &BastionId {
&self.id
}
/// Sends a message to the child this `ChildRef` is referencing.
/// This message is intended to be used outside of Bastion context when
/// there is no way for receiver to identify message sender
///
/// This method returns `()` if it succeeded, or `Err(msg)`
/// otherwise.
///
/// # Argument
///
/// * `msg` - The message to send.
///
/// # Example
///
/// ```rust
/// # use bastion::prelude::*;
/// #
/// # fn main() {
/// # Bastion::init();
/// // The message that will be "told"...
/// const TELL_MSG: &'static str = "A message containing data (tell).";
///
/// # let children_ref =
/// // Create a new child...
/// Bastion::children(|children| {
/// children.with_exec(|ctx: BastionContext| {
/// async move {
/// // ...which will receive the message "told"...
/// msg! { ctx.recv().await?,
/// msg: &'static str => {
/// assert_eq!(msg, TELL_MSG);
/// // Handle the message...
/// };
/// // This won't happen because this example
/// // only "tells" a `&'static str`...
/// _: _ => ();
/// }
///
/// Ok(())
/// }
/// })
/// }).expect("Couldn't create the children group.");
///
/// # let child_ref = &children_ref.elems()[0];
/// // Later, the message is "told" to the child...
/// child_ref.tell_anonymously(TELL_MSG).expect("Couldn't send the message.");
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn tell_anonymously<M: Message>(&self, msg: M) -> Result<(), M> {
debug!("ChildRef({}): Telling message: {:?}", self.id(), msg);
let msg = BastionMessage::tell(msg);
let env = Envelope::from_dead_letters(msg);
// FIXME: panics?
self.send(env).map_err(|env| env.into_msg().unwrap())
}
/// Sends a message to the child this `ChildRef` is referencing,
/// allowing it to answer.
/// This message is intended to be used outside of Bastion context when
/// there is no way for receiver to identify message sender
///
/// This method returns [`Answer`](../message/struct.Answer.html) if it succeeded, or `Err(msg)`
/// otherwise.
///
/// # Argument
///
/// * `msg` - The message to send.
///
/// # Example
///
/// ```
/// # use bastion::prelude::*;
/// #
/// # fn main() {
/// # Bastion::init();
/// // The message that will be "asked"...
/// const ASK_MSG: &'static str = "A message containing data (ask).";
/// // The message the will be "answered"...
/// const ANSWER_MSG: &'static str = "A message containing data (answer).";
///
/// # let children_ref =
/// // Create a new child...
/// Bastion::children(|children| {
/// children.with_exec(|ctx: BastionContext| {
/// async move {
/// // ...which will receive the message asked...
/// msg! { ctx.recv().await?,
/// msg: &'static str =!> {
/// assert_eq!(msg, ASK_MSG);
/// // Handle the message...
///
/// // ...and eventually answer to it...
/// answer!(ctx, ANSWER_MSG);
/// };
/// // This won't happen because this example
/// // only "asks" a `&'static str`...
/// _: _ => ();
/// }
///
/// Ok(())
/// }
/// })
/// }).expect("Couldn't create the children group.");
///
/// # Bastion::children(|children| {
/// # children.with_exec(move |ctx: BastionContext| {
/// # let child_ref = children_ref.elems()[0].clone();
/// # async move {
/// // Later, the message is "asked" to the child...
/// let answer: Answer = child_ref.ask_anonymously(ASK_MSG).expect("Couldn't send the message.");
///
/// // ...and the child's answer is received...
/// msg! { answer.await.expect("Couldn't receive the answer."),
/// msg: &'static str => {
/// assert_eq!(msg, ANSWER_MSG);
/// // Handle the answer...
/// };
/// // This won't happen because this example
/// // only answers a `&'static str`...
/// _: _ => ();
/// }
/// #
/// # Ok(())
/// # }
/// # })
/// # }).unwrap();
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
///
/// [`Answer`]: message/struct.Answer.html
pub fn ask_anonymously<M: Message>(&self, msg: M) -> Result<Answer, M> {
debug!("ChildRef({}): Asking message: {:?}", self.id(), msg);
let (msg, answer) = BastionMessage::ask(msg);
let env = Envelope::from_dead_letters(msg);
// FIXME: panics?
self.send(env).map_err(|env| env.into_msg().unwrap())?;
Ok(answer)
}
/// Sends a message to the child this `ChildRef` is referencing
/// to tell it to stop its execution.
///
/// This method returns `()` if it succeeded, or `Err(())`
/// otherwise.
///
/// # Example
///
/// ```
/// # use bastion::prelude::*;
/// #
/// # fn main() {
/// # Bastion::init();
/// #
/// # let children_ref = Bastion::children(|children| children).unwrap();
/// # let child_ref = &children_ref.elems()[0];
/// child_ref.stop().expect("Couldn't send the message.");
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn stop(&self) -> Result<(), ()> {
debug!("ChildRef({}): Stopping.", self.id);
let msg = BastionMessage::stop();
let env = Envelope::from_dead_letters(msg);
self.send(env).map_err(|_| ())
}
/// Sends a message to the child this `ChildRef` is referencing
/// to tell it to suicide.
///
/// This method returns `()` if it succeeded, or `Err(())`
/// otherwise.
///
/// # Example
///
/// ```
/// # use bastion::prelude::*;
/// #
/// # fn main() {
/// # Bastion::init();
/// #
/// # let children_ref = Bastion::children(|children| children).unwrap();
/// # let child_ref = &children_ref.elems()[0];
/// child_ref.kill().expect("Couldn't send the message.");
/// #
/// # Bastion::start();
/// # Bastion::stop();
/// # Bastion::block_until_stopped();
/// # }
/// ```
pub fn kill(&self) -> Result<(), ()> {
debug!("ChildRef({}): Killing.", self.id());
let msg = BastionMessage::kill();
let env = Envelope::from_dead_letters(msg);
self.send(env).map_err(|_| ())
}
/// Returns [`RefAddr`] for the child
pub fn addr(&self) -> RefAddr {
RefAddr::new(self.path.clone(), self.sender.clone())
}
pub(crate) fn send(&self, env: Envelope) -> Result<(), Envelope> {
trace!("ChildRef({}): Sending message: {:?}", self.id(), env);
self.sender
.unbounded_send(env)
.map_err(|err| err.into_inner())
}
pub(crate) fn sender(&self) -> &Sender {
&self.sender
}
/// Returns the [`BastionPath`] of the child
pub fn path(&self) -> &Arc<BastionPath> {
&self.path
}
/// Return the [`name`] of the child
pub fn name(&self) -> &str {
&self.name
}
}
impl PartialEq for ChildRef {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for ChildRef {}
impl Hash for ChildRef {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}