Closed SamuelSchlesinger closed 4 years ago
Ah, but the immortal actor approach has a bug: when the new actor is created, their message queue is the same, but their ThreadId
is different. Thus, we would need to hide the ThreadId
from the user to implement it, and I think this is a stupid thing to do, as it would abstract this model away from the underlying concurrency primitives. A better solution is to make dead actors more obvious, i.e. closing their Queue
or having a TVar
with a Bool
indicating whether or not they are alive or dead. The former would require changes to the stm-queue
library and the latter would not, and they don't feel particularly different. I think the send
and addAfterEffect
operations should throw ActorDeadHere'sWhy
exceptions (maybe with a better name, maybe not) with the exception that killed them as a payload.
In the example program in the README, what happens if the etcd update receiver dies? Well, their queue builds up and the actor sending them things gleefully continues to send things to the inbox without an owner. This can be fixed in a number of ways, each with their own costs and benefits.
For one, we can keep the model as is, and introduce a model of addressing that is centralized, something like DNS in-memory. That would probably best be done ad hoc in individual programs, not introduced as a part of this library. That may look like a
Map PersistentActorId DynamicActor
wheredata DynamicActor = forall message. Typeable message => DynamicActor (Actor message)
, andPersistentActorId
is a newtype over whateverOrd
thing, for instance.Another solution is allowing for persistent actors which respawn upon their thread being killed, resuming their consumption at the queue that they were sipping from upon death. This solution must be implemented in the library as-it-exists, or requires changes to the offered interface, as we don't allow access to the underlying
Queue
of an actor. The problems with this are multi-fold: well, why did the actor die? If it was because of a message it tried to consume off of theQueue
, then theQueue
itself might be poisoned. Even worse, if the exception occurred inside of anSTM
block inreceiveSTM
, the message that killed the actor might still be there. Thus, the actor will be respawned and die, respawned and die, over and over and over again. On the other hand, this also strikes me as an example of unstructured concurrency, and reminds me of Notes on Structures Concurrency, or: Go statement considered harmful, which I agree with for the most part.In general, I'm feeling rather torn! The former approach is much more similar to what erlang does, but that is in a very different context where actors really are talking over a network and the comparison to DNS is a lot more warranted. Thankfully, implementing the latter approach does leave the former still usable, as long as you avoid the immortal actor combinators. It does seem like I want to offer some ability to resume consumption on a particular queue, and not drop messages upon thread death, so I think I will end up implementing the latter.