compio-actor 0.1.0

Actors for Compio
Documentation
use std::{convert::Infallible, num::NonZeroUsize};

use compio_actor::{
    Actor, ActorExit, ActorHandle, Call, Cluster, Handler, Mailbox, supervisor::SupervisionEvent,
};
use compio_dispatcher::Dispatcher;

fn cluster() -> Cluster {
    let dispatcher = Dispatcher::builder()
        .worker_threads(NonZeroUsize::new(2).unwrap())
        .build()
        .unwrap();
    Cluster::from_dispatcher(dispatcher)
}

#[derive(Debug)]
struct Stop;

#[derive(Debug)]
struct Fail;

#[derive(Debug)]
struct Ready;

struct Child;

impl Actor for Child {
    type Arguments = ();
    type Error = &'static str;
    type State = ();

    async fn pre_start(
        &self,
        _myself: &Mailbox<Self>,
        (): Self::Arguments,
    ) -> Result<Self::State, Self::Error> {
        Ok(())
    }
}

impl Handler<Stop> for Child {
    async fn handle(
        &self,
        myself: &Mailbox<Self>,
        _stop: Stop,
        _state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        myself.stop();
        Ok(())
    }
}

impl Handler<Fail> for Child {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        Fail: Fail,
        _state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        Err("child failed")
    }
}

impl Handler<Call<Ready, ()>> for Child {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        call: Call<Ready, ()>,
        _state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        call.reply(()).ok();
        Ok(())
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ObservedEvent {
    Started,
    Terminated,
    Failed,
}

#[derive(Debug)]
struct Events;

struct Parent;

impl Actor for Parent {
    type Arguments = ();
    type Error = Infallible;
    type State = Vec<ObservedEvent>;

    async fn pre_start(
        &self,
        _myself: &Mailbox<Self>,
        (): Self::Arguments,
    ) -> Result<Self::State, Self::Error> {
        Ok(Vec::new())
    }
}

impl Handler<SupervisionEvent<Child>> for Parent {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        event: SupervisionEvent<Child>,
        state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        state.push(match event {
            SupervisionEvent::ActorStarted(_) => ObservedEvent::Started,
            SupervisionEvent::ActorTerminated(_) => ObservedEvent::Terminated,
            SupervisionEvent::ActorFailed(_) => ObservedEvent::Failed,
        });
        Ok(())
    }
}

impl Handler<Call<Events, Vec<ObservedEvent>>> for Parent {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        call: Call<Events, Vec<ObservedEvent>>,
        state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        call.reply(state.clone()).ok();
        Ok(())
    }
}

#[compio_macros::test]
async fn reports_child_start_termination_and_failure() {
    let cluster = cluster();
    let (parent, parent_handle) = cluster.spawn(|| Parent, ()).await.unwrap();

    let (stopping, stopping_handle) = cluster
        .spawn(|| Child, ())
        .with_supervisor(&parent)
        .await
        .unwrap();
    stopping.send(Stop).unwrap();
    assert_eq!(stopping_handle.await.unwrap(), ActorExit::Stopped);
    assert_eq!(
        parent.call(Events).await.unwrap(),
        [ObservedEvent::Started, ObservedEvent::Terminated]
    );

    let (failing, failing_handle) = cluster
        .spawn(|| Child, ())
        .with_supervisor(&parent)
        .await
        .unwrap();
    failing.send(Fail).unwrap();
    assert_eq!(
        failing_handle.await.unwrap(),
        ActorExit::Failed("child failed")
    );
    assert_eq!(
        parent.call(Events).await.unwrap(),
        [
            ObservedEvent::Started,
            ObservedEvent::Terminated,
            ObservedEvent::Started,
            ObservedEvent::Failed,
        ]
    );

    parent.stop();
    assert_eq!(parent_handle.await.unwrap(), ActorExit::Stopped);
    cluster.join().await.unwrap();
}

#[compio_macros::test]
async fn stopping_a_supervisor_does_not_stop_its_children() {
    let cluster = cluster();
    let (parent, parent_handle) = cluster.spawn(|| Parent, ()).await.unwrap();
    let (child, child_handle) = cluster
        .spawn(|| Child, ())
        .with_supervisor(&parent)
        .await
        .unwrap();
    child.call(Ready).await.unwrap();

    parent.stop();
    assert_eq!(parent_handle.await.unwrap(), ActorExit::Stopped);
    assert!(!child.is_closed());

    child.stop();
    assert_eq!(child_handle.await.unwrap(), ActorExit::Stopped);
    cluster.join().await.unwrap();
}

struct StoppingParent;

impl Actor for StoppingParent {
    type Arguments = ();
    type Error = Infallible;
    type State = ();

    async fn pre_start(
        &self,
        _myself: &Mailbox<Self>,
        (): Self::Arguments,
    ) -> Result<Self::State, Self::Error> {
        Ok(())
    }
}

impl Handler<SupervisionEvent<Child>> for StoppingParent {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        event: SupervisionEvent<Child>,
        _state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        if let SupervisionEvent::ActorStarted(child) = event {
            child.stop();
        }
        Ok(())
    }
}

#[compio_macros::test]
async fn a_supervisor_can_control_a_child_from_its_event() {
    let cluster = cluster();
    let (parent, parent_handle) = cluster.spawn(|| StoppingParent, ()).await.unwrap();
    let (_child, child_handle) = cluster
        .spawn(|| Child, ())
        .with_supervisor(&parent)
        .await
        .unwrap();

    assert_eq!(child_handle.await.unwrap(), ActorExit::Stopped);
    assert!(!parent.is_closed());
    parent.stop();
    assert_eq!(parent_handle.await.unwrap(), ActorExit::Stopped);
    cluster.join().await.unwrap();
}

struct RestartingParent;

struct RestartState {
    child: Option<Mailbox<Child>>,
    handle: Option<ActorHandle<&'static str>>,
}

impl Actor for RestartingParent {
    type Arguments = ();
    type Error = Infallible;
    type State = RestartState;

    async fn pre_start(
        &self,
        _myself: &Mailbox<Self>,
        (): Self::Arguments,
    ) -> Result<Self::State, Self::Error> {
        Ok(RestartState {
            child: None,
            handle: None,
        })
    }
}

impl Handler<SupervisionEvent<Child>> for RestartingParent {
    async fn handle(
        &self,
        myself: &Mailbox<Self>,
        event: SupervisionEvent<Child>,
        state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        if let SupervisionEvent::ActorFailed(child) = event {
            let (child, handle) = Cluster::current()
                .spawn(|| Child, ())
                .with_name(child.name().expect("failed child is registered").to_owned())
                .with_supervisor(myself)
                .await
                .unwrap();
            state.child = Some(child);
            state.handle = Some(handle);
        }
        Ok(())
    }
}

#[derive(Debug)]
struct Replacement;

impl Handler<Call<Replacement, Mailbox<Child>>> for RestartingParent {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        call: Call<Replacement, Mailbox<Child>>,
        state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        call.reply(state.child.as_ref().unwrap().clone()).ok();
        Ok(())
    }
}

#[derive(Debug)]
struct WaitForReplacement;

impl Handler<Call<WaitForReplacement, ActorExit<&'static str>>> for RestartingParent {
    async fn handle(
        &self,
        _myself: &Mailbox<Self>,
        call: Call<WaitForReplacement, ActorExit<&'static str>>,
        state: &mut Self::State,
    ) -> Result<(), Self::Error> {
        let exit = state.handle.take().unwrap().await.unwrap();
        call.reply(exit).ok();
        Ok(())
    }
}

#[compio_macros::test]
async fn a_supervisor_can_respawn_a_failed_named_actor() {
    let cluster = cluster();
    let (parent, parent_handle) = cluster.spawn(|| RestartingParent, ()).await.unwrap();
    let (child, child_handle) = cluster
        .spawn(|| Child, ())
        .with_name("worker")
        .with_supervisor(&parent)
        .await
        .unwrap();

    child.send(Fail).unwrap();
    assert_eq!(
        child_handle.await.unwrap(),
        ActorExit::Failed("child failed")
    );

    let replacement = parent.call(Replacement).await.unwrap();
    assert_eq!(replacement.name(), Some("worker"));
    assert_eq!(
        cluster.lookup::<Child, _>("worker").unwrap().name(),
        Some("worker")
    );

    replacement.stop();
    assert_eq!(
        parent.call(WaitForReplacement).await.unwrap(),
        ActorExit::Stopped
    );
    parent.stop();
    assert_eq!(parent_handle.await.unwrap(), ActorExit::Stopped);
    cluster.join().await.unwrap();
}