Actors
Describes the XOOM platform Reactive foundation and demonstrates how it is used.
In 1973, Dr. Carl Hewitt and his colleagues formulated the Actor Model. In recent years, the inventor of object orientation, Alan Kay, has stated that the Actor Model retained more of what he thought were the important object ideas. So, when you think of the Actor Model, think of objects done right.

A Resource actor sending a command message to a Model Entity actor.
The XOOM Actors toolkit is an implementation of the Actor Model, and all primary platform components are built on our Actor Model implementation. Read on for detailed information on the use of XOOM Actors.
XOOM Actors is an implementation of the Actor Model. The ideas behind the Actor Model are pretty simple, and these points show how XOOM Actors implement it.
- 1.The basic unit of computation is expressed through actors. Actors are basically objects, but their behaviors are requested by sending messages asynchronously rather than through directly invoking their methods. This enables all communication between actors to be performed through asynchronous messaging passing. Determined by a scheduler, each actor that has been sent a message will be given the opportunity to receive and process it, which also happens asynchronously.
- 2.Actors can create other actors. As Carl Hewitt is known to say, “One actor is no actors. Actors come in systems.” Thus, your applications should use not just some actors, but many actors. Understand that once you start down the road of asynchronous behaviors, you are all in. Just as you don’t kind of go swimming, because you are either completely wet, or you are not, you don't kind of use actors. If you try to fight the asynchrony, you will experience pain and software with very strange bugs.
- 3.Each actor can designate the behavior it will exhibit for the next message it receives. This is roughly the State pattern. Any actor using the XOOM Actors toolkit can dynamically become another kind of actor in preparation for handling its current state and any subsequent messages.
- 4.What is most unique about the XOOM Actors implementation is type safety by design, and the simplicity with which is it implemented and consumed. All that a programmer needs to understand is interfaces and implementation classes, and they get the asynchrony for free.
- 5.Most other actor implementations use a receive method or code block that takes Object (or Any) as a parameter. Thus, the receive needs to determine which messages to accept and which ones are not permitted at any given time; this may be especially necessary when the actor designates its next behavior (it becomes another type of actor). Also what is unique about XOOM Actors is that the current designated behavior can be based on a different interface that is implemented by the actor. In other words, any one actor can implement multiple interfaces and receive messages for any given interface when it chooses to.
- 6.Most modern actor model implementations use mailboxes to deliver messages. Each actor has a mailbox where messages are received into a FIFO queue, and each message is processed one at a time on an available thread. This is true for XOOM Actors, yet there are special kinds of mailboxes that have certain advantages (and possibly disadvantages).
- 7.You can tune your actor’s world and stage to support any number of threads, but it’s best to limit this number based on the available number of processor hyper-threaded cores, or a bit more. Fundamentally, you can’t run more threads simultaneously than there are available cores, e.g.
Runtime.getRuntime().availableProcessors()
.
Using objects in a typical fashion, such as with Java or C#, we have become accustomed, even addicted, to a blocking paradigm.

A Client object invoking a method on a Server object, which blocks the Client.
Here a Client object invokes a method on a Server object. Understand that this is an in-process (in-VM) invocation, not a remote client and a remote server. The point is, when a method invocation occurs, the Client is blocked until the Server returns from the method invocation. In contrast, the Actor Model works differently.

The Actor Model is message-driven and processes messages asynchronously.
When the Sender actor wants another actor to provide a service, it sends that actor a message. The message is sent to the Receiver actor and handled asynchronously, but not until a thread is available. The Sender continues moving forward with its current activities, and when completed returns from its own message handling.
As previously stated, with the various Actor Model implementations (e.g. Erlang and Elixir), neither messages or the message receiver are strongly typed. Yet, with XOOM Actors type-safe messages are the fundamental building block, not an experimental afterthought. A strongly-typed Actor Model implementation is important at this time when type safety is in high demand and can provide much more reliable systems.
With its careful but simple design, XOOM Actors are a great foundation on which to build the other tools in the XOOM platform.
Actors collaborate by sending messages, one actor to another. When there are hundreds, thousands, or millions of actors, there are many actors sending messages simultaneously. Still, any one actor can send only one message to one other actor at a time. Following that, the same actor can send a message to the same actor or a different actor. This fulfills the first point of the following description of the actor message-receiving contract.
An actor is a computational entity that, in response to a message it receives, can concurrently:
send a finite number of messages to other actors; create a finite number of new actors; designate the behavior to be used for the next message it receives.There is no assumed sequence to the above actions and they could be carried out in parallel.
Actors also must be able to fulfill the second and third points: actors can create child actors, and actors can prepare themselves for subsequent message receipt.
Now consider a brief tutorial on XOOM Actors. This tutorial takes you through preparing your build environment and also how to implement two actors that collaborate to accomplish a goal.
To get started, create your own playground project to work with. You can name this project
playground
. If you use Maven, place a dependency into your playground’s pom.xml
file. ...
<dependencies>
<dependency>
<groupId>io.vlingo.xoom</groupId>
<artifactId>xoom-actors</artifactId>
<version>x.y.z</version>
</dependency>
</dependencies>
...
If you prefer Gradle, insert the following into your
build.gradle
.dependencies {
compile 'io.vlingo.xoom:xoom-actors:x.y.z'
}
repositories {
jcenter()
}
The
x.y.z
is a semantic version number and reflects the version of xoom-actors JAR file that you depend on. This number may be something such as 1.8.0
. You will find the available versions, including the most recent version, available on one of the supported public repositories.Additionally, add a JUnit dependency into your build script since the tutorial uses JUnit to run the actor collaboration.
...
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
...
Or if using Gradle:
dependencies {
compile 'junit:junit:4.11'
}
Although there are a few different ways to configure the XOOM Actors runtime environment, we will skip that step here. It's easier to start with reasonable defaults instead. The configuration approaches are presented later.
Without delay, consider the really fun part—the programming. You are going to create a really basic Ping Pong game.

The Pinger and Ponger send message to and receive messages from each other.
You need to create a few Java interfaces and classes. There’s a Java interface that acts as the type safe messaging protocol of the first actor that you will create. For now, create a
Pinger
interface with a single method definition, named ping()
, which takes a Ponger
as a parameter.package playground;
import io.vlingo.xoom.actors.Stoppable;
public interface Pinger extends Stoppable {
void ping(final Ponger ponger);
}
Next create a
Ponger
interface the same way, but with a pong()
method that takes a Pinger
as a parameter.package playground;
import io.vlingo.xoom.actors.Stoppable;
public interface Ponger extends Stoppable {
void pong(final Pinger pinger);
}
Now you have two protocols or two different actors. These define the type-safe behaviors that one or more actors will implement, and the means by which clients will interact with the actors. In case it’s not obvious,
Pinger
is a client of Ponger
, and Ponger
is a client of Pinger
.It’s time to create two simple actors. First create one to implement the
Pinger
protocol.package playground;
import io.vlingo.xoom.actors.Actor;
public class PingerActor extends Actor implements Pinger {
private final Pinger self;
public PingerActor() {
self = selfAs(Pinger.class);
}
public void ping(final Ponger ponger) {
ponger.pong(self);
}
}
After that, create another actor to implement the
Ponger
protocol.package playground;
import io.vlingo.xoom.actors.Actor;
public class PongerActor extends Actor implements Ponger {
private final Ponger self;
public PongerActor() {
self = selfAs(Ponger.class);
}
public void pong(final Pinger pinger) {
pinger.ping(self);
}
}
You now have two actors that collaborate to play ping pong. The problem is that these actors will play ping pong nonstop, forever, unless we do something to prevent that. Doing so demonstrates how actors can maintain their own state, just like typical objects.
package playground;
import io.vlingo.xoom.actors.Actor;
public class PingerActor extends Actor implements Pinger {
private int count;
private final Pinger self;
public PingerActor() {
count = 0;
self = selfAs(Pinger.class);
}
public void ping(final Ponger ponger) {
if (++count >= 10) {
self.stop();
ponger.stop();
} else {
ponger.pong(self);
}
}
}
Looking back at the
Pinger
and Ponger
interface definitions, you will notice that both of these protocols extend the Stoppable
protocol. Thus, they can both be stopped by other actors that have a Stoppable
reference to them. We use that capability from within PingerActor
to cause both actors to stop when the count
reaches 10
.Note that in this case the actors are not required to implement their own
stop()
methods. That’s because the abstract base class, Actor
, implements stop()
for them. You could override stop()
to find out when your actor is being stopped, but that’s not necessarily a good idea. What if you forgot to invoke the super’s stop()
? That would make you think that your actor was going to stop, but the actor would never shut down because the Actor
base class behavior would never be run. If you want to know when you are being stopped, you can override one of the four life cycle methods instead of stop()
.package playground;
import io.vlingo.xoom.actors.Actor;
public class PingerActor extends Actor implements Pinger {
private int count;
private final Pinger self;
public PingerActor() {
count = 0;
self = selfAs(Pinger.class);
}
public void ping(final Ponger ponger) {
if (++count >= 10) {
self.stop();
ponger.stop();
} else {
ponger.pong(self);
}
}
@Override
protected void afterStop() {
logger().log("Pinger " + address() + " just stopped!");
super.afterStop();
}
}
All five life cycle methods are:
beforeStart()
afterStop()
beforeRestart(final Throwable reason)
afterRestart(final Throwable reason)
beforeResume(final Throwable reason)
These enable you to see when significant life cycle events occur with your actor. The restart life cycle methods are related to actor supervision. When your actor’s supervisor sees your actor failed with an
Exception
, it can take a number of actions. Your supervisor can tell your actor to resume, to stop, or to restart. If it tells your actor to resume, the beforeResume()
is invoked. When it tells your actor to restart, the beforeRestart()
is invoked first, and then the afterRestart()
is invoked. Since your actor has failed, it may have been left in an invalid state. In such cases, these three life cycle methods give your actor the opportunity to clean up after the problem that caused the Exception
and also reinitialize itself before reacting to its next available protocol message.The
Exception
recovery methods DO NOT cause the Actor
instance to be completely discarded and recreated. Therefore, it is the responsibility of the Actor
to set its state to a safe point before message processing resumes.The above
afterStop()
method shows two additional perks of XOOM Actors. All actors have a personal address, which is available through your inherited address()
method. Also, all actors have a Logger
available via its logger()
method. Any information that you log will be output asynchronously through a registered Logger
actor, so your actor won't block while file output is performed.Alright, we have two actors, but how do we bring the actors to life in the first place, and how do we get them to start collaborating in game play? Here’s how you start up the
World
for your actors to play in.package playground;
import org.junit.Test;
import io.vlingo.xoom.actors.Definition;
import io.vlingo.xoom.actors.World;
public class PlaygroundTest {
@Test
public void testPlayPingPong() {
final World world = World.startWithDefaults("playground");
final Pinger pinger = world.actorFor(Pinger.class, PingerActor.class);
final Ponger ponger = world.actorFor(Ponger.class, PongerActor.class);
pinger.ping(ponger);
pauseThisThread();
world.terminate();
}
}
When this test is run, a
World
is created. The World
is a major component of XOOM Actors. In a nutshell, a World
is the primary container within which actors live and play. Generally you would create only one World
per service instance. In DDD terms, a World
is the root of a Bounded Context. (Don't worry about the use of pauseThisThread()
; it is explained below.)After the
World
is started, two actors are created, and a reference to their respective protocol is returned. Each actor is created by passing its protocol and the concrete actor type. You may also create actors by means of a Definition
. The Definition
indicates the class of the actor that implements the protocol, such as PingerActor.class
, which implements the Pinger.class
protocol. There are four ways to instantiate an actor by means of its constructor:- Design the actor with a zero-parameter constructor, which is the case in the above example.
- Pass the implementation class type as the second parameter to
actorFor()
as seen above, and also pass each constructor parameter following the implementation class type. This has the advantage of making the parameters visible, but they are passed as varargs each of typeObject
, and are thus not type-safe. - Create and pass a
Definition
object as the second parameter toactorFor()
. TheDefinition
contains the class of the actor implementation and a possibly one or more constructor parameters, or it can passDefinition.NoParameters
. See the next code example for how to pass constructor parameters. This approach is also not type safe. - All three of the above actor instantiation options use reflection to call the actor's constructor. Use of reflection can be avoided and at the same time also provide absolute type-safe constructor parameters. To accomplish this, implement a factory for your various actor types using the
ActorInstantiator
, a functional interface included in thexoom-actors
SDK and runtime. Construct your specificActorInstantiator
type, pass any actor constructor parameters into theActorInstantiator
constructor. When the actor is ready to be created by the runtime, theActorInstantiator
method blah will be called. At that time call the specific actor's constructor when usingnew
. (See example provided below.)
With these options available, consider the following (non-working) example of the
PingerActor
taking two parameters, a String
and an int
, using the Definition
approach: final Pinger pinger = world.actorFor(
Pinger.class,
Definition.has(
PingerActor.class,
Definition.parameters("Hey, yo!", 42),
"name-that-actor"));
The simplest way to create an actor with constructor parameters is by means of the following shorthand method, but with the downside that the parameters are not checked for type safety by the compiler but instead at runtime when matching parameter types to a specific constructor:
final Pinger pinger = world.actorFor(Pinger.class, PingerActor.class, "Hey, yo!", 42);
The following example employs a type-safe
ActorInstantiator
, which does not require the use of reflection:public class PingerInstantiator implements ActorInstantiator<PingerActor> {
private final String message;
private final int value;
public ProtocolInstantiator(final String message, final int value) {
this.message = message;
this.value = value;
}
@Override
public ProtocolActor instantiate() {
return new PingerActor(message, value);
}
@Override
public Class<PingerActor> type() {
return PingerActor.class;
}
}
There are three ways to use the
PingerInstantiator
, each of which is demonstrated separately in the follow example:// Pass in a Definition
final PingerInstantiator instantiator = new PingerInstantiator("Hey, Yo!", 42);
final Pinger pinger = world.actorFor(Pinger.class, Definition.has(PingerActor.class, instantiator));
// Pass directly as an ActorInstantiator
final Pinger pinger = world.actorFor(Pinger.class, PingerActor.class, new PingerInstantiator("Hey, Yo!", 42));
// Use the FunctionalInterface for lazy instantiation
final Pinger pinger = world.actorFor(Pinger.class, () -> new PingerInstantiator("Hey, Yo!", 42));
Look over the XOOM Actors source code repository for the several different ways that an actor can be created, including with a specific parent, a non-default logger, and a specialized supervisor.
One additional point about the unit test is appropriate. As you probably noticed, a method named
pauseThisThread()
is used. ...
private void pauseThisThread() {
try { Thread.sleep(100); } catch (Exception e) { }
}
...
Some sort of coordination is necessary because the actors send and receive all protocol messages asynchronously. Recall that there will be a total of 10 pings. Since the messages are all delivered and reacted to asynchronously, there is no “automatic” way to know when all the messages, including the
stop()
for both actors, have been delivered.Don't use
Thread.sleep()
in your tests or your production services.Even so, this particular sleep approach is not correct, because on different machines a given sleep time may be insufficient for all messages to process. It's actually guess work to try to get this right. Additionally, if you configure a long-enough sleep time that will work for every possible machine and process load, it's going to make your tests slow on very fast machines and environments. So, one reason for showing you this in the example is to emphasize that you should not use thread sleeps.
The following shows how you can more conveniently test actors without using the thread sleep artifice. It uses the
io.vlingo.xoom.actors.testkit.TestUntil
component. This example also demonstrates how actors take constructor parameters. In the test method, create an instance of the TestUntil
to pass to the PingerActor
constructor.public class PlaygroundTest {
@Test
public void testPlayPingPong() {
final World world = World.start("playground");
final TestUntil until = TestUntil.happenings(1);
final Pinger pinger = world.actorFor(Pinger.class, Definition.has(PingerActor.class, Definition.parameters(until)));
final Ponger ponger = world.actorFor(Ponger.class, Definition.has(PongerActor.class, Definition.NoParameters));
pinger.ping(ponger);
until.completes();
world.terminate();
}
}
Then refactor
PingerActor
to take a TestUntil
instance as a constructor parameter. Using the
Proxy
ProtocolNote that the above test is provided with
Pinger
and Ponger
instances. These are not direct references to the underlying PingerActor
and PongerActor
instances, but are instead proxies. Invoking a method on a proxy causes a message to be created and enqueued for the actor that backs the proxy.Every such proxy implements a
Proxy
type. This can be used to access the Address
of the actor using address()
. In addition, all proxies supports working equals()
, hashCode()
, and toString()
implementations.Since the
Proxy
interface is not available by way of the Pinger
protocol (or any other actor protocols), there is a way to obtain the Proxy instance:final Address address = Proxy.from(pinger).address();
The
Proxy
type is available in the io.vlingo.xoom.actors
package.Additionally, the
PingerActor
must cause a happened()
in its afterStop()
method to signal to the test that the Pinger
has stopped:public class PingerActor extends Actor implements Pinger {
private int count;
private final Pinger self;
private final TestUntil until;
public PingerActor(final TestUntil until) {
this.until = until;
this.count = 0;
this.self = selfAs(Pinger.class);
}
...
@Override
protected void afterStop() {
logger().log("Pinger " + address() + " just stopped!");
until.happened();
super.afterStop();
}
}
Before the
afterStop()
method causes the until.happened()
the test method will block. As soon as the until.happened()
causes its state to transition from 1
to 0
, the test will unblock and the World
will terminate. This enables the test to complete.Don't use
TestUntil
in code that will be used in production. Further, as is seen later in this chapter, you should actually use AccessSafely
rather than TestUntil
. It is not only thread safe, but also provides memory fences/gates around multi-threaded state modifications during tests.Although passing a test construct into a production-quality actor is poor design choice, this example is only to show you that there are very reliable ways to test actors in an asynchronous messaging environment. Later you will see much better uses of
TestUntil
and other io.vlingo.xoom.actors.testkit
tools.In order to make the ping pong playground produce some output, create some log output in the
ping()
and pong()
methods. // in Pinger
...
public void ping(final Ponger ponger) {
++count;
logger().log("ping " + count);
if (count >= 10) {
self.stop();
ponger.stop();
} else {
ponger.pong(self);
}
}
...
// in Ponger
public void pong(final Pinger pinger) {
logger().log("pong");
pinger.ping(self);
}
...
When the test is run, you will see the following output.
vlingo/actors(test): ping 1
vlingo/actors(test): pong
vlingo/actors(test): ping 2
vlingo/actors(test): pong
vlingo/actors(test): ping 3
vlingo/actors(test): pong
vlingo/actors(test): ping 4
vlingo/actors(test): pong
vlingo/actors(test): ping 5
vlingo/actors(test): pong
vlingo/actors(test): ping 6
vlingo/actors(test): pong
vlingo/actors(test): ping 7
vlingo/actors(test): pong
vlingo/actors(test): ping 8
vlingo/actors(test): pong
vlingo/actors(test): ping 9
vlingo/actors(test): pong
vlingo/actors(test): ping 10
Now that this tutorial has given you some of the most import knowledge about XOOM Actors, you are ready to take a deeper dive into more details about the other facilities provided by XOOM Actors.
This section provides a how-to for the XOOM Actors toolkit API. The details are covered in sections. Some of these details are already demonstrated in the previous sections, including the tutorial.
An integral component used to manage asynchronous behaviors provided by actors is the
Completes<T>
protocol with its backing implementation. Being cited in this chapter and others, it's best to understand how it works. To do so, refer to our discussion provided in the Completes<T>
documentation.To start up the XOOM Actors runtime you start the
World
object as follows.final World world = World.startWithDefaults("my-world");
This starts a
World
with normal runtime defaults in which Actor
instances are created and run. For many uses of XOOM Actors the defaults are the easiest and safest way to use the Actor
runtime.
The XOOM Actors World contains components that manage the reactive runtime.
There are a few different ways to start a
World
. The following is a summary.Use this API when you want to start a
World
by loading configurations from the file named xoom-actors.properties
. The name
is used to name the World
instance.public static World start(final String name)
The following API is used when you want to start a
World
with your own name-value pairs using java.util.Properties
defined in code. The details of the xoom-actors.properties
file are discussed below.public static World start(final String name, final java.util.Properties properties)
A
World
can be started using fluent configuration.public static World start(final String name, final Configuration configuration)
The details of programatic
Configuration
are discussed below.When you are preparing to shut down your application or service that is using the
World
, you should use the following to terminate the Actor
runtime.world.terminate();
The
World::terminate()
method is currently a synchronous operation, but in the future will become asynchronous. When the World
terminates asynchronously there will be a Completes<T>
or callback construct to inform the client when the termination has completed.The
Actor
runtime may be configured by means of a file that adheres to the java.util.Properties
conventions. Each property is defined by a name
followed by =
and then a value
. For the XOOM Actors toolkit the file must be named xoom-actors.properties
and be located in the runtime classpath
. The following shows how the standard ConcurrentQueueMailbox
can be defined in this properties file.plugin.name.queueMailbox = true
plugin.queueMailbox.classname =\
io.vlingo.xoom.actors.plugin.mailbox.concurrentqueue.ConcurrentQueueMailboxPlugin
plugin.queueMailbox.defaultMailbox = true
plugin.queueMailbox.numberOfDispatchersFactor = 1.5
plugin.queueMailbox.dispatcherThrottlingCount = 1
When defining properties in a properties file, long lines may be continued by placing an escape character of
\
at the end of the line to be continued on the next line. To see example properties that can be used, you should review: xoom-actors/src/test/resources/xoom-actors.properties
Also the next subsection shows several configuration type objects and options.
As an alternative to using the file-based configuration, you can instead employ a configuration approach that provides a fluent API, an example of which follows.
final Configuration configuration =
Configuration
.define()
.with(PooledCompletesPluginConfiguration
.define()
.mailbox("queueMailbox")
.poolSize(10))
.with(SharedRingBufferMailboxPluginConfiguration
.define()
.ringSize(65535)
.fixedBackoff(2)
.dispatcherThrottlingCount(10))
.with(ManyToOneConcurrentArrayQueuePluginConfiguration
.define()
.ringSize(65535)
.fixedBackoff(2)
.dispatcherThrottlingCount(10)
.sendRetires(10))
.with(ConcurrentQueueMailboxPluginConfiguration
.define()
.defaultMailbox()
.numberOfDispatchersFactor(1.5f)
.dispatcherThrottlingCount(10))
.with(JDKLoggerPluginConfiguration
.define()
.defaultLogger()
.name("vlingo/actors(test)")
.handlerClass(DefaultHandler.class)
.handlerName("vlingo")
.handlerLevel("ALL"))
.with(CommonSupervisorsPluginConfiguration
.define()
.supervisor("default", "pingSupervisor", Ping.class, PingSupervisorActor.class)
.supervisor("default", "pongSupervisor", Pong.class, PongSupervisorActor.class))
.with(DefaultSupervisorOverridePluginConfiguration
.define()
.supervisor("default", "overrideSupervisor", DefaultSupervisorOverride.class))
.usingMainProxyGeneratedClassesPath("target/classes/")
.usingMainProxyGeneratedSourcesPath("target/generated-sources/")
.usingTestProxyGeneratedClassesPath("target/test-classes/")
.usingTestProxyGeneratedSourcesPath("target/generated-test-sources/");
Follow the configuration definition it can be used to start the
World
instance.final World world = World.start("my-world", configuration);
In addition to the above facilities, a
World
provides the following. All concrete Actor
instance may obtain both their Stage
and their World
instances as follows, which enables the Actor
to reach specific facilities offered by each.// inside an actor
final Configuration configuration = stage().world().configuration();
You may require the use of the means to create unique
Actor
addresses or a way to produce an address from a primitive or String
value. To do so, request it by means of the method addressFactory()
.public AddressFactory addressFactory()
You may obtain the immutable
Configuration
of the World
runtime. Even if you load your runtime properties from the xoom-actors.properties
or your own java.util.Properties
definition, all of your runtime configurations are placed in the Configuration
.public Configuration configuration()
All messages sent to
Actor
instances that cannot be delivered for any reasons, such as the Actor
instance has previously been stopped, are delivered to the special Actor
know as DeadLetters
. You may subscribe to receive DeadLetters
messages.public DeadLetters deadLetters()
Use the following
DeadLetters
protocol method to subscribe to its received messages. Your listener Actor must implement the DeadLettersListener
protocol.void registerListener(final DeadLettersListener listener)
If you want to obtain the default
Logger
that is provided to all Actor
instances, use the following method.public Logger defaultLogger()
Every top-level application- or service-created
Actor
is a child of the default parent Actor
.public Actor defaultParent()
Every
Actor
must be assigned to a overarching supervisor. The following provides a reference to the default supervisor of all newly created Actor
instance.public Supervisor defaultSupervisor()
Although the default
Logger
is available through the World
interface, there may also be a number of named Logger
instances. If you use non-default Logger
instances, they may be obtained via the following World
facility. All Logger
instances obtained through the standard XOOM Actors plugins are backed by actors, and are thus asynchronous by default.public Logger logger(final String name)
The
Logger
implementation is based on SLF4J. You may configure your Logger
to your standards. The following is a simple example that outputs strictly to the console.<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Another example logs to both the console and a file.
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
There are many references available with far more details about the SLF4J facility. Note that one such configuration supports asynchronous log appending. You may or may not find that this enhances the asynchronous logging that already exists through the logging actor.
The
Logger
protocol provides several facilities for logging application/service output and exceptions.void trace(String message)
void trace(String message, Object... args)
void trace(String message, final Throwable throwable)
void debug(String message)
void debug(String message, Object... args)
void debug(String message, final Throwable throwable)
void info(String message)
void info(String message, Object... args)
void info(String message, final Throwable throwable)
void warn(String message)
void warn(String message, Object... args)
void warn(String message, final Throwable throwable)
void error(String message)
void error(String message, Object... args)
void error(String message, final Throwable throwable)
The
World
interface provides some additional facilities, but ones that are useful only for plugins. Those are documented below.Once you have started a
World
you are able to create Actor
instance to run in it. As a reminder, there is nothing mysterious about actors. Actors are basically objects, but their behaviors are requested by sending messages asynchronously rather than through directly invoking their methods. The following shows you how to create an actor.final Simple simple = world.actorFor(Simple.class, SimpleActor.class);
In this example the
World
is used to create a new instance of the SimpleActor
type. The SimpleActor
type implements the protocol defined by the interface named Simple
. Thus, actors provide protocol implementations, and they can implement multiple protocols.In this example the SimpleActor takes no constructor parameters. If it did accept parameters then the parameters could be listed as follows.
final Simple simple = world.actorFor(Simple.class, SimpleActor.class, p1, p2, p3);
The parameters must be listed in the order in which to constructor accepts them. In the above example the parameters are listed as
p1
, p2
, and p3
. These parameters could be of any type, and each parameter is required to follow the convention that the constructor contract requires.When the
World
method actorFor()
returns, the requesting client is given a reference to the protocol that provides asynchronous access to the Actor
. This reference is used to send messages via methods on the protocol. The Simple
protocol is defined as follows.public interface Simple {
void simpleSay();
}
There is a single method named
simpleSay()
. Messages are sent asynchronously to the concrete Actor
instance, which in the case of the above example is an instance of SimpleActor
. This behavior is demonstrated by the following expression.simple.simpleSay();
The fundamental behavior of a method invocation on the protocol reference is to reify the method invocation to a message that is sent asynchronously to the
Actor
. The reification is accomplished by creating a Function
that represents the method invocation and enqueuing the Function
instance on to the Actor
's Mailbox
. The Function
will later be applied to the Actor
instance when all previously enqueued messages have been processed and a thread becomes available to process the message at the front of the Mailbox
. Thus, the method invocation is temporally decoupled from the sending side and the receiving side.Creating an instance of a concrete
Actor
type might involve specifying its configuration, such as selecting a non-default mailbox type and setting a non-default Actor
name. To do this, use the Definition
type:Simple simple1 =
world.actorFor(Simple.class,
Definition.has(
SimpleActor.class,
Definition.NoParameters,
"arrayQueueMailbox",
"simple-1"));
In the above example the
Definition
is used to pass the SimpleActor
type that implements the Simple
protocol. Note that this SimpleActor
type requires no constructor parameters, as indicated by Definition.NoParameters
. The two interesting Definition.has()
arguments are "arrayQueueMailbox"
and "simple-1"
. As you likely determined already, the first of the two, "arrayQueueMailbox"
, is the name of a non-default mailbox type to be used by this SimpleActor
instance. The second is the name to be given to the SimpleActor
instance.There are several different predefined
Definition.has()
overrides. See the Javadoc for the complete set. There are currently three mailbox types for use by actors.
Text | Description |
---|---|
queueMailbox | A non-blocking unbounded mailbox based on the Java ConcurrentLinkedQueue implementation. This is the default mailbox, unless changed by the user. As the default mailbox it used by all actors unless the actor is specifically created using a different mailbox name. An unbounded mailbox can cause the VM an out-of-memory condition if the actor cannot process messages at least as fast as they are sent. |
arrayQueueMailbox | A very fast mailbox based on the Agrona project's non-blocking many-to-one concurrent array queue. The array queue is basically a ring buffer, which means that it can become full and cause incoming messages to be discarded, or at least rejected until space becomes available. Rejecting a message is signaled to the client by the IllegalStateException . Yet, this mailbox will not cause an out-of-memory condition. |
ringMailbox | Another fast mailbox based on a non-blocking ring buffer algorithm. The contract of this mailbox is like the other ring buffer implementation, arrayQueueMailbox , but internally is implemented with a different algorithm. However, this mailbox will never reject incoming messages, but this comes at the expense of possible slower enqueuing times. This mailbox will not cause an out-of-memory condition. |
For any mailbox that can become full and cause some kind of contention or failure, it can be useful for the owning actor to be a router to some number of fan-out workers. One way to do that is with XOOM Streams and another is with our actor Routing tools.
The
World
does not directly create an Actor
. Instead, the World
dispatches actorFor()
requests to the default Stage
. It is the Stage
that provides the concrete implementation of actorFor()
.A
Stage
is the abstraction within which Actor
instances are maintained. A World
has at least one Stage
, which is known as the default Stage
. Its name is "__defaultStage"
. If you need to query the instance of the default stage, you use the following World
query:public class World {
...
public Stage stage() {
...
}
...
}
Every
Stage
supports two important behaviors, actorFor()
and actorOf()
. The actorFor()
is a creational method, and there are several overloads supporting various parameter options. Using the method various implementations of the method actorFor()
will create a new Actor
instance and answer one or more protocols. Once you have a reference to a protocol you are able to send messages to the given Actor
that implements the protocol(s).The
actorOf()
method has a different purpose. Given an existing Actor
that is contained within a given Stage
, you may use actorOf()
to find the existing Actor
by passing the Address
of the Actor
. The actorOf()
answers a Completes<T>
because it is an asynchronous operation, answering the requested protocol eventually.An
Actor
may obtain its containing Stage
and its World
as follows.// inside an actor
final Stage myStage = stage();
final World myWorld = myStage.world();
You may create additional
Stage
instances within the World
, but the default Stage
is automatically provided when a World
starts. The World
default Stage
is responsible for holding the private root actor and the public root actor. These two instances act as default supervisors, which are responsible for protecting a World
from catastrophic failures. Supervision is explained in detail below.Since the
World
must create some default operational Actor
instances when it is started, it may be best to segregate your application/service Actor
instances into another Stage
. It's simple to accomplish this. To obtain a reference to an existing Stage
by an other name, or to obtain or a newly created Stage
by name if non-existing, use one of the two following queries:public class World {
...
public Stage stageNamed(final String name) {
...
}
public Stage stageNamed(final String name, final Class<? extends Stage> stageType, final AddressFactory addressFactory) {
...
}
...
}
If the
Stage
instance with the given name
does not yet exist, a new Stage
with that name is created and returned.We don't suggest creating several or many
Stage
instances. It's likely that the default Stage
and one application/service Stage
instance will be sufficient. Yet, we don't set a limit on the number of Stage
instances in case more than two would be useful.