top | item 31200266

(no title)

JoeHegarty | 3 years ago

Many actor frameworks (including Orbit, though I advise you don't use it for the reasons in my above messages) are strictly single threaded in that only one thread can operate on an actor at any one time, and by default they are also not concurrent in that only one message gets processed at once.

However, while the single-threadedness is a hard requirement and behavior of the system, the rule that only one message is processed at a time is not.

In Orbit (and Orleans) the ability to process multiple messages at the same time is called reentrancy. it can be specified at the actor or method level usually.

So in your example, say your initial message kicks off a write that is going to take 30 seconds. You start the write and assuming it's async, the actor then suspends while it waits for that to complete. While the actor is suspended another message asking for the progress arrives and that message is reentrant, it can process that message and respond with the progress, then the actor suspends again and at some point another reentrant message arrives or the initial write finishes and the first message finally gets a response. If a none reentrant message is received it will queue in the mailbox until after the first one is completed.

So, with reentrancy you still get the single threaded guarantee but you effectively get interleaving of messages when the actor is suspended waiting for some other async task to complete. This means that if you have an actor with reentrant messages you have to make that code safe to run during any potential interleaving point in another message (for example an await in async C#).

Hopefully that made sense, Orleans has some docs about reentrancy here you might find useful: https://dotnet.github.io/orleans/docs/grains/reentrancy.html

Other actor frameworks (particularly ones that are not based on virtual actors) may have different guarantees and behaviors but from what I've seen there is always some equivalent.

discuss

order

tsss|3 years ago

Doesn't reentrancy defeat the serial processing property that makes locks unnecessary? If a new message is accepted before the previous one has finished processing, then it can change the mutable state inside the actor. Essentially, you can never know what the state will be because at any blocking point some other message can make arbitrary changes. At that point the actor is just a crappy class with message passing instead of method calls and all the annoyances that entails.

layer8|3 years ago

That makes sense, thanks for the explanation.