Closed Dessix closed 11 months ago
So is the main reason the factories don't work for you is cancellation?
So a few things to note that might address what you've outlined here.
kill()
the actor. That will interrupt work at the next async await point, cancelling the downstream tasks. This however isn't a super clean pattern, if you have some custom initialization logic that you might need to clean up for example. If you just want a long-running task actor, which still has a higher interactive control on it, you have basically 3 options.
kill()
primitive, and capture the death via supervision callback, then do whatever it wants to restart/exit/etc.closing as answered, feel free to re-open if you have additional concerns
While the documentation around it is quite sparse, the current implementation notes that long-running asynchronous tasks "block" the actor's message handler, and that delays or similar should be translated to timers and message events.
For use as a sort of in-app microservice architecture, this makes it difficult to run long-running tasks as an actor, as something like sending them a graceful stop request could have the message handler trigger a cancellation token stored in the actor's state, but the message pump is not being polled as long as the actor's state is claimed.
Suggested solution
If actor state were held under async lock
guard
s, messages could be queued immediately any time the state is not currently locked, allowing for actors to "yield" back to the loop any time they are not currently processing work.For any task which must be performed in a serial manner, holding the
guard
across theawait
allows the message pump to be disabled until the runtime can achieve a new lock, allowing actors to opt in to non-concurrency.This may even be simpler to implement than the above- in that the message handler could always be called by the runtime, with the expectation that any message handler that uses "state" will simply
await
a lock on the state guard. This means that messages will be processed as concurrently as possible, but has the performance drawback in that it could result in many message futures being queued and awaiting a lock on that actor's state.Potential alternatives
futures
-esque combinators atop timers to allow network-serializable poll-able eventsyield(myState)
-like construct required in order to allow concurrency by temporarily returning state control to the runtimeAdditional context
The root of the issue is that concurrency is limited by mutable access to
Actor::State
. Bevy's scheduling approach of reader / writer isolation may also be of interest here, in that queries which read a resource may be concurrent, while queries which write to that resource must be run in isolation from each other.