Commit daef499b authored by Dominik Charousset's avatar Dominik Charousset

manual update for upcoming 0.9 release

parent 25641451
......@@ -349,3 +349,5 @@ unit_testing/test_typed_spawn.cpp
unit_testing/test_uniform_type.cpp
unit_testing/test_yield_interface.cpp
cppa/may_have_timeout.hpp
examples/message_passing/typed_calculator.cpp
examples/aout.cpp
......@@ -78,21 +78,10 @@ class behavior {
behavior(const timeout_definition<F>& arg);
template<typename F>
behavior(util::duration d, F f);
behavior(const util::duration& d, F f);
template<typename... Cs>
behavior(const match_expr<Cs...>& arg);
template<typename... Cs, typename T, typename... Ts>
behavior(const match_expr<Cs...>& arg0, const T& arg1, const Ts&... args);
template<typename F,
typename Enable = typename std::enable_if<
util::is_callable<F>::value
&& !std::is_same<F, behavior>::value
&& !std::is_same<F, partial_function>::value
>::type>
behavior(F fun);
template<typename T, typename... Ts>
behavior(const T& arg, Ts&&... args);
/**
* @brief Invokes the timeout callback.
......@@ -132,10 +121,11 @@ class behavior {
* @brief Creates a behavior from a match expression and a timeout definition.
* @relates behavior
*/
template<typename... Cs, typename F>
inline behavior operator,(const match_expr<Cs...>& lhs,
const timeout_definition<F>& rhs) {
return match_expr_convert(lhs, rhs);
return {lhs, rhs};
}
......@@ -143,26 +133,20 @@ inline behavior operator,(const match_expr<Cs...>& lhs,
* inline and template member function implementations *
******************************************************************************/
template<typename F, typename Enable>
behavior::behavior(F fun)
: m_impl(detail::match_expr_from_functor(std::move(fun)).as_behavior_impl()) { }
template<typename T, typename... Ts>
behavior::behavior(const T& arg, Ts&&... args)
: m_impl(detail::match_expr_concat(
detail::lift_to_match_expr(arg),
detail::lift_to_match_expr(std::forward<Ts>(args))...)) { }
template<typename F>
behavior::behavior(const timeout_definition<F>& arg)
: m_impl(detail::new_default_behavior(arg.timeout, arg.handler)) { }
template<typename F>
behavior::behavior(util::duration d, F f)
behavior::behavior(const util::duration& d, F f)
: m_impl(detail::new_default_behavior(d, f)) { }
template<typename... Cs>
behavior::behavior(const match_expr<Cs...>& arg)
: m_impl(arg.as_behavior_impl()) { }
template<typename... Cs, typename T, typename... Ts>
behavior::behavior(const match_expr<Cs...>& v0, const T& v1, const Ts&... vs)
: m_impl(detail::match_expr_concat(v0, v1, vs...)) { }
inline behavior::behavior(impl_ptr ptr) : m_impl(std::move(ptr)) { }
inline void behavior::handle_timeout() {
......@@ -178,7 +162,6 @@ inline optional<any_tuple> behavior::operator()(T&& arg) {
return (m_impl) ? m_impl->invoke(std::forward<T>(arg)) : none;
}
inline auto behavior::as_behavior_impl() const -> const impl_ptr& {
return m_impl;
}
......
......@@ -58,7 +58,6 @@ class blocking_actor
/**************************************************************************
* utility stuff and receive() member function family *
**************************************************************************/
typedef std::chrono::high_resolution_clock::time_point timeout_type;
struct receive_while_helper {
......@@ -70,7 +69,7 @@ class blocking_actor
void operator()(Ts&&... args) {
static_assert(sizeof...(Ts) > 0,
"operator() requires at least one argument");
behavior bhvr = lift_and_convert(std::forward<Ts>(args)...);
behavior bhvr{std::forward<Ts>(args)...};
while (m_stmt()) m_dq(bhvr);
}
......@@ -85,7 +84,7 @@ class blocking_actor
template<typename... Ts>
void operator()(Ts&&... args) {
behavior bhvr = lift_and_convert(std::forward<Ts>(args)...);
behavior bhvr{std::forward<Ts>(args)...};
for ( ; begin != end; ++begin) m_dq(bhvr);
}
......@@ -111,7 +110,8 @@ class blocking_actor
template<typename... Ts>
void receive(Ts&&... args) {
static_assert(sizeof...(Ts), "at least one argument required");
dequeue(lift_and_convert(std::forward<Ts>(args)...));
behavior bhvr{std::forward<Ts>(args)...};
dequeue(bhvr);
}
/**
......@@ -120,7 +120,7 @@ class blocking_actor
*/
template<typename... Ts>
void receive_loop(Ts&&... args) {
behavior bhvr = lift_and_convert(std::forward<Ts>(args)...);
behavior bhvr{std::forward<Ts>(args)...};
for (;;) dequeue(bhvr);
}
......@@ -191,8 +191,7 @@ class blocking_actor
*/
template<typename... Ts>
do_receive_helper do_receive(Ts&&... args) {
return { make_dequeue_callback()
, lift_and_convert(std::forward<Ts>(args)...)};
return {make_dequeue_callback(), behavior{std::forward<Ts>(args)...}};
}
optional<behavior&> sync_handler(message_id msg_id) override {
......
......@@ -405,7 +405,50 @@ unique_uti new_member_tinfo(GRes (C::*getter)() const,
template<typename T>
class default_uniform_type_info : public util::abstract_uniform_type_info<T> {
std::vector<unique_uti> m_members;
public:
template<typename... Ts>
default_uniform_type_info(Ts&&... args) {
push_back(std::forward<Ts>(args)...);
}
default_uniform_type_info() {
typedef member_tinfo<T, fake_access_policy<T> > result_type;
m_members.push_back(unique_uti(new result_type));
}
void serialize(const void* obj, serializer* s) const override {
// serialize each member
for (auto& m : m_members) m->serialize(obj, s);
}
void deserialize(void* obj, deserializer* d) const override {
// deserialize each member
for (auto& m : m_members) m->deserialize(obj, d);
}
protected:
bool pod_mems_equals(const T& lhs, const T& rhs) const override {
return pod_eq(lhs, rhs);
}
private:
template<class C>
typename std::enable_if<std::is_pod<C>::value, bool>::type
pod_eq(const C& lhs, const C& rhs) const {
for (auto& member : m_members) {
if (!member->equals(&lhs, &rhs)) return false;
}
return true;
}
template<class C>
typename std::enable_if<!std::is_pod<C>::value, bool>::type
pod_eq(const C&, const C&) const {
return false;
}
// terminates recursion
inline void push_back() { }
......@@ -459,27 +502,7 @@ class default_uniform_type_info : public util::abstract_uniform_type_info<T> {
push_back(std::forward<Ts>(args)...);
}
public:
template<typename... Ts>
default_uniform_type_info(Ts&&... args) {
push_back(std::forward<Ts>(args)...);
}
default_uniform_type_info() {
typedef member_tinfo<T, fake_access_policy<T> > result_type;
m_members.push_back(unique_uti(new result_type));
}
void serialize(const void* obj, serializer* s) const override {
// serialize each member
for (auto& m : m_members) m->serialize(obj, s);
}
void deserialize(void* obj, deserializer* d) const override {
// deserialize each member
for (auto& m : m_members) m->deserialize(obj, d);
}
std::vector<unique_uti> m_members;
};
......
......@@ -486,9 +486,6 @@ Result unroll_expr(PPFPs&, std::uint64_t, minus1l, const std::type_info&,
return none;
}
template<typename A, typename B>
struct wtf { };
template<typename Result, class PPFPs, long N, typename PtrType, class Tuple>
Result unroll_expr(PPFPs& fs,
std::uint64_t bitmask,
......@@ -851,6 +848,16 @@ class match_expr {
};
template<typename T>
struct is_match_expr {
static constexpr bool value = false;
};
template<typename... Cs>
struct is_match_expr<match_expr<Cs...>> {
static constexpr bool value = true;
};
template<class List>
struct match_expr_from_type_list;
......@@ -865,7 +872,6 @@ inline match_expr<Lhs..., Rhs...> operator,(const match_expr<Lhs...>& lhs,
return lhs.or_else(rhs);
}
template<typename... Cs>
match_expr<Cs...>& match_expr_collect(match_expr<Cs...>& arg) {
return arg;
......@@ -987,13 +993,23 @@ behavior_impl_ptr concat_rec(const tdata<>& data,
return combine(pfun, concat_rec(data, token, arg, args...));
}
template<typename T, typename... Ts>
behavior_impl_ptr match_expr_concat(const T& arg, const Ts&... args) {
template<typename T0, typename T1, typename... Ts>
behavior_impl_ptr match_expr_concat(const T0& arg0,
const T1& arg1,
const Ts&... args) {
detail::tdata<> dummy;
return concat_rec(dummy, util::empty_type_list{}, arg, args...);
return concat_rec(dummy, util::empty_type_list{}, arg0, arg1, args...);
}
template<typename T>
behavior_impl_ptr match_expr_concat(const T& arg) {
return arg.as_behavior_impl();
}
template<typename F>
// some more convenience functions
template<typename F,
class E = typename std::enable_if<util::is_callable<F>::value>::type>
match_expr<
typename get_case<
false,
......@@ -1002,7 +1018,7 @@ match_expr<
util::empty_type_list,
util::empty_type_list
>::type>
match_expr_from_functor(F fun) {
lift_to_match_expr(F fun) {
typedef typename get_case<
false,
F,
......@@ -1017,6 +1033,12 @@ match_expr_from_functor(F fun) {
empty_value_guard{}}};
}
template<typename T,
class E = typename std::enable_if<!util::is_callable<T>::value>::type>
inline T lift_to_match_expr(T arg) {
return arg;
}
} // namespace detail
} // namespace cppa
......
......@@ -411,20 +411,6 @@ class on_the_fly_rvalue_builder {
constexpr detail::on_the_fly_rvalue_builder on_arg_match;
// and even more convenience
template<typename F,
class E = typename std::enable_if<util::is_callable<F>::value>::type>
inline auto lift_to_match_expr(F fun) -> decltype(on_arg_match >> fun) {
return (on_arg_match >> fun);
}
template<typename T,
class E = typename std::enable_if<!util::is_callable<T>::value>::type>
inline T lift_to_match_expr(T arg) {
return arg;
}
#endif // CPPA_DOCUMENTATION
} // namespace cppa
......
......@@ -79,11 +79,8 @@ class partial_function {
partial_function& operator=(partial_function&&) = default;
partial_function& operator=(const partial_function&) = default;
template<typename... Cs>
partial_function(const match_expr<Cs...>& arg);
template<typename... Cs, typename T, typename... Ts>
partial_function(const match_expr<Cs...>& arg0, const T& arg1, const Ts&... args);
template<typename T, typename... Ts>
partial_function(const T& arg, Ts&&... args);
/**
* @brief Returns @p true if this partial function is defined for the
......@@ -121,44 +118,6 @@ class partial_function {
};
template<typename T>
typename std::conditional<
may_have_timeout<T>::value,
behavior,
partial_function
>::type
match_expr_convert(const T& arg) {
return {arg};
}
template<typename T0, typename T1, typename... Ts>
typename std::conditional<
util::disjunction<
may_have_timeout<T0>::value,
may_have_timeout<T1>::value,
may_have_timeout<Ts>::value...
>::value,
behavior,
partial_function
>::type
match_expr_convert(const T0& arg0, const T1& arg1, const Ts&... args) {
return detail::match_expr_concat(arg0, arg1, args...);
}
// calls match_expr_convert(lift_to_match_expr(args)...)
template<typename... Ts>
typename std::conditional<
util::disjunction<
may_have_timeout<Ts>::value...
>::value,
behavior,
partial_function
>::type
lift_and_convert(Ts&&... args) {
static_assert(sizeof...(Ts) > 0, "at least one argument required");
return match_expr_convert(lift_to_match_expr(std::forward<Ts>(args))...);
}
template<typename... Cases>
partial_function operator,(const match_expr<Cases...>& mexpr,
const partial_function& pfun) {
......@@ -175,13 +134,11 @@ partial_function operator,(const partial_function& pfun,
* inline and template member function implementations *
******************************************************************************/
template<typename... Cs>
partial_function::partial_function(const match_expr<Cs...>& arg)
: m_impl(arg.as_behavior_impl()) { }
template<typename... Cs, typename T, typename... Ts>
partial_function::partial_function(const match_expr<Cs...>& arg0, const T& arg1, const Ts&... args)
: m_impl(detail::match_expr_concat(arg0, arg1, args...)) { }
template<typename T, typename... Ts>
partial_function::partial_function(const T& arg, Ts&&... args)
: m_impl(detail::match_expr_concat(
detail::lift_to_match_expr(arg),
detail::lift_to_match_expr(std::forward<Ts>(args))...)) { }
inline bool partial_function::defined_at(const any_tuple& value) {
return (m_impl) && m_impl->defined_at(value);
......@@ -201,7 +158,9 @@ typename std::conditional<
partial_function
>::type
partial_function::or_else(Ts&&... args) const {
auto tmp = match_expr_convert(std::forward<Ts>(args)...);
// using a behavior is safe here, because we "cast"
// it back to a partial_function when appropriate
behavior tmp{std::forward<Ts>(args)...};
return m_impl->or_else(tmp.as_behavior_impl());
}
......
......@@ -129,7 +129,11 @@ class response_handle<Self, util::type_list<Ts...>, nonblocking_response_handle_
response_handle& operator=(const response_handle&) = default;
template<typename F>
template<typename F,
typename Enable = typename std::enable_if<
util::is_callable<F>::value
&& !is_match_expr<F>::value
>::type>
typed_continue_helper<
typename detail::lifted_result_type<
typename util::get_callable_trait<F>::result_type
......@@ -177,7 +181,8 @@ class response_handle<Self, any_tuple, blocking_response_handle_tag> {
template<typename... Cs, typename... Ts>
void await(const match_expr<Cs...>& arg, const Ts&... args) {
await(match_expr_convert(arg, args...));
behavior bhvr{arg, args...};
await(bhvr);
}
template<typename... Fs>
......
......@@ -154,8 +154,8 @@ class typed_behavior {
template<typename T, typename... Ts>
typed_behavior(T arg, Ts&&... args) {
set(match_expr_collect(lift_to_match_expr(std::move(arg)),
lift_to_match_expr(std::forward<Ts>(args))...));
set(match_expr_collect(detail::lift_to_match_expr(std::move(arg)),
detail::lift_to_match_expr(std::forward<Ts>(args))...));
}
template<typename... Cs>
......
......@@ -35,6 +35,8 @@
#include "cppa/deserializer.hpp"
#include "cppa/uniform_type_info.hpp"
#include "cppa/util/type_traits.hpp"
#include "cppa/detail/to_uniform_name.hpp"
#include "cppa/detail/uniform_type_info_map.hpp"
......@@ -61,15 +63,6 @@ class abstract_uniform_type_info : public uniform_type_info {
return make_any_tuple(deref(instance));
}
protected:
abstract_uniform_type_info() {
auto uname = detail::to_uniform_name<T>();
auto cname = detail::mapped_name_by_decorated_name(uname.c_str());
if (cname == uname.c_str()) m_name = std::move(uname);
else m_name = cname;
}
bool equals(const void* lhs, const void* rhs) const override {
return eq(deref(lhs), deref(rhs));
}
......@@ -82,6 +75,15 @@ class abstract_uniform_type_info : public uniform_type_info {
delete reinterpret_cast<T*>(instance);
}
protected:
abstract_uniform_type_info() {
auto uname = detail::to_uniform_name<T>();
auto cname = detail::mapped_name_by_decorated_name(uname.c_str());
if (cname == uname.c_str()) m_name = std::move(uname);
else m_name = cname;
}
static inline const T& deref(const void* ptr) {
return *reinterpret_cast<const T*>(ptr);
}
......@@ -90,22 +92,42 @@ class abstract_uniform_type_info : public uniform_type_info {
return *reinterpret_cast<T*>(ptr);
}
// can be overridden in subclasses to compare POD types
// by comparing each individual member
virtual bool pod_mems_equals(const T&, const T&) const {
return false;
}
std::string m_name;
private:
template<class C>
typename std::enable_if<std::is_empty<C>::value == true , bool>::type
typename std::enable_if<std::is_empty<C>::value, bool>::type
eq(const C&, const C&) const {
return true;
}
template<class C>
typename std::enable_if<std::is_empty<C>::value == false, bool>::type
typename std::enable_if<
!std::is_empty<C>::value && util::is_comparable<C, C>::value,
bool
>::type
eq(const C& lhs, const C& rhs) const {
return lhs == rhs;
}
template<class C>
typename std::enable_if<
!std::is_empty<C>::value
&& std::is_pod<C>::value
&& !util::is_comparable<C, C>::value,
bool
>::type
eq(const C& lhs, const C& rhs) const {
return pod_mems_equals(lhs, rhs);
}
};
} } // namespace cppa::util
......
......@@ -17,7 +17,9 @@ add(announce_5 type_system)
add(dancing_kirby message_passing)
add(dining_philosophers message_passing)
add(hello_world .)
add(aout .)
add(calculator message_passing)
add(typed_calculator message_passing)
add(distributed_calculator remote_actors)
add(group_server remote_actors)
add(group_chat remote_actors)
......
......@@ -6,10 +6,11 @@ using namespace std;
using namespace cppa;
behavior mirror(event_based_actor* self) {
// wait for messages
return (
// invoke this lambda expression if we receive a string
on_arg_match >> [=](const string& what) -> string {
// return the (initial) actor behavior
return {
// a handler for messages containing a single string
// that replies with a string
[=](const string& what) -> string {
// prints "Hello World!" via aout (thread-safe cout wrapper)
aout(self) << what << endl;
// terminates this actor ('become' otherwise loops forever)
......@@ -17,15 +18,15 @@ behavior mirror(event_based_actor* self) {
// reply "!dlroW olleH"
return string(what.rbegin(), what.rend());
}
);
};
}
void hello_world(event_based_actor* self, const actor& buddy) {
// send "Hello World!" to our buddy ...
self->sync_send(buddy, "Hello World!").then(
// ... and wait for a response
on_arg_match >> [=](const string& what) {
// prints "!dlroW olleH"
// ... wait for a response ...
[=](const string& what) {
// ... and print it
aout(self) << what << endl;
}
);
......@@ -34,7 +35,7 @@ void hello_world(event_based_actor* self, const actor& buddy) {
int main() {
// create a new actor that calls 'mirror()'
auto mirror_actor = spawn(mirror);
// create another actor that calls 'hello_world(mirror_actor)'
// create another actor that calls 'hello_world(mirror_actor)';
spawn(hello_world, mirror_actor);
// wait until all other actors we have spawned are done
await_all_actors_done();
......
......@@ -3,7 +3,6 @@
* for both the blocking and the event-based API. *
\******************************************************************************/
#include <string>
#include <cassert>
#include <iostream>
#include "cppa/cppa.hpp"
......
......@@ -11,19 +11,20 @@ The default behavior for actors receiving such an exit message is to die for the
Actors can \textit{trap} exit messages to handle them manually.
\begin{lstlisting}
actor_ptr worker = ...;
actor worker = ...;
// receive exit messages as regular messages
self->trap_exit(true);
// monitor spawned actor
self->link_to(worker);
// wait until worker exited
become (
on(atom("EXIT"), exit_reason::normal) >> [] {
self->become (
[](const exit_msg& e) >> [=] {
if (e.reason == exit_reason::normal) {
// worker finished computation
},
on(atom("EXIT"), arg_match) >> [](std::uint32_t reason) {
else {
// worker died unexpectedly
}
}
);
\end{lstlisting}
......@@ -36,17 +37,18 @@ Unlike exit messages, down messages are always treated like any other ordinary m
An actor will receive one down message for each time it called \lstinline^self->monitor(...)^, even if it adds a monitor to the same actor multiple times.
\begin{lstlisting}
actor_ptr worker = ...;
actor worker = ...;
// monitor spawned actor
self->monitor(worker);
// wait until worker exited
receive (
on(atom("DOWN"), exit_reason::normal) >> [] {
self->become (
on(const down_msg& d) >> [] {
if (d.reason == exit_reason::normal) {
// worker finished computation
},
on(atom("DOWN"), arg_match) >> [](std::uint32_t reason) {
} else {
// worker died unexpectedly
}
}
);
\end{lstlisting}
......@@ -84,14 +86,14 @@ Thus, \lstinline^self^ refers to the terminating actor and not to the actor that
\begin{lstlisting}
auto worker = spawn(...);
actor_ptr observer = self;
actor observer = self;
// "monitor" spawned actor
worker->attach_functor([observer](std::uint32_t reason) {
// this callback is invoked from worker
send(observer, atom("DONE"));
anon_send(observer, atom("DONE"));
});
// wait until worker exited
become (
self->become (
on(atom("DONE")) >> [] {
// worker terminated
}
......
......@@ -12,7 +12,8 @@ Thread-mapped actors can be used to opt-out of this cooperative scheduling.
\subsection{Implicit \texttt{self} Pointer}
When using a function or functor to implement an actor, the first argument \emph{can} be used to capture a pointer to the actor itself.
The type of this pointer is \lstinline^event_based_actor^ or \lstinline^blocking_actor^ when using the \lstinline^blocking_api^ flag or their typed counterparts when dealing with typed actors.
The type of this pointer is \lstinline^event_based_actor*^ per default and \lstinline^blocking_actor*^ when using the \lstinline^blocking_api^ flag.
When dealing with typed actors, the types are \lstinline^typed_event_based_actor<...>*^ and \lstinline^typed_blocking_actor<...>*^.
\clearpage
\subsection{Interface}
......@@ -33,26 +34,20 @@ class local_actor;
\hline
\lstinline^bool trap_exit()^ & Checks whether this actor traps exit messages \\
\hline
\lstinline^bool chaining()^ & Checks whether this actor uses the ``chained send'' optimization (see Section \ref{Sec::Send::ChainedSend}) \\
\hline
\lstinline^any_tuple last_dequeued()^ & Returns the last message that was dequeued from the actor's mailbox\newline\textbf{Note}: Only set during callback invocation \\
\hline
\lstinline^actor_ptr last_sender()^ & Returns the sender of the last dequeued message\newline\textbf{Note$_{1}$}: Only set during callback invocation\newline\textbf{Note$_{2}$}: Used implicitly to send response messages (see Section \ref{Sec::Send::Reply}) \\
\lstinline^actor_addr last_sender()^ & Returns the sender of the last dequeued message\newline\textbf{Note}: Only set during callback invocation \\
\hline
\lstinline^vector<group_ptr> joined_groups()^ & Returns all subscribed groups \\
\lstinline^vector<group> joined_groups()^ & Returns all subscribed groups \\
\hline
\\
\multicolumn{2}{l}{\textbf{Modifiers}\vspace{3pt}} \\
\hline
\lstinline^void trap_exit(bool enabled)^ & Enables or disables trapping of exit messages \\
\hline
\lstinline^void chaining(bool enabled)^ & Enables or disables chained send \\
\hline
\lstinline^void join(const group_ptr& g)^ & Subscribes to group \lstinline^g^ \\
\lstinline^void join(const group& g)^ & Subscribes to group \lstinline^g^ \\
\hline
\lstinline^void leave(const group_ptr& g)^ & Unsubscribes group \lstinline^g^ \\
\hline
\lstinline^auto make_response_handle()^ & Creates a handle that can be used to respond to the last received message later on, e.g., after receiving another message \\
\lstinline^void leave(const group& g)^ & Unsubscribes group \lstinline^g^ \\
\hline
\lstinline^void on_sync_failure(auto fun)^ & Sets a handler, i.e., a functor taking no arguments, for unexpected synchronous response messages (default action is to kill the actor for reason \lstinline^unhandled_sync_failure^) \\
\hline
......@@ -62,8 +57,6 @@ class local_actor;
\hline
\lstinline^void demonitor(actor_ptr whom)^ & Removes a monitor from \lstinline^whom^ \\
\hline
\lstinline^void exec_behavior_stack()^ & Executes an actor's behavior stack until it is empty \\
\hline
\lstinline^bool has_sync_failure_handler()^ & Checks wheter this actor has a user-defined sync failure handler \\
\hline
\end{tabular*}
......
......@@ -54,16 +54,18 @@ Represents an optional value.
\subsection{Using \texttt{aout} -- A Concurrency-safe Wrapper for \texttt{cout}}
When using \lstinline^cout^ from multiple actors, output often appears interleaved.
Moreover, using \lstinline^cout^ from multiple actors -- and thus multiple threads -- in parallel should be avoided, since the standard does not guarantee a thread-safe implementation.
Moreover, using \lstinline^cout^ from multiple actors -- and thus from multiple threads -- in parallel should be avoided regardless, since the standard does not guarantee a thread-safe implementation.
By replacing \texttt{std::cout} with \texttt{cppa::aout}, actors can achieve a concurrency-safe text output.
The header \lstinline^cppa/cppa.hpp^ also defines overloads for \texttt{std::endl} and \texttt{std::flush} for \lstinline^aout^, but does not support the full range of ostream operations (yet).
Each write operation to \texttt{aout} sends a message to a `hidden' actor (keep in mind, sending messages from actor constructors is not safe).
This actor only prints lines, unless output is forced using \lstinline^flush^.
The example below illustrates printing of lines of text from multiple actors (in random order).
\begin{lstlisting}
#include <chrono>
#include <cstdlib>
#include <iostream>
#include "cppa/cppa.hpp"
using namespace cppa;
......@@ -72,17 +74,19 @@ using std::endl;
int main() {
std::srand(std::time(0));
for (int i = 1; i <= 50; ++i) {
spawn([i] {
aout << "Hi there! This is actor nr. " << i << "!" << endl;
spawn<blocking_api>([i](blocking_actor* self) {
aout(self) << "Hi there! This is actor nr. "
<< i << "!" << endl;
std::chrono::milliseconds tout{std::rand() % 1000};
delayed_send(self, tout, atom("done"));
receive(others() >> [i] {
aout << "Actor nr. " << i << " says goodbye!" << endl;
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_others_done();
await_all_actors_done();
// done
shutdown();
return 0;
......
......@@ -13,7 +13,7 @@ It takes a partial function that is applied to the elements in the mailbox until
An actor calling \lstinline^receive^ is blocked until it successfully dequeued a message from its mailbox or an optional timeout occurs.
\begin{lstlisting}
receive (
self->receive (
on<int>().when(_x1 > 0) >> // ...
);
\end{lstlisting}
......@@ -80,9 +80,9 @@ The examples above illustrate the correct usage of the three loops \lstinline^re
It is possible to nest receives and receive loops.
\begin{lstlisting}
receive_loop (
on<int>() >> [](int value1) {
receive (
self->receive_loop (
on<int>() >> [&](int value1) {
self->receive (
on<float>() >> [&](float value2) {
cout << value1 << " => " << value2 << endl;
}
......@@ -94,27 +94,18 @@ receive_loop (
\clearpage
\subsection{Receiving Synchronous Responses}
\begin{lstlisting}
actor_ptr testee = ...; // replies with a string to 'get'
Analogous to \lstinline^sync_send(...).then(...)^ for event-based actors, blocking actors can use \lstinline^sync_send(...).await(...)^.
auto future = sync_send(testee, atom("get"));
receive_response (future) (
on_arg_match >> [&](const std::string& str) {
// handle str
},
after(std::chrono::seconds(30)) >> [&]() {
// handle error
}
);
// or:
sync_send(testee, atom("get")).await (
on_arg_match >> [&](const std::string& str) {
\begin{lstlisting}
void foo(blocking_actor* self, actor testee) {
// testee replies with a string to 'get'
self->sync_send(testee, atom("get")).await(
[&](const std::string& str) {
// handle str
},
after(std::chrono::seconds(30)) >> [&]() {
// handle error
}
);
);
}
\end{lstlisting}
......@@ -5,37 +5,21 @@
\begin{itemize}
\item The functions \lstinline^become^ and \lstinline^handle_response^ do not block, i.e., always return immediately.
Thus, you should \textit{always} capture by value in event-based actors, because all references on the stack will cause undefined behavior if a lambda is executed.
\end{itemize}
\subsection{Mixing Event-Based and Blocking API}
\begin{itemize}
\item Blocking \libcppa function such as \lstinline^receive^ will \emph{throw an exception} if accessed from an event-based actor.
%To catch as many errors as possible at compile-time, \libcppa will produce an error if \lstinline^receive^ is called and the \lstinline^this^ pointer is set and points to an event-based actor.
\item Context-switching and thread-mapped actors \emph{can} use the \lstinline^become^ API.
Whenever a non-event-based actor calls \lstinline^become()^ for the first time, it will create a behavior stack and execute it until the behavior stack is empty.
Thus, the \textit{initial} \lstinline^become^ blocks until the behavior stack is empty, whereas all subsequent calls to \lstinline^become^ will return immediately.
Related functions, e.g., \lstinline^sync_send(...).then(...)^, behave the same, as they manipulate the behavior stack as well.
Thus, one should \textit{always} capture by value in lambda expressions, because all references on the stack will cause undefined behavior if the lambda expression is executed.
\end{itemize}
\subsection{Synchronous Messages}
\begin{itemize}
\item
\lstinline^send(self->last_sender(), ...)^ does \textbf{not} send a response message.
\item
A handle returned by \lstinline^sync_send^ represents \emph{exactly one} response message.
Therefore, it is not possible to receive more than one response message.
%Calling \lstinline^reply^ more than once will result in lost messages and calling \lstinline^handle_response^ or \lstinline^receive_response^ more than once on a future will throw an exception.
\item
The future returned by \lstinline^sync_send^ is bound to the calling actor.
It is not possible to transfer such a future to another actor.
Calling \lstinline^receive_response^ or \lstinline^handle_response^ for a future bound to another actor is undefined behavior.
The handle returned by \lstinline^sync_send^ is bound to the calling actor.
It is not possible to transfer a handle to a response to another actor.
\end{itemize}
......@@ -44,29 +28,27 @@ Calling \lstinline^receive_response^ or \lstinline^handle_response^ for a future
\begin{itemize}
\item
\lstinline^send(whom, ...)^ is syntactic sugar for \lstinline^whom << make_any_tuple(...)^.
\lstinline^send(whom, ...)^ is defined as \lstinline^send_tuple(whom, make_any_tuple(...))^.
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^any_tuple^ instance.
The correct way of forwarding messages is \lstinline^self->forward_to(whom)^.
\end{itemize}
\clearpage
\subsection{Sharing}
\begin{itemize}
\item It is strongly recommended to \textbf{not} share states between actors.
In particular, no actor shall ever access member variables or member functions of another actor.
Accessing shared memory segments concurrently can cause undefined behavior that is incredibly hard to find and debug.
However, sharing \textit{data} between actors is fine, as long as the data is \textit{immutable} and all actors access the data only via smart pointers such as \lstinline^std::shared_ptr^.
However, sharing \textit{data} between actors is fine, as long as the data is \textit{immutable} and its lifetime is guaranteed to outlive all actors.
The simplest way to meet the lifetime guarantee is by storing the data in smart pointers such as \lstinline^std::shared_ptr^.
Nevertheless, the recommended way of sharing informations is message passing.
Sending data to multiple actors does \textit{not} result in copying the data several times.
Sending data to multiple actors does not necessarily result in copying the data several times.
Read Section \ref{Sec::Tuples} to learn more about \libcppa's copy-on-write optimization for tuples.
\end{itemize}
\subsection{Constructors of Class-based Actors}
\begin{itemize}
\item During constructor invocation, \lstinline^self^ does \textbf{not} point to \lstinline^this^. It points to the invoking actor instead.
\item You should \textbf{not} send or receive messages in a constructor or destructor.
\item You should \textbf{not} try to send or receive messages in a constructor or destructor, because the actor is not fully initialized at this point.
\end{itemize}
......@@ -57,18 +57,20 @@ We will support this platform as soon as Microsoft's compiler implements all req
\begin{lstlisting}
#include <string>
#include <iostream>
#include "cppa/cppa.hpp"
using namespace std;
using namespace cppa;
behavior mirror(event_based_actor* self) {
// wait for messages
return (
// invoke this lambda expression if we receive a string
on_arg_match >> [=](const string& what) -> string {
// return the (initial) actor behavior
return {
// a handler for messages containing a single string
// that replies with a string
[=](const string& what) -> string {
// prints "Hello World!" via aout
// (a thread-safe cout wrapper)
// (thread-safe cout wrapper)
aout(self) << what << endl;
// terminates this actor
// ('become' otherwise loops forever)
......@@ -76,15 +78,15 @@ behavior mirror(event_based_actor* self) {
// reply "!dlroW olleH"
return string(what.rbegin(), what.rend());
}
);
};
}
void hello_world(event_based_actor* self, const actor& buddy) {
// send "Hello World!" to our buddy ...
self->sync_send(buddy, "Hello World!").then(
// ... and wait for a response
on_arg_match >> [=](const string& what) {
// prints "!dlroW olleH"
// ... wait for a response ...
[=](const string& what) {
// ... and print it
aout(self) << what << endl;
}
);
......@@ -93,7 +95,7 @@ void hello_world(event_based_actor* self, const actor& buddy) {
int main() {
// create a new actor that calls 'mirror()'
auto mirror_actor = spawn(mirror);
// create another actor that calls 'hello_world(mirror_actor)'
// create another actor that calls 'hello_world(mirror_actor)';
spawn(hello_world, mirror_actor);
// wait until all other actors we have spawned are done
await_all_actors_done();
......
......@@ -9,7 +9,7 @@ std::string group_module = ...;
std::string group_id = ...;
auto grp = group::get(group_module, group_id);
self->join(grp);
send(grp, atom("test"));
self->send(grp, atom("test"));
self->leave(grp);
\end{lstlisting}
......
......@@ -9,18 +9,21 @@ By decoupling concurrently running software components via message passing, the
Actors can create -- ``spawn'' -- new actors and monitor each other to build fault-tolerant, hierarchical systems.
Since message passing is network transparent, the actor model applies to both concurrency and distribution.
When dealing with dozens of cores, mutexes, semaphores and thread primitives are the wrong level of abstraction.
When dealing with dozens of cores, mutexes, semaphores and other threading primitives are the wrong level of abstraction.
Implementing applications on top of those primitives has proven challenging and error-prone.
Additionally, mutex-based implementations can cause queueing and unmindful sharing can lead to false sharing -- both decreasing performance significantly, up to the point that an application actually runs when adding more cores.
Additionally, mutex-based implementations can cause queueing and unmindful access to (even distinct) data from separate threads in parallel can lead to false sharing -- both decreasing performance significantly, up to the point that an application actually runs slower when adding more cores.
The actor model has gained momentum over the last decade due to its high level of abstraction and its ability to make efficient use of multicore and multiprocessor machines.
However, the actor model has not yet been widely adopted in the native programming domain.
With \libcppa, we contribute a library for actor programming in C++ as open source software to ease development of concurrent as well as distributed software without sacrificing performance.
With \libcppa, we contribute a library for actor programming in C++ as open source software to ease native development of concurrent as well as distributed systems.
In this regard, \libcppa follows the C++ philosophy ``building the highest abstraction possible without sacrificing performance''.
\subsection{Terminology}
You will find that \libcppa has not simply adopted exiting implementations based on the actor model such as Erlang or the Akka library.
Instead, \libcppa aims to provide an modern C++ API allowing for type-safe messaging.
Instead, \libcppa aims to provide a modern C++ API allowing for type-safe as well as dynamically typed messaging.
Hence, most aspects of our system are familiar to developers having experience with other actor systems, but there are also slight differences in terminology.
However, neither \libcppa nor this manual require any foreknowledge.
\subsubsection{Actor Address}
......@@ -35,6 +38,18 @@ An actor handle contains the address of an actor along with its type information
In order to send an actor a message, one needs to have a handle to it -- the address alone is not sufficient.
The distinction between handles and addresses -- which is unique to \libcppa when comparing it to other actor systems -- is a consequence of the design decision to support both untyped and typed actors.
\subsubsection{Untyped Actors}
An untyped actor does not constrain the type of messages it receives, i.e., a handle to an untyped actor accepts any kind of message.
That does of course not mean that untyped actors must handle all possible types of messages.
Choosing typed vs untyped actors is mostly a matter of taste.
Untyped actors allow developers to build prototypes faster, while typed actors allow the compiler to fetch more errors at compile time.
\subsubsection{Typed Actor}
A typed actor defines its messaging interface, i.e., both input and output types, in its type.
This allows the compiler to check message types statically.
\subsubsection{Spawning}
``Spawning'' an actor means to create and run a new actor.
......@@ -45,7 +60,7 @@ The distinction between handles and addresses -- which is unique to \libcppa whe
A monitored actor sends a ``down message'' to all actors monitoring it as part of its termination.
This allows actors to supervise other actors and to take measures when one of the supervised actors failed, i.e., terminated with a non-normal exit reason.
\subsubsection{Links}
\subsubsection{Link}
A link is bidirectional connection between two actors.
Each actor sends an ``exit message'' to all of its links as part of its termination.
......
......@@ -6,30 +6,30 @@ 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}
void testee() {
behavior testee(event_based_actor* self) {
// send 'b' with normal priority
send(self, atom("b"));
self->send(self, atom("b"));
// send 'a' with high priority
send({self, message_priority::high}, atom("a"));
self->send(message_priority::high, self, atom("a"));
// terminate after receiving a 'b'
become (
on(atom("b")) >> [] {
aout << "received 'b' => quit" << endl;
return {
on(atom("b")) >> [=] {
aout(self) << "received 'b' => quit" << endl;
self->quit();
},
on(atom("a")) >> [] {
aout << "received 'a'" << endl;
on(atom("a")) >> [=] {
aout(self) << "received 'a'" << endl;
},
);
};
}
int main() {
// will print "received 'b' => quit"
spawn(testee);
await_all_others_done();
await_all_actors_done();
// will print "received 'a'" and then "received 'b' => quit"
spawn<priority_aware>(testee);
await_all_others_done();
await_all_actors_done();
shutdown();
}
\end{lstlisting}
......@@ -6,7 +6,7 @@ Remote actors are represented by actor proxies that forward all messages.
\subsection{Publishing of Actors}
\begin{lstlisting}
void publish(actor_ptr whom, std::uint16_t port, const char* addr = 0)
void publish(actor whom, std::uint16_t port, const char* addr = 0)
\end{lstlisting}
The function \lstinline^publish^ binds an actor to a given port.
......@@ -16,7 +16,7 @@ Otherwise, the actor accepts all incoming connections (\lstinline^INADDR_ANY^).
\begin{lstlisting}
publish(self, 4242);
become (
self->become (
on(atom("ping"), arg_match) >> [](int i) {
return make_cow_tuple(atom("pong"), i);
}
......@@ -26,7 +26,7 @@ become (
\subsection{Connecting to Remote Actors}
\begin{lstlisting}
actor_ptr remote_actor(const char* host, std::uint16_t port)
actor remote_actor(const char* host, std::uint16_t port)
\end{lstlisting}
The function \lstinline^remote_actor^ connects to the actor at given host and port.
......@@ -34,12 +34,12 @@ A \lstinline^network_error^ is thrown if the connection failed.
\begin{lstlisting}
auto pong = remote_actor("localhost", 4242);
send(pong, atom("ping"), 0);
become (
on(atom("pong"), 10) >> [] {
self->send(pong, atom("ping"), 0);
self->become (
on(atom("pong"), 10) >> [=] {
self->quit();
},
on(atom("pong"), arg_match) >> [](int i) {
on(atom("pong"), arg_match) >> [=](int i) {
return make_cow_tuple(atom("ping"), i+1);
}
);
......
......@@ -73,6 +73,7 @@ Note that the second version does call \lstinline^on^ without template parameter
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) { /*...*/ }
......
......@@ -8,15 +8,14 @@ The given behavior is then executed until it is replaced by another call to \lst
\subsection{Class-based actors}
A class-based actor is a subtype of \lstinline^event_based_actor^ and must implement the pure virtual member function \lstinline^init^.
An implementation of \lstinline^init^ shall set an initial behavior by using \lstinline^become^.
A class-based actor is a subtype of \lstinline^event_based_actor^ and must implement the pure virtual member function \lstinline^make_behavior^ returning the initial behavior.
\begin{lstlisting}
class printer : public event_based_actor {
behavior make_behavior() override {
return {
others() >> [] {
cout << to_string(self->last_received()) << endl;
cout << to_string(last_received()) << endl;
}
};
}
......@@ -28,11 +27,11 @@ This base class simply returns \lstinline^init_state^ (defined in the subclass)
\begin{lstlisting}
struct printer : sb_actor<printer> {
behavior init_state = (
behavior init_state {
others() >> [] {
cout << to_string(self->last_received()) << endl;
}
);
};
};
\end{lstlisting}
......@@ -46,7 +45,7 @@ class fixed_stack : public sb_actor<fixed_stack> {
friend class sb_actor<fixed_stack>;
size_t max_size = 10;
size_t max_size;
vector<int> data;
......@@ -103,22 +102,22 @@ An actor can set a new behavior by calling \lstinline^become^ with the \lstinlin
\begin{lstlisting}
// receives {int, float} sequences
void testee() {
become (
on_arg_match >> [=](int value1) {
become (
behavior testee(event_based_actor* self) {
return {
[=](int value1) {
self->become (
// the keep_behavior policy stores the current behavior
// on the behavior stack to be able to return to this
// behavior later on by calling unbecome()
keep_behavior,
on_arg_match >> [=](float value2) {
[=](float value2) {
cout << value1 << " => " << value2 << endl;
// restore previous behavior
unbecome();
self->unbecome();
}
);
}
);
};
}
\end{lstlisting}
......@@ -146,16 +145,16 @@ But often, we need to be able to recover if an expected messages does not arrive
using std::endl;
void eager_actor() {
become (
on_arg_match >> [](int i) { /* ... */ },
on_arg_match >> [](float i) { /* ... */ },
behavior eager_actor(event_based_actor* self) {
return {
[](int i) { /* ... */ },
[](float i) { /* ... */ },
others() >> [] { /* ... */ },
after(std::chrono::seconds(10)) >> []() {
aout << "received nothing within 10 seconds..." << endl;
after(std::chrono::seconds(10)) >> [] {
aout(self) << "received nothing within 10 seconds..." << endl;
// ...
}
);
};
}
\end{lstlisting}
......@@ -181,18 +180,18 @@ 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}
void server() {
behavior server(event_based_actor* self) {
auto die = [=] { self->quit(exit_reason::user_defined); };
become (
return {
on(atom("idle")) >> [=] {
auto worker = last_sender();
become (
self->become (
keep_behavior,
on(atom("request")) >> [=] {
// forward request to idle worker
forward_to(worker);
self->forward_to(worker);
// await next idle message
unbecome();
self->unbecome();
},
on(atom("idle")) >> skip_message,
others() >> die
......@@ -200,6 +199,6 @@ void server() {
},
on(atom("request")) >> skip_message,
others() >> die
);
};
}
\end{lstlisting}
\section{Sending Messages}
\label{Sec::Send}
Messages can be sent by using \lstinline^send^, \lstinline^send_tuple^, or \lstinline^operator<<^. The variadic template function \lstinline^send^ has the following signature.
Messages can be sent by using the member function \lstinline^send^ or \lstinline^send_tuple^.
The variadic template function \lstinline^send^ has the following signature.
\begin{lstlisting}
template<typename... Args>
......@@ -9,41 +10,31 @@ void send(actor whom, Args&&... what);
\end{lstlisting}
The variadic template pack \lstinline^what...^ is converted to a dynamically typed tuple (see Section \ref{Sec::Tuples::DynamicallyTypedTuples}) and then enqueued to the mailbox of \lstinline^whom^.
The following example shows two equal sends, one using \lstinline^send^ and the other using \lstinline^operator<<^.
\begin{lstlisting}
actor other = spawn(...);
send(other, 1, 2, 3);
other << make_any_tuple(1, 2, 3);
\end{lstlisting}
Using the function \lstinline^send^ is more compact, but does not have any other benefit.
However, note that you should not use \lstinline^send^ if you already have an instance of \lstinline^any_tuple^, because it creates a new tuple containing the old one.
\begin{lstlisting}
actor other = spawn(...);
auto msg = make_any_tuple(1, 2, 3);
send(other, msg); // oops, creates a new tuple that contains msg
send_tuple(other, msg); // ok
other << msg; // ok
void some_fun(event_based_actor* self) {
actor other = spawn(...);
auto msg = make_any_tuple(1, 2, 3);
self->send(other, msg); // oops, creates a new tuple containing msg
self->send_tuple(other, msg); // ok
}
\end{lstlisting}
The function \lstinline^send_tuple^ is equal to \lstinline^operator<<^.
Choosing one or the other is merely a matter of personal preferences.
\clearpage
\subsection{Replying to Messages}
\label{Sec::Send::Reply}
The return value of a message handler is used as response message.
During callback invocation, \lstinline^self->last_sender()^ is set to the address of the sender.
Actors can also use the result of a \lstinline^send^ to answer to a request, as shown below.
Actors can also use the result of a \lstinline^sync_send^ to answer to a request, as shown below.
\begin{lstlisting}
void client(const actor& master) {
void client(event_based_actor* self, const actor& master) {
become (
on("foo", arg_match) >> [=](const string& request) -> string {
return sync_send(master, atom("bar"), request).then(
return self->sync_send(master, atom("bar"), request).then(
on_arg_match >> [=](const std::string& response) {
return response;
}
......@@ -59,20 +50,23 @@ Messages can be delayed, e.g., to implement time-based polling strategies, by us
The following example illustrates a polling strategy using \lstinline^delayed_send^.
\begin{lstlisting}
delayed_send(self, std::chrono::seconds(1), atom("poll"));
become (
on(atom("poll")) >> []() {
// poll a resource...
behavior poller(event_based_actor* self) {
self->delayed_send(self, std::chrono::seconds(1), atom("poll"));
return {
on(atom("poll")) >> [] {
// poll a resource
// ...
// schedule next polling
delayed_send(self, std::chrono::seconds(1), atom("poll"));
self->delayed_send(self, std::chrono::seconds(1), atom("poll"));
}
);
};
}
\end{lstlisting}
\clearpage
\subsection{Forwarding Messages}
\subsection{Forwarding Messages in Untyped Actors}
The function \lstinline^forward_to^ forwards the last dequeued message to an other actor.
The member function \lstinline^forward_to^ forwards the last dequeued message to an other actor.
Forwarding a synchronous message will also transfer responsibility for the request, i.e., the receiver of the forwarded message can reply as usual and the original sender of the message will receive the response.
The following diagram illustrates forwarding of a synchronous message from actor \texttt{B} to actor \texttt{C}.
......
......@@ -5,9 +5,9 @@ Actors are created using the function \lstinline^spawn^.
%\subsection{Using \lstinline^spawn^ to Create Actors from Functors or Classes}
The easiest way to implement actors is to use functors, e.g., a free function or lambda expression.
The arguments to the functor are passed to \lstinline^spawn^ as additional arguments.
The function \lstinline^spawn^ also takes optional flags as template paremeter.
The function \lstinline^spawn^ also takes optional flags as template parameter.
%The optional template parameter of \lstinline^spawn^ decides whether an actor should run in its own thread or takes place in the cooperative scheduling.
The flag \lstinline^detached^ causes \lstinline^spawn^ to create a thread-mapped actor (opt-out of the cooperative scheduling), the flag \lstinline^linked^ links the newly created actor to its parent, and the flag \lstinline^monitored^ automatically adds a monitor to the new actor.
The flag \lstinline^detached^ causes \lstinline^spawn^ to create a thread-mapped actor (opt-out of the cooperative scheduling), the flag \lstinline^linked^ links the newly created actor to its parent -- not available on top-level spawn -- and the flag \lstinline^monitored^ automatically adds a monitor to the new actor.
Actors that make use of the blocking API (see Section \ref{Sec::BlockingAPI}) must be spawned using the flag \lstinline^blocking_api^.
Flags are concatenated using the operator \lstinline^+^, as shown in the examples below.
......@@ -17,7 +17,7 @@ Flags are concatenated using the operator \lstinline^+^, as shown in the example
using namespace cppa;
void my_actor1();
void my_actor2(int arg1, const std::string& arg2);
void my_actor2(event_based_actor*, int arg1, const std::string& arg2);
void ugly_duckling();
class my_actor3 : public event_based_actor { /* ... */ };
......@@ -26,26 +26,34 @@ class my_actor4 : public sb_actor<my_actor4> {
/* ... */
};
int main() {
// whenever we want to link to or monitor a spawned actor,
// we have to spawn it using the self pointer, otherwise
// we can use the free function 'spawn' (top-level spawn)
void server(event_based_actor* self) {
// spawn function-based actors
auto a0 = spawn(my_actor1);
auto a1 = spawn<linked>(my_actor2, 42, "hello actor");
auto a2 = spawn<monitored>([] { /* ... */ });
auto a1 = self->spawn<linked>(my_actor2, 42, "hello actor");
auto a2 = self->spawn<monitored>([] { /* ... */ });
auto a3 = spawn([](int) { /* ... */ }, 42);
// spawn thread-mapped actors
auto a4 = spawn<detached>(my_actor1);
auto a5 = spawn<detached + linked>([] { /* ... */ });
auto a5 = self->spawn<detached + linked>([] { /* ... */ });
auto a6 = spawn<detached>(my_actor2, 0, "zero");
// spawn class-based actors
auto a7 = spawn<my_actor3>();
auto a8 = spawn<my_actor4, monitored>(42);
auto a8 = self->spawn<my_actor4, monitored>(42);
// spawn thread-mapped actors using a class
auto a9 = spawn<my_actor4, detached>(42);
// spawn actors that need access to the blocking API
auto b0 = spawn<blocking_api>(ugly_duckling);
auto aa = self->spawn<blocking_api>(ugly_duckling);
// compiler error: my_actor2 captures the implicit
// self pointer as event_based_actor* and thus cannot
// be spawned using blocking_api flag
/*-auto ab = self->spawn<blocking_api>(my_actor2);-*/
}
\end{lstlisting}
\textbf{Note}: \lstinline^spawn(fun, arg0, ...)^ is \textbf{not} equal to \lstinline^spawn(std::bind(fun, arg0, ...))^!
For example, a call to \lstinline^spawn(fun, self, ...)^ will pass a pointer to the calling actor to the newly created actor, as expected, whereas \lstinline^spawn(std::bind(fun, self, ...))^ wraps the type of \lstinline^self^ into the function wrapper and evaluates \lstinline^self^ on function invocation.
Thus, the actor will end up having a pointer \emph{to itself} rather than a pointer to its parent.
%TODO: check how std::bind(..., self) behaves atm
%\textbf{Note}: \lstinline^spawn(fun, arg0, ...)^ is \textbf{not} equal to \lstinline^spawn(std::bind(fun, arg0, ...))^!
%For example, a call to \lstinline^spawn(fun, self, ...)^ will pass a pointer to the calling actor to the newly created actor, as expected, whereas \lstinline^spawn(std::bind(fun, self, ...))^ wraps the type of \lstinline^self^ into the function wrapper and evaluates \lstinline^self^ on function invocation.
%Thus, the actor will end up having a pointer \emph{to itself} rather than a pointer to its parent.
\section{Strongly Typed Actors}
Strongly typed actors provide a convenient way of defining type-safe messaging interfaces.
Unlike ``dynamic actors'', typed actors are not allowed to change their behavior at runtime, neither are typed actors allowed to use guard expressions.
When calling \lstinline^become^ in a strongly typed actor, the actor will be killed with exit reason \lstinline^unallowed_function_call^.
Typed actors use \lstinline^typed_actor_ptr<...>^ instead of \lstinline^actor_ptr^, whereas the template parameters hold the messaging interface.
For example, an actor responding to two integers with a dobule would use the type \lstinline^typed_actor_ptr<replies_to<int, int>::with<double>>^.
Unlike untyped actorsd, typed actors are not allowed to use guard expressions.
When calling \lstinline^become^ in a strongly typed actor, \emph{all} message handlers from the typed interface must be set.
Typed actors use handles of type \lstinline^typed_actor<...>^ rather than \lstinline^actor^, whereas the template parameters hold the messaging interface.
For example, an actor responding to two integers with a dobule would use the type \lstinline^typed_actor<replies_to<int, int>::with<double>>^.
All functions for message passing, linking and monitoring are overloaded to accept both types of actors.
As of version 0.8, strongly typed actors cannot be published and do not support message priorities (those are planned feature for future releases).
\subsection{Spawning Typed Actors}
\label{sec:strong:spawn}
Actors are spawned using the function \lstinline^spawn_typed^.
Typed actors are spawned using the function \lstinline^spawn_typed^.
The argument to this function call \emph{must} be a match expression as shown in the example below, because the runtime of libcppa needs to evaluate the signature of each message handler.
\begin{lstlisting}
......@@ -46,32 +44,94 @@ subtype2 p3 = p0;
\clearpage
\subsection{Class-based Typed Actors}
Typed actors can be implemented using a class by inheriting from \lstinline^typed_actor<...>^, whereas the template parameter pack denotes the messaging interface.
Derived classes have to implemented the pure virtual member function \lstinline^make_behavior^, as shown in the example below.
Typed actors are spawned using the function \lstinline^spawn_typed^ and define their message passing interface as list of \lstinline^replies_to<...>::with<...>^ statements.
This interface is used in (1) \lstinline^typed_event_based_actor<...>^, which is the base class for typed actors, (2) the handle type \lstinline^typed_actor<...>^, and (3) \lstinline^typed_behavior<...>^, i.e., the behavior definition for typed actors.
Since this is rather redundant, the actor handle provides definitions for the behavior as well as the base class, as shown in the example below.
It is worth mentioning that all typed actors always use the event-based implementation, i.e., there is no typed actor implementation providing a blocking API.
\begin{lstlisting}
// implementation
class typed_testee : public typed_actor<replies_to<int>::with<bool>> {
struct shutdown_request { };
struct plus_request { int a; int b; };
struct minus_request { int a; int b; };
protected:
typedef typed_actor<replies_to<plus_request>::with<int>,
replies_to<minus_request>::with<int>,
replies_to<shutdown_request>::with<void>>
calculator_type;
behavior_type make_behavior() final {
// returning a non-matching expression
// results in a compile-time error
return (
on_arg_match >> [](int value) {
return value == 42;
}
);
calculator_type::behavior_type
typed_calculator(calculator_type::pointer self) {
return {
[](const plus_request& pr) {
return pr.a + pr.b;
},
[](const minus_request& pr) {
return pr.a - pr.b;
},
[=](const shutdown_request&) {
self->quit();
}
};
}
class typed_calculator_class : public calculator_type::base {
protected: behavior_type make_behavior() override {
return {
[](const plus_request& pr) {
return pr.a + pr.b;
},
[](const minus_request& pr) {
return pr.a - pr.b;
},
[=](const shutdown_request&) {
quit();
}
};
}
};
// instantiation
auto testee = spawn_typed<typed_testee>();
\end{lstlisting}
It is worth mentioning that \lstinline^typed_actor^ implements the member function \lstinline^init()^ using the \lstinline^final^ qualifier.
Hence, derived classes are not allowed to override \lstinline^init()^.
However, typed actors are allowed to override other member functions such as \lstinline^on_exit()^.
The return type of \lstinline^make_behavior^ is \lstinline^typed_behavior<...>^, which is aliased as \lstinline^behavior_type^ for convenience.
\clearpage
\begin{lstlisting}
void tester(event_based_actor* self, const calculator_type& testee) {
self->link_to(testee);
// will be invoked if we receive an unexpected response message
self->on_sync_failure([=] {
aout(self) << "AUT (actor under test) failed" << endl;
self->quit(exit_reason::user_shutdown);
});
// first test: 2 + 1 = 3
self->sync_send(testee, plus_request{2, 1}).then(
[=](int r1) {
assert(r1 == 3);
// second test: 2 - 1 = 1
self->sync_send(testee, minus_request{2, 1}).then(
[=](int r2) {
assert(r2 == 1);
// both tests succeeded
aout(self) << "AUT (actor under test) "
<< "seems to be ok"
<< endl;
self->send(testee, shutdown_request{});
}
);
}
);
}
int main() {
// announce custom message types
announce<shutdown_request>();
announce<plus_request>(&plus_request::a, &plus_request::b);
announce<minus_request>(&minus_request::a, &minus_request::b);
// test function-based impl
spawn(tester, spawn_typed(typed_calculator));
await_all_actors_done();
// test class-based impl
spawn(tester, spawn_typed<typed_calculator_class>());
await_all_actors_done();
// done
shutdown();
return 0;
}
\end{lstlisting}
\section{Synchronous Communication}
\label{Sec::Sync}
\libcppa uses a future-based API for synchronous communication.
The functions \lstinline^sync_send^ and \lstinline^sync_send_tuple^ send synchronous request messages to the receiver and return a future to the response message.
Note that the returned future is \textit{actor-local}, i.e., only the actor that has send the corresponding request message is able to receive the response identified by such a future.
\libcppa supports both asynchronous and synchronous communication.
The member functions \lstinline^sync_send^ and \lstinline^sync_send_tuple^ send synchronous request messages.
\begin{lstlisting}
template<typename... Args>
message_future sync_send(actor_ptr whom, Args&&... what);
__unspecified__ sync_send(actor_ptr whom, Args&&... what);
message_future sync_send_tuple(actor_ptr whom, any_tuple what);
__unspecified__ sync_send_tuple(actor_ptr whom, any_tuple what);
template<typename Duration, typename... Args>
message_future timed_sync_send(actor_ptr whom,
__unspecified__ timed_sync_send(actor_ptr whom,
Duration timeout,
Args&&... what);
template<typename Duration, typename... Args>
message_future timed_sync_send_tuple(actor_ptr whom,
__unspecified__ timed_sync_send_tuple(actor_ptr whom,
Duration timeout,
any_tuple what);
\end{lstlisting}
......@@ -27,42 +26,39 @@ 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}).
When using \lstinline^timed_sync_send^ function, \lstinline^after(...)^ statements are ignored and the actor will receive a \lstinline^'TIMEOUT'^ message after the given duration instead.
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}
When using synchronous messaging, \libcppa's runtime environment will send ...
\begin{itemize}
\item \lstinline^{'EXITED', uint32_t exit_reason}^ if the receiver is not alive
\item \lstinline^{'VOID'}^ if the receiver handled the message but did not respond to it
\item \lstinline^{'TIMEOUT'}^ if a message send by \lstinline^timed_sync_send^ timed out
\item if the receiver is not alive:\newline\lstinline^sync_exited_msg { actor_addr source; std::uint32_t reason; };^
%\item \lstinline^{'VOID'}^ if the receiver handled the message but did not respond to it
\item if a message send by \lstinline^timed_sync_send^ timed out: \lstinline^sync_timeout_msg^
\end{itemize}
\clearpage
\subsection{Receive Response Messages}
The function \lstinline^handle_response^ can be used to set a one-shot handler receiving the response message send by \lstinline^sync_send^.
When sending a synchronous message, the response handler can be passed by either using \lstinline^then^ (event-based actors) or \lstinline^await^ (blocking actors).
\begin{lstlisting}
actor_ptr testee = ...; // replies with a string to 'get'
// "handle_response" usage example
auto handle = sync_send(testee, atom("get"));
handle_response (handle) (
void foo(event_based_actor* self, actor testee) {
// testee replies with a string to 'get'
self->sync_send(testee, atom("get")).then(
on_arg_match >> [=](const std::string& str) {
// handle str
},
after(std::chrono::seconds(30)) >> [=]() {
// handle error
}
);
);
\end{lstlisting}
Similar to \lstinline^become^, the function \lstinline^handle_response^ modifies an actor's behavior stack.
However, it is used as ``one-shot handler'' and automatically returns the previous actor behavior afterwards.
It is possible to ``stack'' multiple \lstinline^handle_response^ calls.
Each response handler is executed once and then automatically discarded.
Similar to \lstinline^become^, the \lstinline^then^ function modifies an actor's behavior stack.
However, it is used as ``one-shot handler'' and automatically returns to the previous behavior afterwards.
\subsection{Synchronous Failures and Error Handlers}
......@@ -70,72 +66,43 @@ An unexpected response message, i.e., a message that is not handled by given beh
The default handler kills the actor by calling \lstinline^self->quit(exit_reason::unhandled_sync_failure)^.
The handler can be overridden by calling \lstinline^self->on_sync_failure(/*...*/)^.
Unhandled \lstinline^'TIMEOUT'^ messages trigger the \lstinline^on_sync_timeout^ handler.
Unhandled timeout messages trigger the \lstinline^on_sync_timeout^ handler.
The default handler kills the actor for reason \lstinline^exit_reason::unhandled_sync_failure^.
It is possible set both error handlers by calling \lstinline^self->on_sync_timeout_or_failure(/*...*)^.
\clearpage
\subsection{Using \lstinline^then^ to Receive a Response}
Often, an actor sends a synchronous message and then wants to wait for the response.
In this case, using either \lstinline^handle_response^ is quite verbose.
To allow for a more compact code, \lstinline^message_future^ provides the member function \lstinline^then^.
Using this member function is equal to using \lstinline^handle_response^, as illustrated by the following example.
\begin{lstlisting}
actor_ptr testee = ...; // replies with a string to 'get'
// set handler for unexpected messages
self->on_sync_failure = [] {
void foo(event_based_actor* self, actor testee) {
// testee replies with a string to 'get'
// set handler for unexpected messages
self->on_sync_failure = [] {
aout << "received: " << to_string(self->last_dequeued()) << endl;
};
// set handler for timeouts
self->on_sync_timeout = [] {
};
// set handler for timeouts
self->on_sync_timeout = [] {
aout << "timeout occured" << endl;
};
// set response handler by using "then"
timed_sync_send(testee, std::chrono::seconds(30), atom("get")).then(
on_arg_match >> [=](const std::string& str) { /* handle str */ }
);
\end{lstlisting}
\subsubsection{Using Functors without Patterns}
To reduce verbosity, \libcppa supports synchronous response handlers without patterns.
In this case, the pattern is automatically deduced by the functor's signature.
\begin{lstlisting}
actor_ptr testee = ...; // replies with a string to 'get'
// (1) functor only usage
sync_send(testee, atom("get")).then(
[=](const std::string& str) { /*...*/ }
);
// statement (1) is equal to:
sync_send(testee, atom("get")).then(
on(any_vals, arg_match) >> [=](const std::string& str) { /*...*/ }
);
};
// set response handler by using "then"
timed_sync_send(testee, std::chrono::seconds(30), atom("get")).then(
[=](const std::string& str) { /* handle str */ }
);
\end{lstlisting}
\clearpage
\subsubsection{Continuations for Event-based Actors}
\libcppa supports continuations to enable chaining of send/receive statements.
The functions \lstinline^handle_response^ and \lstinline^message_future::then^ both return a helper object offering the member function \lstinline^continue_with^, which takes a functor $f$ without arguments.
The functions \lstinline^then^ returns a helper object offering the member function \lstinline^continue_with^, which takes a functor $f$ without arguments.
After receiving a message, $f$ is invoked if and only if the received messages was handled successfully, i.e., neither \lstinline^sync_failure^ nor \lstinline^sync_timeout^ occurred.
\begin{lstlisting}
actor_ptr d_or_s = ...; // replies with either a double or a string
sync_send(d_or_s, atom("get")).then(
void foo(event_based_actor* self) {
actor d_or_s = ...; // replies with either a double or a string
sync_send(d_or_s, atom("get")).then(
[=](double value) { /* functor f1 */ },
[=](const string& value) { /* functor f2*/ }
).continue_with([=] {
).continue_with([=] {
// this continuation is invoked in both cases
// *after* f1 or f2 is done, but *not* in case
// of sync_failure or sync_timeout
});
});
\end{lstlisting}
......@@ -28,8 +28,7 @@ struct foo { int a; int b; };
int main() {
announce<foo>(&foo::a, &foo::b);
send(self, foo{1,2});
return 0;
// ... foo can now safely be used in messages ...
}
\end{lstlisting}
......
......@@ -45,7 +45,7 @@
%BEGIN LATEX
\texttt{\huge{\textbf{libcppa}}}\\~\\A C++ library for actor programming\\~\\~\\~\\%
%END LATEX
User Manual\\\normalsize{\texttt{libcppa} version 0.9.0} BETA\vfill}
User Manual\\\normalsize{\texttt{libcppa} version 0.9.0} PRERELEASE\vfill}
\author{Dominik Charousset}
......
......@@ -83,7 +83,8 @@ class continuation_decorator : public detail::behavior_impl {
behavior::behavior(const partial_function& fun) : m_impl(fun.m_impl) { }
behavior behavior::add_continuation(continuation_fun fun) {
return {new continuation_decorator(std::move(fun), m_impl)};
return behavior::impl_ptr{new continuation_decorator(std::move(fun),
m_impl)};
}
} // namespace cppa
......@@ -49,7 +49,7 @@ bool operator==(const my_request& lhs, const my_request& rhs) {
server_type::behavior_type typed_server1() {
return {
on_arg_match >> [](const my_request& req) {
[](const my_request& req) {
return req.a == req.b;
}
};
......
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