Package net.sodacan.core.actor
A small island of compute sending and receiving messages to/from other Actors. Let's get started:
Message Processing
An Actor executes in a single thread of execution. Messages for a given Actor line up in a queue and are processed one at a time.
When not processing a message, the actor
and its state can cease to exist as long as when the next message is ready to process the Actor has the same state as it left behind since the
last message was processed. This process is handled by Sodacan, the Actor just needs to implement the Actor.processMessage()
method.
Actor Lifecycle
An actor has an interesting lifecycle: Starting with an actor that needs to "create" another actor. For example, say a shopping cart Actor needs to
create a new Invoice actor. In Sodacan,
one actor cannot have direct access to another Actor, this includes creating another Actor. An Actor does not own another Actor.
So, how does an Actor create another Actor?
Sodacan solves this problem with a simple rule: If an Actor
puts together a valid ActorId
, then sending a message to that ActorId is enough for the Actor to exist. Now, you might be thinking that
doing so leads to trouble. And that would be true but for two features. First, the ActorId has to be valid which means the type of Actor has to be know (registered).
Second, the ActorGroup number has to be valid. The interesting part is the Id itself. And there's two ways to create the Id part of an ActorId: The easiest is to let Sodacan
generate the Id. Sodacan puts together an ActorId that is unique. The other approach is to carry some business identifier such as employee id, shopping cart id, etc.
The second approach is discouraged.
Once the new ActorId is known, the creating Actor is able to store the ActorId so that it can be accessed later. Also, the creating Actor or any other Actor is free to send Messages to that new Actor.
Usually, a Message will flow through one or more Actors that translate from a business Id, such as Employee Id into an ActorId. See Collections below.
Actor as a State Machine
When processing a message, an Actor has two two bits of information: the message, specifically, the "Verb" of the message. This is the "ask"; The event. The other is the current state of the actor: How things stand with the actor. Together, these identify the transitions within a State Machine.
Host Bound Actors
A host bound actor enjoys a different lifecycle from normal actor. This type of Actor is commonly used for device interfaces, network connections, or other functions that require access to the specific that they run on.
When an ActoryType has a name beginning with a dollar sign ($). That actor will be treated as a Host-bound Actor. Such Actors have a slightly different lifecycle:
- The Actor will be created immediately when a Host starts up. (Normal Actors are not instantiated until they receive their first message.)
- The Actor will not be closed until a specific handshake occurs. (Normal actors can be removed from memory at any time, that is, between messages.)
- The Host-bound actor will receive an initial
Builtin.Start
message. This alerts the Actor to open connections or other system resources, as needed. - Host-bound Actors can send and receive messages just like normal Actors.
- When a Host is ready to shut down, it first asks any HostBound Actors to close by calling a close method on the Actor. This process blocks until all HostBound actors have returned from the close method.
- The Host can then complete its shutdown process.
A normal Actor acts in a simple call-return protocol: Sodacan calls
processMessage with an inbound message. The Actor does something and populates the Stage in response to that inbound message.
However, it would be common for a Host-bound Actor to perform some asynchronous IO operations. That doesn't change
for Host-bound Actors. All actors must
remain responsive to processMessage calls.
If a Host-bound Actor is, for example, an HTTP server, it will also need to hand new messages to Sodacan asynchronously. In that case,
a different method is available to an Actor: send(Message)
. It will deliver messages outside the main processMessage loop.
Naturally the Actor will need to be more careful because it might be working in
two or more threads at the same time! At least the send
method is thread safe relative the Actor.
Actor State
Collections
Sodacan does not internally maintain a definitive directory of all Actors. Simply put, if a message is addressed to an Actor's Id, the Actor is assumed to exist. This is done to support ephemeral and special purpose Actors. Of course, if the Actor *does* have state, that state is available when needed. How that is done depends on the implementation of the Actor interface.
An Actor has four general kinds of state:
- LocalRecord(s) - One or more Records that are always available to the actor. They are part of commit/rollback semantics in the Actor. Sodacan considers this data durable. More on that elsewhere. However, it is important to know that local Records are persisted automatically and reliably.
- Foreign Record(s) - One or more Records that are read-only to this Actor but delivered to this Actor from another Actor that *does* own the Record.
- Transient data - For example, local variables in Java. This kind of data is used during processing of a message. Class variables should be avoided because they could be lost between messages.
- Collections - A collection maintained by an Actor is just as durable as local Record storage and subject to the same commit/rollback semantics. But the underlying mechanism is a bit different because collections can grow very large. Most collections refer to other Actors, such as a Department having a list of Employees that work in the Department. Large collection example: Taylor Swift's social media followers.
Processing a message in an Actor goes through several phases.
- Instantiate - Sodacan may or may not have an instance of the Java object when the message arrives. If not, it is instantiated now. The configuration allows custom Actor instantiation but in any case, Sodacan decides *when* to create the Actor instance.
- RestoreState - This must be done before a message can be processed. If it's already been done from previous messages, it is not repeated.
- Precheck - Precheck generally applies to messages that depend on Foreign Records: Precheck can consider the message(s) about to be processed (some messages may not need Foreign Data) and the current state of the Actor (if the Foreign Data is "new enough", an update is not needed and the message can continue through). A Precheck may cause Messages in the queue to be stalled until the required Foreign Data has arrived from the owning Actor. This can lead to Messages being processed out of order. If that is not desired, all prechecks can depend on the same Foreign Record. That is all messages except the UpdateMessage carrying the updated Foreign Data.
- Deserialize Payload of the message.
- Message Match - Each message Match considers the type of message and the current state of the Actor and takes some action as a result. Once a message is matched, processing continues at the next phase.
- State Match - Regardless of the message being processed, decisions may be needed that only depend on the current state of an actor. For example, If the Actor, say a shopping cart, is in the active state and it's been a while since the last reminder has been sent to the customer, a new message can be sent.
- Commit/Rollback - A go-nogo decision is made by the Actor. A rollback restores the actor to it's state before the message causing the rollback arrived.
- Process the Stage - Outbound messages are serialized and sent. State is saved, serialized and sent to journal(s).
- The message being processed is removed from the queue so that the next message can be considered.
See the message package for more details about message flow between Actors.
This package contains the core and default implementations of the Actor interface.
- Author:
- John Churin
- See Also:
-
ClassDescriptionThe scheduler for a actorGroup manages a map of ActorEntries.This special Actor handles outbound messages from an ActorGroup to another ActorGroup.This host-bound actor displays statistics periodically.A host bound Actor is locked to a specific host rather than moving with its actor group.A JournalWriter Actor is responsible for writing state to a journal.Create a sequential ActorId for testing.A kind of WorkerActor that Sleeps for a while, sends a message, and then goes away.This timer actor provides a simple but important service for other Actors: It will accept a message, hold on to the message for a specified amount of time and then forward that message to its next step which could be the Actor that sent the message or any other actor.A WorkerActor has no state, it's ephemeral.