Commit 1a3de3f7 authored by Dominik Charousset's avatar Dominik Charousset

Use atom constants in manual; several improvements

parent 82a6511b
......@@ -66,30 +66,33 @@ The example below illustrates printing of lines of text from multiple actors (in
#include <chrono>
#include <cstdlib>
#include <iostream>
#include "caf/all.hpp"
using namespace caf;
using std::endl;
using done_atom = atom_constant<atom("done")>;
int main() {
std::srand(std::time(0));
for (int i = 1; i <= 50; ++i) {
spawn<blocking_api>([i](blocking_actor* self) {
aout(self) << "Hi there! This is actor nr. "
<< i << "!" << endl;
std::chrono::milliseconds tout{std::rand() % 1000};
self->delayed_send(self, tout, atom("done"));
self->receive(others() >> [i, self] {
aout(self) << "Actor nr. "
<< i << " says goodbye!" << endl;
});
});
}
// wait until all other actors we've spawned are done
await_all_actors_done();
// done
shutdown();
return 0;
std::srand(std::time(0));
for (int i = 1; i <= 50; ++i) {
spawn<blocking_api>([i](blocking_actor* self) {
aout(self) << "Hi there! This is actor nr. "
<< i << "!" << endl;
std::chrono::milliseconds tout{std::rand() % 1000};
self->delayed_send(self, tout, done_atom::value);
self->receive(
[i, self](done_atom) {
aout(self) << "Actor nr. "
<< i << " says goodbye!" << endl;
}
);
});
}
// wait until all other actors we've spawned are done
await_all_actors_done();
shutdown();
}
\end{lstlisting}
......
......@@ -99,7 +99,7 @@ Analogous to \lstinline^sync_send(...).then(...)^ for event-based actors, blocki
\begin{lstlisting}
void foo(blocking_actor* self, actor testee) {
// testee replies with a string to 'get'
self->sync_send(testee, atom("get")).await(
self->sync_send(testee, get_atom::value).await(
[&](const std::string& str) {
// handle str
},
......
......@@ -23,17 +23,6 @@ It is not possible to transfer a handle to a response to another actor.
\end{itemize}
\subsection{Sending Messages}
\begin{itemize}
\item
\lstinline^send(whom, ...)^ is defined as \lstinline^send_tuple(whom, make_message(...))^.
Hence, a message sent via \lstinline^send(whom, self->last_dequeued())^ will not yield the expected result, since it wraps \lstinline^self->last_dequeued()^ into another \lstinline^message^ instance.
The correct way of forwarding messages is \lstinline^self->forward_to(whom)^.
\end{itemize}
\subsection{Sharing}
\begin{itemize}
......
......@@ -9,7 +9,7 @@ std::string group_module = ...;
std::string group_id = ...;
auto grp = group::get(group_module, group_id);
self->join(grp);
self->send(grp, atom("test"));
self->send(grp, "test");
self->leave(grp);
\end{lstlisting}
......
......@@ -6,18 +6,21 @@ This flag causes the actor to use a priority-aware mailbox implementation.
It is not possible to change this implementation dynamically at runtime.
\begin{lstlisting}
using a_atom = atom_constant<atom("a")>;
using b_atom = atom_constant<atom("b")>;
behavior testee(event_based_actor* self) {
// send 'b' with normal priority
self->send(self, atom("b"));
self->send(self, b_atom::value);
// send 'a' with high priority
self->send(message_priority::high, self, atom("a"));
self->send(message_priority::high, self, a_atom::value);
// terminate after receiving a 'b'
return {
on(atom("b")) >> [=] {
[=](b_atom) {
aout(self) << "received 'b' => quit" << endl;
self->quit();
},
on(atom("a")) >> [=] {
[=](a_atom) {
aout(self) << "received 'a'" << endl;
},
};
......
......@@ -28,8 +28,8 @@ With \lstinline^reuse_addr = true^ binding would succeed because 10.0.0.1 and
\begin{lstlisting}
publish(self, 4242);
self->become (
on(atom("ping"), arg_match) >> [](int i) {
return make_message(atom("pong"), i);
[](ping_atom, int i) {
return std::make_tuple(pong_atom::value, i);
}
);
\end{lstlisting}
......@@ -53,13 +53,14 @@ A \lstinline^network_error^ is thrown if the connection failed.
\begin{lstlisting}
auto pong = remote_actor("localhost", 4242);
self->send(pong, atom("ping"), 0);
self->send(pong, ping_atom::value, 0);
self->become (
on(atom("pong"), 10) >> [=] {
self->quit();
},
on(atom("pong"), arg_match) >> [=](int i) {
return make_message(atom("ping"), i+1);
[=](pong_value, int i) {
if (i >= 10) {
self->quit();
return;
}
self->send(pong, ping_atom::value, i + 1);
}
);
\end{lstlisting}
......@@ -11,42 +11,115 @@ Hence, we provide an internal domain-specific language to match incoming message
\subsection{Basics}
\label{Sec::PatternMatching::Basics}
A match expression begins with a call to the function \lstinline^on^, which returns an intermediate object providing \lstinline^operator>>^.
The right-hand side of the operator denotes a callback, usually a lambda expression, that should be invoked if a tuple matches the types given to \lstinline^on^,
as shown in the example below.
Actors can store a set of message callbacks using either \lstinline^behavior^ or \lstinline^message_handler^.
The difference between the two is that the former stores an optional timeout.
The most basic way to define a pattern is to store a set of lambda expressions using one of the two container types.
\begin{lstlisting}
on<int>() >> [](int i) { /*...*/ }
on<int, float>() >> [](int i, float f) { /*...*/ }
on<int, int, int>() >> [](int a, int b, int c) { /*...*/ }
behavior bhvr1{
[](int i) { /*...*/ },
[](int i, float f) { /*...*/ },
[](int a, int b, int c) { /*...*/ }
};
\end{lstlisting}
The result of \lstinline^operator>>^ is a \textit{match statement}.
A message handler can consist of any number of match statements.
At most one callback is invoked, since the evaluation stops at the first match.
In our first example, \lstinline^bhvr1^ models a pattern accepting messages that consist of either exactly one \lstinline^int^, or one \lstinline^int^ followed by a \lstinline^float^, or three \lstinline^int^s.
Any other message is not matched and will remain in the mailbox until it is consumed eventually.
This caching mechanism allows actors to ignore messages until a state change replaces its message handler.
However, this can lead to a memory leak if an actor receives messages it handles in no state.
To allow actors to specify a default message handlers for otherwise unmatched messages, \lib provides the function \lstinline^others()^.
\begin{lstlisting}
message_handler fun {
on<int>() >> [](int i) {
// case1
behavior bhvr2{
[](int i) { /*...*/ },
[](int i, float f) { /*...*/ },
[](int a, int b, int c) { /*...*/ },
others() >> [] { /*...*/ }
};
\end{lstlisting}
Please note the change in syntax for the default case.
The lambda expression passed to the constructor of \lstinline^behavior^ is prefixed by a ''match expression'' and the operator \lstinline^>>^.
\subsection{Atoms}
\label{Sec::PatternMatching::Atoms}
Assume an actor provides a mathematical service for integers.
It takes two arguments, performs a predefined operation and returns the result.
It cannot determine an operation, such as multiply or add, by receiving two operands.
Thus, the operation must be encoded into the message.
The Erlang programming language introduced an approach to use non-numerical
constants, so-called \textit{atoms}, which have an unambiguous, special-purpose type and do not have the runtime overhead of string constants.
Atoms are mapped to integer values at compile time in \lib.
This mapping is guaranteed to be collision-free and invertible, but limits atom literals to ten characters and prohibits special characters.
Legal characters are ``\lstinline[language=C++]^_0-9A-Za-z^'' and the whitespace character.
Atoms are created using the \lstinline^constexpr^ function \lstinline^atom^, as the following example illustrates.
\begin{lstlisting}
atom_value a1 = atom("add");
atom_value a2 = atom("multiply");
// ...
\end{lstlisting}
\textbf{Warning}: The compiler cannot enforce the restrictions at compile time, except for a length check.
The assertion \lstinline^atom("!?") != atom("?!")^ is not true, because each invalid character is mapped to the whitespace character.
An \lstinline^atom_value^ alone does not help us statically annotate function handlers.
To accomplish this, \lib offers compile-time \emph{atom constants}.
\begin{lstlisting}
using add_atom = atom_constant<atom("add")>;
using multiply_atom = atom_constant<atom("multiply")>;
\end{lstlisting}
Using the constants, we can now define message passing interfaces in a convenient way.
\begin{lstlisting}
behavior do_math{
[](add_atom, int a, int b) {
return a + b;
},
on<int>() >> [](int i) {
// case2; never invoked, since case1 always matches first
[](multiply_atom, int a, int b) {
return a * b;
}
};
\end{lstlisting}
The function ``\lstinline^on^'' can be used in two ways.
Atom constants define a static member \lstinline^value^ that can be used on the caller side (see Section \ref{Sec::Send}), e.g., \lstinline^send(math_actor, add_atom::value, 1, 2)^.
Please note that the static \lstinline^value^ member does \emph{not} have the type \lstinline^atom_value^, unlike \lstinline^std::integral_constant^ for example.
\clearpage
\subsection{Match Expressions}
Match expressions are an advanced feature of \lib and allow you to match on values and to extract data while matching.
Using lambda expressions and atom constants---cf. \ref{Sec::PatternMatching::Atoms}---suffices for most use cases.
A match expression begins with a call to the function \lstinline^on^, which returns an intermediate object providing \lstinline^operator>>^.
The function \lstinline^others()^ is an alias for \lstinline^on<anything>()^.
The right-hand side of the operator denotes a callback, usually a lambda expression, that should be invoked if a tuple matches the types given to \lstinline^on^,
When using the basic syntax, \lib generates the match expression automatically.
A verbose version of the \lstinline^bhvr1^ from \ref{Sec::PatternMatching::Basics} is shown below.
\begin{lstlisting}
behavior verbose_bhvr1{
on<int>() >> [](int i) { /*...*/ },
on<int, float>() >> [](int i, float f) { /*...*/ },
on<int, int, int>() >> [](int a, int b, int c) { /*...*/ }
};
\end{lstlisting}
The function \lstinline^on^ can be used in two ways.
Either with template parameters only or with function parameters only.
The latter version deduces all types from its arguments and matches for both type and value.
To match for any value of a given type, ``\lstinline^val^'' can be used, as shown in the following example.
To match for any value of a given type, the template \lstinline^val<T>^ can be used, as shown in the following example.
\begin{lstlisting}
on(42) >> [](int i) { assert(i == 42); }
on("hello world") >> [] { /* ... */ }
on("print", val<std::string>) >> [](const std::string& what) {
behavior bhvr3{
on(42) >> [](int i) { assert(i == 42); },
on("hello world") >> [] { /* ... */ },
on("print", val<std::string>) >> [](const std::string& what) {
// ...
}
}
};
\end{lstlisting}
\textbf{Note:} The given callback can have less arguments than the pattern.
......@@ -60,11 +133,8 @@ on<int, float, double>() >> [](int, float, double) { /*...*/ } // ok
on<int, float, double>() >> [](int i) { /*...*/ } // compiler error
\end{lstlisting}
\subsection{Reducing Redundancy with ``\lstinline^arg_match^'' and ``\lstinline^on_arg_match^''}
Our previous examples always used the most verbose form, which is quite redundant, since you have to type the types twice -- as template parameter and as argument type for the lambda.
To avoid such redundancy, \lstinline^arg_match^ can be used as last argument to the function \lstinline^on^.
This causes the compiler to deduce all further types from the signature of the given callback.
To avoid redundancy when working with match expressions, \lstinline^arg_match^ can be used as last argument to the function \lstinline^on^.
This causes the compiler to deduce all further types from the signature of any given callback.
\begin{lstlisting}
on<int, int>() >> [](int a, int b) { /*...*/ }
......@@ -72,38 +142,9 @@ on<int, int>() >> [](int a, int b) { /*...*/ }
on(arg_match) >> [](int a, int b) { /*...*/ }
\end{lstlisting}
Note that the second version does call \lstinline^on^ without template parameters.
Furthermore, \lstinline^arg_match^ must be passed as last parameter.
If all types should be deduced from the callback signature, \lstinline^on_arg_match^ can be used.
It is equal to \lstinline^on(arg_match)^.
However, when using a pattern to initialize the behavior of an actor, \lstinline^on_arg_match^ is used implicitly whenever a functor is passed without preceding it with an \lstinline^on^ clause.
\begin{lstlisting}
on_arg_match >> [](const std::string& str) { /*...*/ }
\end{lstlisting}
\subsection{Atoms}
\label{Sec::PatternMatching::Atoms}
Assume an actor provides a mathematical service for integers.
It takes two arguments, performs a predefined operation and returns the result.
It cannot determine an operation, such as multiply or add, by receiving two operands.
Thus, the operation must be encoded into the message.
The Erlang programming language introduced an approach to use non-numerical
constants, so-called \textit{atoms}, which have an unambiguous, special-purpose type and do not have the runtime overhead of string constants.
Atoms are mapped to integer values at compile time in \lib.
This mapping is guaranteed to be collision-free and invertible, but limits atom literals to ten characters and prohibits special characters.
Legal characters are ``\lstinline[language=C++]^_0-9A-Za-z^'' and the whitespace character.
Atoms are created using the \lstinline^constexpr^ function \lstinline^atom^, as the following example illustrates.
\begin{lstlisting}
on(atom("add"), arg_match) >> [](int a, int b) { /*...*/ },
on(atom("multiply"), arg_match) >> [](int a, int b) { /*...*/ },
// ...
\end{lstlisting}
\textbf{Note}: The compiler cannot enforce the restrictions at compile time, except for a length check.
The assertion \lstinline^atom("!?") != atom("?!")^ is not true, because each invalid character is mapped to the whitespace character.
Note that \lstinline^arg_match^ must be passed as last parameter.
If all types should be deduced from the callback signature, \lstinline^on_arg_match^ can be used, which is an alias for \lstinline^on(arg_match)^.
However, \lstinline^on_arg_match^ is used implicitly whenever a callback is used without preceding match expression.
\clearpage
\subsection{Wildcards}
......@@ -166,13 +207,13 @@ If the functor returns an \lstinline^option<T>^, then \lstinline^T^ is deduced.
\subsection{Dynamically Building Messages}
Usually, messages are created implicitly when sending messages but can also be created explicitly using \lstinline^make_message^.
In both cases, types and number of elements is fixed at compile time.
In both cases, types and number of elements are known at compile time.
To allow for fully dynamic message generation, \lib also offers a third option to create messages by using a \lstinline^message_builder^:
\begin{lstlisting}
message_builder mb;
// prefix message with some atom
mb.append(atom("strings"));
mb.append(strings_atom::value);
// fill message with some strings
std::vector<std::string> strings{/*...*/};
for (auto& str : strings) {
......
......@@ -41,50 +41,56 @@ The following example illustrates a more advanced state-based actor that impleme
\clearpage
\begin{lstlisting}
using pop_atom = atom_constant<atom("pop")>;
using push_atom = atom_constant<atom("push")>;
class fixed_stack : public sb_actor<fixed_stack> {
public:
fixed_stack(size_t max) : max_size(max) {
assert(max_size > 0);
full = (
on(atom("push"), arg_match) >> [=](int) { /* discard */ },
on(atom("pop")) >> [=]() -> tuple<atom_value, int> {
full.assign(
[=](push_atom, int) {
/* discard */
},
[=](pop_atom) -> message {
auto result = data.back();
data.pop_back();
if (data.empty()) become(empty);
else become(filled);
return {atom("ok"), result};
become(filled);
return make_message(ok_atom::value, result);
}
);
filled = (
on(atom("push"), arg_match) >> [=](int what) {
filled.assign(
[=](push_atom, int what) {
data.push_back(what);
if (data.size() == max_size) become(full);
if (data.size() == max_size) {
become(full);
}
},
on(atom("pop")) >> [=]() -> tuple<atom_value, int> {
[=](pop_atom) -> message {
auto result = data.back();
data.pop_back();
if (data.empty()) become(empty);
return {atom("ok"), result};
if (data.empty()) {
become(empty);
}
return make_message(ok_atom::value, result);
}
);
empty = (
on(atom("push"), arg_match) >> [=](int what) {
empty.assign(
[=](push_atom, int what) {
data.push_back(what);
if (data.size() == max_size) become(full);
else become(filled);
become(filled);
},
on(atom("pop")) >> [=] {
return atom("failure");
[=](pop_atom) {
return error_atom::value;
}
);
}
private:
size_t max_size;
vector<int> data;
behavior full;
behavior filled;
behavior empty;
behavior& init_state = empty;
size_t max_size;
std::vector<int> data;
behavior full;
behavior filled;
behavior empty;
behavior& init_state = empty;
};
\end{lstlisting}
......@@ -168,24 +174,31 @@ Afterwards, the server returns to its initial behavior, i.e., awaits the next \l
The server actor will exit for reason \lstinline^user_defined^ whenever it receives a message that is neither a request, nor an idle message.
\begin{lstlisting}
using idle_atom = atom_constant<atom("idle")>;
using request_atom = atom_constant<atom("request")>;
behavior server(event_based_actor* self) {
auto die = [=] { self->quit(exit_reason::user_defined); };
return {
on(atom("idle")) >> [=] {
[=](idle_atom) {
auto worker = last_sender();
self->become (
keep_behavior,
on(atom("request")) >> [=] {
[=](request_atom) {
// forward request to idle worker
self->forward_to(worker);
// await next idle message
self->unbecome();
},
on(atom("idle")) >> skip_message,
[=](idle_atom) {
return skip_message();
},
others() >> die
);
},
on(atom("request")) >> skip_message,
[=](request_atom) {
return skip_message();
},
others() >> die
};
}
......
......@@ -19,7 +19,6 @@ void some_fun(event_based_actor* self) {
}
\end{lstlisting}
\clearpage
\subsection{Replying to Messages}
\label{Sec::Send::Reply}
......@@ -27,33 +26,34 @@ The return value of a message handler is used as response message.
Actors can also use the result of a \lstinline^sync_send^ to answer to a request, as shown below.
\begin{lstlisting}
void client(event_based_actor* self, const actor& master) {
become (
on("foo", arg_match) >> [=](const string& request) {
return self->sync_send(master, atom("bar"), request).then(
behavior client(event_based_actor* self, const actor& master) {
return {
[=](const string& request) {
return self->sync_send(master, request).then(
[=](const std::string& response) {
return response;
}
);
}
);
};
};
\end{lstlisting}
\subsection{Delaying Messages}
Messages can be delayed, e.g., to implement time-based polling strategies, by using one of \lstinline^delayed_send^, \lstinline^delayed_send_tuple^, \lstinline^delayed_reply^, or \lstinline^delayed_reply_tuple^.
The following example illustrates a polling strategy using \lstinline^delayed_send^.
Messages can be delayed by using the function \lstinline^delayed_send^.
\begin{lstlisting}
using poll_atom = atom_constant<atom("poll")>;
behavior poller(event_based_actor* self) {
self->delayed_send(self, std::chrono::seconds(1), atom("poll"));
using std::chrono::seconds;
self->delayed_send(self, seconds(1), poll_atom::value);
return {
on(atom("poll")) >> [] {
[](poll_atom) {
// poll a resource
// ...
// schedule next polling
self->delayed_send(self, std::chrono::seconds(1), atom("poll"));
self->delayed_send(self, seconds(1), poll_atom::value);
}
};
}
......
......@@ -2,30 +2,23 @@
\label{Sec::Sync}
\lib supports both asynchronous and synchronous communication.
The member functions \lstinline^sync_send^ and \lstinline^sync_send_tuple^ send synchronous request messages.
The member function \lstinline^sync_send^ sends synchronous request messages.
\begin{lstlisting}
template<typename... Args>
__unspecified__ sync_send(actor whom, Args&&... what);
__unspecified__ sync_send_tuple(actor whom, message what);
template<typename Duration, typename... Args>
__unspecified__ timed_sync_send(actor whom,
Duration timeout,
Args&&... what);
template<typename Duration, typename... Args>
__unspecified__ timed_sync_send_tuple(actor whom,
Duration timeout,
message what);
\end{lstlisting}
A synchronous message is sent to the receiving actor's mailbox like any other asynchronous message.
A synchronous message is sent to the receiving actor's mailbox like any other (asynchronous) message.
The response message, on the other hand, is treated separately.
The difference between \lstinline^sync_send^ and \lstinline^timed_sync_send^ is how timeouts are handled.
The behavior of \lstinline^sync_send^ is analogous to \lstinline^send^, i.e., timeouts are specified by using \lstinline^after(...)^ statements (see \ref{Sec::Receive::Timeouts}).
The behavior of \lstinline^sync_send^ is analogous to \lstinline^send^, i.e., timeouts are specified by using \lstinline^after(...)^ statements (see Section \ref{Sec::Receive::Timeouts}).
When using \lstinline^timed_sync_send^ function, \lstinline^after(...)^ statements are ignored and the actor will receive a \lstinline^sync_timeout_msg^ after the given duration instead.
\subsection{Error Messages}
......@@ -46,7 +39,7 @@ When sending a synchronous message, the response handler can be passed by either
\begin{lstlisting}
void foo(event_based_actor* self, actor testee) {
// testee replies with a string to 'get'
self->sync_send(testee, atom("get")).then(
self->sync_send(testee, get_atom::value).then(
[=](const std::string& str) {
// handle str
},
......@@ -82,7 +75,7 @@ void foo(event_based_actor* self, actor testee) {
aout << "timeout occured" << endl;
};
// set response handler by using "then"
timed_sync_send(testee, std::chrono::seconds(30), atom("get")).then(
timed_sync_send(testee, std::chrono::seconds(30), get_atom::value).then(
[=](const std::string& str) { /* handle str */ }
);
\end{lstlisting}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment