Hello world example with actors
HelloWorldActors is a simple program to get you started using the Coyote actors programming model.
What you will need
To run the Hello World Actors example, you will need to:
- Install Visual Studio 2022.
- Install the .NET 8.0 version of the coyote tool.
- Be familiar with the
coyotetool. See using Coyote. - Clone the Coyote git repo.
Build the sample
You can build the sample by following the instructions here.
Run the HelloWorldActors application
Now you can run the HelloWorldActors application:
"./Samples/bin/net8.0/HelloWorldActors.exe"
Press the ENTER key to terminate the program when it is done. Note that a bug has been inserted into
the code intentionally and will be caught by a Coyote Assert. The program should display 1 to 5
greetings like Hello World! or Good Morning. The intentional bug is hard to reproduce when you
run the program manually.
The typical “normal” run will look like this:
press ENTER to terminate...
Requesting 5 greetings
Received greeting: Good Morning
Received greeting: Hello World!
Received greeting: Hello World!
Received greeting: Hello World!
Received greeting: Good Morning
When the error is caught, an extra line of output is written saying:
Exception 'Microsoft.Coyote.Runtime.AssertionFailureException' was thrown in 0 (action '1'):
Too many greetings returned!
Finding the bug using Coyote test tool
See Using Coyote for information on how to
find the coyote test tool and setup your environment to use it.
Enter the following from the command line:
coyote test ./Samples/bin/net8.0/HelloWorldActors.dll --iterations 30
The result is:
. Testing .\Samples\bin\net8.0\HelloWorldActors.dll
Starting TestingProcessScheduler in process 16432
... Created '1' testing task.
... Task 0 is using 'random' strategy (seed:308255541).
..... Iteration #1
..... Iteration #2
... Task 0 found a bug.
... Emitting task 0 traces:
..... Writing .\Samples\bin\net8.0\Output\HelloWorldActors.exe\CoyoteOutput\HelloWorldActors_0_2.txt
..... Writing .\Samples\bin\net8.0\Output\HelloWorldActors.exe\CoyoteOutput\HelloWorldActors_0_2.trace
... Elapsed 0.0906639 sec.
... Testing statistics:
..... Found 1 bug.
... Exploration statistics:
..... Explored 2 schedules: 2 fair and 0 unfair.
..... Found 50.00% buggy schedules.
..... Number of scheduling decisions in fair terminating schedules: 23 (min), 30 (avg), 37 (max).
... Elapsed 0.1819877 sec.
. Done
The Coyote tester has found the bug very quickly—something that takes much longer if testing manually. When using Coyote for testing more complex, real-world systems the time savings can be huge! This will give you the ability to find and fix even the most difficult to reproduce concurrency bugs before pushing to production.
To learn more about testing with Coyote read here.
The Greeter
Please read Programming model: asynchronous actors to gain a basic understanding of the Actors programming model.
The core of an Actor based Coyote program, is, well, an Actor class! The following Actor named
Greeter is able to receive a RequestGreetingEvent to which it responds with a GreetingEvent:
[OnEventDoAction(typeof(RequestGreetingEvent), nameof(HandleGreeting))]
public class Greeter : Actor
{
/// This method is called when this actor receives a RequestGreetingEvent.
private void HandleGreeting(Event e)
{
if (e is RequestGreetingEvent ge)
{
string greeting = this.RandomBoolean() ? "Hello World!" : "Good Morning";
this.SendEvent(ge.Caller, new GreetingEvent(greeting));
if (this.RandomBoolean(10))
{
// bug: a 1 in 10 chance of sending too many greetings.
this.SendEvent(ge.Caller, new GreetingEvent(greeting));
}
}
}
}
Notice the OnEventDoAction custom attribute decorating the class. This tells the Coyote runtime
that this class expects to receive RequestGreetingEvents. These events will be queued in the
inbox for this Actor until the right time to dequeue them and process them. The custom attribute
tells the Coyote runtime to call the HandleGreeting method. The Coyote runtime will also report
an error if a different type of event is sent to the Greeter.
The HandleGreeting method is called with an Event parameter, and you know in this case the
event will be of type RequestGreetingEvent. This event is defined in Events.cs like this:
internal class RequestGreetingEvent : Event
{
public readonly ActorId Caller;
public RequestGreetingEvent(ActorId caller)
{
this.Caller = caller;
}
}
The inbox is crucial to the Actor model
You can think of an Event declaration as a type of interface definition for Actors. Events
really define the interface of an Actor because the caller will never get to see the Greeter
class, so the caller can’t just call HandleGreeting directly. The caller only gets to see an
ActorId, which is like a Coyote runtime handle to the actor.
There is a really important reason for this.
Events are queued in an inbox managed by the Actor base class. This serializes the incoming
events so that at any given time, only one event is being handled in the Greeter. This greatly
simplifies concurrent programming. For example, there is no need to use lock to protect against
data race conditions, and therefore no need to worry about deadlocks. Each Actor instance can run
in a separate thread, so all actors in the system can be hugely parallel, but at the same time the
processing inside an Actor is incredibly simple. So you get the best of both worlds, code that
scales but is also easy to write. An Actor receives messages, and can create other Actor
objects, and can send events.
Notice the Greeter gets an ActorId from the RequestGreetingEvent and uses that to send a
greeting back to the caller using SendEvent:
this.SendEvent(ge.Caller, new GreetingEvent(greeting));
You can also see the bug which has been injected deliberately, namely, it randomly returns more than
one GreetingEvent.
A TestActor
To test this Greeter you will need to setup a TestActor which is done in Program.cs. The
TestActor creates the Greeter and sends between 1 and 5 RequestGreetingEvents. The TestActor
is declared in a way that tells the Coyote runtime it is expecting to receive GreetingEvents as
follows:
[OnEventDoAction(typeof(GreetingEvent), nameof(HandleGreeting))]
To make this a good test, the TestActor keeps track of how many greetings were sent and how many
were received, and it writes an Assert to ensure it doesn’t receive too many as follows:
private void HandleGreeting(Event e)
{
// this is perfectly thread safe, because all message handling in actors is
// serialized within the Actor class.
this.Count--;
string greeting = ((GreetingEvent)e).Greeting;
Console.WriteLine("Received greeting: {0}", greeting);
this.Assert(this.Count >= 0, "Too many greetings returned!");
}
Lastly, to test the TestActor you need to fire up the Coyote runtime which is done in the HostProgram
Main entry point as follows:
public static void Main()
{
var config = Configuration.Create();
IActorRuntime runtime = RuntimeFactory.Create(config);
Execute(runtime);
runtime.OnFailure += OnRuntimeFailure;
Console.WriteLine("press ENTER to terminate...");
Console.ReadLine();
}
Notice that Coyote actors run in parallel, so you have to stop the program from terminating
prematurely, which can be done with a Console.ReadLine call. In order for this program to also be
testable using the coyote test tool, you need to declare a test method as follows:
[Microsoft.Coyote.SystematicTesting.Test]
public static void Execute(IActorRuntime runtime)
{
runtime.CreateActor(typeof(TestActor));
}
The coyote test tool will call this method. But it will not call your Main entry point. So
this Execute method needs to be standalone. It cannot depend on anything except statically
initialized variables. There is no way to get command line arguments from coyote test through to
this method.
This Execute method is very simple, it just creates the TestActor. In Coyote once an actor is
created it lives forever until it is halted. Note that Coyote programs can run forever, the coyote test
tool has ways of interrupting and restarting this Execute method based on --iterations and
--max-steps arguments provided to the test tool.
So now you know what happened when you ran the following command line:
coyote test ./Samples/bin/net8.0/HelloWorldActors.exe --iterations 30
A special coyote TestingEngine was created, it invoked the Execute method 30 times, and during
those executions the test engine took over all concurrent activity and the non-determinism (like
RandomInteger) to ensure the program covered lots of async non-deterministic choices, and recorded
all this in a way that when a bug was found, it is able to reproduce that bug. See also Using
Coyote for information on how to replay a test that found a bug
using coyote replay.
Summary
In this tutorial you learned:
- How to create an
Actorand useSendEventto send messages to it. - How an
Actorcan handle events usingOnEventDoAction. - The importance of the inbox to simplify parallel programming.
- How to run the HelloWorldActors sample from the command-line.
- How to use
Assertin Coyote code. - How to write a
[Test]method for use incoyote testruns. - How to run that test using the
coyote testtool.