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

Change default implementation of Actor::stopping #58

Conversation

thomaseizinger
Copy link
Collaborator

@thomaseizinger thomaseizinger commented Feb 4, 2022

Returning StopAll is a dangerous default because it makes supervisor
patterns hard to implement. With StopAll, spawning a new instance of
an actor in a context if the old one dies results in the newly created
actor to also be shut down.

See get10101/itchysats@dc5c6bd.

Returning `StopAll` is a dangerous default because it makes supervisor
patterns hard to implement. With `StopAll`, spawning a new instance of
an actor in a context if the old one dies results in the newly created
actor to also be shut down.
@Restioson
Copy link
Owner

I'm okay with this in principle, but there is one footgun that would be good if we could avoid: Context::stop. I think this is probably the default stop method for single-actor configurations, and in this case StopSelf is equivalent to StopAll. This is why I made StopAll the default - so that when you spawn extra actors onto the same address, you don't need to change existing invocations of Context::stop. I'm fine with having this change, but do you have any ideas on how this footgun could be alleviated somewhat? Maybe Context::stop could be renamed a bit?

@thomaseizinger
Copy link
Collaborator Author

It is interesting that you are calling this a footgun because I would have expected Context::stop to always just stop the current actor and not all of them.

Is that not what you intended with this function?

@Restioson
Copy link
Owner

It is interesting that you are calling this a footgun because I would have expected Context::stop to always just stop the current actor and not all of them.

Maybe this is a flaw in my mental model - originally, xtra could not have multiple actors on the same address, so I guess I still think of Context::stop in this light of not having multiple actors on the address (i.e, once Context::stop is called, usually the address will stop responding to messages). Maybe it's fine to keep it like this though as it is and merge the PR.

@thomaseizinger
Copy link
Collaborator Author

thomaseizinger commented Feb 5, 2022

I haven't personally used multiple actors on the same address with xtra yet.

All of our actors track some kind of state and load-balancing all messages across all instances doesn't play well with that. For the case where we have multiple instances of the same actor running, I'd usually need to route to a very concrete instance (because its internal state is relevant to the incoming message).

It might be worthwhile to consider cloning the messages and sending it to all actors on an address. Then the instances that don't care could just drop the message.

Coming back to Context::stop: We mostly use this to shut down an actor if its internal state is somehow corrupted (and needs to be restarted by its supervisor). It would actually be even better if there would be a mechanism of terminating the actor and passing a value along in one go. These actors basically follow the RAII model.

Another use of stop for us is for actors that have a limited lifetime because they represent a "process" that is running and will eventually finish with some result, even in the happy path.

So, however it is exposed, there should IMO be a way of terminating just the one instance of the actor that is handling a message.

@Restioson
Copy link
Owner

All of our actors track some kind of state and load-balancing all messages across all instances doesn't play well with that. For the case where we have multiple instances of the same actor running, I'd usually need to route to a very concrete instance (because its internal state is relevant to the incoming message).

Ah, ok. My usecase was for database actors, or search actors where they share some data that is internally synchronised, so they are stateless in and of themselves.

It might be worthwhile to consider cloning the messages and sending it to all actors on an address. Then the instances that don't care could just drop the message.

This can be done with Context::broadcast or similar

It would actually be even better if there would be a mechanism of terminating the actor and passing a value along in one go

How do you mean passing along a value?

So, however it is exposed, there should IMO be a way of terminating just the one instance of the actor that is handling a message.

Of course :)

@thomaseizinger
Copy link
Collaborator Author

It would actually be even better if there would be a mechanism of terminating the actor and passing a value along in one go

How do you mean passing along a value?

Something like: Context::stop(ThisIsMyReturnValue).

I am not sure if it is easy to balance all the tradeoffs:

  • If we pass a value, it would be good to make it typesafe to not being able to pass more than one
  • What if the actor stops without stop being called?

So perhaps it is not great ideal overall. For RAII actors, it is usually pretty clear as to why they are stopping (some error occurred and the connection is unusable) and having to store that as state just to retrieve it again in stopping was mildly annoying.

@Restioson
Copy link
Owner

Would that return value then be passed to stopping? Is there any advantage (except ergonomics) to using the value to determine whether to stop before calling ctx.stop() in the first place, or is it mainly ergonomic?

@thomaseizinger
Copy link
Collaborator Author

Would that return value then be passed to stopping?

Either that or we directly return it.

or is it mainly ergonomic?

In some ways it is ergonomics but there is another subtly to it. Managing the Stop value for the user within Context would force us to handle all the cases why an actor can be stopped explicitly (i.e. if we stop an actor because all strong addresses are gone, stop is not called by the user but the actor will still shut down.). Thus, it could create a more easy to debug foundation for why an actor is stopping in the first place. At the moment, this is pretty intransparent and hard to debug if all you have is logs.

@Restioson
Copy link
Owner

That's true! I think it could definitely be a good change.

@thomaseizinger
Copy link
Collaborator Author

thomaseizinger commented Feb 17, 2022

I just realized an unintended side-effect of this change: StopSelf does not call mark_as_disconnected on the address and thus, a stopped actor's address still returns true for is_connected even after it has been stopped.

I'll see to add a test for this and then check if we can still return StopSelf for this.

@thomaseizinger
Copy link
Collaborator Author

I just realized an unintended side-effect of this change: StopSelf does not call mark_as_disconnected on the address and thus, a stopped actor's address still returns true for is_connected even after it has been stopped.

I'll see to add a test for this and then check if we can still return StopSelf for this.

I've opened a PR that demonstrates what I would consider to be a bug: #60

@thomaseizinger
Copy link
Collaborator Author

Closing in favor of #64.

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.

None yet

2 participants