Commit 899766cd authored by Dominik Charousset's avatar Dominik Charousset

Add test coordinator and per-actor configs

parent fdd24706
...@@ -88,6 +88,7 @@ set (LIBCAF_CORE_SRCS ...@@ -88,6 +88,7 @@ set (LIBCAF_CORE_SRCS
src/splitter.cpp src/splitter.cpp
src/sync_request_bouncer.cpp src/sync_request_bouncer.cpp
src/stringification_inspector.cpp src/stringification_inspector.cpp
src/test_coordinator.cpp
src/timestamp.cpp src/timestamp.cpp
src/try_match.cpp src/try_match.cpp
src/type_erased_value.cpp src/type_erased_value.cpp
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "caf/abstract_actor.hpp" #include "caf/abstract_actor.hpp"
#include "caf/actor_registry.hpp" #include "caf/actor_registry.hpp"
#include "caf/string_algorithms.hpp" #include "caf/string_algorithms.hpp"
#include "caf/named_actor_config.hpp"
#include "caf/scoped_execution_unit.hpp" #include "caf/scoped_execution_unit.hpp"
#include "caf/uniform_type_info_map.hpp" #include "caf/uniform_type_info_map.hpp"
#include "caf/composable_behavior_based_actor.hpp" #include "caf/composable_behavior_based_actor.hpp"
...@@ -163,6 +164,9 @@ public: ...@@ -163,6 +164,9 @@ public:
using module_array = std::array<module_ptr, module::num_ids>; using module_array = std::array<module_ptr, module::num_ids>;
using named_actor_config_map = std::unordered_map<std::string,
named_actor_config>;
/// @warning The system stores a reference to `cfg`, which means the /// @warning The system stores a reference to `cfg`, which means the
/// config object must outlive the actor system. /// config object must outlive the actor system.
explicit actor_system(actor_system_config& cfg); explicit actor_system(actor_system_config& cfg);
...@@ -454,6 +458,11 @@ public: ...@@ -454,6 +458,11 @@ public:
return cfg_; return cfg_;
} }
/// Returns configuration parameters for individual named actors types.
inline const named_actor_config_map& named_actor_configs() const {
return named_actor_configs_;
}
/// @cond PRIVATE /// @cond PRIVATE
/// Increases running-detached-threads-count by one. /// Increases running-detached-threads-count by one.
...@@ -521,6 +530,7 @@ private: ...@@ -521,6 +530,7 @@ private:
std::mutex logger_dtor_mtx_; std::mutex logger_dtor_mtx_;
std::condition_variable logger_dtor_cv_; std::condition_variable logger_dtor_cv_;
volatile bool logger_dtor_done_; volatile bool logger_dtor_done_;
named_actor_config_map named_actor_configs_;
}; };
} // namespace caf } // namespace caf
......
...@@ -92,7 +92,6 @@ ...@@ -92,7 +92,6 @@
#include "caf/composable_behavior.hpp" #include "caf/composable_behavior.hpp"
#include "caf/typed_actor_pointer.hpp" #include "caf/typed_actor_pointer.hpp"
#include "caf/scoped_execution_unit.hpp" #include "caf/scoped_execution_unit.hpp"
#include "caf/typed_continue_helper.hpp"
#include "caf/typed_response_promise.hpp" #include "caf/typed_response_promise.hpp"
#include "caf/typed_event_based_actor.hpp" #include "caf/typed_event_based_actor.hpp"
#include "caf/abstract_composable_behavior.hpp" #include "caf/abstract_composable_behavior.hpp"
...@@ -105,6 +104,7 @@ ...@@ -105,6 +104,7 @@
#include "caf/meta/load_callback.hpp" #include "caf/meta/load_callback.hpp"
#include "caf/meta/omittable_if_empty.hpp" #include "caf/meta/omittable_if_empty.hpp"
#include "caf/scheduler/test_coordinator.hpp"
#include "caf/scheduler/abstract_coordinator.hpp" #include "caf/scheduler/abstract_coordinator.hpp"
/// @author Dominik Charousset <dominik.charousset (at) haw-hamburg.de> /// @author Dominik Charousset <dominik.charousset (at) haw-hamburg.de>
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2015 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* 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. *
******************************************************************************/
#ifndef CAF_NAMED_ACTOR_CONFIG_HPP
#define CAF_NAMED_ACTOR_CONFIG_HPP
#include <cstddef>
#include "caf/atom.hpp"
#include "caf/deep_to_string.hpp"
namespace caf {
/// Stores a flow-control configuration.
struct named_actor_config {
atom_value strategy;
size_t low_watermark;
size_t max_pending;
};
template <class Processor>
void serialize(Processor& proc, named_actor_config& x, const unsigned int) {
proc & x.strategy;
proc & x.low_watermark;
proc & x.max_pending;
}
inline std::string to_string(const named_actor_config& x) {
return "named_actor_config"
+ deep_to_string_as_tuple(x.strategy, x.low_watermark, x.max_pending);
}
} // namespace caf
#endif //CAF_NAMED_ACTOR_CONFIG_HPP
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2016 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* 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. *
******************************************************************************/
#ifndef CAF_TEST_COORDINATOR_HPP
#define CAF_TEST_COORDINATOR_HPP
#include "caf/config.hpp"
#include <deque>
#include <chrono>
#include <cstddef>
#include "caf/scheduler/abstract_coordinator.hpp"
namespace caf {
namespace scheduler {
/// Coordinator for testing purposes.
class test_coordinator : public abstract_coordinator {
public:
using super = abstract_coordinator;
test_coordinator(actor_system& sys);
std::deque<resumable*> jobs;
struct delayed_msg {
strong_actor_ptr from;
strong_actor_ptr to;
message_id mid;
message msg;
};
using hrc = std::chrono::high_resolution_clock;
std::multimap<hrc::time_point, delayed_msg> delayed_messages;
template <class T = resumable>
T& next_job() {
if (jobs.empty())
CAF_RAISE_ERROR("jobs.empty())");
return dynamic_cast<T&>(*jobs.front());
}
/// Tries to execute a single event.
bool run_once();
/// Executes events until the job queue is empty and no pending timeouts are
/// left. Returns the number of processed events.
size_t run();
/// Tries to dispatch a single delayed message.
bool dispatch_once();
/// Dispatches all pending delayed messages. Returns the number of dispatched
/// messages.
size_t dispatch();
/// Loops until no job or delayed message remains. Returns the total number of
/// events (first) and dispatched delayed messages (second).
std::pair<size_t, size_t> run_dispatch_loop();
protected:
void start() override;
void stop() override;
void enqueue(resumable* ptr) override;
};
} // namespace scheduler
} // namespace caf
#endif // CAF_TEST_COORDINATOR_HPP
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "caf/policy/work_stealing.hpp" #include "caf/policy/work_stealing.hpp"
#include "caf/scheduler/coordinator.hpp" #include "caf/scheduler/coordinator.hpp"
#include "caf/scheduler/test_coordinator.hpp"
#include "caf/scheduler/abstract_coordinator.hpp" #include "caf/scheduler/abstract_coordinator.hpp"
#include "caf/scheduler/profiled_coordinator.hpp" #include "caf/scheduler/profiled_coordinator.hpp"
...@@ -205,6 +206,7 @@ actor_system::actor_system(actor_system_config& cfg) ...@@ -205,6 +206,7 @@ actor_system::actor_system(actor_system_config& cfg)
if (clptr) if (clptr)
opencl_manager_ = reinterpret_cast<opencl::manager*>(clptr->subtype_ptr()); opencl_manager_ = reinterpret_cast<opencl::manager*>(clptr->subtype_ptr());
auto& sched = modules_[module::scheduler]; auto& sched = modules_[module::scheduler];
using test = scheduler::test_coordinator;
using share = scheduler::coordinator<policy::work_sharing>; using share = scheduler::coordinator<policy::work_sharing>;
using steal = scheduler::coordinator<policy::work_stealing>; using steal = scheduler::coordinator<policy::work_stealing>;
using profiled_share = scheduler::profiled_coordinator<policy::work_sharing>; using profiled_share = scheduler::profiled_coordinator<policy::work_sharing>;
...@@ -214,6 +216,7 @@ actor_system::actor_system(actor_system_config& cfg) ...@@ -214,6 +216,7 @@ actor_system::actor_system(actor_system_config& cfg)
enum sched_conf { enum sched_conf {
stealing = 0x0001, stealing = 0x0001,
sharing = 0x0002, sharing = 0x0002,
testing = 0x0003,
profiled = 0x0100, profiled = 0x0100,
profiled_stealing = 0x0101, profiled_stealing = 0x0101,
profiled_sharing = 0x0102 profiled_sharing = 0x0102
...@@ -221,6 +224,8 @@ actor_system::actor_system(actor_system_config& cfg) ...@@ -221,6 +224,8 @@ actor_system::actor_system(actor_system_config& cfg)
sched_conf sc = stealing; sched_conf sc = stealing;
if (cfg.scheduler_policy == atom("sharing")) if (cfg.scheduler_policy == atom("sharing"))
sc = sharing; sc = sharing;
else if (cfg.scheduler_policy == atom("testing"))
sc = testing;
else if (cfg.scheduler_policy != atom("stealing")) else if (cfg.scheduler_policy != atom("stealing"))
std::cerr << "[WARNING] " << deep_to_string(cfg.scheduler_policy) std::cerr << "[WARNING] " << deep_to_string(cfg.scheduler_policy)
<< " is an unrecognized scheduler pollicy, " << " is an unrecognized scheduler pollicy, "
...@@ -241,6 +246,8 @@ actor_system::actor_system(actor_system_config& cfg) ...@@ -241,6 +246,8 @@ actor_system::actor_system(actor_system_config& cfg)
case profiled_sharing: case profiled_sharing:
sched.reset(new profiled_share(*this)); sched.reset(new profiled_share(*this));
break; break;
case testing:
sched.reset(new test(*this));
} }
} }
// initialize state for each module and give each module the opportunity // initialize state for each module and give each module the opportunity
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2016 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* 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. *
******************************************************************************/
#include "caf/scheduler/test_coordinator.hpp"
#include "caf/resumable.hpp"
#include "caf/monitorable_actor.hpp"
namespace caf {
namespace scheduler {
namespace {
class dummy_worker : public execution_unit {
public:
dummy_worker(test_coordinator* parent)
: execution_unit(&parent->system()),
parent_(parent) {
// nop
}
void exec_later(resumable* ptr) override {
parent_->jobs.push_back(ptr);
}
private:
test_coordinator* parent_;
};
class dummy_printer : public monitorable_actor {
public:
dummy_printer(actor_config& cfg) : monitorable_actor(cfg) {
mh_.assign(
[&](add_atom, actor_id, const std::string& str) {
std::cout << str;
}
);
}
void enqueue(mailbox_element_ptr what, execution_unit*) override {
mh_(what->content());
}
private:
message_handler mh_;
};
class dummy_timer : public monitorable_actor {
public:
dummy_timer(actor_config& cfg, test_coordinator* parent)
: monitorable_actor(cfg),
parent_(parent) {
mh_.assign(
[&](const duration& d, strong_actor_ptr& from,
strong_actor_ptr& to, message_id mid, message& msg) {
auto tout = test_coordinator::hrc::now();
tout += d;
using delayed_msg = test_coordinator::delayed_msg;
parent_->delayed_messages.emplace(tout, delayed_msg{std::move(from),
std::move(to), mid,
std::move(msg)});
}
);
}
void enqueue(mailbox_element_ptr what, execution_unit*) override {
mh_(what->content());
}
private:
test_coordinator* parent_;
message_handler mh_;
};
} // namespace <anonymous>
test_coordinator::test_coordinator(actor_system& sys) : super(sys) {
// nop
}
void test_coordinator::start() {
dummy_worker worker{this};
actor_config cfg{&worker};
auto& sys = system();
timer_ = make_actor<dummy_timer, strong_actor_ptr>(
sys.next_actor_id(), sys.node(), &sys, cfg, this);
printer_ = make_actor<dummy_printer, strong_actor_ptr>(
sys.next_actor_id(), sys.node(), &sys, cfg);
}
void test_coordinator::stop() {
run_dispatch_loop();
}
void test_coordinator::enqueue(resumable* ptr) {
jobs.push_back(ptr);
}
bool test_coordinator::run_once() {
if (jobs.empty())
return false;
auto job = jobs.front();
jobs.pop_front();
dummy_worker worker{this};
switch (job->resume(&worker, 1)) {
case resumable::resume_later:
jobs.push_front(job);
break;
case resumable::done:
case resumable::awaiting_message:
intrusive_ptr_release(job);
break;
case resumable::shutdown_execution_unit:
break;
}
return true;
}
size_t test_coordinator::run() {
size_t res = 0;
while (run_once())
++res;
return res;
}
bool test_coordinator::dispatch_once() {
auto i = delayed_messages.begin();
if (i == delayed_messages.end())
return false;
auto& dm = i->second;
dm.to->enqueue(dm.from, dm.mid, std::move(dm.msg), nullptr);
delayed_messages.erase(i);
return true;
}
size_t test_coordinator::dispatch() {
size_t res = 0;
while (dispatch_once())
++res;
return res;
}
std::pair<size_t, size_t> test_coordinator::run_dispatch_loop() {
std::pair<size_t, size_t> res;
size_t i = 0;
do {
auto x = run();
auto y = dispatch();
res.first += x;
res.second += y;
i = x + y;
} while (i > 0);
return res;
}
} // namespace caf
} // namespace scheduler
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
using namespace caf; using namespace caf;
using std::string;
using std::chrono::seconds; using std::chrono::seconds;
using std::chrono::milliseconds; using std::chrono::milliseconds;
...@@ -36,142 +37,256 @@ namespace { ...@@ -36,142 +37,256 @@ namespace {
using ping_atom = atom_constant<atom("ping")>; using ping_atom = atom_constant<atom("ping")>;
using pong_atom = atom_constant<atom("pong")>; using pong_atom = atom_constant<atom("pong")>;
using send_ping_atom = atom_constant<atom("send_ping")>; using timeout_atom = atom_constant<atom("timeout")>;
behavior pong() { struct pong_state {
static const char* name;
};
const char* pong_state::name = "pong";
behavior pong(stateful_actor<pong_state>*) {
return { return {
[=] (ping_atom) { [=] (ping_atom) {
std::this_thread::sleep_for(seconds(1));
return pong_atom::value; return pong_atom::value;
} }
}; };
} }
behavior ping1(event_based_actor* self, const actor& pong_actor) { struct ping_state {
self->link_to(pong_actor); static const char* name;
self->send(self, send_ping_atom::value); bool had_first_timeout = false; // unused in ping_singleN functions
};
const char* ping_state::name = "ping";
using ping_actor = stateful_actor<ping_state>;
using fptr = behavior (*)(ping_actor*, bool*, const actor&);
using test_vec = std::vector<std::pair<fptr, string>>;
// assumes to receive a timeout (sent via delayed_send) before pong replies
behavior ping_single1(ping_actor* self, bool* had_timeout, const actor& buddy) {
self->send(buddy, ping_atom::value);
self->delayed_send(self, std::chrono::seconds(1), timeout_atom::value);
return { return {
[=](send_ping_atom) {
self->request(pong_actor, milliseconds(100), ping_atom::value).then(
[=](pong_atom) { [=](pong_atom) {
CAF_ERROR("received pong atom"); CAF_ERROR("received pong atom");
self->quit(exit_reason::user_shutdown);
}, },
[=](const error& err) { [=](timeout_atom) {
CAF_REQUIRE(err == sec::request_timeout); *had_timeout = true;
self->quit(exit_reason::user_shutdown); self->quit();
}
);
} }
}; };
} }
behavior ping2(event_based_actor* self, const actor& pong_actor) { // assumes to receive a timeout (via after()) before pong replies
self->link_to(pong_actor); behavior ping_single2(ping_actor* self, bool* had_timeout, const actor& buddy) {
self->send(self, send_ping_atom::value); self->send(buddy, ping_atom::value);
auto received_inner = std::make_shared<bool>(false);
return { return {
[=](send_ping_atom) {
self->request(pong_actor, milliseconds(100), ping_atom::value).then(
[=](pong_atom) { [=](pong_atom) {
CAF_ERROR("received pong atom"); CAF_ERROR("received pong atom");
self->quit(exit_reason::user_shutdown); },
after(std::chrono::seconds(1)) >> [=] {
*had_timeout = true;
self->quit();
}
};
}
// assumes to receive a timeout (via request error handler) before pong replies
behavior ping_single3(ping_actor* self, bool* had_timeout, const actor& buddy) {
self->request(buddy, milliseconds(100), ping_atom::value).then(
[=](pong_atom) {
CAF_ERROR("received pong atom");
}, },
[=](const error& err) { [=](const error& err) {
CAF_REQUIRE(err == sec::request_timeout); CAF_REQUIRE(err == sec::request_timeout);
CAF_MESSAGE("inner timeout: check"); *had_timeout = true;
*received_inner = true;
} }
); );
return {}; // dummy value in order to give all 3 variants the same fun sig
}
// assumes to receive an inner timeout (sent via delayed_send) before pong
// replies, then second timeout fires
behavior ping_nested1(ping_actor* self, bool* had_timeout,
const actor& buddy) {
self->send(buddy, ping_atom::value);
self->delayed_send(self, std::chrono::seconds(1), timeout_atom::value);
return {
[=](pong_atom) {
CAF_ERROR("received pong atom");
}, },
[=](timeout_atom) {
self->state.had_first_timeout = true;
self->become(
after(milliseconds(100)) >> [=] { after(milliseconds(100)) >> [=] {
CAF_CHECK_EQUAL(*received_inner, true); CAF_CHECK(self->state.had_first_timeout);
self->quit(exit_reason::user_shutdown); *had_timeout = true;
self->quit();
}
);
} }
}; };
} }
behavior ping3(event_based_actor* self, const actor& pong_actor) { // assumes to receive an inner timeout (via after()) before pong replies, then a
self->link_to(pong_actor); // second timeout fires
self->send(self, send_ping_atom::value); behavior ping_nested2(ping_actor* self, bool* had_timeout, const actor& buddy) {
self->send(buddy, ping_atom::value);
return { return {
[=](send_ping_atom) {
self->request(pong_actor, milliseconds(100),
ping_atom::value).then(
[=](pong_atom) { [=](pong_atom) {
CAF_ERROR("received pong atom"); CAF_ERROR("received pong atom");
self->quit(exit_reason::user_shutdown);
}, },
[=](const error& err) { after(std::chrono::seconds(1)) >> [=] {
CAF_REQUIRE(err == sec::request_timeout); self->state.had_first_timeout = true;
CAF_MESSAGE("async timeout: check"); self->become(
self->quit(exit_reason::user_shutdown); after(milliseconds(100)) >> [=] {
CAF_CHECK(self->state.had_first_timeout);
*had_timeout = true;
self->quit();
} }
); );
} }
}; };
} }
behavior ping4(event_based_actor* self, const actor& pong_actor) { // assumes to receive an inner timeout (via request error handler) before pong
self->link_to(pong_actor); // replies, then a second timeout fires
self->send(self, send_ping_atom::value); behavior ping_nested3(ping_actor* self, bool* had_timeout, const actor& buddy) {
auto received_outer = std::make_shared<bool>(false); self->request(buddy, milliseconds(100), ping_atom::value).then(
[=](pong_atom) {
CAF_ERROR("received pong atom");
self->quit(sec::unexpected_message);
},
[=](const error& err) {
CAF_REQUIRE_EQUAL(err, sec::request_timeout);
self->state.had_first_timeout = true;
}
);
return { return {
[=](send_ping_atom) { after(milliseconds(100)) >> [=] {
self->request(pong_actor, milliseconds(100), CAF_CHECK(self->state.had_first_timeout);
ping_atom::value).then( *had_timeout = true;
self->quit();
}
};
}
// uses .then on both requests
behavior ping_multiplexed1(ping_actor* self, bool* had_timeout,
const actor& pong_actor) {
self->request(pong_actor, milliseconds(100), ping_atom::value).then(
[=](pong_atom) { [=](pong_atom) {
CAF_ERROR("received pong atom"); CAF_ERROR("received pong atom");
self->quit(exit_reason::user_shutdown);
}, },
[=](const error& err) { [=](const error& err) {
CAF_REQUIRE(err == sec::request_timeout); CAF_REQUIRE_EQUAL(err, sec::request_timeout);
CAF_CHECK_EQUAL(*received_outer, true); if (!self->state.had_first_timeout)
self->quit(exit_reason::user_shutdown); self->state.had_first_timeout = true;
else
*had_timeout = true;
} }
); );
self->request(pong_actor, milliseconds(100), ping_atom::value).then(
[=](pong_atom) {
CAF_ERROR("received pong atom");
}, },
after(milliseconds(50)) >> [=] { [=](const error& err) {
CAF_MESSAGE("outer timeout: check"); CAF_REQUIRE_EQUAL(err, sec::request_timeout);
*received_outer = true; if (!self->state.had_first_timeout)
self->state.had_first_timeout = true;
else
*had_timeout = true;
} }
}; );
return {};
} }
void ping5(event_based_actor* self, const actor& pong_actor) { // uses .await on both requests
self->link_to(pong_actor); behavior ping_multiplexed2(ping_actor* self, bool* had_timeout,
auto timeouts = std::make_shared<int>(0); const actor& pong_actor) {
self->request(pong_actor, milliseconds(100), self->request(pong_actor, milliseconds(100), ping_atom::value).await(
ping_atom::value).then(
[=](pong_atom) { [=](pong_atom) {
CAF_ERROR("received pong atom"); CAF_ERROR("received pong atom");
}, },
[=](const error& err) { [=](const error& err) {
CAF_REQUIRE(err == sec::request_timeout); CAF_REQUIRE_EQUAL(err, sec::request_timeout);
if (++*timeouts == 2) if (!self->state.had_first_timeout)
self->quit(); self->state.had_first_timeout = true;
else
*had_timeout = true;
} }
); );
self->request(pong_actor, milliseconds(100), self->request(pong_actor, milliseconds(100), ping_atom::value).await(
ping_atom::value).await(
[=](pong_atom) { [=](pong_atom) {
CAF_ERROR("received pong atom"); CAF_ERROR("received pong atom");
}, },
[=](const error& err) { [=](const error& err) {
CAF_REQUIRE(err == sec::request_timeout); CAF_REQUIRE_EQUAL(err, sec::request_timeout);
if (++*timeouts == 2) if (!self->state.had_first_timeout)
self->quit(); self->state.had_first_timeout = true;
else
*had_timeout = true;
} }
); );
return {};
} }
struct fixture { // uses .await and .then
fixture() : system(cfg) { behavior ping_multiplexed3(ping_actor* self, bool* had_timeout,
// nop const actor& pong_actor) {
self->request(pong_actor, milliseconds(100), ping_atom::value).then(
[=](pong_atom) {
CAF_ERROR("received pong atom");
},
[=](const error& err) {
CAF_REQUIRE_EQUAL(err, sec::request_timeout);
if (!self->state.had_first_timeout)
self->state.had_first_timeout = true;
else
*had_timeout = true;
} }
);
self->request(pong_actor, milliseconds(100), ping_atom::value).await(
[=](pong_atom) {
CAF_ERROR("received pong atom");
},
[=](const error& err) {
CAF_REQUIRE_EQUAL(err, sec::request_timeout);
if (!self->state.had_first_timeout)
self->state.had_first_timeout = true;
else
*had_timeout = true;
}
);
return {};
}
actor_system_config cfg; struct config : actor_system_config {
config() {
scheduler_policy = atom("testing");
}
};
struct fixture {
config cfg;
actor_system system; actor_system system;
scoped_actor self;
scheduler::test_coordinator& sched;
fixture()
: system(cfg),
self(system),
sched(dynamic_cast<scheduler::test_coordinator&>(system.scheduler())) {
CAF_REQUIRE(sched.jobs.empty());
CAF_REQUIRE(sched.delayed_messages.empty());
}
~fixture() {
sched.run_dispatch_loop();
}
}; };
} // namespace <anonymous> } // namespace <anonymous>
...@@ -179,17 +294,79 @@ struct fixture { ...@@ -179,17 +294,79 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(request_timeout_tests, fixture) CAF_TEST_FIXTURE_SCOPE(request_timeout_tests, fixture)
CAF_TEST(single_timeout) { CAF_TEST(single_timeout) {
system.spawn(ping1, system.spawn(pong)); test_vec fs{{ping_single1, "ping_single1"},
system.spawn(ping3, system.spawn(pong)); {ping_single2, "ping_single2"},
{ping_single3, "ping_single3"}};
for (auto f : fs) {
bool had_timeout = false;
CAF_MESSAGE("test implemenation " << f.second);
auto testee = system.spawn(f.first, &had_timeout,
system.spawn<lazy_init>(pong));
CAF_REQUIRE_EQUAL(sched.jobs.size(), 1);
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"ping"});
sched.run_once();
CAF_REQUIRE_EQUAL(sched.jobs.size(), 1);
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"pong"});
sched.dispatch();
CAF_REQUIRE_EQUAL(sched.jobs.size(), 2);
// now, the timeout message is already dispatched, while pong did
// not respond to the message yet, i.e., timeout arrives before response
CAF_CHECK_EQUAL(sched.run(), 2);
CAF_CHECK(had_timeout);
}
} }
CAF_TEST(scoped_timeout) { CAF_TEST(nested_timeout) {
system.spawn(ping2, system.spawn(pong)); test_vec fs{{ping_nested1, "ping_nested1"},
system.spawn(ping4, system.spawn(pong)); {ping_nested2, "ping_nested2"},
{ping_nested3, "ping_nested3"}};
for (auto f : fs) {
bool had_timeout = false;
CAF_MESSAGE("test implemenation " << f.second);
auto testee = system.spawn(f.first, &had_timeout,
system.spawn<lazy_init>(pong));
CAF_REQUIRE_EQUAL(sched.jobs.size(), 1);
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"ping"});
sched.run_once();
CAF_REQUIRE_EQUAL(sched.jobs.size(), 1);
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"pong"});
sched.dispatch();
CAF_REQUIRE_EQUAL(sched.jobs.size(), 2);
// now, the timeout message is already dispatched, while pong did
// not respond to the message yet, i.e., timeout arrives before response
sched.run();
// dispatch second timeout
CAF_REQUIRE(!sched.delayed_messages.empty());
sched.dispatch();
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"ping"});
CAF_CHECK(!had_timeout);
CAF_CHECK(sched.next_job<ping_actor>().state.had_first_timeout);
sched.run();
CAF_CHECK(had_timeout);
}
} }
CAF_TEST(awaited_multiplexed_timeout) { CAF_TEST(multiplexed_timeout) {
system.spawn(ping5, system.spawn(pong)); test_vec fs{{ping_multiplexed1, "ping_multiplexed1"},
{ping_multiplexed2, "ping_multiplexed2"},
{ping_multiplexed3, "ping_multiplexed3"}};
for (auto f : fs) {
bool had_timeout = false;
CAF_MESSAGE("test implemenation " << f.second);
auto testee = system.spawn(f.first, &had_timeout,
system.spawn<lazy_init>(pong));
CAF_REQUIRE_EQUAL(sched.jobs.size(), 1);
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"ping"});
sched.run_once();
CAF_REQUIRE_EQUAL(sched.jobs.size(), 1);
CAF_REQUIRE_EQUAL(sched.next_job<local_actor>().name(), string{"pong"});
sched.dispatch();
CAF_REQUIRE_EQUAL(sched.jobs.size(), 2);
// now, the timeout message is already dispatched, while pong did
// not respond to the message yet, i.e., timeout arrives before response
sched.run();
CAF_CHECK(had_timeout);
}
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
...@@ -36,17 +36,20 @@ using ms = std::chrono::milliseconds; ...@@ -36,17 +36,20 @@ using ms = std::chrono::milliseconds;
using reset_atom = atom_constant<atom("reset")>; using reset_atom = atom_constant<atom("reset")>;
using timer = typed_actor<reacts_to<reset_atom>>; using timer = typed_actor<reacts_to<reset_atom>>;
timer::behavior_type timer_impl(timer::pointer self) { struct timer_state {
auto had_reset = std::make_shared<bool>(false); bool had_reset = false;
};
timer::behavior_type timer_impl(timer::stateful_pointer<timer_state> self) {
self->delayed_send(self, ms(100), reset_atom::value); self->delayed_send(self, ms(100), reset_atom::value);
return { return {
[=](reset_atom) { [=](reset_atom) {
CAF_MESSAGE("timer reset"); CAF_MESSAGE("timer reset");
*had_reset = true; self->state.had_reset = true;
}, },
after(ms(600)) >> [=] { after(ms(600)) >> [=] {
CAF_MESSAGE("timer expired"); CAF_MESSAGE("timer expired");
CAF_REQUIRE(*had_reset); CAF_REQUIRE(self->state.had_reset);
self->quit(); self->quit();
} }
}; };
...@@ -68,18 +71,34 @@ timer::behavior_type timer_impl2(timer::pointer self) { ...@@ -68,18 +71,34 @@ timer::behavior_type timer_impl2(timer::pointer self) {
}; };
} }
struct fixture { struct config : actor_system_config {
fixture() : system(cfg) { config() {
// nop scheduler_policy = atom("testing");
} }
};
actor_system_config cfg; struct fixture {
config cfg;
actor_system system; actor_system system;
scoped_actor self;
scheduler::test_coordinator& sched;
fixture()
: system(cfg),
self(system),
sched(dynamic_cast<scheduler::test_coordinator&>(system.scheduler())) {
CAF_REQUIRE(sched.jobs.empty());
CAF_REQUIRE(sched.delayed_messages.empty());
}
~fixture() {
sched.run_dispatch_loop();
}
}; };
} // namespace <anonymous> } // namespace <anonymous>
CAF_TEST_FIXTURE_SCOPE(timeout_tests, fixture) CAF_TEST_FIXTURE_SCOPE(simple_timeout_tests, fixture)
CAF_TEST(duration_conversion) { CAF_TEST(duration_conversion) {
duration d1{time_unit::milliseconds, 100}; duration d1{time_unit::milliseconds, 100};
......
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