Unverified Commit 1de110dd authored by Noir's avatar Noir Committed by GitHub

Merge branch 'master' into issue/1195

parents 39cb34c8 c089c764
...@@ -22,6 +22,8 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -22,6 +22,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
trying to parse the input of `x` if it contains a string. The function trying to parse the input of `x` if it contains a string. The function
`get_or` already existed for `settings`, but we have added new overloads for `get_or` already existed for `settings`, but we have added new overloads for
generalizing the function to `config_value` as well. generalizing the function to `config_value` as well.
- The `typed_response_promise` received additional member functions to mirror
the interface of the untyped `response_promise`.
### Deprecated ### Deprecated
...@@ -84,6 +86,8 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -84,6 +86,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Skipping high-priority messages resulted in CAF lowering the priority to - Skipping high-priority messages resulted in CAF lowering the priority to
normal. This unintentional demotion has been fixed (#1171). normal. This unintentional demotion has been fixed (#1171).
- Fix undefined behavior in the experimental datagram brokers (#1174). - Fix undefined behavior in the experimental datagram brokers (#1174).
- Response promises no longer send empty messages in response to asynchronous
messages.
- `CAF_ADD_TYPE_ID` now works with types that live in namespaces that also exist - `CAF_ADD_TYPE_ID` now works with types that live in namespaces that also exist
as nested namespace in CAF such as `detail` or `io` (#1195). as nested namespace in CAF such as `detail` or `io` (#1195).
......
cmake_minimum_required(VERSION 3.5...3.18 FATAL_ERROR) cmake_minimum_required(VERSION 3.5...3.18 FATAL_ERROR)
project(CAF CXX) project(CAF CXX)
cmake_policy(PUSH)
cmake_policy(VERSION 3.5...3.18)
# -- includes ------------------------------------------------------------------ # -- includes ------------------------------------------------------------------
include(CMakeDependentOption) include(CMakeDependentOption)
...@@ -49,10 +46,7 @@ cmake_dependent_option(CAF_ENABLE_OPENSSL_MODULE "Build OpenSSL module" ON ...@@ -49,10 +46,7 @@ cmake_dependent_option(CAF_ENABLE_OPENSSL_MODULE "Build OpenSSL module" ON
set(CAF_LOG_LEVEL "QUIET" CACHE STRING "Set log verbosity of CAF components") set(CAF_LOG_LEVEL "QUIET" CACHE STRING "Set log verbosity of CAF components")
set(CAF_SANITIZERS "" CACHE STRING set(CAF_SANITIZERS "" CACHE STRING
"Comma separated sanitizers, e.g., 'address,undefined'") "Comma separated sanitizers, e.g., 'address,undefined'")
set(CAF_INSTALL_CMAKEDIR set(CAF_BUILD_INFO_FILE_PATH "" CACHE FILEPATH
"${CMAKE_INSTALL_FULL_LIBDIR}/cmake/CAF" CACHE PATH
"Path for installing CMake files, enables 'find_package(CAF)'")
set(CAF_BUILD_INFO_FILE_PATH "" CACHE FILEPATH
"Optional path for writing CMake and compiler version information") "Optional path for writing CMake and compiler version information")
# -- macOS-specific options ---------------------------------------------------- # -- macOS-specific options ----------------------------------------------------
...@@ -450,7 +444,7 @@ endif() ...@@ -450,7 +444,7 @@ endif()
export(EXPORT CAFTargets FILE CAFTargets.cmake NAMESPACE CAF::) export(EXPORT CAFTargets FILE CAFTargets.cmake NAMESPACE CAF::)
install(EXPORT CAFTargets install(EXPORT CAFTargets
DESTINATION "${CAF_INSTALL_CMAKEDIR}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/CAF"
NAMESPACE CAF::) NAMESPACE CAF::)
write_basic_package_version_file( write_basic_package_version_file(
...@@ -461,15 +455,14 @@ write_basic_package_version_file( ...@@ -461,15 +455,14 @@ write_basic_package_version_file(
configure_package_config_file( configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/CAFConfig.cmake.in" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CAFConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/CAFConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/CAFConfig.cmake"
INSTALL_DESTINATION "${CAF_INSTALL_CMAKEDIR}") INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/CAF")
install( install(
FILES FILES
"${CMAKE_CURRENT_BINARY_DIR}/CAFConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/CAFConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/CAFConfigVersion.cmake" "${CMAKE_CURRENT_BINARY_DIR}/CAFConfigVersion.cmake"
DESTINATION DESTINATION
"${CAF_INSTALL_CMAKEDIR}") "${CMAKE_INSTALL_LIBDIR}/cmake/CAF")
# -- extra file output (primarily for CAF CI) ---------------------------------- # -- extra file output (primarily for CAF CI) ----------------------------------
...@@ -478,5 +471,3 @@ if(CAF_BUILD_INFO_FILE_PATH) ...@@ -478,5 +471,3 @@ if(CAF_BUILD_INFO_FILE_PATH)
"${CAF_BUILD_INFO_FILE_PATH}" "${CAF_BUILD_INFO_FILE_PATH}"
@ONLY) @ONLY)
endif() endif()
cmake_policy(POP)
...@@ -45,7 +45,7 @@ add_core_example(custom_type custom_types_3) ...@@ -45,7 +45,7 @@ add_core_example(custom_type custom_types_3)
# testing DSL # testing DSL
add_example(testing ping_pong) add_example(testing ping_pong)
target_link_libraries(ping_pong PRIVATE CAF::internal CAF::core CAF::test) target_link_libraries(ping_pong PRIVATE CAF::internal CAF::core CAF::test)
add_test(NAME "examples.ping-pong" COMMAND ping_pong -r300 -n -v5)
# -- examples for CAF::io ------------------------------------------------------ # -- examples for CAF::io ------------------------------------------------------
......
#include <random>
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <random>
#include "caf/all.hpp" #include "caf/actor_ostream.hpp"
#include "caf/actor_system.hpp"
#include "caf/caf_main.hpp"
#include "caf/event_based_actor.hpp"
using namespace caf; using namespace caf;
using std::endl;
void caf_main(actor_system& system) { behavior printer(event_based_actor* self, int32_t num, int32_t delay) {
for (int i = 1; i <= 50; ++i) { aout(self) << "Hi there! This is actor nr. " << num << "!" << std::endl;
system.spawn([i](blocking_actor* self) { std::chrono::milliseconds timeout{delay};
aout(self) << "Hi there! This is actor nr. " self->delayed_send(self, timeout, timeout_atom_v);
<< i << "!" << endl; return {
[=](timeout_atom) {
aout(self) << "Actor nr. " << num << " says goodbye after waiting for "
<< delay << "ms!" << std::endl;
},
};
}
void caf_main(actor_system& sys) {
std::random_device rd; std::random_device rd;
std::default_random_engine re(rd()); std::minstd_rand re(rd());
std::chrono::milliseconds tout{re() % 10}; std::uniform_int_distribution<int32_t> dis{1, 99};
self->delayed_send(self, tout, 42); for (int32_t i = 1; i <= 50; ++i)
self->receive( sys.spawn(printer, i, dis(re));
[i, self](int) {
aout(self) << "Actor nr. "
<< i << " says goodbye!" << endl;
}
);
});
}
} }
CAF_MAIN() CAF_MAIN()
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
* - simple_broker -c localhost 4242 * * - simple_broker -c localhost 4242 *
\******************************************************************************/ \******************************************************************************/
// Manual refs: 42-47 (Actors.tex)
#include "caf/config.hpp" #include "caf/config.hpp"
#ifdef CAF_WINDOWS #ifdef CAF_WINDOWS
...@@ -38,12 +36,14 @@ using namespace caf::io; ...@@ -38,12 +36,14 @@ using namespace caf::io;
namespace { namespace {
// --(rst-attach-begin)--
// Utility function to print an exit message with custom name. // Utility function to print an exit message with custom name.
void print_on_exit(const actor& hdl, const std::string& name) { void print_on_exit(const actor& hdl, const std::string& name) {
hdl->attach_functor([=](const error& reason) { hdl->attach_functor([=](const error& reason) {
cout << name << " exited: " << to_string(reason) << endl; cout << name << " exited: " << to_string(reason) << endl;
}); });
} }
// --(rst-attach-end)--
enum class op : uint8_t { enum class op : uint8_t {
ping, ping,
......
// Showcases how to add custom POD message types. // Showcases how to add custom POD message types.
// Manual refs: 24-27, 30-34, 75-78, 81-84 (ConfiguringActorApplications)
// 23-33 (TypeInspection)
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
#include <string> #include <string>
......
#include <string> #include <string>
#include <iostream> #include <iostream>
#include "caf/all.hpp" #include "caf/actor_ostream.hpp"
#include "caf/actor_system.hpp"
using std::endl; #include "caf/caf_main.hpp"
using std::string; #include "caf/event_based_actor.hpp"
using namespace caf; using namespace caf;
...@@ -13,32 +13,32 @@ behavior mirror(event_based_actor* self) { ...@@ -13,32 +13,32 @@ behavior mirror(event_based_actor* self) {
return { return {
// a handler for messages containing a single string // a handler for messages containing a single string
// that replies with a string // that replies with a string
[=](const string& what) -> string { [=](const std::string& what) -> std::string {
// prints "Hello World!" via aout (thread-safe cout wrapper) // prints "Hello World!" via aout (thread-safe cout wrapper)
aout(self) << what << endl; aout(self) << what << std::endl;
// reply "!dlroW olleH" // reply "!dlroW olleH"
return string(what.rbegin(), what.rend()); return std::string{what.rbegin(), what.rend()};
} },
}; };
} }
void hello_world(event_based_actor* self, const actor& buddy) { void hello_world(event_based_actor* self, const actor& buddy) {
// send "Hello World!" to our buddy ... // send "Hello World!" to our buddy ...
self->request(buddy, std::chrono::seconds(10), "Hello World!").then( self->request(buddy, std::chrono::seconds(10), "Hello World!")
.then(
// ... wait up to 10s for a response ... // ... wait up to 10s for a response ...
[=](const string& what) { [=](const std::string& what) {
// ... and print it // ... and print it
aout(self) << what << endl; aout(self) << what << std::endl;
} });
);
} }
void caf_main(actor_system& system) { void caf_main(actor_system& sys) {
// create a new actor that calls 'mirror()' // create a new actor that calls 'mirror()'
auto mirror_actor = system.spawn(mirror); auto mirror_actor = sys.spawn(mirror);
// create another actor that calls 'hello_world(mirror_actor)'; // create another actor that calls 'hello_world(mirror_actor)';
system.spawn(hello_world, mirror_actor); sys.spawn(hello_world, mirror_actor);
// system will wait until both actors are destroyed before leaving main // the system will wait until both actors are done before exiting the program
} }
// creates a main function for us that calls our caf_main // creates a main function for us that calls our caf_main
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
* for both the blocking and the event-based API. * * for both the blocking and the event-based API. *
\******************************************************************************/ \******************************************************************************/
// Manual refs: lines 17-18, 21-26, 28-56, 58-92, 123-128 (Actor)
#include <iostream> #include <iostream>
#include "caf/all.hpp" #include "caf/all.hpp"
...@@ -14,18 +12,22 @@ using namespace caf; ...@@ -14,18 +12,22 @@ using namespace caf;
namespace { namespace {
// --(rst-calculator-actor-begin)--
using calculator_actor using calculator_actor
= typed_actor<result<int32_t>(add_atom, int32_t, int32_t), = typed_actor<result<int32_t>(add_atom, int32_t, int32_t),
result<int32_t>(sub_atom, int32_t, int32_t)>; result<int32_t>(sub_atom, int32_t, int32_t)>;
// --(rst-calculator-actor-end)--
// prototypes and forward declarations // --(rst-prototypes-begin)--
behavior calculator_fun(event_based_actor* self); behavior calculator_fun(event_based_actor* self);
void blocking_calculator_fun(blocking_actor* self); void blocking_calculator_fun(blocking_actor* self);
calculator_actor::behavior_type typed_calculator_fun(); calculator_actor::behavior_type typed_calculator_fun();
class calculator; class calculator;
class blocking_calculator; class blocking_calculator;
class typed_calculator; class typed_calculator;
// --(rst-prototypes-end)--
// --(rst-function-based-begin)--
// function-based, dynamically typed, event-based API // function-based, dynamically typed, event-based API
behavior calculator_fun(event_based_actor*) { behavior calculator_fun(event_based_actor*) {
return { return {
...@@ -55,7 +57,9 @@ calculator_actor::behavior_type typed_calculator_fun() { ...@@ -55,7 +57,9 @@ calculator_actor::behavior_type typed_calculator_fun() {
[](sub_atom, int32_t a, int32_t b) { return a - b; }, [](sub_atom, int32_t a, int32_t b) { return a - b; },
}; };
} }
// --(rst-function-based-end)--
// --(rst-class-based-begin)--
// class-based, dynamically typed, event-based API // class-based, dynamically typed, event-based API
class calculator : public event_based_actor { class calculator : public event_based_actor {
public: public:
...@@ -91,6 +95,7 @@ public: ...@@ -91,6 +95,7 @@ public:
return typed_calculator_fun(); return typed_calculator_fun();
} }
}; };
// --(rst-class-based-end)--
void tester(scoped_actor&) { void tester(scoped_actor&) {
// end of recursion // end of recursion
...@@ -120,14 +125,16 @@ void tester(scoped_actor& self, const Handle& hdl, int32_t x, int32_t y, ...@@ -120,14 +125,16 @@ void tester(scoped_actor& self, const Handle& hdl, int32_t x, int32_t y,
tester(self, std::forward<Ts>(xs)...); tester(self, std::forward<Ts>(xs)...);
} }
void caf_main(actor_system& system) { void caf_main(actor_system& sys) {
auto a1 = system.spawn(blocking_calculator_fun); // --(rst-spawn-begin)--
auto a2 = system.spawn(calculator_fun); auto a1 = sys.spawn(blocking_calculator_fun);
auto a3 = system.spawn(typed_calculator_fun); auto a2 = sys.spawn(calculator_fun);
auto a4 = system.spawn<blocking_calculator>(); auto a3 = sys.spawn(typed_calculator_fun);
auto a5 = system.spawn<calculator>(); auto a4 = sys.spawn<blocking_calculator>();
auto a6 = system.spawn<typed_calculator>(); auto a5 = sys.spawn<calculator>();
scoped_actor self{system}; auto a6 = sys.spawn<typed_calculator>();
// --(rst-spawn-end)--
scoped_actor self{sys};
tester(self, a1, 1, 2, a2, 3, 4, a3, 5, 6, a4, 7, 8, a5, 9, 10, a6, 11, 12); tester(self, a1, 1, 2, a2, 3, 4, a3, 5, 6, a4, 7, 8, a5, 9, 10, a6, 11, 12);
self->send_exit(a1, exit_reason::user_shutdown); self->send_exit(a1, exit_reason::user_shutdown);
self->send_exit(a4, exit_reason::user_shutdown); self->send_exit(a4, exit_reason::user_shutdown);
......
...@@ -40,7 +40,7 @@ behavior unchecked_cell(stateful_actor<cell_state>* self) { ...@@ -40,7 +40,7 @@ behavior unchecked_cell(stateful_actor<cell_state>* self) {
// --(rst-cell-end)-- // --(rst-cell-end)--
void caf_main(actor_system& system) { void caf_main(actor_system& system) {
// --(rst-spawn-cell-end)-- // --(rst-spawn-cell-begin)--
// Create one cell for each implementation. // Create one cell for each implementation.
auto cell1 = system.spawn(type_checked_cell); auto cell1 = system.spawn(type_checked_cell);
auto cell2 = system.spawn(unchecked_cell); auto cell2 = system.spawn(unchecked_cell);
...@@ -50,7 +50,7 @@ void caf_main(actor_system& system) { ...@@ -50,7 +50,7 @@ void caf_main(actor_system& system) {
f(put_atom_v, 20); f(put_atom_v, 20);
cout << "cell value (after setting to 20): " << f(get_atom_v) << endl; cout << "cell value (after setting to 20): " << f(get_atom_v) << endl;
// Get an unchecked cell and send it some garbage. Triggers an "unexpected // Get an unchecked cell and send it some garbage. Triggers an "unexpected
// message" error. // message" error (and terminates cell2!).
anon_send(cell2, "hello there!"); anon_send(cell2, "hello there!");
} }
......
...@@ -55,11 +55,14 @@ void draw_kirby(const animation_step& animation) { ...@@ -55,11 +55,14 @@ void draw_kirby(const animation_step& animation) {
cout.flush(); cout.flush();
} }
// --(rst-delayed-send-begin)--
// uses a message-based loop to iterate over all animation steps // uses a message-based loop to iterate over all animation steps
void dancing_kirby(event_based_actor* self) { behavior dancing_kirby(event_based_actor* self) {
using namespace std::literals::chrono_literals;
// let's get it started // let's get it started
self->send(self, update_atom_v, size_t{0}); self->send(self, update_atom_v, size_t{0});
self->become([=](update_atom, size_t step) { return {
[=](update_atom, size_t step) {
if (step == sizeof(animation_step)) { if (step == sizeof(animation_step)) {
// we've printed all animation steps (done) // we've printed all animation steps (done)
cout << endl; cout << endl;
...@@ -68,11 +71,12 @@ void dancing_kirby(event_based_actor* self) { ...@@ -68,11 +71,12 @@ void dancing_kirby(event_based_actor* self) {
} }
// print given step // print given step
draw_kirby(animation_steps[step]); draw_kirby(animation_steps[step]);
// animate next step in 150ms // schedule next animation step
self->delayed_send(self, std::chrono::milliseconds(150), update_atom_v, self->delayed_send(self, 150ms, update_atom_v, step + 1);
step + 1); },
}); };
} }
// --(rst-delayed-send-end)--
void caf_main(actor_system& system) { void caf_main(actor_system& system) {
system.spawn(dancing_kirby); system.spawn(dancing_kirby);
......
#include <iostream>
#include "caf/all.hpp" #include "caf/all.hpp"
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 15-36 (MessagePassing.tex)
using namespace caf; using namespace caf;
// using add_atom = atom_constant<atom("add")>; (defined in atom.hpp) // --(rst-delegate-begin)--
using adder_actor = typed_actor<result<int32_t>(add_atom, int32_t, int32_t)>;
using calc = typed_actor<result<int32_t>(add_atom, int32_t, int32_t)>;
void actor_a(event_based_actor* self, const calc& worker) { adder_actor::behavior_type worker_impl() {
self->request(worker, std::chrono::seconds(10), add_atom_v, 1, 2) return {
.then([=](int32_t result) { // [](add_atom, int32_t x, int32_t y) { return x + y; },
aout(self) << "1 + 2 = " << result << std::endl; };
});
} }
adder_actor::behavior_type server_impl(adder_actor::pointer self,
calc::behavior_type actor_b(calc::pointer self, const calc& worker) { adder_actor worker) {
return { return {
[=](add_atom add, int32_t x, int32_t y) { [=](add_atom add, int32_t x, int32_t y) {
return self->delegate(worker, add, x, y); return self->delegate(worker, add, x, y);
...@@ -27,14 +19,19 @@ calc::behavior_type actor_b(calc::pointer self, const calc& worker) { ...@@ -27,14 +19,19 @@ calc::behavior_type actor_b(calc::pointer self, const calc& worker) {
}; };
} }
calc::behavior_type actor_c() { void client_impl(event_based_actor* self, adder_actor adder, int32_t x,
return { int32_t y) {
[](add_atom, int32_t x, int32_t y) { return x + y; }, using namespace std::literals::chrono_literals;
}; self->request(adder, 10s, add_atom_v, x, y).then([=](int32_t result) {
aout(self) << x << " + " << y << " = " << result << std::endl;
});
} }
void caf_main(actor_system& system) { void caf_main(actor_system& sys) {
system.spawn(actor_a, system.spawn(actor_b, system.spawn(actor_c))); auto worker = sys.spawn(worker_impl);
auto server = sys.spawn(server_impl, sys.spawn(worker_impl));
sys.spawn(client_impl, server, 1, 2);
} }
// --(rst-delegate-end)--
CAF_MAIN() CAF_MAIN()
...@@ -2,18 +2,11 @@ ...@@ -2,18 +2,11 @@
* A very basic, interactive divider. * * A very basic, interactive divider. *
\******************************************************************************/ \******************************************************************************/
// Manual refs: 17-19, 49-59, 70-76 (MessagePassing);
// 17-47 (Error)
#include <iostream> #include <iostream>
#include "caf/all.hpp" #include "caf/all.hpp"
using std::cout; // --(rst-math-error-begin)--
using std::endl;
using std::flush;
using namespace caf;
enum class math_error : uint8_t { enum class math_error : uint8_t {
division_by_zero = 1, division_by_zero = 1,
}; };
...@@ -27,6 +20,29 @@ std::string to_string(math_error x) { ...@@ -27,6 +20,29 @@ std::string to_string(math_error x) {
} }
} }
bool from_string(caf::string_view in, math_error& out) {
if (in == "division_by_zero") {
out = math_error::division_by_zero;
return true;
} else {
return false;
}
}
bool from_integer(uint8_t in, math_error& out) {
if (in == 1) {
out = math_error::division_by_zero;
return true;
} else {
return false;
}
}
template <class Inspector>
bool inspect(Inspector& f, math_error& x) {
return caf::default_enum_inspect(f, x);
}
CAF_BEGIN_TYPE_ID_BLOCK(divider, first_custom_type_id) CAF_BEGIN_TYPE_ID_BLOCK(divider, first_custom_type_id)
CAF_ADD_TYPE_ID(divider, (math_error)) CAF_ADD_TYPE_ID(divider, (math_error))
...@@ -34,7 +50,15 @@ CAF_BEGIN_TYPE_ID_BLOCK(divider, first_custom_type_id) ...@@ -34,7 +50,15 @@ CAF_BEGIN_TYPE_ID_BLOCK(divider, first_custom_type_id)
CAF_END_TYPE_ID_BLOCK(divider) CAF_END_TYPE_ID_BLOCK(divider)
CAF_ERROR_CODE_ENUM(math_error) CAF_ERROR_CODE_ENUM(math_error)
// --(rst-math-error-end)--
using std::cout;
using std::endl;
using std::flush;
using namespace caf;
// --(rst-divider-begin)--
using divider = typed_actor<result<double>(div_atom, double, double)>; using divider = typed_actor<result<double>(div_atom, double, double)>;
divider::behavior_type divider_impl() { divider::behavior_type divider_impl() {
...@@ -46,6 +70,7 @@ divider::behavior_type divider_impl() { ...@@ -46,6 +70,7 @@ divider::behavior_type divider_impl() {
}, },
}; };
} }
// --(rst-divider-end)--
void caf_main(actor_system& system) { void caf_main(actor_system& system) {
double x; double x;
...@@ -54,6 +79,7 @@ void caf_main(actor_system& system) { ...@@ -54,6 +79,7 @@ void caf_main(actor_system& system) {
std::cin >> x; std::cin >> x;
cout << "y: " << flush; cout << "y: " << flush;
std::cin >> y; std::cin >> y;
// --(rst-request-begin)--
auto div = system.spawn(divider_impl); auto div = system.spawn(divider_impl);
scoped_actor self{system}; scoped_actor self{system};
self->request(div, std::chrono::seconds(10), div_atom_v, x, y) self->request(div, std::chrono::seconds(10), div_atom_v, x, y)
...@@ -63,6 +89,7 @@ void caf_main(actor_system& system) { ...@@ -63,6 +89,7 @@ void caf_main(actor_system& system) {
aout(self) << "*** cannot compute " << x << " / " << y << " => " aout(self) << "*** cannot compute " << x << " / " << y << " => "
<< to_string(err) << endl; << to_string(err) << endl;
}); });
// --(rst-request-end)--
} }
CAF_MAIN(id_block::divider) CAF_MAIN(id_block::divider)
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 86-98 (MessagePassing.tex)
#include <cassert> #include <cassert>
#include <chrono> #include <chrono>
#include <iomanip> #include <iomanip>
...@@ -101,6 +97,7 @@ matrix::behavior_type matrix_actor(matrix::stateful_pointer<matrix_state> self, ...@@ -101,6 +97,7 @@ matrix::behavior_type matrix_actor(matrix::stateful_pointer<matrix_state> self,
[=](error& err) mutable { rp.deliver(std::move(err)); }); [=](error& err) mutable { rp.deliver(std::move(err)); });
return rp; return rp;
}, },
// --(rst-fan-out-begin)--
[=](get_atom get, average_atom, column_atom, int column) { [=](get_atom get, average_atom, column_atom, int column) {
assert(column < columns); assert(column < columns);
std::vector<cell> columns; std::vector<cell> columns;
...@@ -118,6 +115,7 @@ matrix::behavior_type matrix_actor(matrix::stateful_pointer<matrix_state> self, ...@@ -118,6 +115,7 @@ matrix::behavior_type matrix_actor(matrix::stateful_pointer<matrix_state> self,
[=](error& err) mutable { rp.deliver(std::move(err)); }); [=](error& err) mutable { rp.deliver(std::move(err)); });
return rp; return rp;
}, },
// --(rst-fan-out-end)--
}; };
} }
......
...@@ -19,6 +19,43 @@ CAF_END_TYPE_ID_BLOCK(fixed_stack) ...@@ -19,6 +19,43 @@ CAF_END_TYPE_ID_BLOCK(fixed_stack)
CAF_ERROR_CODE_ENUM(fixed_stack_errc) CAF_ERROR_CODE_ENUM(fixed_stack_errc)
std::string to_string(fixed_stack_errc x) {
switch (x) {
case fixed_stack_errc::push_to_full:
return "push_to_full";
case fixed_stack_errc::pop_from_empty:
return "pop_from_empty";
default:
return "-unknown-error-";
}
}
bool from_string(caf::string_view in, fixed_stack_errc& out) {
if (in == "push_to_full") {
out = fixed_stack_errc::push_to_full;
return true;
} else if (in == "pop_from_empty") {
out = fixed_stack_errc::pop_from_empty;
return true;
} else {
return false;
}
}
bool from_integer(uint8_t in, fixed_stack_errc& out) {
if (in > 0 && in < 1) {
out = static_cast<fixed_stack_errc>(in);
return true;
} else {
return false;
}
}
template <class Inspector>
bool inspect(Inspector& f, fixed_stack_errc& x) {
return caf::default_enum_inspect(f, x);
}
using std::endl; using std::endl;
using namespace caf; using namespace caf;
......
/******************************************************************************\
* Illustrates response promises. *
\******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 18-43 (MessagePassing.tex)
#include <iostream>
#include "caf/all.hpp" #include "caf/all.hpp"
using std::cout;
using std::endl;
using namespace caf; using namespace caf;
using adder = typed_actor<result<int32_t>(add_atom, int32_t, int32_t)>; // --(rst-promise-begin)--
using adder_actor = typed_actor<result<int32_t>(add_atom, int32_t, int32_t)>;
// function-based, statically typed, event-based API adder_actor::behavior_type worker_impl() {
adder::behavior_type worker() {
return { return {
[](add_atom, int32_t a, int32_t b) { return a + b; }, [](add_atom, int32_t x, int32_t y) { return x + y; },
}; };
} }
adder_actor::behavior_type server_impl(adder_actor::pointer self,
// function-based, statically typed, event-based API adder_actor worker) {
adder::behavior_type calculator_master(adder::pointer self) {
auto w = self->spawn(worker);
return { return {
[=](add_atom x, int32_t y, int32_t z) -> result<int32_t> { [=](add_atom, int32_t y, int32_t z) {
auto rp = self->make_response_promise<int32_t>(); auto rp = self->make_response_promise<int32_t>();
self->request(w, infinite, x, y, z).then([=](int32_t result) mutable { self->request(worker, infinite, add_atom_v, y, z)
rp.deliver(result); .then([rp](int32_t result) mutable { rp.deliver(result); });
});
return rp; return rp;
}, },
}; };
} }
void caf_main(actor_system& system) { void client_impl(event_based_actor* self, adder_actor adder, int32_t x,
auto f = make_function_view(system.spawn(calculator_master)); int32_t y) {
cout << "12 + 13 = " << f(add_atom_v, 12, 13) << endl; using namespace std::literals::chrono_literals;
self->request(adder, 10s, add_atom_v, x, y).then([=](int32_t result) {
aout(self) << x << " + " << y << " = " << result << std::endl;
});
}
void caf_main(actor_system& sys) {
auto worker = sys.spawn(worker_impl);
auto server = sys.spawn(server_impl, sys.spawn(worker_impl));
sys.spawn(client_impl, server, 1, 2);
} }
// --(rst-promise-end)--
CAF_MAIN() CAF_MAIN()
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
* Illustrates semantics of request().{then|await|receive}. * * Illustrates semantics of request().{then|await|receive}. *
\******************************************************************************/ \******************************************************************************/
// This file is partially included in the manual, do not modify
// without updating the references in the *.tex files!
// Manual references: lines 20-37, 39-51, 53-64, 67-69 (MessagePassing.tex)
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
...@@ -18,22 +14,38 @@ using std::vector; ...@@ -18,22 +14,38 @@ using std::vector;
using std::chrono::seconds; using std::chrono::seconds;
using namespace caf; using namespace caf;
// --(rst-cell-begin)--
using cell using cell
= typed_actor<result<void>(put_atom, int32_t), result<int32_t>(get_atom)>; = typed_actor<result<void>(put_atom, int32_t), // 'put' writes to the cell
result<int32_t>(get_atom)>; // 'get 'reads from the cell
struct cell_state { struct cell_state {
int32_t value = 0; static constexpr inline const char* name = "cell";
};
cell::pointer self;
int32_t value;
cell_state(cell::pointer ptr, int32_t val) : self(ptr), value(val) {
// nop
}
cell::behavior_type cell_impl(cell::stateful_pointer<cell_state> self, cell_state(const cell_state&) = delete;
int32_t x0) {
self->state.value = x0; cell_state& operator=(const cell_state&) = delete;
cell::behavior_type make_behavior() {
return { return {
[=](put_atom, int32_t val) { self->state.value = val; }, [=](put_atom, int32_t val) { value = val; },
[=](get_atom) { return self->state.value; }, [=](get_atom) { return value; },
}; };
} }
};
using cell_impl = cell::stateful_impl<cell_state>;
// --(rst-cell-end)--
// --(rst-testees-begin)--
void waiting_testee(event_based_actor* self, vector<cell> cells) { void waiting_testee(event_based_actor* self, vector<cell> cells) {
for (auto& x : cells) for (auto& x : cells)
self->request(x, seconds(1), get_atom_v).await([=](int32_t y) { self->request(x, seconds(1), get_atom_v).await([=](int32_t y) {
...@@ -59,11 +71,13 @@ void blocking_testee(blocking_actor* self, vector<cell> cells) { ...@@ -59,11 +71,13 @@ void blocking_testee(blocking_actor* self, vector<cell> cells) {
aout(self) << "cell #" << x.id() << " -> " << to_string(err) << endl; aout(self) << "cell #" << x.id() << " -> " << to_string(err) << endl;
}); });
} }
// --(rst-testees-end)--
// --(rst-main-begin)--
void caf_main(actor_system& system) { void caf_main(actor_system& system) {
vector<cell> cells; vector<cell> cells;
for (auto i = 0; i < 5; ++i) for (int32_t i = 0; i < 5; ++i)
cells.emplace_back(system.spawn(cell_impl, i * i)); cells.emplace_back(system.spawn<cell_impl>(i * i));
scoped_actor self{system}; scoped_actor self{system};
aout(self) << "waiting_testee" << endl; aout(self) << "waiting_testee" << endl;
auto x1 = self->spawn(waiting_testee, cells); auto x1 = self->spawn(waiting_testee, cells);
...@@ -74,5 +88,6 @@ void caf_main(actor_system& system) { ...@@ -74,5 +88,6 @@ void caf_main(actor_system& system) {
aout(self) << "blocking_testee" << endl; aout(self) << "blocking_testee" << endl;
system.spawn(blocking_testee, cells); system.spawn(blocking_testee, cells);
} }
// --(rst-main-end)--
CAF_MAIN() CAF_MAIN()
...@@ -203,6 +203,7 @@ optional<int> toint(const string& str) { ...@@ -203,6 +203,7 @@ optional<int> toint(const string& str) {
return none; return none;
} }
// --(rst-config-begin)--
class config : public actor_system_config { class config : public actor_system_config {
public: public:
uint16_t port = 0; uint16_t port = 0;
...@@ -216,6 +217,7 @@ public: ...@@ -216,6 +217,7 @@ public:
.add(server_mode, "server-mode,s", "enable server mode"); .add(server_mode, "server-mode,s", "enable server mode");
} }
}; };
// --(rst-config-end)--
void client_repl(actor_system& system, const config& cfg) { void client_repl(actor_system& system, const config& cfg) {
// keeps track of requests and tries to reconnect on server failures // keeps track of requests and tries to reconnect on server failures
......
// Manual refs: lines 12-60 (Testing)
#define CAF_SUITE ping_pong #define CAF_SUITE ping_pong
#include "caf/test/dsl.hpp" #include "caf/test/dsl.hpp"
...@@ -9,6 +7,7 @@ ...@@ -9,6 +7,7 @@
using namespace caf; using namespace caf;
// --(rst-ping-pong-begin)--
namespace { namespace {
behavior ping(event_based_actor* self, actor pong_actor, int n) { behavior ping(event_based_actor* self, actor pong_actor, int n) {
...@@ -58,3 +57,4 @@ CAF_TEST(three pings) { ...@@ -58,3 +57,4 @@ CAF_TEST(three pings) {
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
// --(rst-ping-pong-end)--
...@@ -308,6 +308,7 @@ caf_add_component( ...@@ -308,6 +308,7 @@ caf_add_component(
policy.select_all policy.select_all
policy.select_any policy.select_any
request_timeout request_timeout
response_promise
result result
save_inspector save_inspector
selective_streaming selective_streaming
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "caf/blocking_actor.hpp" #include "caf/blocking_actor.hpp"
#include "caf/byte_buffer.hpp" #include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp" #include "caf/byte_span.hpp"
#include "caf/caf_main.hpp"
#include "caf/config_option.hpp" #include "caf/config_option.hpp"
#include "caf/config_option_adder.hpp" #include "caf/config_option_adder.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
#include <cstdint> #include <cstdint>
#include <type_traits> #include <type_traits>
#include "caf/detail/type_traits.hpp"
#pragma once #pragma once
namespace caf { namespace caf {
...@@ -15,31 +13,31 @@ namespace caf { ...@@ -15,31 +13,31 @@ namespace caf {
enum class byte : uint8_t {}; enum class byte : uint8_t {};
template <class IntegerType, template <class IntegerType,
class = detail::enable_if_tt<std::is_integral<IntegerType>>> class = std::enable_if_t<std::is_integral<IntegerType>::value>>
constexpr IntegerType to_integer(byte x) noexcept { constexpr IntegerType to_integer(byte x) noexcept {
return static_cast<IntegerType>(x); return static_cast<IntegerType>(x);
} }
template <class IntegerType, template <class IntegerType,
class E = detail::enable_if_tt<std::is_integral<IntegerType>>> class E = std::enable_if_t<std::is_integral<IntegerType>::value>>
constexpr byte& operator<<=(byte& x, IntegerType shift) noexcept { constexpr byte& operator<<=(byte& x, IntegerType shift) noexcept {
return x = static_cast<byte>(to_integer<uint8_t>(x) << shift); return x = static_cast<byte>(to_integer<uint8_t>(x) << shift);
} }
template <class IntegerType, template <class IntegerType,
class E = detail::enable_if_tt<std::is_integral<IntegerType>>> class E = std::enable_if_t<std::is_integral<IntegerType>::value>>
constexpr byte operator<<(byte x, IntegerType shift) noexcept { constexpr byte operator<<(byte x, IntegerType shift) noexcept {
return static_cast<byte>(to_integer<uint8_t>(x) << shift); return static_cast<byte>(to_integer<uint8_t>(x) << shift);
} }
template <class IntegerType, template <class IntegerType,
class E = detail::enable_if_tt<std::is_integral<IntegerType>>> class E = std::enable_if_t<std::is_integral<IntegerType>::value>>
constexpr byte& operator>>=(byte& x, IntegerType shift) noexcept { constexpr byte& operator>>=(byte& x, IntegerType shift) noexcept {
return x = static_cast<byte>(to_integer<uint8_t>(x) >> shift); return x = static_cast<byte>(to_integer<uint8_t>(x) >> shift);
} }
template <class IntegerType, template <class IntegerType,
class E = detail::enable_if_tt<std::is_integral<IntegerType>>> class E = std::enable_if_t<std::is_integral<IntegerType>::value>>
constexpr byte operator>>(byte x, IntegerType shift) noexcept { constexpr byte operator>>(byte x, IntegerType shift) noexcept {
return static_cast<byte>(static_cast<unsigned char>(x) >> shift); return static_cast<byte>(static_cast<unsigned char>(x) >> shift);
} }
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
// For historic reasons, CAF_MAIN is implemented in exec_main.hpp. Eventually
// the implementation of the macro should move here.
#include "caf/exec_main.hpp"
...@@ -64,7 +64,7 @@ CAF_ADD_CONFIG_VALUE_TYPE(dictionary<config_value>); ...@@ -64,7 +64,7 @@ CAF_ADD_CONFIG_VALUE_TYPE(dictionary<config_value>);
template <class T> template <class T>
constexpr bool is_config_value_type_v = is_config_value_type<T>::value; constexpr bool is_config_value_type_v = is_config_value_type<T>::value;
}; // namespace caf::detail } // namespace caf::detail
namespace caf { namespace caf {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <atomic> #include <atomic>
#include <cstdlib> #include <cstdlib>
#include <new>
#include "caf/byte.hpp" #include "caf/byte.hpp"
#include "caf/config.hpp" #include "caf/config.hpp"
...@@ -46,6 +47,8 @@ public: ...@@ -46,6 +47,8 @@ public:
message_data* copy() const; message_data* copy() const;
static intrusive_ptr<message_data> make_uninitialized(type_id_list types);
// -- reference counting ----------------------------------------------------- // -- reference counting -----------------------------------------------------
/// Increases reference count by one. /// Increases reference count by one.
...@@ -110,9 +113,53 @@ public: ...@@ -110,9 +113,53 @@ public:
init_impl(storage(), std::forward<Ts>(xs)...); init_impl(storage(), std::forward<Ts>(xs)...);
} }
byte* stepwise_init(byte* pos) {
return pos;
}
template <class T, class... Ts>
byte* stepwise_init(byte* pos, T&& x, Ts&&... xs) {
using type = strip_and_convert_t<T>;
new (pos) type(std::forward<T>(x));
++constructed_elements_;
return stepwise_init(pos + padded_size_v<type>, std::forward<Ts>(xs)...);
}
byte* stepwise_init_from(byte* pos, const message& msg);
byte* stepwise_init_from(byte* pos, const message_data* other);
template <class Tuple, size_t... Is>
byte* stepwise_init_from(byte* pos, Tuple&& tup, std::index_sequence<Is...>) {
return stepwise_init(pos, std::get<Is>(std::forward<Tuple>(tup))...);
}
template <class... Ts>
byte* stepwise_init_from(byte* pos, std::tuple<Ts...>&& tup) {
return stepwise_init_from(pos, std::move(tup),
std::make_index_sequence<sizeof...(Ts)>{});
}
template <class... Ts>
byte* stepwise_init_from(byte* pos, std::tuple<Ts...>& tup) {
return stepwise_init_from(pos, tup,
std::make_index_sequence<sizeof...(Ts)>{});
}
template <class... Ts>
byte* stepwise_init_from(byte* pos, const std::tuple<Ts...>& tup) {
return stepwise_init_from(pos, tup,
std::make_index_sequence<sizeof...(Ts)>{});
}
template <class... Ts>
void init_from(Ts&&... xs) {
init_from_impl(storage(), std::forward<Ts>(xs)...);
}
private: private:
void init_impl(byte*) { void init_impl(byte*) {
// nop // End of recursion.
} }
template <class T, class... Ts> template <class T, class... Ts>
...@@ -123,6 +170,16 @@ private: ...@@ -123,6 +170,16 @@ private:
init_impl(storage + padded_size_v<type>, std::forward<Ts>(xs)...); init_impl(storage + padded_size_v<type>, std::forward<Ts>(xs)...);
} }
void init_from_impl(byte*) {
// End of recursion.
}
template <class T, class... Ts>
void init_from_impl(byte* pos, T&& x, Ts&&... xs) {
init_from_impl(stepwise_init_from(pos, std::forward<T>(x)),
std::forward<Ts>(xs)...);
}
mutable std::atomic<size_t> rc_; mutable std::atomic<size_t> rc_;
type_id_list types_; type_id_list types_;
size_t constructed_elements_; size_t constructed_elements_;
......
...@@ -12,8 +12,6 @@ ...@@ -12,8 +12,6 @@
#include "caf/detail/type_pair.hpp" #include "caf/detail/type_pair.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/none.hpp" #include "caf/none.hpp"
#include "caf/type_id.hpp"
#include "caf/type_id_list.hpp"
#include "caf/unit.hpp" #include "caf/unit.hpp"
namespace caf::detail { namespace caf::detail {
...@@ -36,21 +34,6 @@ struct strip_param<param<T>> { ...@@ -36,21 +34,6 @@ struct strip_param<param<T>> {
using type = T; using type = T;
}; };
template <class List>
struct to_type_id_list_helper;
template <class... Ts>
struct to_type_id_list_helper<type_list<Ts...>> {
static constexpr type_id_list get() {
return make_type_id_list<typename strip_param<Ts>::type...>();
}
};
template <class List>
constexpr type_id_list to_type_id_list() {
return to_type_id_list_helper<List>::get();
}
/// Denotes the empty list. /// Denotes the empty list.
using empty_type_list = type_list<>; using empty_type_list = type_list<>;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
namespace caf { namespace caf {
// --(rst-exit-reason-begin)--
/// This error category represents fail conditions for actors. /// This error category represents fail conditions for actors.
enum class exit_reason : uint8_t { enum class exit_reason : uint8_t {
/// Indicates that an actor finished execution without error. /// Indicates that an actor finished execution without error.
...@@ -40,6 +41,7 @@ enum class exit_reason : uint8_t { ...@@ -40,6 +41,7 @@ enum class exit_reason : uint8_t {
/// Indicates that an actor was killed because it became unreachable. /// Indicates that an actor was killed because it became unreachable.
unreachable unreachable
}; };
// --(rst-exit-reason-end)--
/// @relates exit_reason /// @relates exit_reason
CAF_CORE_EXPORT std::string to_string(exit_reason); CAF_CORE_EXPORT std::string to_string(exit_reason);
......
...@@ -130,7 +130,6 @@ class string_view; ...@@ -130,7 +130,6 @@ class string_view;
class tracing_data; class tracing_data;
class tracing_data_factory; class tracing_data_factory;
class type_id_list; class type_id_list;
class type_id_list_builder;
class uri; class uri;
class uri_builder; class uri_builder;
class uuid; class uuid;
......
...@@ -43,6 +43,17 @@ public: ...@@ -43,6 +43,17 @@ public:
message& operator=(const message&) noexcept = default; message& operator=(const message&) noexcept = default;
// -- concatenation ----------------------------------------------------------
template <class... Ts>
static message concat(Ts&&... xs) {
static_assert(sizeof...(Ts) >= 2);
auto types = type_id_list::concat(types_of(xs)...);
auto ptr = detail::message_data::make_uninitialized(types);
ptr->init_from(std::forward<Ts>(xs)...);
return message{data_ptr{ptr.release(), false}};
}
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
auto types() const noexcept { auto types() const noexcept {
......
...@@ -53,27 +53,31 @@ public: ...@@ -53,27 +53,31 @@ public:
"mixing expected<T> with regular values is not supported"); "mixing expected<T> with regular values is not supported");
if constexpr (sizeof...(Ts) == 0 if constexpr (sizeof...(Ts) == 0
&& std::is_same<message, std::decay_t<T>>::value) && std::is_same<message, std::decay_t<T>>::value)
return deliver_impl(std::forward<T>(x)); deliver_impl(std::forward<T>(x));
else else
return deliver_impl( deliver_impl(make_message(std::forward<T>(x), std::forward<Ts>(xs)...));
make_message(std::forward<T>(x), std::forward<Ts>(xs)...));
} }
/// Satisfies the promise by sending either an error or a non-error response /// Satisfies the promise by sending either an error or a non-error response
/// message. /// message.
template <class T> template <class T>
void deliver(expected<T> x) { void deliver(expected<T> x) {
if (x) if (x) {
return deliver(std::move(*x)); if constexpr (std::is_same<T, void>::value)
return deliver(std::move(x.error())); deliver();
else
deliver(std::move(*x));
} else {
deliver(std::move(x.error()));
}
} }
/// Satisfies the promise by delegating to another actor. /// Satisfies the promise by delegating to another actor.
template <message_priority P = message_priority::normal, class Handle = actor, template <message_priority P = message_priority::normal, class Handle = actor,
class... Ts> class... Ts>
typename response_type<typename Handle::signatures, delegated_response_type_t<
detail::implicit_conversions_t< typename Handle::signatures,
typename std::decay<Ts>::type>...>::delegated_type detail::implicit_conversions_t<typename std::decay<Ts>::type>...>
delegate(const Handle& dest, Ts&&... xs) { delegate(const Handle& dest, Ts&&... xs) {
static_assert(sizeof...(Ts) > 0, "nothing to delegate"); static_assert(sizeof...(Ts) > 0, "nothing to delegate");
using token = detail::type_list<typename detail::implicit_conversions< using token = detail::type_list<typename detail::implicit_conversions<
...@@ -96,7 +100,13 @@ public: ...@@ -96,7 +100,13 @@ public:
/// Satisfies the promise by sending an empty message if this promise has a /// Satisfies the promise by sending an empty message if this promise has a
/// valid message ID, i.e., `async() == false`. /// valid message ID, i.e., `async() == false`.
void deliver(unit_t x); void deliver();
/// Satisfies the promise by sending an empty message if this promise has a
/// valid message ID, i.e., `async() == false`.
void deliver(unit_t) {
deliver();
}
/// Returns whether this response promise replies to an asynchronous message. /// Returns whether this response promise replies to an asynchronous message.
bool async() const; bool async() const;
......
...@@ -58,11 +58,18 @@ struct response_type<detail::type_list<Out(In...), Fs...>, In...> { ...@@ -58,11 +58,18 @@ struct response_type<detail::type_list<Out(In...), Fs...>, In...> {
using delegated_type = delegated<Out>; using delegated_type = delegated<Out>;
}; };
/// Computes the response message for input `In...` from the list of message /// Computes the response message type for input `In...` from the list of
/// passing interfaces `Fs`. /// message passing interfaces `Fs`.
template <class Fs, class... In> template <class Fs, class... In>
using response_type_t = typename response_type<Fs, In...>::type; using response_type_t = typename response_type<Fs, In...>::type;
/// Computes the response message type for input `In...` from the list of
/// message passing interfaces `Fs` and returns the corresponding
/// `delegated<T>`.
template <class Fs, class... In>
using delegated_response_type_t =
typename response_type<Fs, In...>::delegated_type;
/// Unboxes `Xs` and calls `response_type`. /// Unboxes `Xs` and calls `response_type`.
template <class Ts, class Xs> template <class Ts, class Xs>
struct response_type_unbox; struct response_type_unbox;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
namespace caf { namespace caf {
// --(rst-sec-begin)--
/// SEC stands for "System Error Code". This enum contains error codes for /// SEC stands for "System Error Code". This enum contains error codes for
/// ::actor_system and its modules. /// ::actor_system and its modules.
enum class sec : uint8_t { enum class sec : uint8_t {
...@@ -159,6 +160,7 @@ enum class sec : uint8_t { ...@@ -159,6 +160,7 @@ enum class sec : uint8_t {
/// A key lookup failed. /// A key lookup failed.
no_such_key = 65, no_such_key = 65,
}; };
// --(rst-sec-end)--
/// @relates sec /// @relates sec
CAF_CORE_EXPORT std::string to_string(sec); CAF_CORE_EXPORT std::string to_string(sec);
...@@ -170,8 +172,8 @@ CAF_CORE_EXPORT bool from_string(string_view, sec&); ...@@ -170,8 +172,8 @@ CAF_CORE_EXPORT bool from_string(string_view, sec&);
CAF_CORE_EXPORT bool from_integer(std::underlying_type_t<sec>, sec&); CAF_CORE_EXPORT bool from_integer(std::underlying_type_t<sec>, sec&);
/// @relates sec /// @relates sec
template <class Inssector> template <class Inspector>
bool inspect(Inssector& f, sec& x) { bool inspect(Inspector& f, sec& x) {
return default_enum_inspect(f, x); return default_enum_inspect(f, x);
} }
......
...@@ -27,16 +27,15 @@ struct is_string_like { ...@@ -27,16 +27,15 @@ struct is_string_like {
template <class U> template <class U>
static bool sfinae( static bool sfinae(
const U* x, const U* x,
// check if `(*x)[0]` returns `const char&` // check if `x->data()` returns const char*
typename std::enable_if< std::enable_if_t<
std::is_same<const char&, decltype((*x)[0])>::value>::type* = nullptr, std::is_same<const char*, decltype(x->data())>::value>* = nullptr,
// check if `x->size()` returns an integer // check if `x->size()` returns an integer
typename std::enable_if< std::enable_if_t<std::is_integral<decltype(x->size())>::value>* = nullptr,
std::is_integral<decltype(x->size())>::value>::type* = nullptr,
// check if `x->find('?', 0)` is well-formed and returns an integer // check if `x->find('?', 0)` is well-formed and returns an integer
// (distinguishes vectors from strings) // (distinguishes vectors from strings)
typename std::enable_if< std::enable_if_t<
std::is_integral<decltype(x->find('?', 0))>::value>::type* = nullptr); std::is_integral<decltype(x->find('?', 0))>::value>* = nullptr);
// SFINAE fallback. // SFINAE fallback.
static void sfinae(void*); static void sfinae(void*);
...@@ -105,15 +104,9 @@ public: ...@@ -105,15 +104,9 @@ public:
template <class T, class = typename std::enable_if< template <class T, class = typename std::enable_if<
detail::is_string_like<T>::value>::type> detail::is_string_like<T>::value>::type>
string_view(const T& str) noexcept { constexpr string_view(const T& str) noexcept
auto len = str.size(); : data_(str.data()), size_(str.size()) {
if (len == 0) { // nop
data_ = nullptr;
size_ = 0;
} else {
data_ = &(str[0]);
size_ = str.size();
}
} }
string_view& operator=(const string_view&) noexcept = default; string_view& operator=(const string_view&) noexcept = default;
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
#include "caf/detail/comparable.hpp" #include "caf/detail/comparable.hpp"
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/detail/implicit_conversions.hpp"
#include "caf/span.hpp"
#include "caf/type_id.hpp" #include "caf/type_id.hpp"
namespace caf { namespace caf {
...@@ -78,6 +80,17 @@ public: ...@@ -78,6 +80,17 @@ public:
/// type-erased tuple for the element types stored in this list. /// type-erased tuple for the element types stored in this list.
size_t data_size() const noexcept; size_t data_size() const noexcept;
/// Concatenates all `lists` into a single type ID list.
static type_id_list concat(span<type_id_list> lists);
/// Concatenates all `lists` into a single type ID list.
template <class... Ts>
static type_id_list
concat(type_id_list list1, type_id_list list2, Ts... lists) {
type_id_list arr[] = {list1, list2, lists...};
return concat(arr);
}
private: private:
pointer data_; pointer data_;
}; };
...@@ -99,6 +112,14 @@ constexpr type_id_list make_type_id_list() { ...@@ -99,6 +112,14 @@ constexpr type_id_list make_type_id_list() {
/// @relates type_id_list /// @relates type_id_list
CAF_CORE_EXPORT std::string to_string(type_id_list xs); CAF_CORE_EXPORT std::string to_string(type_id_list xs);
/// @relates type_id_list
CAF_CORE_EXPORT type_id_list types_of(const message& msg);
template <class... Ts>
type_id_list types_of(const std::tuple<Ts...>&) {
return make_type_id_list<detail::strip_and_convert_t<Ts>...>();
}
} // namespace caf } // namespace caf
namespace caf::detail { namespace caf::detail {
...@@ -118,4 +139,19 @@ type_id_list make_argument_type_id_list() { ...@@ -118,4 +139,19 @@ type_id_list make_argument_type_id_list() {
return argument_type_id_list_factory<F>::make(); return argument_type_id_list_factory<F>::make();
} }
template <class List>
struct to_type_id_list_helper;
template <class... Ts>
struct to_type_id_list_helper<type_list<Ts...>> {
static constexpr type_id_list get() {
return make_type_id_list<typename strip_param<Ts>::type...>();
}
};
template <class List>
constexpr type_id_list to_type_id_list() {
return to_type_id_list_helper<List>::get();
}
} // namespace caf::detail } // namespace caf::detail
...@@ -54,6 +54,9 @@ public: ...@@ -54,6 +54,9 @@ public:
// tell actor_cast which semantic this type uses // tell actor_cast which semantic this type uses
static constexpr bool has_weak_ptr_semantics = false; static constexpr bool has_weak_ptr_semantics = false;
/// Stores the template parameter pack.
using signatures = detail::type_list<Sigs...>;
/// Creates a new `typed_actor` type by extending this one with `Es...`. /// Creates a new `typed_actor` type by extending this one with `Es...`.
template <class... Es> template <class... Es>
using extend = typed_actor<Sigs..., Es...>; using extend = typed_actor<Sigs..., Es...>;
...@@ -68,15 +71,31 @@ public: ...@@ -68,15 +71,31 @@ public:
/// for their behavior stack. /// for their behavior stack.
using behavior_type = typed_behavior<Sigs...>; using behavior_type = typed_behavior<Sigs...>;
/// The default, event-based type for implementing this messaging interface.
using impl = typed_event_based_actor<Sigs...>;
/// Identifies pointers to instances of this kind of actor. /// Identifies pointers to instances of this kind of actor.
using pointer = typed_event_based_actor<Sigs...>*; using pointer = impl*;
/// Allows a view to an actor implementing this messaging interface without /// A view to an actor that implements this messaging interface without
/// knowledge of the actual type.. /// knowledge of the actual type.
using pointer_view = typed_actor_pointer<Sigs...>; using pointer_view = typed_actor_pointer<Sigs...>;
/// Identifies the base class for this kind of actor. /// A class type suitable as base type class-based implementations.
using base = typed_event_based_actor<Sigs...>; using base = impl;
/// The default, event-based type for implementing this messaging interface as
/// a stateful actor.
template <class State>
using stateful_impl = stateful_actor<State, impl>;
template <class State>
using stateful_base [[deprecated("use stateful_impl instead")]]
= stateful_actor<State, base>;
/// Convenience alias for `stateful_impl<State>*`.
template <class State>
using stateful_pointer = stateful_impl<State>*;
/// Identifies pointers to brokers implementing this interface. /// Identifies pointers to brokers implementing this interface.
using broker_pointer = io::typed_broker<Sigs...>*; using broker_pointer = io::typed_broker<Sigs...>*;
...@@ -84,17 +103,6 @@ public: ...@@ -84,17 +103,6 @@ public:
/// Identifies the base class of brokers implementing this interface. /// Identifies the base class of brokers implementing this interface.
using broker_base = io::typed_broker<Sigs...>; using broker_base = io::typed_broker<Sigs...>;
/// Stores the template parameter pack.
using signatures = detail::type_list<Sigs...>;
/// Identifies the base class for this kind of actor with actor.
template <class State>
using stateful_base = stateful_actor<State, base>;
/// Identifies the base class for this kind of actor with actor.
template <class State>
using stateful_pointer = stateful_actor<State, base>*;
/// Identifies the broker_base class for this kind of actor with actor. /// Identifies the broker_base class for this kind of actor with actor.
template <class State> template <class State>
using stateful_broker_base = stateful_actor<State, broker_base>; using stateful_broker_base = stateful_actor<State, broker_base>;
......
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
#pragma once #pragma once
#include "caf/response_promise.hpp" #include <type_traits>
#include "caf/detail/type_list.hpp" #include "caf/detail/type_list.hpp"
#include "caf/make_message.hpp"
#include "caf/response_promise.hpp"
namespace caf { namespace caf {
...@@ -48,42 +50,38 @@ public: ...@@ -48,42 +50,38 @@ public:
} }
/// Satisfies the promise by sending a non-error response message. /// Satisfies the promise by sending a non-error response message.
template <class U, class... Us> template <class... Us>
typename std::enable_if<(sizeof...(Us) > 0) std::enable_if_t<(std::is_constructible<Ts, Us>::value && ...)>
|| !std::is_convertible<U, error>::value, deliver(Us... xs) {
typed_response_promise>::type promise_.deliver(make_message(Ts{std::forward<Us>(xs)}...));
deliver(U&& x, Us&&... xs) { }
static_assert(
std::is_same<detail::type_list<Ts...>, /// Satisfies the promise by sending an empty response message.
detail::type_list<typename std::decay<U>::type, template <class L = detail::type_list<Ts...>>
typename std::decay<Us>::type...>>::value, std::enable_if_t<std::is_same<L, detail::type_list<void>>::value> deliver() {
"typed_response_promise: message type mismatched"); promise_.deliver();
promise_.deliver(std::forward<U>(x), std::forward<Us>(xs)...); }
return *this;
/// Satisfies the promise by sending an error response message.
/// For non-requests, nothing is done.
void deliver(error x) {
promise_.deliver(std::move(x));
} }
/// Satisfies the promise by sending either an error or a non-error response /// Satisfies the promise by sending either an error or a non-error response
/// message. /// message.
template <class T> template <class T>
void deliver(expected<T> x) { std::enable_if_t<
if (x) std::is_same<detail::type_list<T>, detail::type_list<Ts...>>::value>
return deliver(std::move(*x)); deliver(expected<T> x) {
return deliver(std::move(x.error())); promise_.deliver(std::move(x));
} }
/// Satisfies the promise by delegating to another actor. /// Satisfies the promise by delegating to another actor.
template <message_priority P = message_priority::normal, class Handle = actor, template <message_priority P = message_priority::normal, class Handle = actor,
class... Us> class... Us>
typed_response_promise delegate(const Handle& dest, Us&&... xs) { auto delegate(const Handle& dest, Us&&... xs) {
promise_.template delegate<P>(dest, std::forward<Us>(xs)...); return promise_.template delegate<P>(dest, std::forward<Us>(xs)...);
return *this;
}
/// Satisfies the promise by sending an error response message.
/// For non-requests, nothing is done.
typed_response_promise deliver(error x) {
promise_.deliver(std::move(x));
return *this;
} }
/// Returns whether this response promise replies to an asynchronous message. /// Returns whether this response promise replies to an asynchronous message.
......
...@@ -220,7 +220,7 @@ expected<bool> config_value::to_boolean() const { ...@@ -220,7 +220,7 @@ expected<bool> config_value::to_boolean() const {
using result_type = expected<bool>; using result_type = expected<bool>;
auto f = detail::make_overload( auto f = detail::make_overload(
no_conversions<bool, none_t, integer, real, timespan, uri, no_conversions<bool, none_t, integer, real, timespan, uri,
config_value::list, config_value::dictionary>(), config_value::list>(),
[](boolean x) { return result_type{x}; }, [](boolean x) { return result_type{x}; },
[](const std::string& x) { [](const std::string& x) {
if (x == "true") { if (x == "true") {
...@@ -233,6 +233,31 @@ expected<bool> config_value::to_boolean() const { ...@@ -233,6 +233,31 @@ expected<bool> config_value::to_boolean() const {
msg += " to a boolean"; msg += " to a boolean";
return result_type{make_error(sec::conversion_failed, std::move(msg))}; return result_type{make_error(sec::conversion_failed, std::move(msg))};
} }
},
[](const dictionary& x) {
if (auto i = x.find("@type");
i != x.end() && holds_alternative<std::string>(i->second)) {
const auto& tn = get<std::string>(i->second);
if (tn == type_name_v<bool>) {
if (auto j = x.find("value"); j != x.end()) {
return j->second.to_boolean();
} else {
std::string msg = "missing value for object of type ";
msg += tn;
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert ";
msg += tn;
msg += " to a boolean";
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert a dictionary to a boolean";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
}
}); });
return visit(f, data_); return visit(f, data_);
} }
...@@ -240,8 +265,7 @@ expected<bool> config_value::to_boolean() const { ...@@ -240,8 +265,7 @@ expected<bool> config_value::to_boolean() const {
expected<config_value::integer> config_value::to_integer() const { expected<config_value::integer> config_value::to_integer() const {
using result_type = expected<integer>; using result_type = expected<integer>;
auto f = detail::make_overload( auto f = detail::make_overload(
no_conversions<integer, none_t, bool, timespan, uri, config_value::list, no_conversions<integer, none_t, bool, timespan, uri, config_value::list>(),
config_value::dictionary>(),
[](integer x) { return result_type{x}; }, [](integer x) { return result_type{x}; },
[](real x) { [](real x) {
using limits = std::numeric_limits<config_value::integer>; using limits = std::numeric_limits<config_value::integer>;
...@@ -269,6 +293,37 @@ expected<config_value::integer> config_value::to_integer() const { ...@@ -269,6 +293,37 @@ expected<config_value::integer> config_value::to_integer() const {
detail::print_escaped(msg, x); detail::print_escaped(msg, x);
msg += " to an integer"; msg += " to an integer";
return result_type{make_error(sec::conversion_failed, std::move(msg))}; return result_type{make_error(sec::conversion_failed, std::move(msg))};
},
[](const dictionary& x) {
if (auto i = x.find("@type");
i != x.end() && holds_alternative<std::string>(i->second)) {
const auto& tn = get<std::string>(i->second);
string_view valid_types[]
= {type_name_v<int16_t>, type_name_v<int32_t>,
type_name_v<int64_t>, type_name_v<int8_t>,
type_name_v<uint16_t>, type_name_v<uint32_t>,
type_name_v<uint64_t>, type_name_v<uint8_t>};
auto eq = [&tn](string_view x) { return x == tn; };
if (std::any_of(std::begin(valid_types), std::end(valid_types), eq)) {
if (auto j = x.find("value"); j != x.end()) {
return j->second.to_integer();
} else {
std::string msg = "missing value for object of type ";
msg += tn;
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert ";
msg += tn;
msg += " to an integer";
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert a dictionary to an integer";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
}
}); });
return visit(f, data_); return visit(f, data_);
} }
...@@ -276,8 +331,7 @@ expected<config_value::integer> config_value::to_integer() const { ...@@ -276,8 +331,7 @@ expected<config_value::integer> config_value::to_integer() const {
expected<config_value::real> config_value::to_real() const { expected<config_value::real> config_value::to_real() const {
using result_type = expected<real>; using result_type = expected<real>;
auto f = detail::make_overload( auto f = detail::make_overload(
no_conversions<real, none_t, bool, timespan, uri, config_value::list, no_conversions<real, none_t, bool, timespan, uri, config_value::list>(),
config_value::dictionary>(),
[](integer x) { [](integer x) {
// This cast may lose precision on the value. We could try and check that, // This cast may lose precision on the value. We could try and check that,
// but refusing to convert on loss of precision could also be unexpected // but refusing to convert on loss of precision could also be unexpected
...@@ -293,6 +347,35 @@ expected<config_value::real> config_value::to_real() const { ...@@ -293,6 +347,35 @@ expected<config_value::real> config_value::to_real() const {
detail::print_escaped(msg, x); detail::print_escaped(msg, x);
msg += " to a floating point number"; msg += " to a floating point number";
return result_type{make_error(sec::conversion_failed, std::move(msg))}; return result_type{make_error(sec::conversion_failed, std::move(msg))};
},
[](const dictionary& x) {
if (auto i = x.find("@type");
i != x.end() && holds_alternative<std::string>(i->second)) {
const auto& tn = get<std::string>(i->second);
string_view valid_types[] = {type_name_v<float>, type_name_v<double>,
type_name_v<long double>};
auto eq = [&tn](string_view x) { return x == tn; };
if (std::any_of(std::begin(valid_types), std::end(valid_types), eq)) {
if (auto j = x.find("value"); j != x.end()) {
return j->second.to_real();
} else {
std::string msg = "missing value for object of type ";
msg += tn;
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg = "cannot convert ";
msg += tn;
msg += " to a floating point number";
return result_type{
make_error(sec::conversion_failed, std::move(msg))};
}
} else {
std::string msg
= "cannot convert a dictionary to a floating point number";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
}
}); });
return visit(f, data_); return visit(f, data_);
} }
......
...@@ -381,7 +381,7 @@ bool config_value_reader::begin_associative_array(size_t& size) { ...@@ -381,7 +381,7 @@ bool config_value_reader::begin_associative_array(size_t& size) {
st_.top() = associative_array{dict->begin(), dict->end()}; st_.top() = associative_array{dict->begin(), dict->end()};
return true; return true;
} }
std::string msg = "expected a dictionary, got a "; std::string msg = "begin_associative_array: expected a dictionary, got a ";
msg += top->type_name(); msg += top->type_name();
emplace_error(sec::conversion_failed, std::move(msg)); emplace_error(sec::conversion_failed, std::move(msg));
return false; return false;
...@@ -419,13 +419,10 @@ bool pull(config_value_reader& reader, T& x) { ...@@ -419,13 +419,10 @@ bool pull(config_value_reader& reader, T& x) {
reader.pop(); reader.pop();
return true; return true;
} else { } else {
std::string msg = "expected a dictionary, got a "; reader.set_error(std::move(val.error()));
msg += to_string(type_name_v<T>);
reader.emplace_error(sec::conversion_failed, std::move(msg));
return false; return false;
} }
} } else if (holds_alternative<config_value_reader::sequence>(top)) {
if (holds_alternative<config_value_reader::sequence>(top)) {
auto& seq = get<config_value_reader::sequence>(top); auto& seq = get<config_value_reader::sequence>(top);
if (seq.at_end()) { if (seq.at_end()) {
reader.emplace_error(sec::runtime_error, "value: sequence out of bounds"); reader.emplace_error(sec::runtime_error, "value: sequence out of bounds");
...@@ -437,13 +434,10 @@ bool pull(config_value_reader& reader, T& x) { ...@@ -437,13 +434,10 @@ bool pull(config_value_reader& reader, T& x) {
seq.advance(); seq.advance();
return true; return true;
} else { } else {
std::string msg = "expected a dictionary, got a "; reader.set_error(std::move(val.error()));
msg += to_string(type_name_v<T>);
reader.emplace_error(sec::conversion_failed, std::move(msg));
return false; return false;
} }
} } else if (holds_alternative<config_value_reader::key_ptr>(top)) {
if (holds_alternative<config_value_reader::key_ptr>(top)) {
auto ptr = get<config_value_reader::key_ptr>(top); auto ptr = get<config_value_reader::key_ptr>(top);
if constexpr (std::is_same<std::string, T>::value) { if constexpr (std::is_same<std::string, T>::value) {
x = *ptr; x = *ptr;
...@@ -453,10 +447,11 @@ bool pull(config_value_reader& reader, T& x) { ...@@ -453,10 +447,11 @@ bool pull(config_value_reader& reader, T& x) {
if (auto err = detail::parse(*ptr, x)) { if (auto err = detail::parse(*ptr, x)) {
reader.set_error(std::move(err)); reader.set_error(std::move(err));
return false; return false;
} } else {
return true; return true;
} }
} }
}
reader.emplace_error(sec::conversion_failed, reader.emplace_error(sec::conversion_failed,
"expected a value, sequence, or key"); "expected a value, sequence, or key");
return false; return false;
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "caf/detail/meta_object.hpp" #include "caf/detail/meta_object.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/error_code.hpp" #include "caf/error_code.hpp"
#include "caf/message.hpp"
#include "caf/raise_error.hpp" #include "caf/raise_error.hpp"
#include "caf/sec.hpp" #include "caf/sec.hpp"
#include "caf/span.hpp" #include "caf/span.hpp"
...@@ -48,18 +49,30 @@ message_data* message_data::copy() const { ...@@ -48,18 +49,30 @@ message_data* message_data::copy() const {
auto vptr = malloc(total_size); auto vptr = malloc(total_size);
if (vptr == nullptr) if (vptr == nullptr)
CAF_RAISE_ERROR(std::bad_alloc, "bad_alloc"); CAF_RAISE_ERROR(std::bad_alloc, "bad_alloc");
auto ptr = new (vptr) message_data(types_); intrusive_ptr<message_data> ptr{new (vptr) message_data(types_), false};
auto src = storage(); auto src = storage();
auto dst = ptr->storage(); auto dst = ptr->storage();
for (auto id : types_) { for (auto id : types_) {
auto& meta = gmos[id]; auto& meta = gmos[id];
// TODO: exception handling.
meta.copy_construct(dst, src); meta.copy_construct(dst, src);
++ptr->constructed_elements_; ++ptr->constructed_elements_;
src += meta.padded_size; src += meta.padded_size;
dst += meta.padded_size; dst += meta.padded_size;
} }
return ptr; return ptr.release();
}
intrusive_ptr<message_data>
message_data::make_uninitialized(type_id_list types) {
auto gmos = global_meta_objects();
size_t storage_size = 0;
for (auto id : types)
storage_size += gmos[id].padded_size;
auto total_size = sizeof(message_data) + storage_size;
auto vptr = malloc(total_size);
if (vptr == nullptr)
CAF_RAISE_ERROR(std::bad_alloc, "bad_alloc");
return {new (vptr) message_data(types), false};
} }
byte* message_data::at(size_t index) noexcept { byte* message_data::at(size_t index) noexcept {
...@@ -82,4 +95,23 @@ const byte* message_data::at(size_t index) const noexcept { ...@@ -82,4 +95,23 @@ const byte* message_data::at(size_t index) const noexcept {
return ptr; return ptr;
} }
byte* message_data::stepwise_init_from(byte* pos, const message& msg) {
return stepwise_init_from(pos, msg.cptr());
}
byte* message_data::stepwise_init_from(byte* pos, const message_data* other) {
CAF_ASSERT(other != nullptr);
CAF_ASSERT(other != this);
auto gmos = global_meta_objects();
auto src = other->storage();
for (auto id : other->types()) {
auto& meta = gmos[id];
meta.copy_construct(pos, src);
++constructed_elements_;
src += meta.padded_size;
pos += meta.padded_size;
}
return pos;
}
} // namespace caf::detail } // namespace caf::detail
...@@ -46,7 +46,7 @@ void response_promise::deliver(error x) { ...@@ -46,7 +46,7 @@ void response_promise::deliver(error x) {
deliver_impl(make_message(std::move(x))); deliver_impl(make_message(std::move(x)));
} }
void response_promise::deliver(unit_t) { void response_promise::deliver() {
deliver_impl(make_message()); deliver_impl(make_message());
} }
...@@ -75,6 +75,11 @@ void response_promise::deliver_impl(message msg) { ...@@ -75,6 +75,11 @@ void response_promise::deliver_impl(message msg) {
CAF_LOG_DEBUG("drop response: invalid promise"); CAF_LOG_DEBUG("drop response: invalid promise");
return; return;
} }
if (msg.empty() && id_.is_async()) {
CAF_LOG_DEBUG("drop response: empty response to asynchronous input");
self_.reset();
return;
}
auto dptr = self_dptr(); auto dptr = self_dptr();
if (!stages_.empty()) { if (!stages_.empty()) {
auto next = std::move(stages_.back()); auto next = std::move(stages_.back());
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#include "caf/type_id_list.hpp" #include "caf/type_id_list.hpp"
#include "caf/detail/meta_object.hpp" #include "caf/detail/meta_object.hpp"
#include "caf/detail/type_id_list_builder.hpp"
#include "caf/message.hpp"
namespace caf { namespace caf {
...@@ -35,4 +37,20 @@ std::string to_string(type_id_list xs) { ...@@ -35,4 +37,20 @@ std::string to_string(type_id_list xs) {
return result; return result;
} }
type_id_list type_id_list::concat(span<type_id_list> lists) {
auto total_size = size_t{0};
for (auto ls : lists)
total_size += ls.size();
detail::type_id_list_builder builder;
builder.reserve(total_size);
for (auto ls : lists)
for (auto id : ls)
builder.push_back(id);
return builder.move_to_list();
}
type_id_list types_of(const message& msg) {
return msg.types();
}
} // namespace caf } // namespace caf
...@@ -97,6 +97,16 @@ SCENARIO("get_as can convert config values to boolean") { ...@@ -97,6 +97,16 @@ SCENARIO("get_as can convert config values to boolean") {
} }
} }
} }
GIVEN("a config value with type annotation 'bool' and the value \"true\"") {
config_value x;
x.as_dictionary().emplace("@type", "bool");
x.as_dictionary().emplace("value", "true");
WHEN("using get_as with bool") {
THEN("conversion succeeds") {
CHECK_EQ(get_as<bool>(x), true);
}
}
}
GIVEN("non-boolean config_values") { GIVEN("non-boolean config_values") {
WHEN("using get_as with bool") { WHEN("using get_as with bool") {
THEN("conversion fails") { THEN("conversion fails") {
...@@ -161,6 +171,23 @@ SCENARIO("get_as can convert config values to integers") { ...@@ -161,6 +171,23 @@ SCENARIO("get_as can convert config values to integers") {
} }
} }
} }
GIVEN("a config value x with type annotation 'int32_t' and the value 50") {
config_value x;
x.as_dictionary().emplace("@type", "int32_t");
x.as_dictionary().emplace("value", 50);
WHEN("using get_as with integer types") {
THEN("CAF parses the integer and performs a bound check") {
CHECK_EQ(get_as<uint64_t>(x), 50u);
CHECK_EQ(get_as<int64_t>(x), 50);
CHECK_EQ(get_as<uint32_t>(x), 50u);
CHECK_EQ(get_as<int32_t>(x), 50);
CHECK_EQ(get_as<uint16_t>(x), 50u);
CHECK_EQ(get_as<int16_t>(x), 50);
CHECK_EQ(get_as<uint8_t>(x), 50u);
CHECK_EQ(get_as<int8_t>(x), 50);
}
}
}
GIVEN("a config value x with value 50.0") { GIVEN("a config value x with value 50.0") {
auto x = config_value{50.0}; auto x = config_value{50.0};
WHEN("using get_as with integer types") { WHEN("using get_as with integer types") {
...@@ -278,6 +305,18 @@ SCENARIO("get_as can convert config values to floating point numbers") { ...@@ -278,6 +305,18 @@ SCENARIO("get_as can convert config values to floating point numbers") {
} }
} }
} }
GIVEN("a config value x with type annotation 'float' and the value 50") {
config_value x;
x.as_dictionary().emplace("@type", "float");
x.as_dictionary().emplace("value", 123.0);
WHEN("using get_as with floating point types") {
THEN("CAF parses the value and performs a bound check") {
CHECK_EQ(get_as<long double>(x), 123.0);
CHECK_EQ(get_as<double>(x), 123.0);
CHECK_EQ(get_as<float>(x), 123.f);
}
}
}
GIVEN("config_values of null, URI, boolean, list or dictionary") { GIVEN("config_values of null, URI, boolean, list or dictionary") {
WHEN("using get_as with integer types") { WHEN("using get_as with integer types") {
THEN("conversion fails") { THEN("conversion fails") {
......
...@@ -121,3 +121,15 @@ CAF_TEST(match_elements exposes element types) { ...@@ -121,3 +121,15 @@ CAF_TEST(match_elements exposes element types) {
CAF_CHECK((msg.match_element<int64_t>(2))); CAF_CHECK((msg.match_element<int64_t>(2)));
CAF_CHECK((msg.match_elements<put_atom, string, int64_t>())); CAF_CHECK((msg.match_elements<put_atom, string, int64_t>()));
} }
CAF_TEST(messages are concatenable) {
using std::make_tuple;
CHECK(message::concat(make_tuple(int16_t{1}), make_tuple(uint8_t{2}))
.matches(int16_t{1}, uint8_t{2}));
CHECK(message::concat(make_message(int16_t{1}), make_message(uint8_t{2}))
.matches(int16_t{1}, uint8_t{2}));
CHECK(message::concat(make_message(int16_t{1}), make_tuple(uint8_t{2}))
.matches(int16_t{1}, uint8_t{2}));
CHECK(message::concat(make_tuple(int16_t{1}), make_message(uint8_t{2}))
.matches(int16_t{1}, uint8_t{2}));
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE response_promise
#include "caf/response_promise.hpp"
#include "core-test.hpp"
using namespace caf;
namespace {
behavior adder() {
return {
[](int x, int y) { return x + y; },
[](ok_atom) {},
};
}
behavior delegator(event_based_actor* self, actor worker) {
return {
[=](int x, int y) {
auto promise = self->make_response_promise();
return promise.delegate(worker, x, y);
},
[=](ok_atom) {
auto promise = self->make_response_promise();
return promise.delegate(worker, ok_atom_v);
},
};
}
behavior requester_v1(event_based_actor* self, actor worker) {
return {
[=](int x, int y) {
auto rp = self->make_response_promise();
self->request(worker, infinite, x, y)
.then(
[rp](int result) mutable {
CHECK(rp.pending());
rp.deliver(result);
},
[rp](error err) mutable {
CHECK(rp.pending());
rp.deliver(std::move(err));
});
return rp;
},
[=](ok_atom) {
auto rp = self->make_response_promise();
self->request(worker, infinite, ok_atom_v)
.then(
[rp]() mutable {
CHECK(rp.pending());
rp.deliver();
},
[rp](error err) mutable {
CHECK(rp.pending());
rp.deliver(std::move(err));
});
return rp;
},
};
}
behavior requester_v2(event_based_actor* self, actor worker) {
return {
[=](int x, int y) {
auto rp = self->make_response_promise();
auto deliver = [rp](expected<int> x) mutable {
CHECK(rp.pending());
rp.deliver(std::move(x));
};
self->request(worker, infinite, x, y)
.then([deliver](int result) mutable { deliver(result); },
[deliver](error err) mutable { deliver(std::move(err)); });
return rp;
},
[=](ok_atom) {
auto rp = self->make_response_promise();
auto deliver = [rp](expected<void> x) mutable {
CHECK(rp.pending());
rp.deliver(std::move(x));
};
self->request(worker, infinite, ok_atom_v)
.then([deliver]() mutable { deliver({}); },
[deliver](error err) mutable { deliver(std::move(err)); });
return rp;
},
};
}
} // namespace
CAF_TEST_FIXTURE_SCOPE(response_promise_tests, test_coordinator_fixture<>)
SCENARIO("response promises allow delaying of response messages") {
auto adder_hdl = sys.spawn(adder);
std::map<std::string, actor> impls;
impls["with a value or an error"] = sys.spawn(requester_v1, adder_hdl);
impls["with an expected<T>"] = sys.spawn(requester_v2, adder_hdl);
for (auto& [desc, hdl] : impls) {
GIVEN("a dispatcher that calls deliver " << desc << " on its promise") {
WHEN("sending a request with two integers to the dispatcher") {
inject((int, int), from(self).to(hdl).with(3, 4));
THEN("clients receive the response from the dispatcher") {
expect((int, int), from(hdl).to(adder_hdl).with(3, 4));
expect((int), from(adder_hdl).to(hdl).with(7));
expect((int), from(hdl).to(self).with(7));
}
}
WHEN("sending ok_atom to the dispatcher synchronously") {
auto res = self->request(hdl, infinite, ok_atom_v);
auto fetch_result = [&] {
message result;
res.receive([] {}, // void result
[&](const error& reason) {
result = make_message(reason);
});
return result;
};
THEN("clients receive an empty response from the dispatcher") {
expect((ok_atom), from(self).to(hdl));
expect((ok_atom), from(hdl).to(adder_hdl));
expect((void), from(adder_hdl).to(hdl));
CHECK(fetch_result().empty());
}
}
WHEN("sending ok_atom to the dispatcher asynchronously") {
THEN("clients receive no response from the dispatcher") {
inject((ok_atom), from(self).to(hdl).with(ok_atom_v));
expect((ok_atom), from(hdl).to(adder_hdl));
expect((void), from(adder_hdl).to(hdl));
CHECK(self->mailbox().empty());
}
}
}
}
}
SCENARIO("response promises allow delegation") {
GIVEN("a dispatcher that calls delegate on its promise") {
auto adder_hdl = sys.spawn(adder);
auto hdl = sys.spawn(delegator, adder_hdl);
WHEN("sending a request to the dispatcher") {
inject((int, int), from(self).to(hdl).with(3, 4));
THEN("clients receive the response from the adder") {
expect((int, int), from(self).to(adder_hdl).with(3, 4));
expect((int), from(adder_hdl).to(self).with(7));
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END()
...@@ -83,3 +83,35 @@ CAF_TEST(type ID lists are convertible to strings) { ...@@ -83,3 +83,35 @@ CAF_TEST(type ID lists are convertible to strings) {
auto xs = make_type_id_list<uint16_t, bool, float, long double>(); auto xs = make_type_id_list<uint16_t, bool, float, long double>();
CAF_CHECK_EQUAL(to_string(xs), "[uint16_t, bool, float, ldouble]"); CAF_CHECK_EQUAL(to_string(xs), "[uint16_t, bool, float, ldouble]");
} }
CAF_TEST(type ID lists are concatenable) {
// 1 + 0
CHECK_EQ((make_type_id_list<int8_t>()),
type_id_list::concat(make_type_id_list<int8_t>(),
make_type_id_list<>()));
CHECK_EQ((make_type_id_list<int8_t>()),
type_id_list::concat(make_type_id_list<>(),
make_type_id_list<int8_t>()));
// 1 + 1
CHECK_EQ((make_type_id_list<int8_t, int16_t>()),
type_id_list::concat(make_type_id_list<int8_t>(),
make_type_id_list<int16_t>()));
// 2 + 0
CHECK_EQ((make_type_id_list<int8_t, int16_t>()),
type_id_list::concat(make_type_id_list<int8_t, int16_t>(),
make_type_id_list<>()));
CHECK_EQ((make_type_id_list<int8_t, int16_t>()),
type_id_list::concat(make_type_id_list<>(),
make_type_id_list<int8_t, int16_t>()));
// 2 + 1
CHECK_EQ((make_type_id_list<int8_t, int16_t, int32_t>()),
type_id_list::concat(make_type_id_list<int8_t, int16_t>(),
make_type_id_list<int32_t>()));
CHECK_EQ((make_type_id_list<int8_t, int16_t, int32_t>()),
type_id_list::concat(make_type_id_list<int8_t>(),
make_type_id_list<int16_t, int32_t>()));
// 2 + 2
CHECK_EQ((make_type_id_list<int8_t, int16_t, int32_t, int64_t>()),
type_id_list::concat(make_type_id_list<int8_t, int16_t>(),
make_type_id_list<int32_t, int64_t>()));
}
...@@ -4,190 +4,159 @@ ...@@ -4,190 +4,159 @@
#define CAF_SUITE typed_response_promise #define CAF_SUITE typed_response_promise
#include "core-test.hpp" #include "caf/typed_response_promise.hpp"
#include <map>
#include "caf/all.hpp" #include "core-test.hpp"
using namespace caf; using namespace caf;
namespace { namespace {
using promise_actor = typed_actor< using testee_actor = typed_actor<result<int>(int, int), result<void>(ok_atom)>;
replies_to<int>::with<int>, replies_to<get_atom, int>::with<int>,
replies_to<get_atom, int, int>::with<int, int>,
replies_to<get_atom, double>::with<double>,
replies_to<get_atom, double, double>::with<double, double>,
reacts_to<put_atom, int, int>, reacts_to<put_atom, int, int, int>>;
using foo_promise = typed_response_promise<int>;
using foo2_promise = typed_response_promise<int, int>;
using foo3_promise = typed_response_promise<double>;
using get1_helper = typed_actor<replies_to<int, int>::with<put_atom, int, int>>; testee_actor::behavior_type adder() {
using get2_helper return {
= typed_actor<replies_to<int, int, int>::with<put_atom, int, int, int>>; [](int x, int y) { return x + y; },
[](ok_atom) {},
class promise_actor_impl : public promise_actor::base { };
public: }
promise_actor_impl(actor_config& cfg) : promise_actor::base(cfg) {
// nop
}
behavior_type make_behavior() override { testee_actor::behavior_type delegator(testee_actor::pointer self,
testee_actor worker) {
return { return {
[=](int x) -> foo_promise { [=](int x, int y) {
auto resp = response(x * 2); auto promise = self->make_response_promise<int>();
CAF_CHECK(!resp.pending()); return promise.delegate(worker, x, y);
return resp.deliver(x * 4); // has no effect
}, },
[=](get_atom, int x) -> foo_promise { [=](ok_atom) {
auto calculator = spawn([]() -> get1_helper::behavior_type { auto promise = self->make_response_promise<void>();
return {[](int promise_id, int value) -> result<put_atom, int, int> { return promise.delegate(worker, ok_atom_v);
return {put_atom_v, promise_id, value * 2};
}};
});
send(calculator, next_id_, x);
auto& entry = promises_[next_id_++];
entry = make_response_promise<foo_promise>();
return entry;
}, },
[=](get_atom, int x, int y) -> foo2_promise { };
auto calculator = spawn([]() -> get2_helper::behavior_type { }
return {[](int promise_id, int v0,
int v1) -> result<put_atom, int, int, int> { testee_actor::behavior_type requester_v1(testee_actor::pointer self,
return {put_atom_v, promise_id, v0 * 2, v1 * 2}; testee_actor worker) {
}}; return {
}); [=](int x, int y) {
send(calculator, next_id_, x, y); auto rp = self->make_response_promise<int>();
auto& entry = promises2_[next_id_++]; self->request(worker, infinite, x, y)
entry = make_response_promise<foo2_promise>(); .then(
// verify move semantics [rp](int result) mutable {
CAF_CHECK(entry.pending()); CHECK(rp.pending());
foo2_promise tmp(std::move(entry)); rp.deliver(result);
CAF_CHECK(!entry.pending());
CAF_CHECK(tmp.pending());
entry = std::move(tmp);
CAF_CHECK(entry.pending());
CAF_CHECK(!tmp.pending());
return entry;
}, },
[=](get_atom, double) -> foo3_promise { [rp](error err) mutable {
auto resp = make_response_promise<double>(); CHECK(rp.pending());
return resp.deliver(make_error(sec::unexpected_message)); rp.deliver(std::move(err));
});
return rp;
}, },
[=](get_atom, double x, double y) { return response(x * 2, y * 2); }, [=](ok_atom) {
[=](put_atom, int promise_id, int x) { auto rp = self->make_response_promise<void>();
auto i = promises_.find(promise_id); self->request(worker, infinite, ok_atom_v)
if (i == promises_.end()) .then(
return; [rp]() mutable {
i->second.deliver(x); CHECK(rp.pending());
promises_.erase(i); rp.deliver();
}, },
[=](put_atom, int promise_id, int x, int y) { [rp](error err) mutable {
auto i = promises2_.find(promise_id); CHECK(rp.pending());
if (i == promises2_.end()) rp.deliver(std::move(err));
return; });
i->second.deliver(x, y); return rp;
promises2_.erase(i);
}, },
}; };
}
private:
int next_id_ = 0;
std::map<int, foo_promise> promises_;
std::map<int, foo2_promise> promises2_;
};
struct fixture {
fixture()
: system(cfg), self(system, true), foo(system.spawn<promise_actor_impl>()) {
// nop
}
actor_system_config cfg;
actor_system system;
scoped_actor self;
promise_actor foo;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(typed_spawn_tests, fixture)
CAF_TEST(typed_response_promise) {
typed_response_promise<int> resp;
CAF_MESSAGE("trigger 'invalid response promise' error");
resp.deliver(1); // delivers on an invalid promise has no effect
auto f = make_function_view(foo);
CAF_CHECK_EQUAL(f(get_atom_v, 42), 84);
CAF_CHECK_EQUAL(f(get_atom_v, 42, 52), std::make_tuple(84, 104));
CAF_CHECK_EQUAL(f(get_atom_v, 3.14, 3.14), std::make_tuple(6.28, 6.28));
} }
CAF_TEST(typed_response_promise_chained) { testee_actor::behavior_type requester_v2(testee_actor::pointer self,
auto f = make_function_view(foo * foo * foo); testee_actor worker) {
CAF_CHECK_EQUAL(f(1), 8); return {
[=](int x, int y) {
auto rp = self->make_response_promise<int>();
auto deliver = [rp](expected<int> x) mutable {
CHECK(rp.pending());
rp.deliver(std::move(x));
};
self->request(worker, infinite, x, y)
.then([deliver](int result) mutable { deliver(result); },
[deliver](error err) mutable { deliver(std::move(err)); });
return rp;
},
[=](ok_atom) {
auto rp = self->make_response_promise<void>();
auto deliver = [rp](expected<void> x) mutable {
CHECK(rp.pending());
rp.deliver(std::move(x));
};
self->request(worker, infinite, ok_atom_v)
.then([deliver]() mutable { deliver({}); },
[deliver](error err) mutable { deliver(std::move(err)); });
return rp;
},
};
} }
// verify that only requests get an error response message } // namespace
CAF_TEST(error_response_message) {
auto f = make_function_view(foo);
CAF_CHECK_EQUAL(f(get_atom_v, 3.14), sec::unexpected_message);
self->send(foo, get_atom_v, 42);
self->receive([](int x) { CAF_CHECK_EQUAL(x, 84); },
[](double x) {
CAF_ERROR(
"unexpected ordinary response message received: " << x);
});
self->send(foo, get_atom_v, 3.14);
self->receive([&](error& err) {
CAF_CHECK_EQUAL(err, sec::unexpected_message);
self->send(self, message{});
});
}
// verify that delivering to a satisfied promise has no effect CAF_TEST_FIXTURE_SCOPE(typed_response_promise_tests, test_coordinator_fixture<>)
CAF_TEST(satisfied_promise) {
self->send(foo, 1); SCENARIO("response promises allow delaying of response messages") {
self->send(foo, get_atom_v, 3.14, 3.14); auto adder_hdl = sys.spawn(adder);
int i = 0; std::map<std::string, testee_actor> impls;
self->receive_for(i, 2)([](int x) { CAF_CHECK_EQUAL(x, 1 * 2); }, impls["with a value or an error"] = sys.spawn(requester_v1, adder_hdl);
[](double x, double y) { impls["with an expected<T>"] = sys.spawn(requester_v2, adder_hdl);
CAF_CHECK_EQUAL(x, 3.14 * 2); for (auto& [desc, hdl] : impls) {
CAF_CHECK_EQUAL(y, 3.14 * 2); GIVEN("a dispatcher that calls deliver " << desc << " on its promise") {
WHEN("sending a request with two integers to the dispatcher") {
inject((int, int), from(self).to(hdl).with(3, 4));
THEN("clients receive the response from the dispatcher") {
expect((int, int), from(hdl).to(adder_hdl).with(3, 4));
expect((int), from(adder_hdl).to(hdl).with(7));
expect((int), from(hdl).to(self).with(7));
}
}
WHEN("sending ok_atom to the dispatcher synchronously") {
auto res = self->request(hdl, infinite, ok_atom_v);
auto fetch_result = [&] {
message result;
res.receive([] {}, // void result
[&](const error& reason) {
result = make_message(reason);
}); });
return result;
};
THEN("clients receive an empty response from the dispatcher") {
expect((ok_atom), from(self).to(hdl));
expect((ok_atom), from(hdl).to(adder_hdl));
expect((void), from(adder_hdl).to(hdl));
CHECK(fetch_result().empty());
}
}
WHEN("sending ok_atom to the dispatcher asynchronously") {
THEN("clients receive no response from the dispatcher") {
inject((ok_atom), from(self).to(hdl).with(ok_atom_v));
expect((ok_atom), from(hdl).to(adder_hdl));
expect((void), from(adder_hdl).to(hdl));
CHECK(self->mailbox().empty());
}
}
}
}
} }
CAF_TEST(delegating_promises) { SCENARIO("response promises allow delegation") {
using task = std::pair<typed_response_promise<int>, int>; GIVEN("a dispatcher that calls delegate on its promise") {
struct state { auto adder_hdl = sys.spawn(adder);
std::vector<task> tasks; auto hdl = sys.spawn(delegator, adder_hdl);
}; WHEN("sending a request to the dispatcher") {
using bar_actor = typed_actor<replies_to<int>::with<int>, reacts_to<ok_atom>>; inject((int, int), from(self).to(hdl).with(3, 4));
auto bar_fun = [](bar_actor::stateful_pointer<state> self, THEN("clients receive the response from the adder") {
promise_actor worker) -> bar_actor::behavior_type { expect((int, int), from(self).to(adder_hdl).with(3, 4));
return { expect((int), from(adder_hdl).to(self).with(7));
[=](int x) -> typed_response_promise<int> { }
auto& tasks = self->state.tasks; }
tasks.emplace_back(self->make_response_promise<int>(), x);
self->send(self, ok_atom_v);
return tasks.back().first;
},
[=](ok_atom) {
auto& tasks = self->state.tasks;
if (!tasks.empty()) {
auto& task = tasks.back();
task.first.delegate(worker, task.second);
tasks.pop_back();
} }
},
};
};
auto f = make_function_view(system.spawn(bar_fun, foo));
CAF_CHECK_EQUAL(f(42), 84);
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
...@@ -69,8 +69,8 @@ CAF_IO_EXPORT bool from_string(string_view, message_type&); ...@@ -69,8 +69,8 @@ CAF_IO_EXPORT bool from_string(string_view, message_type&);
CAF_IO_EXPORT bool from_integer(std::underlying_type_t<message_type>, CAF_IO_EXPORT bool from_integer(std::underlying_type_t<message_type>,
message_type&); message_type&);
template <class Inssector> template <class Inspector>
bool inspect(Inssector& f, message_type& x) { bool inspect(Inspector& f, message_type& x) {
return default_enum_inspect(f, x); return default_enum_inspect(f, x);
} }
......
...@@ -391,7 +391,7 @@ strong_actor_ptr middleman::remote_lookup(std::string name, ...@@ -391,7 +391,7 @@ strong_actor_ptr middleman::remote_lookup(std::string name,
make_message(registry_lookup_atom_v, std::move(name))); make_message(registry_lookup_atom_v, std::move(name)));
self->receive( self->receive(
[&](strong_actor_ptr& addr) { result = std::move(addr); }, [&](strong_actor_ptr& addr) { result = std::move(addr); },
others >> [](message& msg) -> skippable_result { others >> []([[maybe_unused]] message& msg) -> skippable_result {
CAF_LOG_ERROR( CAF_LOG_ERROR(
"middleman received unexpected remote_lookup result:" << msg); "middleman received unexpected remote_lookup result:" << msg);
return message{}; return message{};
......
...@@ -250,14 +250,15 @@ messaging interface for a simple calculator. ...@@ -250,14 +250,15 @@ messaging interface for a simple calculator.
.. literalinclude:: /examples/message_passing/calculator.cpp .. literalinclude:: /examples/message_passing/calculator.cpp
:language: C++ :language: C++
:lines: 17-18 :start-after: --(rst-calculator-actor-begin)--
:end-before: --(rst-calculator-actor-end)--
It is not required to create a type alias such as ``calculator_actor``, It is not required to create a type alias such as ``calculator_actor``,
but it makes dealing with statically typed actors much easier. Also, a central but it makes dealing with statically typed actors much easier. Also, a central
alias definition eases refactoring later on. alias definition eases refactoring later on.
Interfaces have set semantics. This means the following two type aliases Interfaces have set semantics. This means the following two type aliases
``i1`` and ``i2`` are equal: ``i1`` and ``i2`` are considered equal by CAF:
.. code-block:: C++ .. code-block:: C++
...@@ -300,13 +301,15 @@ parameter. For example, the following functions and classes represent actors. ...@@ -300,13 +301,15 @@ parameter. For example, the following functions and classes represent actors.
.. literalinclude:: /examples/message_passing/calculator.cpp .. literalinclude:: /examples/message_passing/calculator.cpp
:language: C++ :language: C++
:lines: 21-26 :start-after: --(rst-prototypes-begin)--
:end-before: --(rst-prototypes-end)--
Spawning an actor for each implementation is illustrated below. Spawning an actor for each implementation is illustrated below.
.. literalinclude:: /examples/message_passing/calculator.cpp .. literalinclude:: /examples/message_passing/calculator.cpp
:language: C++ :language: C++
:lines: 123-128 :start-after: --(rst-spawn-begin)--
:end-before: --(rst-spawn-end)--
Additional arguments to ``spawn`` are passed to the constructor of a Additional arguments to ``spawn`` are passed to the constructor of a
class or used as additional function arguments, respectively. In the example class or used as additional function arguments, respectively. In the example
...@@ -344,7 +347,8 @@ dynamically typed). ...@@ -344,7 +347,8 @@ dynamically typed).
.. literalinclude:: /examples/message_passing/calculator.cpp .. literalinclude:: /examples/message_passing/calculator.cpp
:language: C++ :language: C++
:lines: 28-56 :start-after: --(rst-function-based-begin)--
:end-before: --(rst-function-based-end)--
.. _class-based: .. _class-based:
...@@ -365,6 +369,14 @@ simple management of state via member variables. However, composing states via ...@@ -365,6 +369,14 @@ simple management of state via member variables. However, composing states via
inheritance can get quite tedious. For dynamically typed actors, composing inheritance can get quite tedious. For dynamically typed actors, composing
states is particularly hard, because the compiler cannot provide much help. states is particularly hard, because the compiler cannot provide much help.
The following three classes implement the prototypes shown in spawn_ by
delegating to the function-based implementations we have seen before:
.. literalinclude:: /examples/message_passing/calculator.cpp
:language: C++
:start-after: --(rst-class-based-begin)--
:end-before: --(rst-class-based-end)--
.. _stateful-actor: .. _stateful-actor:
Stateful Actors Stateful Actors
...@@ -405,7 +417,8 @@ printing a custom string on exit. ...@@ -405,7 +417,8 @@ printing a custom string on exit.
.. literalinclude:: /examples/broker/simple_broker.cpp .. literalinclude:: /examples/broker/simple_broker.cpp
:language: C++ :language: C++
:lines: 42-47 :start-after: --(rst-attach-begin)--
:end-before: --(rst-attach-end)--
It is possible to attach code to remote actors. However, the cleanup code will It is possible to attach code to remote actors. However, the cleanup code will
run on the local machine. run on the local machine.
...@@ -461,7 +474,7 @@ before the optional timeout, as shown in the example below. ...@@ -461,7 +474,7 @@ before the optional timeout, as shown in the example below.
[&](const exit_msg& x) { [&](const exit_msg& x) {
// ... // ...
}, },
others >> [](message_view& x) -> result<message> { others >> [](message& x) -> skippable_result {
// report unexpected message back to client // report unexpected message back to client
return sec::unexpected_message; return sec::unexpected_message;
} }
......
...@@ -135,7 +135,8 @@ adds three options to the ``global`` category. ...@@ -135,7 +135,8 @@ adds three options to the ``global`` category.
.. literalinclude:: /examples/remoting/distributed_calculator.cpp .. literalinclude:: /examples/remoting/distributed_calculator.cpp
:language: C++ :language: C++
:lines: 206-218 :begin-after: --(rst-config-begin)--
:end-before: --(rst-config-end)--
We create a new ``global`` category in ``custom_options_``. Each following call We create a new ``global`` category in ``custom_options_``. Each following call
to ``add`` then appends individual options to the category. The first argument to ``add`` then appends individual options to the category. The first argument
......
...@@ -5,9 +5,7 @@ Errors ...@@ -5,9 +5,7 @@ Errors
Errors in CAF have a code and a category, similar to ``std::error_code`` and Errors in CAF have a code and a category, similar to ``std::error_code`` and
``std::error_condition``. Unlike its counterparts from the C++ standard library, ``std::error_condition``. Unlike its counterparts from the C++ standard library,
``error`` is plattform-neutral and serializable. Instead of using category ``error`` is plattform-neutral and serializable.
singletons, CAF stores categories as atoms (see :ref:`atom`). Errors can also
include a message to provide additional context information.
Class Interface Class Interface
--------------- ---------------
...@@ -15,19 +13,17 @@ Class Interface ...@@ -15,19 +13,17 @@ Class Interface
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| **Constructors** | | | **Constructors** | |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| ``(Enum x)`` | Construct error by calling ``make_error(x)`` | | ``(Enum code)`` | Constructs an error with given error code. |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| ``(uint8_t x, atom_value y)`` | Construct error with code ``x`` and category ``y`` | | ``(Enum code, messag context)`` | Constructs an error with given error code and additional context. |
+-----------------------------------------+--------------------------------------------------------------------+
| ``(uint8_t x, atom_value y, message z)``| Construct error with code ``x``, category ``y``, and context ``z`` |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| | | | | |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| **Observers** | | | **Observers** | |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| ``uint8_t code()`` | Returns the error code | | ``uint8_t code()`` | Returns the error code as 8-bit integer. |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| ``atom_value category()`` | Returns the error category | | ``type_id_t category()`` | Returns the type ID of the Enum type used to construct this error. |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
| ``message context()`` | Returns additional context information | | ``message context()`` | Returns additional context information |
+-----------------------------------------+--------------------------------------------------------------------+ +-----------------------------------------+--------------------------------------------------------------------+
...@@ -39,43 +35,52 @@ Class Interface ...@@ -39,43 +35,52 @@ Class Interface
Add Custom Error Categories Add Custom Error Categories
--------------------------- ---------------------------
Adding custom error categories requires three steps: (1) declare an enum class Adding custom error categories requires these steps:
of type ``uint8_t`` with the first value starting at 1, (2) specialize
``error_category`` to give your type a custom ID (value 0-99 are * Declare an enum class of type ``uint8_t`` with error codes starting at 1. CAF
reserved by CAF), and (3) add your error category to the actor system config. always interprets the value 0 as *no error*.
The following example adds custom error codes for math errors.
* Assign a type ID to your enum type.
* Specialize ``caf::is_error_code_enum`` for your enum type. For this step, CAF
offers the macro ``CAF_ERROR_CODE_ENUM`` to generate the boilerplate code
necessary.
The following example illustrates all these steps for a custom error code enum
called ``math_error``.
.. literalinclude:: /examples/message_passing/divider.cpp .. literalinclude:: /examples/message_passing/divider.cpp
:language: C++ :language: C++
:lines: 17-47 :start-after: --(rst-math-error-begin)--
:end-before: --(rst-math-error-end)--
.. _sec: .. _sec:
System Error Codes Default Error Codes
------------------ -------------------
System Error Codes (SECs) use the error category ``"system"``. They The enum type ``sec`` (for System Error Code) provides many error codes for
represent errors in the actor system or one of its modules and are defined as common failures in actor systems:
follows.
.. literalinclude:: /libcaf_core/caf/sec.hpp .. literalinclude:: /libcaf_core/caf/sec.hpp
:language: C++ :language: C++
:lines: 32-117 :start-after: --(rst-sec-begin)--
:end-before: --(rst-sec-end)--
.. _exit-reason: .. _exit-reason:
Default Exit Reasons Default Exit Reasons
-------------------- --------------------
CAF uses the error category ``"exit"`` for default exit reasons. These errors A special kind of error codes are exit reasons of actors. These errors are
are usually fail states set by the actor system itself. The two exceptions are usually fail states set by the actor system itself. The two exceptions are
``exit_reason::user_shutdown`` and ``exit_reason::kill``. The former is used in ``exit_reason::user_shutdown`` and ``exit_reason::kill``. The former is used in
CAF to signalize orderly, user-requested shutdown and can be used by programmers CAF to signalize orderly, user-requested shutdown and can be used by programmers
in the same way. The latter terminates an actor unconditionally when used in in the same way. The latter terminates an actor unconditionally when used in
``send_exit``, even if the default handler for exit messages (see ``send_exit``, even for actors that override the default handler (see
:ref:`exit-message`) is overridden. :ref:`exit-message`).
.. literalinclude:: /libcaf_core/caf/exit_reason.hpp .. literalinclude:: /libcaf_core/caf/exit_reason.hpp
:language: C++ :language: C++
:lines: 29-49 :start-after: --(rst-exit-reason-begin)--
:end-before: --(rst-exit-reason-end)--
...@@ -36,19 +36,5 @@ generation, CAF also offers ``message_builder``: ...@@ -36,19 +36,5 @@ generation, CAF also offers ``message_builder``:
What Debugging Tools Exist? What Debugging Tools Exist?
--------------------------- ---------------------------
The ``scripts/`` and ``tools/`` directories contain some useful tools to aid in The ``scripts/`` directory contains some useful tools to aid in analyzing CAF
development and debugging. log output.
``scripts/atom.py`` converts integer atom values back into strings.
``scripts/demystify.py`` replaces cryptic ``typed_mpi<...>``
templates with ``replies_to<...>::with<...>`` and
``atom_constant<...>`` with a human-readable representation of the
actual atom.
``scripts/caf-prof`` is an R script that generates plots from CAF
profiler output.
``caf-vec`` is a (highly) experimental tool that annotates CAF logs
with vector timestamps. It gives you happens-before relations and a nice
visualization via `ShiViz <https://bestchai.bitbucket.io/shiviz/>`_.
...@@ -10,10 +10,8 @@ name, joining, and leaving. ...@@ -10,10 +10,8 @@ name, joining, and leaving.
.. code-block:: C++ .. code-block:: C++
std::string module = "local"; auto expected_grp = system.groups().get("local", "foo");
std::string id = "foo"; if (!expected_grp) {
auto expected_grp = system.groups().get(module, id);
if (! expected_grp) {
std::cerr << "*** cannot load group: " << to_string(expected_grp.error()) std::cerr << "*** cannot load group: " << to_string(expected_grp.error())
<< std::endl; << std::endl;
return; return;
......
...@@ -85,10 +85,9 @@ This policy models split/join or scatter/gather work flows, where a work item ...@@ -85,10 +85,9 @@ This policy models split/join or scatter/gather work flows, where a work item
is split into as many tasks as workers are available and then the individuals is split into as many tasks as workers are available and then the individuals
results are joined together before sending the full result back to the client. results are joined together before sending the full result back to the client.
The join function is responsible for ``glueing'' all result messages together The join function is responsible for "glueing" all result messages together to
to create a single result. The function is called with the result object create a single result. The function is called with the result object (initialed
(initialed using ``init``) and the current result messages from a using ``init``) and the current result messages from a worker.
worker.
The first argument of a split function is a mapping from actors (workers) to The first argument of a split function is a mapping from actors (workers) to
tasks (messages). The second argument is the input message. The default split tasks (messages). The second argument is the input message. The default split
......
...@@ -17,9 +17,9 @@ change its behavior when not receiving message after a certain amount of time. ...@@ -17,9 +17,9 @@ change its behavior when not receiving message after a certain amount of time.
.. code-block:: C++ .. code-block:: C++
message_handler x1{ message_handler x1{
[](int i) { /*...*/ }, [](int32_t i) { /*...*/ },
[](double db) { /*...*/ }, [](double db) { /*...*/ },
[](int a, int b, int c) { /*...*/ } [](int32_t a, int32_t b, int32_t c) { /*...*/ }
}; };
In our first example, ``x1`` models a behavior accepting messages that consist In our first example, ``x1`` models a behavior accepting messages that consist
...@@ -70,48 +70,33 @@ introduced an approach to use non-numerical constants, so-called ...@@ -70,48 +70,33 @@ introduced an approach to use non-numerical constants, so-called
*atoms*, which have an unambiguous, special-purpose type and do not have *atoms*, which have an unambiguous, special-purpose type and do not have
the runtime overhead of string constants. the runtime overhead of string constants.
Atoms in CAF are mapped to integer values at compile time. This mapping is Atoms in CAF are tag types, i.e., usually defined as en empty ``struct``. These
guaranteed to be collision-free and invertible, but limits atom literals to ten types carry no data on their own and only exist to annotate messages. For
characters and prohibits special characters. Legal characters are example, we could create the two tag types ``add_atom`` and ``multiply_atom``
``_0-9A-Za-z`` and the whitespace character. Atoms are created using for implementing a simple math actor like this:
the ``constexpr`` function ``atom``, as the following example
illustrates.
.. code-block:: C++ .. code-block:: C++
atom_value a1 = atom("add"); CAF_BEGIN_TYPE_ID_BLOCK(my_project, caf::first_custom_type_id)
atom_value a2 = atom("multiply");
**Warning**: The compiler cannot enforce the restrictions at compile time, CAF_ADD_ATOM(my_project, add_atom)
except for a length check. The assertion ``atom("!?") != atom("?!")`` CAF_ADD_ATOM(my_project, multiply_atom)
is not true, because each invalid character translates to a whitespace
character.
While the ``atom_value`` is computed at compile time, it is not CAF_END_TYPE_ID_BLOCK(my_project)
uniquely typed and thus cannot be used in the signature of a callback. To
accomplish this, CAF offers compile-time *atom constants*.
.. code-block:: C++
using add_atom = atom_constant<atom("add")>;
using multiply_atom = atom_constant<atom("multiply")>;
Using these constants, we can now define message passing interfaces in a
convenient way:
.. code-block:: C++
behavior do_math{ behavior do_math{
[](add_atom, int a, int b) { [](add_atom, int32_t a, int32_t b) {
return a + b; return a + b;
}, },
[](multiply_atom, int a, int b) { [](multiply_atom, int32_t a, int32_t b) {
return a * b; return a * b;
} }
}; };
// caller side: send(math_actor, add_atom::value, 1, 2)
Atom constants define a static member ``value``. Please note that this // caller side: send(math_actor, add_atom_v, int32_t{1}, int32_t{2})
static ``value`` member does *not* have the type
``atom_value``, unlike ``std::integral_constant`` for example. The macro ``CAF_ADD_ATOM`` defined an empty ``struct`` with the given name as
well as a ``constexpr`` variable for conveniently creating a value of that type
that uses the type name plus a ``_v`` suffix. In the example above,
``atom_value`` is the type name and ``atom_value_v`` is the constant.
...@@ -3,18 +3,12 @@ ...@@ -3,18 +3,12 @@
Message Passing Message Passing
=============== ===============
Message passing in CAF is always asynchronous. Further, CAF neither guarantees
message delivery nor message ordering in a distributed setting. CAF uses TCP
per default, but also enables nodes to send messages to other nodes without
having a direct connection. In this case, messages are forwarded by
intermediate nodes and can get lost if one of the forwarding nodes fails.
Likewise, forwarding paths can change dynamically and thus cause messages to
arrive out of order.
The messaging layer of CAF has three primitives for sending messages: ``send``, The messaging layer of CAF has three primitives for sending messages: ``send``,
``request``, and ``delegate``. The former simply enqueues a message to the ``request``, and ``delegate``. The former simply enqueues a message to the
mailbox the receiver. The latter two are discussed in more detail in mailbox the receiver. The latter two are discussed in more detail in
:ref:`request` and :ref:`delegate`. :ref:`request` and :ref:`delegate`. Before we go into the details of the message
passing API itself, we first discuss the building blocks that enable message
passing in the first place.
.. _mailbox-element: .. _mailbox-element:
...@@ -37,17 +31,12 @@ request. The ``stages`` vector stores the path of the message. Response ...@@ -37,17 +31,12 @@ request. The ``stages`` vector stores the path of the message. Response
messages, i.e., the returned values of a message handler, are sent to messages, i.e., the returned values of a message handler, are sent to
``stages.back()`` after calling ``stages.pop_back()``. This allows CAF to build ``stages.back()`` after calling ``stages.pop_back()``. This allows CAF to build
pipelines of arbitrary size. If no more stage is left, the response reaches the pipelines of arbitrary size. If no more stage is left, the response reaches the
sender. Finally, ``content()`` grants access to the type-erased tuple storing sender. Finally, ``payload`` is the actual content of the message.
the message itself.
Mailbox elements are created by CAF automatically and are usually invisible to Mailbox elements are created by CAF automatically and are usually invisible to
the programmer. However, understanding how messages are processed internally the programmer. However, understanding how messages are processed internally
helps understanding the behavior of the message passing layer. helps understanding the behavior of the message passing layer.
It is worth mentioning that CAF usually wraps the mailbox element and its
content into a single object in order to reduce the number of memory
allocations.
.. _copy-on-write: .. _copy-on-write:
Copy on Write Copy on Write
...@@ -65,19 +54,15 @@ Requirements for Message Types ...@@ -65,19 +54,15 @@ Requirements for Message Types
Message types in CAF must meet the following requirements: Message types in CAF must meet the following requirements:
1. Serializable or inspectable (see :ref:`type-inspection`) 1. Inspectable (see :ref:`type-inspection`)
2. Default constructible 2. Default constructible
3. Copy constructible 3. Copy constructible
A type is serializable if it provides free function A type ``T`` is inspectable if it provides a free function
``serialize(Serializer&, T&)`` or ``inspect(Inspector&, T&)`` or specializes ``inspector_access``.
``serialize(Serializer&, T&, const unsigned int)``. Accordingly, a type is
inspectable if it provides a free function ``inspect(Inspector&, T&)``.
Requirement 2 is a consequence of requirement 1, because CAF needs to be able to Requirement 2 is a consequence of requirement 1, because CAF needs to be able to
create an object of a type before it can call ``serialize`` or ``inspect`` on create an object for ``T`` when deserializing incoming messages. Requirement 3
it. Requirement 3 allows CAF to implement Copy on Write (see allows CAF to implement Copy on Write (see :ref:`copy-on-write`).
:ref:`copy-on-write`).
.. _special-handler: .. _special-handler:
...@@ -158,12 +143,12 @@ Requests ...@@ -158,12 +143,12 @@ Requests
-------- --------
A main feature of CAF is its ability to couple input and output types via the A main feature of CAF is its ability to couple input and output types via the
type system. For example, a ``typed_actor<replies_to<int>::with<int>>`` type system. For example, a ``typed_actor<result<int32_t>(int32_t)>``
essentially behaves like a function. It receives a single ``int`` as essentially behaves like a function. It receives a single ``int32_t`` as input
input and responds with another ``int``. CAF embraces this functional and responds with another ``int32_t``. CAF embraces this functional take on
take on actors by simply creating response messages from the result of message actors by simply creating response messages from the result of message handlers.
handlers. This allows CAF to match *request* to *response* messages This allows CAF to match *request* to *response* messages and to provide a
and to provide a convenient API for this style of communication. convenient API for this style of communication.
.. _handling-response: .. _handling-response:
...@@ -181,42 +166,31 @@ responses arrive and handles requests in LIFO order. Blocking actors always use ...@@ -181,42 +166,31 @@ responses arrive and handles requests in LIFO order. Blocking actors always use
Actors receive a ``sec::request_timeout`` (see :ref:`sec`) error message (see Actors receive a ``sec::request_timeout`` (see :ref:`sec`) error message (see
:ref:`error-message`) if a timeout occurs. Users can set the timeout to :ref:`error-message`) if a timeout occurs. Users can set the timeout to
``infinite`` for unbound operations. This is only recommended if the receiver is ``infinite`` for unbound operations. This is only recommended if the receiver is
running locally. known to run locally.
In our following example, we use the simple cell actors shown below as
communication endpoints.
.. literalinclude:: /examples/message_passing/request.cpp
:language: C++
:lines: 20-37
The first part of the example illustrates how event-based actors can use either In our following example, we use the simple cell actor shown below as
``then`` or ``await``. communication endpoint.
.. literalinclude:: /examples/message_passing/request.cpp .. literalinclude:: /examples/message_passing/request.cpp
:language: C++ :language: C++
:lines: 39-51 :start-after: --(rst-cell-begin)--
:end-before: --(rst-cell-end)--
The second half of the example shows a blocking actor making use of To showcase the slight differences in API and processing order, we implement
``receive``. Note that blocking actors have no special-purpose handler three testee actors that all operate on a list of cell actors.
for error messages and therefore are required to pass a callback for error
messages when handling response messages.
.. literalinclude:: /examples/message_passing/request.cpp .. literalinclude:: /examples/message_passing/request.cpp
:language: C++ :language: C++
:lines: 53-64 :start-after: --(rst-testees-begin)--
:end-before: --(rst-testees-end)--
We spawn five cells and assign the values 0, 1, 4, 9, and 16.
.. literalinclude:: /examples/message_passing/request.cpp Our ``caf_main`` for the examples spawns five cells and assign the initial
:language: C++ values 0, 1, 4, 9, and 16. Then it spawns one instance for each of our testee
:lines: 67-69 implementations and we can observe the different outputs.
When passing the ``cells`` vector to our three different Our ``waiting_testee`` actor will always print:
implementations, we observe three outputs. Our ``waiting_testee`` actor
will always print:
.. :: .. code-block:: none
cell #9 -> 16 cell #9 -> 16
cell #8 -> 9 cell #8 -> 9
...@@ -225,15 +199,17 @@ will always print: ...@@ -225,15 +199,17 @@ will always print:
cell #5 -> 0 cell #5 -> 0
This is because ``await`` puts the one-shots handlers onto a stack and This is because ``await`` puts the one-shots handlers onto a stack and
enforces LIFO order by re-ordering incoming response messages. enforces LIFO order by re-ordering incoming response messages as necessary.
The ``multiplexed_testee`` implementation does not print its results in The ``multiplexed_testee`` implementation does not print its results in
a predicable order. Response messages arrive in arbitrary order and are handled a predicable order. Response messages arrive in arbitrary order and are handled
immediately. immediately.
Finally, the ``blocking_testee`` implementation will always print: Finally, the ``blocking_testee`` has a deterministic output again. This is
because it blocks on each request until receiving the result before making the
next request.
.. :: .. code-block:: none
cell #5 -> 0 cell #5 -> 0
cell #6 -> 1 cell #6 -> 1
...@@ -254,17 +230,17 @@ manager fans-out the request to all of its workers and then collects the ...@@ -254,17 +230,17 @@ manager fans-out the request to all of its workers and then collects the
results. The function ``fan_out_request`` combined with the merge policy results. The function ``fan_out_request`` combined with the merge policy
``select_all`` streamlines this exact use case. ``select_all`` streamlines this exact use case.
In the following snippet, we have a matrix actor (``self``) that stores In the following snippet, we have a matrix actor ``self`` that stores worker
worker actors for each cell (each simply storing an integer). For computing the actors for each cell (each simply storing an integer). For computing the average
average over a row, we use ``fan_out_request``. The result handler over a row, we use ``fan_out_request``. The result handler passed to ``then``
passed to ``then`` now gets called only once with a ``vector`` now gets called only once with a ``vector`` holding all collected results. Using
holding all collected results. Using a response promise promise_ further a response promise promise_ further allows us to delay responding to the client
allows us to delay responding to the client until we have collected all worker until we have collected all worker results.
results.
.. literalinclude:: /examples/message_passing/fan_out_request.cpp .. literalinclude:: /examples/message_passing/fan_out_request.cpp
:language: C++ :language: C++
:lines: 86-98 :start-after: --(rst-fan-out-begin)--
:end-before: --(rst-fan-out-end)--
The policy ``select_any`` models a second common use case: sending a The policy ``select_any`` models a second common use case: sending a
request to multiple receivers but only caring for the first arriving response. request to multiple receivers but only caring for the first arriving response.
...@@ -286,26 +262,32 @@ by zero. This examples uses a custom error category (see :ref:`error`). ...@@ -286,26 +262,32 @@ by zero. This examples uses a custom error category (see :ref:`error`).
.. literalinclude:: /examples/message_passing/divider.cpp .. literalinclude:: /examples/message_passing/divider.cpp
:language: C++ :language: C++
:lines: 17-19,49-59 :start-after: --(rst-divider-begin)--
:end-before: --(rst-divider-end)--
When sending requests to the divider, we use a custom error handlers to report When sending requests to the divider, we can use a custom error handlers to
errors to the user. report errors to the user like this:
.. literalinclude:: /examples/message_passing/divider.cpp .. literalinclude:: /examples/message_passing/divider.cpp
:language: C++ :language: C++
:lines: 70-76 :start-after: --(rst-request-begin)--
:end-before: --(rst-request-end)--
.. _delay-message: .. _delay-message:
Delaying Messages Delaying Messages
----------------- -----------------
Messages can be delayed by using the function ``delayed_send``, as Messages can be delayed by using the function ``delayed_send``, as illustrated
illustrated in the following time-based loop example. in the following time-based loop example.
.. literalinclude:: /examples/message_passing/dancing_kirby.cpp .. literalinclude:: /examples/message_passing/dancing_kirby.cpp
:language: C++ :language: C++
:lines: 58-75 :start-after: --(rst-delayed-send-begin)--
:end-before: --(rst-delayed-send-end)--
Delayed send schedules messages based on relative timeouts. For absolute
timeouts, use ``scheduled_send`` instead.
.. _delegate: .. _delegate:
...@@ -318,7 +300,7 @@ by returning a value from its message handler---and the original sender of the ...@@ -318,7 +300,7 @@ by returning a value from its message handler---and the original sender of the
message will receive the response. The following diagram illustrates request message will receive the response. The following diagram illustrates request
delegation from actor B to actor C. delegation from actor B to actor C.
.. :: .. code-block:: none
A B C A B C
| | | | | |
...@@ -330,11 +312,6 @@ delegation from actor B to actor C. ...@@ -330,11 +312,6 @@ delegation from actor B to actor C.
| |<--/ | |<--/
| <-------------(reply)-------------- | | <-------------(reply)-------------- |
| X | X
|---\
| | handle
| | response
|<--/
|
X X
Returning the result of ``delegate(...)`` from a message handler, as Returning the result of ``delegate(...)`` from a message handler, as
...@@ -343,7 +320,8 @@ the compiler to check the result type when using statically typed actors. ...@@ -343,7 +320,8 @@ the compiler to check the result type when using statically typed actors.
.. literalinclude:: /examples/message_passing/delegating.cpp .. literalinclude:: /examples/message_passing/delegating.cpp
:language: C++ :language: C++
:lines: 15-36 :start-after: --(rst-delegate-begin)--
:end-before: --(rst-delegate-end)--
.. _promise: .. _promise:
...@@ -360,14 +338,36 @@ a promise, an actor can fulfill it by calling the member function ...@@ -360,14 +338,36 @@ a promise, an actor can fulfill it by calling the member function
.. literalinclude:: /examples/message_passing/promises.cpp .. literalinclude:: /examples/message_passing/promises.cpp
:language: C++ :language: C++
:lines: 18-43 :start-after: --(rst-promise-begin)--
:end-before: --(rst-promise-end)--
This example is almost identical to the example for delegating messages.
However, there is a big difference in the flow of messages. In our first
version, the worker (C) directly responded to the client (A). This time, the
worker sends the result to the server (B), which then fulfills the promise and
thereby sends the result to the client:
.. code-block:: none
A B C
| | |
| ---(request)---> | |
| | ---(request)---> |
| | |---\
| | | | compute
| | | | result
| | |<--/
| | <----(reply)---- |
| | X
| <----(reply)---- |
| X
X
Message Priorities Message Priorities
------------------ ------------------
By default, all messages have the default priority, i.e., By default, all messages have the default priority, i.e.,
``message_priority::normal``. Actors can send urgent messages by ``message_priority::normal``. Actors can send urgent messages by setting the
setting the priority explicitly: priority explicitly: ``send<message_priority::high>(dst, ...)``. Urgent messages
``send<message_priority::high>(dst,...)``. Urgent messages are put into are put into a different queue of the receiver's mailbox. Hence, long wait
a different queue of the receiver's mailbox. Hence, long wait delays can be delays can be avoided for urgent communication.
avoided for urgent communication.
.. _message:
Type-Erased Tuples, Messages and Message Views
==============================================
Messages in CAF are stored in type-erased tuples. The actual message type
itself is usually hidden, as actors use pattern matching to decompose messages
automatically. However, the classes ``message`` and
``message_builder`` allow more advanced use cases than only sending
data from one actor to another.
The interface ``type_erased_tuple`` encapsulates access to arbitrary
data. This data can be stored on the heap or on the stack. A
``message`` is a type-erased tuple that is always heap-allocated and
uses copy-on-write semantics. When dealing with "plain" type-erased tuples,
users are required to check if a tuple is referenced by others via
``type_erased_tuple::shared`` before modifying its content.
The convenience class ``message_view`` holds a reference to either a
stack-located ``type_erased_tuple`` or a ``message``. The
content of the data can be access via ``message_view::content`` in both
cases, which returns a ``type_erased_tuple&``. The content of the view
can be forced into a message object by calling
``message_view::move_content_to_message``. This member function either
returns the stored message object or moves the content of a stack-allocated
tuple into a new message.
RTTI and Type Numbers
---------------------
All builtin types in CAF have a non-zero 6-bit *type number*. All
user-defined types are mapped to 0. When querying the run-time type information
(RTTI) for individual message or tuple elements, CAF returns a pair consisting
of an integer and a pointer to ``std::type_info``. The first value is
the 6-bit type number. If the type number is non-zero, the second value is a
pointer to the C++ type info, otherwise the second value is null. Additionally,
CAF generates 32 bit *type tokens*. These tokens are *type hints*
that summarizes all types in a type-erased tuple. Two type-erased tuples are of
different type if they have different type tokens (the reverse is not true).
Class ``type_erased_tuple``
---------------------------
**Note**: Calling modifiers on a shared type-erased tuple is undefined
behavior.
+----------------------------------------+------------------------------------------------------------+
| **Observers** | |
+----------------------------------------+------------------------------------------------------------+
| ``bool empty()`` | Returns whether this message is empty. |
+----------------------------------------+------------------------------------------------------------+
| ``size_t size()`` | Returns the size of this message. |
+----------------------------------------+------------------------------------------------------------+
| ``rtti_pair type(size_t pos)`` | Returns run-time type information for the nth element. |
+----------------------------------------+------------------------------------------------------------+
| ``error save(serializer& x)`` | Writes the tuple to ``x``. |
+----------------------------------------+------------------------------------------------------------+
| ``error save(size_t n, serializer& x)``| Writes the nth element to ``x``. |
+----------------------------------------+------------------------------------------------------------+
| ``const void* get(size_t n)`` | Returns a const pointer to the nth element. |
+----------------------------------------+------------------------------------------------------------+
| ``std::string stringify()`` | Returns a string representation of the tuple. |
+----------------------------------------+------------------------------------------------------------+
| ``std::string stringify(size_t n)`` | Returns a string representation of the nth element. |
+----------------------------------------+------------------------------------------------------------+
| ``bool matches(size_t n, rtti_pair)`` | Checks whether the nth element has given type. |
+----------------------------------------+------------------------------------------------------------+
| ``bool shared()`` | Checks whether more than one reference to the data exists. |
+----------------------------------------+------------------------------------------------------------+
| ``bool match_element<T>(size_t n)`` | Checks whether element ``n`` has type ``T``. |
+----------------------------------------+------------------------------------------------------------+
| ``bool match_elements<Ts...>()`` | Checks whether this message has the types ``Ts...``. |
+----------------------------------------+------------------------------------------------------------+
| ``const T& get_as<T>(size_t n)`` | Returns a const reference to the nth element. |
+----------------------------------------+------------------------------------------------------------+
| | |
+----------------------------------------+------------------------------------------------------------+
| **Modifiers** | |
+----------------------------------------+------------------------------------------------------------+
| ``void* get_mutable(size_t n)`` | Returns a mutable pointer to the nth element. |
+----------------------------------------+------------------------------------------------------------+
| ``T& get_mutable_as<T>(size_t n)`` | Returns a mutable reference to the nth element. |
+----------------------------------------+------------------------------------------------------------+
| ``void load(deserializer& x)`` | Reads the tuple from ``x``. |
+----------------------------------------+------------------------------------------------------------+
Class ``message``
-----------------
The class ``message`` includes all member functions of
``type_erased_tuple``. However, calling modifiers is always guaranteed
to be safe. A ``message`` automatically detaches its content by copying
it from the shared data on mutable access. The class further adds the following
member functions over ``type_erased_tuple``. Note that
``apply`` only detaches the content if a callback takes mutable
references as arguments.
+-----------------------------------------------+------------------------------------------------------------+
| **Observers** | |
+-----------------------------------------------+------------------------------------------------------------+
| ``message drop(size_t n)`` | Creates a new message with all but the first ``n`` values. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message drop_right(size_t n)`` | Creates a new message with all but the last ``n`` values. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message take(size_t n)`` | Creates a new message from the first ``n`` values. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message take_right(size_t n)`` | Creates a new message from the last ``n`` values. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message slice(size_t p, size_t n)`` | Creates a new message from ``[p, p + n)``. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message extract(message_handler)`` | See extract_. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message extract_opts(...)`` | See extract-opts_. |
+-----------------------------------------------+------------------------------------------------------------+
| | |
+-----------------------------------------------+------------------------------------------------------------+
| **Modifiers** | |
+-----------------------------------------------+------------------------------------------------------------+
| ``optional<message> apply(message_handler f)``| Returns ``f(*this)``. |
+-----------------------------------------------+------------------------------------------------------------+
| | |
+-----------------------------------------------+------------------------------------------------------------+
| **Operators** | |
+-----------------------------------------------+------------------------------------------------------------+
| ``message operator+(message x, message y)`` | Concatenates ``x`` and ``y``. |
+-----------------------------------------------+------------------------------------------------------------+
| ``message& operator+=(message& x, message y)``| Concatenates ``x`` and ``y``. |
+-----------------------------------------------+------------------------------------------------------------+
Class ``message_builder``
-------------------------
+---------------------------------------------------+-----------------------------------------------------+
| **Constructors** | |
+---------------------------------------------------+-----------------------------------------------------+
| ``(void)`` | Creates an empty message builder. |
+---------------------------------------------------+-----------------------------------------------------+
| ``(Iter first, Iter last)`` | Adds all elements from range ``[first, last)``. |
+---------------------------------------------------+-----------------------------------------------------+
| | |
+---------------------------------------------------+-----------------------------------------------------+
| **Observers** | |
+---------------------------------------------------+-----------------------------------------------------+
| ``bool empty()`` | Returns whether this message is empty. |
+---------------------------------------------------+-----------------------------------------------------+
| ``size_t size()`` | Returns the size of this message. |
+---------------------------------------------------+-----------------------------------------------------+
| ``message to_message( )`` | Converts the buffer to an actual message object. |
+---------------------------------------------------+-----------------------------------------------------+
| ``append(T val)`` | Adds ``val`` to the buffer. |
+---------------------------------------------------+-----------------------------------------------------+
| ``append(Iter first, Iter last)`` | Adds all elements from range ``[first, last)``. |
+---------------------------------------------------+-----------------------------------------------------+
| ``message extract(message_handler)`` | See extract_. |
+---------------------------------------------------+-----------------------------------------------------+
| ``message extract_opts(...)`` | See extract-opts_. |
+---------------------------------------------------+-----------------------------------------------------+
| | |
+---------------------------------------------------+-----------------------------------------------------+
| **Modifiers** | |
+---------------------------------------------------+-----------------------------------------------------+
| ``optional<message>`` ``apply(message_handler f)``| Returns ``f(*this)``. |
+---------------------------------------------------+-----------------------------------------------------+
| ``message move_to_message()`` | Transfers ownership of its data to the new message. |
+---------------------------------------------------+-----------------------------------------------------+
.. _extract:
Extracting
----------
The member function ``message::extract`` removes matched elements from a
message. x Messages are filtered by repeatedly applying a message handler to the
greatest remaining slice, whereas slices are generated in the sequence
``[0, size)``, ``[0, size-1)``, ``...``, ``[1, size-1)``, ``...``,
``[size-1, size)``. Whenever a slice is matched, it is removed from the message
and the next slice starts at the same index on the reduced message.
For example:
.. code-block:: C++
auto msg = make_message(1, 2.f, 3.f, 4);
// remove float and integer pairs
auto msg2 = msg.extract({
[](float, float) { },
[](int, int) { }
});
assert(msg2 == make_message(1, 4));
Step-by-step explanation:
* Slice 1: ``(1, 2.f, 3.f, 4)``, no match
* Slice 2: ``(1, 2.f, 3.f)``, no match
* Slice 3: ``(1, 2.f)``, no match
* Slice 4: ``(1)``, no match
* Slice 5: ``(2.f, 3.f, 4)``, no match
* Slice 6: ``(2.f, 3.f)``, *match*; new message is ``(1, 4)``
* Slice 7: ``(4)``, no match
Slice 7 is ``(4)``, i.e., does not contain the first element, because
the match on slice 6 occurred at index position 1. The function
``extract`` iterates a message only once, from left to right. The
returned message contains the remaining, i.e., unmatched, elements.
.. _extract-opts:
Extracting Command Line Options
-------------------------------
The class ``message`` also contains a convenience interface to
``extract`` for parsing command line options: the member function
``extract_opts``.
.. code-block:: C++
int main(int argc, char** argv) {
uint16_t port;
string host = "localhost";
auto res = message_builder(argv + 1, argv + argc).extract_opts({
{"port,p", "set port", port},
{"host,H", "set host (default: localhost)", host},
{"verbose,v", "enable verbose mode"}
});
if (! res.error.empty()) {
// read invalid CLI arguments
cerr << res.error << endl;
return 1;
}
if (res.opts.count("help") > 0) {
// CLI arguments contained "-h", "--help", or "-?" (builtin);
cout << res.helptext << endl;
return 0;
}
if (! res.remainder.empty()) {
// res.remainder stors all extra arguments that weren't consumed
}
if (res.opts.count("verbose") > 0) {
// enable verbose mode
}
// ...
}
/*
Output of ./program_name -h:
Allowed options:
-p [--port] arg : set port
-H [--host] arg : set host (default: localhost)
-v [--verbose] : enable verbose mode
*/
Overview Overview
======== ========
Compiling CAF requires CMake and a C++11-compatible compiler. To get and Compiling CAF requires CMake and a recent C++ compiler. To get and compile the
compile the sources on UNIX-like systems, type the following in a terminal: sources on UNIX-like systems, type the following in a terminal:
.. :: .. code-block:: bash
git clone https://github.com/actor-framework/actor-framework git clone https://github.com/actor-framework/actor-framework
cd actor-framework cd actor-framework
./configure ./configure
make make -C build
make install [as root, optional] make -C build install [as root, optional]
We recommended to run the unit tests as well: Running ``configure`` is not a mandatory step. The script merely automates the
CMake setup and makes setting build options slightly more convenient. On
.. :: Windows, use CMake directly to generate an MSVC project file.
make test
If the output indicates an error, please submit a bug report that includes (a)
your compiler version, (b) your OS, and (c) the content of the file
``build/Testing/Temporary/LastTest.log``.
Features Features
-------- --------
...@@ -32,19 +26,13 @@ Features ...@@ -32,19 +26,13 @@ Features
* Thread-mapped actors for soft migration of existing applications * Thread-mapped actors for soft migration of existing applications
* Publish/subscribe group communication * Publish/subscribe group communication
Minimal Compiler Versions
-------------------------
* GCC 4.8
* Clang 3.4
* Visual Studio 2015, Update 3
Supported Operating Systems Supported Operating Systems
--------------------------- ---------------------------
* Linux * Linux
* Mac OS X * Windows
* Windows (static library only) * macOS
* FreeBSD
Hello World Example Hello World Example
------------------- -------------------
......
...@@ -139,6 +139,7 @@ The following example implements two actors, ``ping`` and ...@@ -139,6 +139,7 @@ The following example implements two actors, ``ping`` and
.. literalinclude:: /examples/testing/ping_pong.cpp .. literalinclude:: /examples/testing/ping_pong.cpp
:language: C++ :language: C++
:lines: 12-60 :start-after: --(rst-ping-pong-begin)--
:end-before: --(rst-ping-pong-end)--
...@@ -21,7 +21,6 @@ Contents ...@@ -21,7 +21,6 @@ Contents
ReferenceCounting ReferenceCounting
Error Error
ConfiguringActorApplications ConfiguringActorApplications
Messages
GroupCommunication GroupCommunication
ManagingGroupsOfWorkers ManagingGroupsOfWorkers
Streaming Streaming
......
manual/mailbox_element.png

20 KB | W: | H:

manual/mailbox_element.png

39.8 KB | W: | H:

manual/mailbox_element.png
manual/mailbox_element.png
manual/mailbox_element.png
manual/mailbox_element.png
  • 2-up
  • Swipe
  • Onion skin
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