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

message not received by Actor when LocalActorRef::notify is used #7

Open
b-dreissigacker opened this issue Jan 6, 2023 · 8 comments

Comments

@b-dreissigacker
Copy link

Hi,
I need to send a message to an actor in a non-async function because a struct from an external crate that I use does not implement Send.

From the signature, it looks like I could use LocalActorRef::notify for that and the code compiles with it but the message never reaches the actor.

I cannot share the actual code but it looks something like this:

impl Handler<StartMessage> for Actor1 {
    async fn handle(
        &mut self,
        _message: StartMessage,
        _ctx: &mut ActorContext,
    ) -> Result<(), String> {
        let ui_actor_ref = self.actor2_ref.clone();
        tokio::spawn(async move { start(actor2_ref) });
        Ok(())
    }
}

 fn start(
    actor2_ref: LocalActorRef<Actor2>,
) -> Result<(), String> {
        // dose some other stuff
        actor2_ref
            .notify(StatusEvent {
                event_type: StatusEventTypes::Progress,
                payload: status.to_string(),
                )
                .unwrap(),
            })
            .unwrap();
}

Do I misunderstand notify?
Is there another blocking way to send messages to Actors?

@b-dreissigacker b-dreissigacker changed the title message not recoeved by actor when LocalActorRef::notify is used message not received by actor when LocalActorRef::notify is used Jan 6, 2023
@b-dreissigacker b-dreissigacker changed the title message not received by actor when LocalActorRef::notify is used message not received by Actor when LocalActorRef::notify is used Jan 6, 2023
@b-dreissigacker
Copy link
Author

b-dreissigacker commented Jan 9, 2023

I found a solution for my problem using tokio::task::block_in_place and send. But I still think there is something wrong with notify or I don't get how to use it.

If someone runs into a similar problem here's my solution:

        tokio::task::block_in_place(move || {
            tokio::runtime::Handle::current().block_on(async move {
              actor2_ref
                .send(StatusEvent {
                    event_type: StatusEventTypes::Progress,
                    payload: status.to_string(),
                    )
                    .unwrap(),
                })
               .await
               .unwrap()
               .unwrap();
            });
        });

@LeonHartley
Copy link
Owner

Hi @b-dreissigacker!

notify will send the message to the actor's mailbox but won't wait for it to be processed or handled, think of it like a fire-and-forget.

If the process exits once the notify has been sent, there's no guarantee that the message will be processed as this will happen in a seperate context. Do you think a send_blocking function or something like that would be useful?

@LeonHartley
Copy link
Owner

Here's 3 examples on how notify works:

This uses the notify_exec method, which just sends an Exec message via notify, rather than send:
https://github.com/LeonHartley/Coerce-rs/blob/dev/coerce/tests/test_actor_messaging.rs#L123

This sends 5 messages via notify, and then sends another Exec, but this time awaiting for the result. This means that the caller is only waiting for the last message to be processed, rather than having to wait for each message to be processed individually:
https://github.com/LeonHartley/Coerce-rs/blob/master/coerce/tests/test_persistent_recovery.rs#L93

This notifies the actor to StopAll, but doesn't wait for that to be processed, it instead waits for a oneshot channel message:
https://github.com/LeonHartley/Coerce-rs/blob/master/coerce/tests/test_actor_supervision.rs#L134

@b-dreissigacker
Copy link
Author

notify will send the message to the actor's mailbox but won't wait for it to be processed or handled, think of it like a fire-and-forget.

That's what I expected it to do.
I put a dbg!() in the Handler function of the Actor and it was never executed with the message I send with notify, but other messages sent from a third Actor with send were executed.

A send_blocking would certainly be useful in some cases and would be a better workaround than my current solution, but it does not explain why the message I sent with notify was never handled. The message I was sending would only update the state of the attached UI so I don't need to wait for the message to be processed.

@LeonHartley
Copy link
Owner

LeonHartley commented Jan 13, 2023

Hey @b-dreissigacker,

Does your application exit straight after the notify was sent? Since notify doesn't wait for completion, it's likely that the process isn't staying up long enough for a worker thread to process the message. Just before the process exits, I would suggest running shutdown() on the actor system, or at least stopping the actor via actor_ref.stop().

I will add in some blocking APIs in the next release to improve things for use-cases like yours.

@uwejan
Copy link

uwejan commented Apr 29, 2023

Hey @LeonHartley
The following test hangs. Can you have a look please.

#[async_trait]
impl Handler<GetCounterRequest0> for TestActor {
    async fn handle(&mut self, _message: GetCounterRequest0, _ctx: &mut ActorContext) -> i32  {
        self.actor_ref(_ctx).send(GetCounterRequest2()).await.unwrap().unwrap()
    }
}

#[async_trait]
impl Handler<GetCounterRequest2> for TestActor {
    async fn handle(&mut self, _message: GetCounterRequest2, _ctx: &mut ActorContext) -> Result<i32, Error> {
        Ok(42)
    }
}


#[tokio::test]
pub async fn test_actor_req_res_multiple_actors() {
    let ctx = ActorSystem::new();

    let test_ref = ctx.new_anon_actor(TestActor::new()).await.unwrap();
    let echo_ref = ctx.new_anon_actor(EchoActor::new()).await.unwrap();

    let test_res = test_ref.send(GetCounterRequest0()).await;
    let echo_res = echo_ref.send(GetCounterRequest()).await;

    assert_eq!(test_res, Ok(42));
    assert_eq!(echo_res, Ok(42));
}


running 8 tests
test test_messages_into_remote_envelope ... ok
test test_actor_req_res ... ok
test test_actor_exec_chain_mutation ... ok
test test_actor_notify ... ok
test test_actor_exec_mutation ... ok
test test_actor_receiver ... ok
test test_actor_req_res_mutation ... ok
test test_actor_req_res_multiple_actors has been running for over 60 seconds

@LeonHartley
Copy link
Owner

LeonHartley commented Apr 29, 2023

Hello @uwejan,

That looks like a sort of deadlock. The actor is still busy processing the first message, so will be blocked until the handling is complete, the second message is still waiting in the actor's mailbox/channel.

You could either use notify to send a message to self, without waiting for it to be processed, or you could call the method directly and not have it send a message to self, in general I would discourage sending messages to self unless absolutely necessary, from my experience it can massively overcomplicate things.

@uwejan
Copy link

uwejan commented Apr 30, 2023

@LeonHartley Yes exactky my thoughts.

  1. How about child to child messaging?
    Both of actorB and actorC are children of actorX, how can A,B message eachother? One way I am thinking is to add b as child of c and vice versa. Please corrent me.
  2. Is there a way for actor properties that does not allow Send, for example one of my actors to have libturbojpeg compressor instance but it does not allow Send. It is triviail to have it. How would you go about it?

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

3 participants