Commit c931984f authored by Dominik Charousset's avatar Dominik Charousset

Make actor lifetime test deterministic

parent fbcc262e
...@@ -22,27 +22,38 @@ ...@@ -22,27 +22,38 @@
#define CAF_SUITE actor_lifetime #define CAF_SUITE actor_lifetime
#include "caf/test/unit_test.hpp" #include "caf/test/unit_test.hpp"
#include <mutex>
#include <atomic> #include <atomic>
#include <condition_variable>
#include "caf/all.hpp" #include "caf/all.hpp"
using namespace caf; #include "caf/test/dsl.hpp"
using check_atom = caf::atom_constant<caf::atom("check")>; using check_atom = caf::atom_constant<caf::atom("check")>;
using namespace caf;
namespace { namespace {
std::mutex s_mtx;
std::condition_variable s_cv;
std::atomic<bool> s_tester_init_done;
std::atomic<bool> s_testee_cleanup_done;
std::atomic<long> s_testees; std::atomic<long> s_testees;
std::atomic<long> s_pending_on_exits; std::atomic<long> s_pending_on_exits;
class testee : public event_based_actor { class testee : public event_based_actor {
public: public:
testee(actor_config& cfg) : event_based_actor(cfg) { testee(actor_config& cfg) : event_based_actor(cfg) {
printf("%s %d\n", __FILE__, __LINE__);
++s_testees; ++s_testees;
++s_pending_on_exits; ++s_pending_on_exits;
} }
~testee() override { ~testee() override {
printf("%s %d\n", __FILE__, __LINE__);
--s_testees; --s_testees;
} }
...@@ -51,6 +62,7 @@ public: ...@@ -51,6 +62,7 @@ public:
} }
void on_exit() override { void on_exit() override {
printf("%s %d\n", __FILE__, __LINE__);
--s_pending_on_exits; --s_pending_on_exits;
} }
...@@ -70,12 +82,7 @@ behavior tester(event_based_actor* self, const actor& aut) { ...@@ -70,12 +82,7 @@ behavior tester(event_based_actor* self, const actor& aut) {
// must be still alive at this point // must be still alive at this point
CAF_CHECK_EQUAL(s_testees.load(), 1); CAF_CHECK_EQUAL(s_testees.load(), 1);
CAF_CHECK_EQUAL(msg.reason, exit_reason::user_shutdown); CAF_CHECK_EQUAL(msg.reason, exit_reason::user_shutdown);
// testee might be still running its cleanup code in self->send(self, check_atom::value);
// another worker thread; by waiting some milliseconds, we make sure
// testee had enough time to return control to the scheduler
// which in turn destroys it by dropping the last remaining reference
self->delayed_send(self, std::chrono::milliseconds(30),
check_atom::value);
}); });
self->link_to(aut); self->link_to(aut);
} else { } else {
...@@ -87,15 +94,23 @@ behavior tester(event_based_actor* self, const actor& aut) { ...@@ -87,15 +94,23 @@ behavior tester(event_based_actor* self, const actor& aut) {
// another worker thread; by waiting some milliseconds, we make sure // another worker thread; by waiting some milliseconds, we make sure
// testee had enough time to return control to the scheduler // testee had enough time to return control to the scheduler
// which in turn destroys it by dropping the last remaining reference // which in turn destroys it by dropping the last remaining reference
self->delayed_send(self, std::chrono::milliseconds(30), self->send(self, check_atom::value);
check_atom::value);
}); });
self->monitor(aut); self->monitor(aut);
} }
anon_send_exit(aut, exit_reason::user_shutdown); anon_send_exit(aut, exit_reason::user_shutdown);
{
std::unique_lock<std::mutex> guard{s_mtx};
s_tester_init_done = true;
s_cv.notify_one();
}
return { return {
[self](check_atom) { [self](check_atom) {
// make sure aut's dtor and on_exit() have been called { // make sure aut's dtor and on_exit() have been called
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_testee_cleanup_done.load())
s_cv.wait(guard);
}
CAF_CHECK_EQUAL(s_testees.load(), 0); CAF_CHECK_EQUAL(s_testees.load(), 0);
CAF_CHECK_EQUAL(s_pending_on_exits.load(), 0); CAF_CHECK_EQUAL(s_pending_on_exits.load(), 0);
self->quit(); self->quit();
...@@ -103,11 +118,22 @@ behavior tester(event_based_actor* self, const actor& aut) { ...@@ -103,11 +118,22 @@ behavior tester(event_based_actor* self, const actor& aut) {
}; };
} }
struct config : actor_system_config {
config() {
scheduler_policy = atom("testing");
}
};
struct fixture { struct fixture {
actor_system_config cfg; using sched_t = scheduler::test_coordinator;
config cfg;
actor_system system; actor_system system;
sched_t& sched;
fixture() : system(cfg) { fixture() : system(cfg), sched(dynamic_cast<sched_t&>(system.scheduler())) {
// nop // nop
} }
...@@ -120,6 +146,43 @@ struct fixture { ...@@ -120,6 +146,43 @@ struct fixture {
actor spawn(Ts&&... xs) { actor spawn(Ts&&... xs) {
return system.spawn<T, Os>(xs...); return system.spawn<T, Os>(xs...);
} }
template <class ExitMsgType, spawn_options TesterOptions,
spawn_options TesteeOptions>
void tst() {
// We re-use these static variables with each run.
s_tester_init_done = false;
s_testee_cleanup_done = false;
// Spawn test subject and tester.
auto tst_subject = spawn<testee, TesteeOptions>();
sched.run();
auto tst_driver = spawn<TesterOptions>(tester<ExitMsgType>, tst_subject);
tst_subject = nullptr;
if (has_detach_flag(TesterOptions)) {
// When dealing with a detached tester we need to insert two
// synchronization points: 1) exit_msg sent and 2) cleanup code of tester
// done.
{ // Wait for the exit_msg from the driver.
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_tester_init_done)
s_cv.wait(guard);
}
// Run the exit_msg.
sched.run_once();
//expect((exit_msg), from(tst_driver).to(tst_subject));
{ // Resume driver.
std::unique_lock<std::mutex> guard{s_mtx};
s_testee_cleanup_done = true;
s_cv.notify_one();
}
} else {
// When both actors are running in the scheduler we don't need any extra
// synchronization.
s_tester_init_done = true;
s_testee_cleanup_done = true;
sched.run();
}
}
}; };
} // namespace <anonymous> } // namespace <anonymous>
...@@ -137,35 +200,19 @@ CAF_TEST(destructor_call) { ...@@ -137,35 +200,19 @@ CAF_TEST(destructor_call) {
CAF_TEST_FIXTURE_SCOPE(actor_lifetime_tests, fixture) CAF_TEST_FIXTURE_SCOPE(actor_lifetime_tests, fixture)
CAF_TEST(no_spawn_options_and_exit_msg) { CAF_TEST(no_spawn_options_and_exit_msg) {
spawn<no_spawn_options>(tester<exit_msg>, spawn<testee, no_spawn_options>()); tst<exit_msg, no_spawn_options, no_spawn_options>();
} }
CAF_TEST(no_spawn_options_and_down_msg) { CAF_TEST(no_spawn_options_and_down_msg) {
spawn<no_spawn_options>(tester<down_msg>, spawn<testee, no_spawn_options>()); tst<down_msg, no_spawn_options, no_spawn_options>();
} }
CAF_TEST(mixed_spawn_options_and_exit_msg) { CAF_TEST(mixed_spawn_options_and_exit_msg) {
spawn<detached>(tester<exit_msg>, spawn<testee, no_spawn_options>()); tst<exit_msg, detached, no_spawn_options>();
} }
CAF_TEST(mixed_spawn_options_and_down_msg) { CAF_TEST(mixed_spawn_options_and_down_msg) {
spawn<detached>(tester<down_msg>, spawn<testee, no_spawn_options>()); tst<down_msg, detached, no_spawn_options>();
}
CAF_TEST(mixed_spawn_options2_and_exit_msg) {
spawn<no_spawn_options>(tester<exit_msg>, spawn<testee, detached>());
}
CAF_TEST(mixed_spawn_options2_and_down_msg) {
spawn<no_spawn_options>(tester<down_msg>, spawn<testee, detached>());
}
CAF_TEST(detached_and_exit_msg) {
spawn<detached>(tester<exit_msg>, spawn<testee, detached>());
}
CAF_TEST(detached_and_down_msg) {
spawn<detached>(tester<down_msg>, spawn<testee, detached>());
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
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