Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActorSelection.select(..) + sel.try_tell(Send,...) using a non String Msg, the ActorSelection.try_tell(Send,...) failes #129

Open
sandro-sole opened this issue Jul 18, 2020 · 8 comments

Comments

@sandro-sole
Copy link

I'm using the lates rust + latest riker version.

thread 'pool-thread-#12' panicked at 'called Result::unwrap() on an Err value: ()', .....cargo\registry\src\github.com-1ecc6299db9ec823\riker-0.4.1\src\actor\selection.rs:105:29
The panic is raced in this piece of code:

        // all: /user/select-actor
        let path = "/user/select-actor-param-6";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
       sel.try_tell(Send, None);

Where the message is not a string but a struct; like in my example: Send

All the String Messages are managed correctly. But if the "non"-String message is supposed to be sent, then an Err() occurs. However this is (in my opinion) not the primary issue. It's rather that the non-string messages handling that failed...

//main-riker-select-params
extern crate riker;
use riker::actors::*;
use riker::system::ActorSystem;

use std::time::Duration;

// a simple minimal actor for use in tests
// #[actor(TestProbe)]
#[derive(Default, Debug)]
struct Child;

impl Actor for Child {
    type Msg = String;

    fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, _sender: Sender) {
        println!("{}: {:?} -> got msg: {}", ctx.myself.name(), self, msg);
    }
}

#[derive(Clone, Default, Debug)]
struct SelectTest;

impl Actor for SelectTest {
    type Msg = String;

    fn pre_start(&mut self, ctx: &Context<Self::Msg>) {
        // create first child actor
        let _ = ctx.actor_of::<Child>("child_a").unwrap();

        // create second child actor
        let _ = ctx.actor_of::<Child>("child_b").unwrap();
    }

    fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, _sender: Sender) {
        println!("!\n{}: {:?} -> got msg: {}", ctx.myself.name(), self, msg);
        std::thread::sleep(Duration::from_millis(100));

        // up and down: ../select-actor/child_a
        let path = "../select-actor/child_a";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        // child: child_a
        let path = "child_a";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        // absolute: /user/select-actor/child_a
        let path = "/user/select-actor/child_a";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        // absolute all: /user/select-actor/*
        let path = "/user/select-actor/*";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        // all: *
        let path = "*";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        // all: /user/select-actor
        let path = "/user/select-actor-param-5/child-3";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        // all: /user/select-actor
        let path = "/user/select-actor";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(path.to_string(), None);

        std::thread::sleep(Duration::from_millis(100));

        //***here is the issue***
        let path = "/user/select-actor-param-6";
        println!("!\n{}: {:?} -> path: {}", ctx.myself.name(), self, path);
        let sel = ctx.select(path).unwrap();
        sel.try_tell(Send, None);
        
    }
}

// a simple minimal actor for use in tests
// #[actor(TestProbe)]
#[derive(Default, Debug)]
struct ChildParam {
    dummy:u32,
    id: i32,
}
impl ActorFactoryArgs<(i32, u32)> for ChildParam {
    fn create_args( (id, dummy): (i32, u32) ) -> Self {
        Self { id, dummy}
    }
}

impl Actor for ChildParam {
    type Msg = String;

    fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, _sender: Sender) {
        println!("@param {}: {:?} -> got msg: {}", ctx.myself.name(), self, msg);
    }
}

#[derive(Clone, Debug)]
pub struct Send;

impl Receive<Send> for SelectTestParam {
    type Msg = SelectTestParamMsg;

    fn receive(&mut self, ctx: &Context<Self::Msg>,  msg: Send, _sender: Sender) {
        println!("({})send to all: {:?}", self.id, Send);
        //let path = "/user/tester";
        //let sel = ctx.select(path).unwrap();
        //println!("({})sel: {:?}", self.id, sel);
        //sel.try_tell(Send, None);
    }
}

#[actor(Send)]
#[derive(Clone, Default, Debug)]
struct SelectTestParam{
    dummy:u32,
    id: i32,
}

impl ActorFactoryArgs<(i32, u32)> for SelectTestParam {
    fn create_args( (id, dummy): (i32, u32) ) -> Self {
        Self { id, dummy}
    }
}
impl Actor for SelectTestParam {
    type Msg = SelectTestParamMsg;

    fn pre_start(&mut self, ctx: &Context<Self::Msg>) {
        // create first child actor
        let _ = ctx.actor_of::<Child>("child_a").unwrap();

        // create second child actor
        for idx in 1..=5 {
            let name = format!("child-{}", idx);
            let _ = ctx.actor_of_args::<ChildParam,_>(&name, (0,idx)).unwrap();
        }
    }

    fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, _sender: Sender) {
        println!("!\n{}: {:?} -> got msg: {:?}", ctx.myself.name(), self, msg);
        std::thread::sleep(Duration::from_millis(100));

    }
}

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

    let actor = sys.actor_of::<SelectTest>("select-actor").unwrap();
    for idx in 5..=8 {
        let name = format!("select-actor-param-{}", idx);
        sys.actor_of_args::<SelectTestParam,_>(&name,(0, idx)).unwrap();
    }

    actor.tell("msg for select-actor", None);

    std::thread::sleep(Duration::from_millis(5000));

    sys.print_tree();
}
@hardliner66
Copy link
Contributor

The problem here is, that selection gives you a BasicActorRef. BasicActorRef is an untyped reference to an actor which internally uses Any and casts the message to the message defined type for the actor. The message type in your example is SelectTestParamMsg as seen in the Actor impl:

impl Actor for SelectTestParam {
    type Msg = SelectTestParamMsg;
    ...
}

The problem is, that the system currently only tries to casts to the exact type, which fails because the types are not the same.

Maybe it is possible to cast it to an Into<T>, because SelectTestParamMsg should implement From<Send>, but I'm not quite sure about that. I will look into it and create a pull request if I find a solution.

@hardliner66
Copy link
Contributor

In the meantime you can use the message type explicitly:

// instead of sel.try_tell(Send, None); use:
sel.try_tell(SelectTestParamMsg::Send(Send), None);

@sandro-sole
Copy link
Author

Hi @hardliner66, thank you. The solution worked out!

@leenozara
Copy link
Contributor

To add to this, an ActorSelection represents all actors in the actor hierarchy at the given path. Since each actor supports different message types ActorSelection.try_tell has no result. Some actors in the selection might support the message, others might not. If any actor in the selection doesn't support the message you're sending then you'll get a panic.

If you use BasicActorRef directly .try_tell will provide you a result, since it is just one actor, which you can then use to determine if the message sent is supported.

BasicActorRef and by extension ActorSelection are really designed to interact with the hierarchy, sending system messages, and only in niche cases, user level messaging.

You can almost always architect your system so that you use ActorRef for user level messaging. If you can't, for example because you need to map a user input to an actor then your own collection of BasicActorRef is better than a selection, since you know that you're messaging a single actor and you can handle any errors.

@igalic
Copy link
Contributor

igalic commented Jul 21, 2020

You can almost always architect your system so that you use ActorRef for user level messaging.

all of our examples currently directly reuse the sys from creating and starting the ActorSystem, and so from those examples it's not clear how to get an ActorRef from a separate part of the application.

do we have to pass that sys around?
can we get an ActorRef "by name"?

@leenozara
Copy link
Contributor

leenozara commented Jul 21, 2020

You can think of an actor system as a collection of contracts. Each actor behaves like a contract to provide a result based on state and behavior. The contract should be easy to read, with input and output, including types, well defined. This is why actor systems are popular with functional programming languages such as Erlang and Scala. By "looking up" an actor reference by path you are getting a variable from outside, e.g. not passed as a parameter. Actors are not queues, although they utilize them for message handling.

There is also a more generic paradigm called Object-Capability Model, which provides rules that limit what an object can interact with. OCM states:

The object-capability model is a computer security model. A capability describes a transferable right to perform one (or more) operations on a given object. It can be obtained by the following combination:

an unforgeable reference (in the sense of object references or protected pointers) that can be sent in messages.
a message that specifies the operation to be performed.
The security model relies on not being able to forge references.

Objects can interact only by sending messages on references.
A reference can be obtained by:

  • initial conditions: In the initial state of the computational world being described, object A may already have a reference to object B.
  • parenthood: If A creates B, at that moment A obtains the only reference to the newly created B.
  • endowment: If A creates B, B is born with that subset of A's references with which A chose to endow it.
  • introduction: If A has references to both B and C, A can send to B a message containing a reference to C. B can retain that reference for subsequent use.

The Actor Model is very similar to this and actor purists will follow OCM to the die. .select breaks the OCM model but we've provided it in Riker to provide flexibility (Akka does the same). I guess the Rust parallel would be unsafe.

Ultimately, the reason OCM fits so well for actors is that it makes nondeterministic systems (concurrent applications) appear to behave in a deterministic way, and so therefore easy to reason about. select is a great example of this. If actor A starts actors B and C, and actor C looks up actor B using select, it might be that B hasn't yet started. When C tries to message B using the ActorSelection.try_tell it might receive the message sometimes, and might not other times, depending on how the underlying OS and CPU have scheduled the work. In large applications it becomes unmaintainable.

It is this set of characteristics that set the actor model apart from other concurrent systems such as queues and make it an ideal means by which to handle state concurrently. In terms of Rust, it fits the lifetime and ownership concepts quite nicely too.

There's an excellent book that explains this and actor patterns: Reactive Messaging Patterns with the Actor Model. While the examples are provided in Scala, this very much applies to all languages using actors.

@igalic
Copy link
Contributor

igalic commented Jul 21, 2020

this ⬆️ would make a good preface in the chapter on selection

@tdrozdowski
Copy link

In the meantime you can use the message type explicitly:

// instead of sel.try_tell(Send, None); use:
sel.try_tell(SelectTestParamMsg::Send(Send), None);

Wow - I spent about a good hour in the CLion debugger to ultimately realize the mailbox was rejecting my messages. I was shocked to see we don't return any messages (logs or otherwise) to help clue people into what's going on. I do think this should be documented somewhere - I ran into it w/o a selection and just trying to communicate back between actors in a hierarchy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants