-
Notifications
You must be signed in to change notification settings - Fork 94
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
feat(actix): Generic Addr type #1405
Conversation
This makes `Addr` generic including the return value, just for the Healthcheck actor for now. The `HealthcheckMessage` has been renamed to `HealthcheckEnvelope` and still needs to be a manual list of all the messages the actor implements. The conversion from messages to this is now a little more involved that an `impl From`, since this also encodes the return type so you need to type this out with the `ActorMessage` trait impl. Sending to the actors is now generic, and the actors main loop also remains reasonable clean to implement.
This is a bit more readable. It also generally renames Actor -> Service since apparently we want to avoid the Actor name for some reason and align us more the Tower services at some point in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great way to model our design principles, and I'm +1 on going forward with this once we have a few more examples to evaluate this on. It's interesting how close this gets the signatures to actix again.
I left a few early comments below on the current state, to start some discussions.
pub trait ServiceMessage<S: Service> { | ||
type Response; | ||
|
||
fn into_envelope(self) -> (S::Envelope, oneshot::Receiver<Self::Response>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will have a good amount of messages that are fire-and-forget, so they do not need a oneshot receiver. Could we make the receiver optional?
The safest way would even be to type this as associated type in the service message, so that we can enforce two kinds of Addr::send
methods via the type system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to approach this with another associated type, the simplest would be to create an entire new trait like "OnewayServiceMessage" or so. Though I'm also not really sure how much we care. We can just let those messages return ()
and not await them (we can abstract this behind do_send
sure). That way you still always have the option of checking a message was actually received (I'm no fan of just swallowing the fact that the receiver might be gone, seems like a huge bug and maybe a panic would be better)
HealthcheckMessage::Health(data) => service.handle_is_healthy(data).await, | ||
match message { | ||
HealthcheckEnvelope::IsHealthy(msg, response_tx) => { | ||
let response = service.handle_is_healthy(msg).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could now introduce something like ServiceMessage::handle(self, service: &S)
which contains the async definition of the handler and returns the result. That would sadly require async_trait though. Also, I have no good answer to model the fire-and-forget case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to leave this as potentially future work. I want to wait a bit longer until I see the patterns better.
pub enum HealthcheckEnvelope { | ||
IsHealthy(IsHealthy, oneshot::Sender<bool>), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, we benchmark whether it's faster to just generally box the messages rather than having an explicit enum type. I'm still very much in favor of having the enum like this, since that takes a lot of hidden magic away and makes it very clear what's happening.
My expectation is that boxing the messages will actually improve performance. We could box them internally here, though that will increase the boilerplate.
The simpler way requiring less boiler plate would be just having a trait object, however.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, this is a followup task
d68902c
to
eb65394
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what it is worth, looks good to me.
/// | ||
/// It is an enum of all the message types that can be handled by this service together | ||
/// with the response [sender](oneshot::Sender) for each message. | ||
type Envelope: Send + 'static; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the design rationale behind having an enum, instead of making the message type a parameter of Service
, and then implementing each message handler separately?
For example
struct MyStruct;
impl Service<Shutdown> for MyStruct;
impl Service<HealthCheck> for MyStruct;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This came out of the aim to be able to send messages without having to do any boxing to put things in the channel. Whether that's always desirable that's questionable, but e.g. I think for healthcheck that probably is beneficial. Really though this should be benchmarked, this version does still allow boxing everything by default quite easily should it turn out to be more performant.
I was initially interested in building both versions to compare, I'm still on the fence myself. But once this one existed no one disliked this enough to carry on with building the other version.
- This PR updates the Store service to use the generic Addr introduced in #1405, to avoid duplicate code, it moves the shared code out to its own module in relay-system called service. - Some minor changes have been made to address the comments of Jan on #1397. ### Future Steps - With the generic Addr in place the time has come to make a new registry for the services, that replaces the hardcoded current solution.
With the generic Addr in place since #1405 this PR makes a new Register for the Services, which replaces the current hardcoded solution. Design choices: - The first approach was to use `Arc<Option<Register>>` however, this didn't work as `Arc::new` is not `const fn`. To avoid this `lazy_static` was used however that in turn didn't work as `DerefMut` was required and `lazy_static` only implements `Deref`. - A `OnceBox` is currently used for the Register which is initialised at the end of `ServiceState::start()` this avoids the need for any Options but also could cause issues in the future as the current registry is created at the beginning of the function and then gradually populated. A similar approach might need to be used in case of issues.
This makes
Addr
generic including the return value, just for theHealthcheck actor for now.
The
HealthcheckMessage
has been renamed toHealthcheckEnvelope
andstill needs to be a manual list of all the messages the actor
implements. The conversion from messages to this is now a little more
involved that an
impl From
, since this also encodes the return typeso you need to type this out with the
ActorMessage
trait impl.Sending to the actors is now generic, and the actors main loop also
remains reasonable clean to implement.
What hasn't been done yet is extracting this out to proper shared
locations etc.
Follow up tasks
relay-system
in a new module.Go home danger, you're drunk:
#skip-changelog