slawlor / ractor

Rust actor framework
MIT License
1.3k stars 66 forks source link

Add support to downcast a BoxedMessage to get a reference to it's wrapped type without consuming it #211

Closed kemitix closed 4 months ago

kemitix commented 4 months ago

Is your feature request related to a problem? Please describe. I want to be able to have an Actor that other actors can subscribe to, to be notified of configuration changes. The subscribing actors still need to be able to receive their normal complement of messages. Unfortunately, Actors in ractor can only receive messages of a single type (i.e. no support for multiple implementations of a Handler<T>). The closest I've been able to come is with BoxedMessage. Unfortunately, when attempting to test the type of the contained message, the wrapper is consumed (i.e. fn from_boxed(mut m: BoxedMessage) -> Result<Self, BoxedDowncastErr>)

Describe the solution you'd like I would like to be able to get a read-only reference to the contained message if it matches the type expected, if it doesn't I can continue to test the message with other message types that my actor can handle.

As an example, the following function would give me the reference I wanted.

/// Get a reference to the wrapped message from a [BoxedMessage] if the type matches
#[cfg(not(feature = "cluster"))]
fn downcast_ref(m: &BoxedMessage) -> Option<&Self> {     
    m.msg.as_ref().and_then(|m| m.downcast_ref::<Self>())
}                                                        

This example doesn't account for when the cluster feature is enabled, which may require a different implementation.

It could then be called like this:

if let Some(settings) = Settings::downcast_ref(&message) {...}
if let Some(message) = MyMessage:downcast_ref(&message) {
   match message {
     MyMessage::Foo => {...}
     MyMessage::Bar => {...}
   }
}

Describe alternatives you've considered Adding a new UpdatedSettings(Settings) variant to the MyMessage enum, doesn't work when I have more than one subscriber, as they would expect a different enum type.

Additional context n/a

slawlor commented 4 months ago

Can you make your message type

dyn Any?

Similar to what boxed message is doing behind the scenes. It means you have to cast to the expected type within your actors handler, however that's pretty similar to what Erlang does since there is no strong typing. If you want to go that route, the supervision model will protect you from unexpected casting errors causing your actor to crash

Does that solve your need? I also have kind of stumbled across this and didn't have a great solution at the time, however I have not myself explored using the Anytype but I think it should work.

kemitix commented 4 months ago

I'll have a look at using Any and see.

the supervision model will protect you from unexpected casting errors causing your actor to crash

Having the Actor crash on a bad cast would prevent the next cast attempt from being tried, no? I may be misunderstanding you.

I'll try adding the Any and see where that gets me.

kemitix commented 4 months ago

That appears to be working for me. Pity to loose some of the strong typing, but that'd be the same with BoxedMessage.

Thanks for the advice.

slawlor commented 4 months ago

Yes you are right, on a crash you will lose the pending message queue as it'll be cleaned up with actor teardown. If you want to persist the message queue, usually what we would do is have a supervisor, of that single actor which is trying the downcast, which manages the queue and only allows the child to process one message at a time and maintain no queue.

It would sort of be like a factory of one, where the factory maintains an internal state of the messages in queue, and the child is a worker. If the child crashes, the supervisor can capture the crash and restart the child without losing the pending message queue