CAF Presentation
CAF Presentation
Muhammad Usman
Actor Model
Conceptual Model of concurrent computation.
Actor:
Fundamental unit of computation (computation has three
elements). So, actor should embody three things:
1. Processing (to get something done)
2. Storage (to remember things)
3. communication
Basic Operations of Actors
message_handler x2{
[](double db) { /*...*/ },
[](double db) { /* - unreachable - */ }
};
Composition
message_handler x3 = x1.or_else(x2);
message_handler x4 = x2.or_else(x1);
Interfaces have set semantics. This means the following two type aliases
i1 and i2 are considered equal by CAF:
using i1 = typed_actor<replies_to<A>::with<B>, replies_to<C>::with<D>>;
using i2 = typed_actor<replies_to<C>::with<D>, replies_to<A>::with<B>>;
Spawning Actors
Both statically and dynamically typed actors are
spawned from an actor_system using the member
function spawn. The function either takes a
function as first argument or a class as first
template parameter.
Spawning example
behavior calculator_fun(event_based_actor* self);
void blocking_calculator_fun(blocking_actor* self);
calculator_actor::behavior_type typed_calculator_fun();
class calculator;
class blocking_calculator;
class typed_calculator;
auto a1 = sys.spawn(calculator_fun);
auto a2 = sys.spawn(blocking_calculator_fun);
auto a3 = sys.spawn(typed_calculator_fun);
auto a4 = sys.spawn<calculator>();
auto a5 = sys.spawn<blocking_calculator>();
auto a6 = sys.spawn<typed_calculator>();
Function-based Actors
When using a function or function object to
implement an actor, the first argument can be
used to capture a pointer to the actor itself. The
type of this pointer is usually event_based_actor*
or blocking_actor*. The proper pointer type for any
typed_actor handle T can be obtained via
T::pointer
Class-based Actors
Implementing an actor using a class requires the following:
●
Provide a constructor taking a reference of type
actor_config& as first argument, which is forwarded to the
base class. The config is passed implicitly to the
constructor when calling spawn, which also forwards any
number of additional arguments to the constructor.
●
Override make_behavior for event-based actors and act for
blocking actors.
Stateful Actors
The stateful actor API makes it easy to maintain state in
function-based actors. It is also safer than putting state in
member variables, because the state ceases to exist after an
actor is done and is not delayed until the destructor runs.
For example, if two actors hold a reference to each other via
member variables, they produce a cycle and neither will get
destroyed. Using stateful actors instead breaks the cycle,
because references are destroyed when an actor calls self-
>quit() (or is killed externally)
Message Passing
The messaging layer of CAF has three primitives
for sending messages: send, request, and delegate
Structure of Mailbox Elements
Default and System Message
Handlers
CAF has three system-level message types
(down_msg, exit_msg, and error) that all actors
should handle regardless of their current state.
Down Handler
Actors can monitor the lifetime of other actors by
calling self->monitor(other). This will cause the
runtime system of CAF to send a down_msg for other
if it dies. Actors drop down messages unless they
provide a custom handler via set_down_handler(f),
where f is a function object with signature void
(down_msg&) or void (scheduled_actor*,
down_msg&).
Exit Handler
Bidirectional monitoring with a strong lifetime coupling is
established by calling self->link_to(other). This will cause the
runtime to send an exit_msg if either this or other dies. Per default,
actors terminate after receiving an exit_msg unless the exit reason
is exit_reason::normal. This mechanism propagates failure states
in an actor system. Linked actors form a sub system in which an
error causes all actors to fail collectively. Actors can override the
default handler via set_exit_handler(f), where f is a function object
with signature void (exit_message&) or void (scheduled_actor*,
exit_message&).
Error Handler
Actors send error messages to others by returning
an error (see Errors) from a message handler.
Default Handler
The default handler is called whenever the behavior of an actor did
not match the input. Actors can change the default handler by
calling set_default_handler. The expected signature of the function
object is result<message> (scheduled_actor*, message_view&),
whereas the self pointer can again be omitted. The default handler
can return a response message or cause the runtime to skip the
input message to allow an actor to handle it in a later state. CAF
provides the following built-in implementations: reflect,
reflect_and_quit, print_and_drop, drop, and skip
Requests Message
Actors send request messages by calling
request(receiver, timeout, content...) . This function
returns an intermediate object that allows an actor
to set a one-shot handler for the response
message. Event-based actors can use either
request(...).then or request(...).await.
request(...).receive
Error Handling in Requests
CAF allows to add an error handler as optional
second parameter to then and await (this
parameter is mandatory for receive). If no such
handler is defined, the default error handler (see
Error Handler) is used as a fallback in scheduled
actors.
Delegating Messages
Response Promises
Scheduler
The CAF runtime maps N actors to M threads on the local machine.
Applications built with CAF scale by decomposing tasks into many
independent steps that are spawned as actors. In this way, sequential
computations performed by individual actors are small compared to the total
runtime of the application.
The performance of actor-based applications depends on the scheduling
algorithm in use and its configuration. Different application scenarios require
different trade-offs. For example, interactive applications such as shells or
GUIs want to stay responsive to user input at all times, while batch
processing applications demand only to perform a given task in the shortest
possible time.
Work Stealing
Work Stealing
Each worker dequeues work items from an individual queue until it is drained. Once
this happens, the worker becomes a thief. It picks one of the other workers—usually
at random—as a victim and tries to steal a work item. As a consequence, tasks
(actors) are bound to workers by default and only migrate between threads as a result
of stealing. This strategy minimizes communication between threads and maximizes
cache locality. Work stealing has become the algorithm of choice for many
frameworks.
CAF uses three polling intervals. Once a worker runs out of work items, it tries to
steal items from others. First, it uses the aggressive polling interval. It falls back to a
moderate interval after a predefined number of trials. After another predefined number
of trials, it will finally use a relaxed interval.
Registry
The actor registry in CAF keeps track of the number of running actors
and allows to map actors to their ID or a custom atom (see Atoms)
representing a name. The registry does not contain all actors. Actors
have to be stored in the registry explicitly. Users can access the
registry through an actor system by calling system.registry(). The registry
stores actors using strong_actor_ptr
Users can use the registry to make actors system-wide available by
name. The Middleman uses the registry to keep track of all actors
known to remote nodes in order to serialize and deserialize them.
Actors are removed automatically when they terminate.
Middleman
The middleman is the main component of the I/O module
and enables distribution. It transparently manages proxy
actor instances representing remote actors, maintains
connections to other nodes, and takes care of serialization
of messages. Applications install a middleman by loading
caf::io::middleman as module (see Configuring Actor
Applications). Users can include "caf/io/all.hpp" to get
access to all public classes of the I/O module.
Network I/O with Brokers
When communicating to other services in the network,
sometimes low-level socket I/O is inevitable. For this reason,
CAF provides brokers. A broker is an event-based actor
running in the middleman that multiplexes socket I/O. It can
maintain any number of acceptors and connections. Since the
broker runs in the middleman, implementations should be
careful to consume as little time as possible in message
handlers. Brokers should outsource any considerable amount
of work by spawning new actors or maintaining worker actors.