slawlor / ractor

Rust actor framework
MIT License
1.4k stars 68 forks source link

Stashing some type of messages during a specific state of an actor #255

Closed contrun closed 3 weeks ago

contrun commented 1 month ago

Is your feature request related to a problem? Please describe.

Sometimes I want to start the actor immediately while the actor is still not in a state of being ready to process messages. For example, I want to start the database actor without blocking the process.

Describe the solution you'd like

Make it more convenient to stash messages and process them when the actor is ready. See Stash • Akka Documentation for an example of how this is done in akka. I want something similar to this.

Describe alternatives you've considered

Using pre_start to make sure the actor is ready is an alternative not ideal in many cases. For example, the following use cases are not easily satisfied with pre_start. 1) I may want to pass the database actor in the above example to another data structure in rust, and I don't want to blockingly wait for the initialization. 2) The database actor may experience some transient failures during normal operations. I want to stash messages and process them when the database actor comes back to life.

Additional context Akka already supports this

slawlor commented 1 month ago

This exists today and it's called someone like spawn_immediate. It's a synchronous call that backgrounds the startup but creates the input channel for messages to be queued immediately.

Let me know if that works for you

slawlor commented 1 month ago

Sorry spawn_instant

https://github.com/slawlor/ractor/blob/main/ractor%2Fsrc%2Factor%2Fmod.rs#L550

But it's called from ActorRuntime not the main Actor entry point so maybe discovery could be improved on this

contrun commented 1 month ago

spawn_instant is indeed useful. But I am afraid that it can not satisfy some of the use cases.

Let's say we are building a p2p application. I want to sync the network changes first and then process the broadcasted messages that are received from the network while I was syncing. I want to start syncing first because the processing of newly broadcasted messages may depend on older network messages. The major blockage for me to use spawn_instant is that it can only initialize the actor without access to the mailbox. In my above example, while I was syncing network changes, I can't send these changes as an actor message and process them in the normal way. This will significantly complicate the message processing logic (especially when messages sent while syncing and broadcasted messages are essentially the same messages, say these are just Sync(RealMessageType) and Broadcast(RealMessageType)). It would be the best if we can stash some messages when we are not in a desired state and at the same time keep the ability to process "normal" actor messages.

slawlor commented 1 month ago

that it can only initialize the actor without access to the mailbox. In my above example, while I was syncing network changes, I can't send these changes as an actor message and process them in the normal way.

I think I'm having trouble understanding this part of it. How is it that the "syncing" here can't access the mailbox?

If you're spawning a downstream actor, you can use spawn_instant to access the mailbox in a blocking, non-async way, which you can start queueing messages while pre_start is running. If you're talking about doing some sync, while the mailbox gets populated by some other process, you'd just so this sync in pre_start where you're guaranteed messages won't be processed until pre_start is completed, then you can handle the enqueued messages.

Am I missing something here?

contrun commented 3 weeks ago

I think I made a mistake while saying stashing messages to the mailbox. What I really meant was to stash some type of messages to the mailbox. In the above example that I need to finish syncing before getting normal operation started, syncing messages and normal operation messages are all sent to the actor mailbox. It is only normal operation messages that are stashed. In this case I need to access the mailbox to process syncing messages. This process is similar to the akka example (inspecting the type of messages arrived at the mailbox, if the actor is not ready for some types, stash the message, otherwise continue to handle the messages in the normal way).

I think spawn_instant can fulfill my requirements as long as I process syncing messages in pre_start instead of sending syncing messages to the actor and processing them by accessing the mailbox.

slawlor commented 3 weeks ago

OK I think I understand better the ask.

So during actor startup, if you need the message channel available immediately, you'll want to use spawn_instant in order to defer actor startup in a non-blocking way. Otherwise in order to "stash" something, you'd just add a VecDeque or something similar to the actor's state such that the message handler can say "a message was received, but i'm not ready so stash it for later". This isn't something however I'm really keen on adding natively, as the added overhead for most users when it'll just be an empty queue is non-trivial for every actor.

The last option, is to push the message back on the actor's input channel (i.e. myself.cast()). However that won't maintain message ordering so it depends on your use-case in that one.

contrun commented 3 weeks ago

Actually I am using VecDeque to stash messages. Since it is very easy to stash the messages by myself and you said you were not very keen to do that natively, I will close this issue.