riker-rs / riker

Easily build efficient, highly concurrent and resilient applications. An Actor Framework for Rust.
https://riker.rs
MIT License
1.02k stars 69 forks source link

Not possible to use ask-pattern from inside recv function of an actor #164

Open Superhepper opened 3 years ago

Superhepper commented 3 years ago

I wonder if it is possible to use the ask pattern from inside recv function in actor to make a blocking call to another actor in the same system. Because when I try to do that I always get an error regarding LocalPool.

I have made a very simple example (see code below) where I try to use the ask-pattern from inside another actors recv function. But this only results in the following error:

thread 'pool-thread-#3' panicked at 'cannot execute `LocalPool` executor from within another executor: EnterError', ~/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-executor-0.3.14/src/local_pool.rs:78:26

main.rs

mod ask;
mod reply;

use ask::{AskActor, AskActorMsg, PrintNewSentenceRequest};
use reply::ReplyActor;

use riker::actors::*;

fn main() {
    let sys = ActorSystem::new().unwrap();

    let reply_actor = sys
        .actor_of_args::<ReplyActor, _>("ReplyActor", "new".to_string())
        .unwrap();
    let ask_actor = sys
        .actor_of_args::<AskActor, _>("AskActor", reply_actor)
        .unwrap();

    ask_actor.tell(
        AskActorMsg::PrintNewSentenceRequest(PrintNewSentenceRequest {}),
        None,
    );

    std::thread::sleep(std::time::Duration::from_millis(500));
}

ask.rs

use crate::reply::{PrefixRequest, PrefixResponse, ReplyActorMsg};
use futures::executor::block_on;
use futures::future::RemoteHandle;
use riker::actors::*;
use riker_patterns::ask::*;

#[derive(Debug, Clone)]
pub struct PrintNewSentenceRequest {}

#[actor(PrintNewSentenceRequest)]
#[derive(Debug, Clone)]
pub struct AskActor {
    reply_actor: ActorRef<ReplyActorMsg>,
}

impl ActorFactoryArgs<ActorRef<ReplyActorMsg>> for AskActor {
    fn create_args(reply_actor: ActorRef<ReplyActorMsg>) -> Self {
        AskActor { reply_actor }
    }
}

impl Actor for AskActor {
    type Msg = AskActorMsg;

    fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, sender: Sender) {
        self.receive(ctx, msg, sender)
    }
}

impl Receive<PrintNewSentenceRequest> for AskActor {
    type Msg = AskActorMsg;

    fn receive(&mut self, ctx: &Context<Self::Msg>, _: PrintNewSentenceRequest, _: Sender) {
        let r: RemoteHandle<PrefixResponse> = ask(
            &ctx.system,
            &self.reply_actor,
            ReplyActorMsg::PrefixRequest(PrefixRequest {
                word: "ball".to_string(),
            }),
        );

        let response = block_on(r);

        println!("Received new sentence: {}", response.sentence);
    }
}

reply.rs

use riker::actors::*;

#[derive(Debug, Clone)]
pub struct PrefixRequest {
    pub word: String,
}

#[derive(Debug, Clone)]
pub struct PrefixResponse {
    pub sentence: String,
}

#[actor(PrefixRequest)]
#[derive(Debug, Clone)]
pub struct ReplyActor {
    prefix: String,
}

impl ActorFactoryArgs<String> for ReplyActor {
    fn create_args(prefix: String) -> Self {
        ReplyActor { prefix }
    }
}

impl Actor for ReplyActor {
    type Msg = ReplyActorMsg;

    fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, sender: Sender) {
        self.receive(ctx, msg, sender)
    }
}

impl Receive<PrefixRequest> for ReplyActor {
    type Msg = ReplyActorMsg;

    fn receive(&mut self, _: &Context<Self::Msg>, request: PrefixRequest, sender: Sender) {
        sender
            .as_ref()
            .clone()
            .unwrap()
            .try_tell(
                PrefixResponse {
                    sentence: format!("{} {}", self.prefix, request.word),
                },
                None,
            )
            .unwrap()
    }
}
gallegogt commented 3 years ago

hi @Superhepper It is a limitation that the ThreadPool used internally by ricker , which belongs to the crate futures::executor

https://github.com/rust-lang/futures-rs/issues/2090

Superhepper commented 3 years ago

So it would be possible if I could bring the future to completion using another thread pool like tokio?

gallegogt commented 3 years ago

So it would be possible if I could bring the future to completion using another thread pool like tokio?

yes, you can use tokio or async_std::task::block_on instead of futures::executor::block_on

Superhepper commented 3 years ago

Thanks for the answer.