State machines

A Coyote state machine is a special type of Actor that inherits from the StateMachine class which lives in the Microsoft.Coyote.Actors namespace. A state machine adds State semantics with explicit information about how Events can trigger State changes in a StateMachine. You can write a state machine version of the Server class shown in Programming model: asynchronous actors like this:

class ReadyEvent : Event { }

class Server : StateMachine
{
    [Start]
    [OnEntry(nameof(InitOnEntry))]
    [OnEventGotoState(typeof(ReadyEvent), typeof(Active))]
    class Init : State { }

    void InitOnEntry()
    {
        this.RaiseEvent(new ReadyEvent());
    }

    [OnEventDoAction(typeof(PingEvent), nameof(HandlePing))]
    class Active : State { }

    void HandlePing(Event e)
    {
        var pe = (PingEvent)e;
        Console.WriteLine("Server received ping event from {0}", pe.Caller.Name);
        this.SendEvent(pe.Caller, new PongEvent());
    }
}

The above class declares a state machine named Server. The StateMachine class itself inherits from Actor so state machines are also actors and, of course, state machines are also normal C# classes. Actors and StateMachines can talk to each other by sending events. State machines in Coyote must also declare one or more states where a state is a nested class that inherits from the coyote State class which is a nested class inside StateMachine. The nested state classes can be private.

The above code snippet declares two states in the Server machine: Init and Active. You must use the Start attribute to declare one of the states the initial state, which will be the first state that the machine will transition to upon initialization. In this example, the Init state has been declared as the initial state of Server. A state declaration can optionally be decorated with a number of state-specific attributes, as seen in the [Init] state:

[OnEntry(nameof(InitOnEntry))]
[OnEventGotoState(typeof(ReadyEvent), typeof(Active))]

The OnEntry attribute denotes an action that will be executed when the machine transitions to the Init state, while the OnExit attribute denotes an action that will be executed when the machine leaves the state. Actions in Coyote are C# methods that take either no input parameters or a single input parameter of type Event, and return either void or async Task. OnExit actions cannot receive an Event argument. Note that Coyote actions are also referred to as event handlers, however these should not be confused with the System.EventHandler, which have a different prototype.

Notice that the InitOnEntry method declared above is similar to the original OnInitializeAsync method on the Server Actor. The RaiseEvent call is used to trigger the state transition defined in the OnEventGotoState custom attribute, in this case it is ready to transition to the Active state:

this.RaiseEvent(new ReadyEvent());

The RaiseEvent call is used to send an event to yourself. Similar to SendEvent, when a machine raises an event on itself, it is also queued so that the method can continue execution until the InitOnEntry method is completed. When control returns to the coyote runtime, instead of dequeuing the next event from the inbox (if there is one), the machine immediately handles the raised event (so raised events are prioritized over any events in the inbox). This prioritization is important in the above case, because it guarantees that the Server will transition to the Active state before the PingEvent is received from the Client.

The attribute OnEventGotoState indicates that if the state machine receives the ReadyEvent event while it is currently in the Init state, it will automatically handle the ReadyEvent by exiting the Init state and transitioning to the Active state. This saves you from having to write that trivial event handler logic.

All this happens as a result of the simple RaiseEvent call and the OnEventGotoState attribute. The Coyote state machine programming model takes a lot of tedium out of managing explicit state machinery. If you ever find yourself building your own state machinery, then you definitely should consider using the Coyote state machine class instead. Note that on a given State of a state machine, you can only define one handler for a given event type.

When you run this new StateMachine based Server you will see the same output as before, with the addition of the state information from HandlePong:

Program+Client(2) initializing
Program+Client(2) sending ping event to server
Program+Client(1) initializing
Program+Client(1) sending ping event to server
Program+Client(3) initializing
Program+Client(3) sending ping event to server
Server received ping event from Program+Client(2)
Server received ping event from Program+Client(1)
Server received ping event from Program+Client(3)
Program+Client(2) received pong event
Program+Client(3) received pong event
Program+Client(1) received pong event

Unlike Actors which declare the events they can receive at the class level, StateMachines can also declare this information on the States. This gives StateMachines more fine grained control, for example, perhaps you want your state machine to only be able to receive a certain type of event when it is in a particular state. In an Actor you would need to check this yourself and throw an exception, whereas in a state machine this is more declarative and is enforced by the Coyote runtime; the Coyote runtime will report an error if an event is received on a State of a StateMachine that was not expecting to receive that event. This reduces the amount of tedious book keeping code you need to write, and keeps your code even cleaner.

For an example of a state machine in action see the state machine demo.

Goto, push and pop states

Besides RaiseEvent, state machine event handlers can request a state change in code rather than depending on OnEventGotoState attributes. This allows conditional goto operations as shown in the following example:

void InitOnEntry()
{
    if (this.Random())
    {
        this.RaiseGotoStateEvent<Active>();
    }
    else
    {
        this.RaiseGotoStateEvent<Busy>();
    }
}

State machines can also push and pop states, effectively creating a stack of active states. Use [OnEventPushState(...)] or RaisePushStateEvent in code to push a new state:

this.RaisePushStateEvent<Active>();

This will push the Active state on the stack, but it will also inherit some actions declared on the Init state. The Active state can pop itself off the stack, returning to the Init state using a RaisePopStateEvent call:

void HandlePing()
{
    Console.WriteLine("Server received ping event while in the {0} state", 
        this.CurrentState.Name);
    // pop the current state off the stack of active states.
    this.RaisePopStateEvent();  
}

Note that this does not result in the OnEntry method being called again, because you never actually exited the Init state in this case. But if you used RaiseGotoStateEvent instead of RaisePushStateEvent and RaisePopStateEvent then InitOnEntry will be called again, and that would make our Server toggle back and forth between the Init and Active states.

The push and pop feature is considered an advanced feature of state machines. It is designed to help you reuse some of your event handling code, where you can put “common event handling” in lower states and more specific event handling in pushed states. If an event handler is defined more than once in the stack, the one closest to the top of the stack is used.

Only one Raise* operation per action

There is an important restriction on the use of the following. Only one of these operations can be queued up per event handling action:

RaiseEvent
RaiseGotoStateEvent
RaisePushStateEvent
RaisePopStateEvent
RaiseHaltEvent

A runtime Assert will be raised if you accidentally try and do two of these operations in a single action. For example, this would be an error because you are trying to do two Raise operations in the InitOnEntry action:

void InitOnEntry()
{
    this.RaiseGotoStateEvent<Active>();
    this.RaiseEvent(new TestEvent());
}

Deferring and ignoring events

Coyote also provides the capability to defer and ignore events while in a particular state:

[DeferEvents(typeof(PingEvent), typeof(PongEvent))]
[IgnoreEvents(typeof(ReadyEvent))]
class SomeState : State { }

The attribute DeferEvents indicates that the PingEvent and PongEvent events should not be dequeued while the machine is in the state SomeState. Instead, the machine should skip over PingEvent and PongEvent (without dropping these events from the queue) and dequeue the next event that is not being deferred. Note that when a state decides to defer an event a subsequent pushed state can choose to receive that event if it wants to, but if the pushed state chooses not to receive the event then it is not an error and it remains deferred.

The attribute IgnoreEvents indicates that whenever ReadyEvent is dequeued while the machine is in SomeState, then the machine should drop ReadyEvent without invoking any action. Note that when a state decides to ignore an event a subsequent pushed state can choose to receive that event if it wants to, but if the pushed state chooses not to receive the event then it is not an error and the event will be ignored and dropped.

Default events

State machines support an interesting concept called default events. A state can request that something be done by default when there is nothing else to do.

[OnEventDoAction(typeof(DefaultEvent), nameof(OnIdle))]
class Idle : State { }

public void OnIdle()
{
    Console.WriteLine("OnIdle");
}

The Coyote runtime will invoke this action handler when Idle is the current active state and the state machine has nothing else to do (the inbox has no events that can be processed). If nothing else happens, (no other actionable events are queued on this state machine) then the OnIdle method will be called over and over until something else changes. It is more efficient to use CreatePeriodicTimer for low priority work.

Default events can also invoke goto, and push state transitions, which brings up an interesting case where you can actually implement an infinite ping pong using the following:

internal class PingPongMachine : StateMachine
{
    [Start]
    [OnEntry(nameof(OnPing))]
    [OnEventGotoState(typeof(DefaultEvent), typeof(Pong))]

    public class Ping : State { }

    public void OnPing()
    {
        Console.WriteLine("OnPing");
    }

    [OnEntry(nameof(OnPong))]
    [OnEventGotoState(typeof(DefaultEvent), typeof(Ping))]
    public class Pong : State { }

    void OnPong()
    {
        Console.WriteLine("OnPong");
    }
}

The difference between this and a timer based ping-pong is that this will run as fast as the Coyote runtime can go. So you have to be careful using DefaultEvents like this as it could use up a lot of CPU time.

WildCard events

State machines also support a special WildcardEvent which acts as a special pattern matching event that matches all event types. This means you can create generic actions, or state transitions as a result of receiving any event (except the DefaultEvent).

The following example shows how the WildcardEvent can be used:

internal class WildMachine : StateMachine
{
    [Start]
    [OnEntry(nameof(OnInit))]
    [OnEventGotoState(typeof(WildCardEvent), typeof(CatchAll))]

    public class Init : State { }

    public void OnInit()
    {
        Console.WriteLine("Entering state {0}", this.CurrentStateName);
    }

    [OnEntry(nameof(OnInit))]
    [OnEntry(nameof(OnCatchAll))]
    [OnEventDoAction(typeof(WildCardEvent), nameof(OnCatchAll))]
    public class CatchAll : State { }

    void OnCatchAll(Event e)
    {
        Console.WriteLine("Catch all state caught event of type {0}", e.GetType().Name);
    }
}

The client of this state machine can send any event it wants and it will cause a transition to the CatchAll state where it will be handled by the OnCatchAll method. For example:

class X : Event { };
var actor = runtime.CreateActor(typeof(WildMachine));
runtime.SendEvent(actor, new X());

And the output of this test is:

Entering state Init
Entering state CatchAll
Catch all state caught event of type X

Precise semantics

There is a lot of interesting combinations of things that you can do with DeferEvents, IgnoreEvents, OnEventDoAction, OnEventGotoState or OnEventPushState and WildcardEvent. The following gives the precise semantics of these operations with regards to push and pop.

First of all only one action per specific event type can be defined on a given State, so the following would be an error:

[DeferEvents(typeof(E1), typeof(E2))]
[OnEventDoAction(typeof(E1), nameof(HandleE1))]
class SomeState : State { }

Because the E1 has both a DeferEvents and OnEventDoAction defined on the same state.

Second, a pushed state inherits DeferEvents, IgnoreEvents, OnEventDoAction actions from all previous states on the active state stack, but it does not inherit OnEventGotoState or OnEventPushState actions.

If multiple states on the stack of active states define an action for a specific event type then the action closest to the top of the stack takes precedence. For example:

[DeferEvents(typeof(E1))]
[OnEventPushState(typeof(E1), typeof(S2))]
class A : State { }

[OnEventDoAction(typeof(E1), nameof(HandleE1))]
class B : State { }

In state B the OnEventDoAction takes precedence over the inherited DeferEvents for event E1.

On a given state actions defined for a specific event type take precedence over actions involving WildcardEvent but a pushed state can override a specific event type action with a WildcardEvent action.

If an event cannot be handled by a pushed state then that state is automatically popped so handling can be attempted again on the lower states. If this auto-popping pops all states then an unhandled event error is raised.