Actor semantics

An Actor in Coyote is an in-memory object. The main APIs that define the semantics of programming with actors are CreateActor, which is used to create a new Actor instance, and SendEvent that is used to pass an event to an existing Actor. It is useful to understand both the synchronous and asynchronous guarantees of these methods.

Semantics of actor creation

Suppose you create an Actor as follows:

ActorId clientId = this.CreateActor(typeof(Client));

This call synchronously creates the inbox of the actor. The initialization of the new actor happens asynchronously in the background. Think of this as follows: the call takes only as long as it takes to create an inbox. It does not wait for the initialization code of the target actor to finish. This is enough to guarantee that you can start sending messages to the new actor immediately after creating it. Basically, if you have an ActorId, then you can send messages to it. The Coyote runtime will initialize the new actor in the background, and ensure that its initialization finishes before letting the actor process its inbox.

Semantics of actor messages

Actors in Coyote have both in-order as well as causal-delivery semantics. Lets break down this guarantee into pieces. If one actor sends two messages to another actor as follows:

this.SendEvent(id, e1);
this.SendEvent(id, e2);

Then it is guaranteed that e1 will be delivered to the inbox of id before e2. This is the in-order part of the message-delivery semantics. To explain causal delivery, we need to consider three actors A, B and C. Suppose that A first sends a message e1 to C and then it sends a message e2 to B. Next, B is programmed so that whenever it receives a message e2, it will forward it to C.

e1 e2 e2 A C B

For this program, the message e1 is guaranteed to reach the inbox of C before e2. There is a simple way of thinking about this guarantee. The call to SendEvent ensures that the message is delivered to the inbox of the target before it returns. (It does not wait for the message to be processed—that can happen asynchronously.) Thus, when A sent message e1 to C, it was delivered to the inbox of C even before the message e2 was sent out. Thus, e2 had no chance of overtaking e1 to reach the inbox of C first.

Distributed systems modeling

Suppose that you have written a distributed system where each node of the system is running its own set of Coyote actors and these actors communicate over the network. When you write a Coyote test for checking end-to-end behaviors (by using a mock of the network to connect all the remote nodes together in the same test), then be sure to consider the difference in the delivery semantics of the network and the Coyote API SendEvent. Usually, real networks will provide more relaxed guarantees than SendEvent for performance reasons. Closing this gap requires modeling. For instance, a network that can either lose a message, deliver it once, or deliver it twice can be modeled as follows:

if(this.Random())
{
   this.SendEvent(destination, message);
}
if(this.Random())
{
   this.SendEvent(destination, message);
}

See also State machine semantics.