SamuelSchlesinger / stm-actor

An implementation of the actor model in Haskell using STM
MIT License
16 stars 3 forks source link

Dealing with Dead Actors #3

Closed SamuelSchlesinger closed 4 years ago

SamuelSchlesinger commented 4 years ago

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 where data DynamicActor = forall message. Typeable message => DynamicActor (Actor message), and PersistentActorId is a newtype over whatever Ord 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 the Queue, then the Queue itself might be poisoned. Even worse, if the exception occurred inside of an STM block in receiveSTM, 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.

SamuelSchlesinger commented 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.