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

feat(actix): Generic Addr type #1405

Merged
merged 11 commits into from
Aug 16, 2022
Merged

feat(actix): Generic Addr type #1405

merged 11 commits into from
Aug 16, 2022

Conversation

flub
Copy link
Contributor

@flub flub commented Aug 10, 2022

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.

What hasn't been done yet is extracting this out to proper shared
locations etc.

Follow up tasks

  • Move this into relay-system in a new module.
  • Benchmark whether we should box the envelopes by default.

Go home danger, you're drunk:
#skip-changelog

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?
Copy link
Member

@jan-auer jan-auer left a 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>);
Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

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.

Comment on lines +222 to +224
pub enum HealthcheckEnvelope {
IsHealthy(IsHealthy, oneshot::Sender<bool>),
}
Copy link
Member

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.

Copy link
Contributor Author

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

@flub flub force-pushed the flub/generic-addr-nobox branch from d68902c to eb65394 Compare August 12, 2022 08:21
@flub flub marked this pull request as ready for review August 12, 2022 10:09
@flub flub requested a review from a team August 12, 2022 10:09
Copy link
Member

@tobias-wilfert tobias-wilfert left a 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;
Copy link
Member

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;

Copy link
Contributor Author

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.

@flub flub merged commit a01af0d into master Aug 16, 2022
@flub flub deleted the flub/generic-addr-nobox branch August 16, 2022 13:24
tobias-wilfert added a commit that referenced this pull request Aug 18, 2022
- 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.
cmanallen pushed a commit that referenced this pull request Aug 19, 2022
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.
@HazAT HazAT added this to the Upgrade Tokio in Relay milestone Nov 21, 2022
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

Successfully merging this pull request may close these issues.

5 participants