Commit 8e28cda4 authored by Dominik Charousset's avatar Dominik Charousset

Reimplement thread-safe actor clock

parent d49879bb
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 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
#include <memory>
namespace caf {
namespace detail {
template <class T, class... Ts>
std::unique_ptr<T> make_unique(Ts&&... xs) {
return std::unique_ptr<T>{new T(std::forward<Ts>(xs)...)};
}
} // namespace detail
} // namespace caf
...@@ -18,10 +18,13 @@ ...@@ -18,10 +18,13 @@
#pragma once #pragma once
#include <cstdint>
#include <map> #include <map>
#include <memory>
#include "caf/actor_clock.hpp" #include "caf/actor_clock.hpp"
#include "caf/actor_control_block.hpp" #include "caf/actor_control_block.hpp"
#include "caf/detail/make_unique.hpp"
#include "caf/group.hpp" #include "caf/group.hpp"
#include "caf/mailbox_element.hpp" #include "caf/mailbox_element.hpp"
#include "caf/message.hpp" #include "caf/message.hpp"
...@@ -35,73 +38,234 @@ class simple_actor_clock : public actor_clock { ...@@ -35,73 +38,234 @@ class simple_actor_clock : public actor_clock {
public: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
/// Request for a `timeout_msg`. using super = simple_actor_clock;
struct ordinary_timeout {
struct event;
struct delayed_event;
/// Owning pointer to events.
using unique_event_ptr = std::unique_ptr<event>;
/// Owning pointer to delayed events.
using unique_delayed_event_ptr = std::unique_ptr<delayed_event>;
/// Maps timeouts to delayed events.
using schedule_map = std::multimap<time_point, unique_delayed_event_ptr>;
/// Maps actor IDs to schedule entries.
using actor_lookup_map = std::multimap<actor_id, schedule_map::iterator>;
/// Lists all possible subtypes of ::event.
enum event_type {
ordinary_timeout_type,
multi_timeout_type,
request_timeout_type,
actor_msg_type,
group_msg_type,
ordinary_timeout_cancellation_type,
multi_timeout_cancellation_type,
request_timeout_cancellation_type,
timeouts_cancellation_type,
drop_all_type,
shutdown_type,
};
/// Base class for clock events.
struct event {
event(event_type t) : subtype(t) {
// nop
}
virtual ~event();
/// Identifies the actual type of this object.
event_type subtype;
};
/// An event with a timeout attached to it.
struct delayed_event : event {
delayed_event(event_type type, time_point due) : event(type), due(due) {
// nop
}
/// Timestamp when this event should trigger.
time_point due;
/// Links back to the actor lookup map.
actor_lookup_map::iterator backlink;
};
/// An ordinary timeout event for actors. Only one timeout for any timeout
/// type can be active.
struct ordinary_timeout final : delayed_event {
static constexpr bool cancellable = true;
ordinary_timeout(time_point due, strong_actor_ptr self, atom_value type,
uint64_t id)
: delayed_event(ordinary_timeout_type, due),
self(std::move(self)),
type(type),
id(id) {
// nop
}
strong_actor_ptr self; strong_actor_ptr self;
atom_value type; atom_value type;
uint64_t id; uint64_t id;
}; };
struct multi_timeout { /// An timeout event for actors that allows multiple active timers for the
/// same type.
struct multi_timeout final : delayed_event {
static constexpr bool cancellable = true;
multi_timeout(time_point due, strong_actor_ptr self, atom_value type,
uint64_t id)
: delayed_event(multi_timeout_type, due),
self(std::move(self)),
type(type),
id(id) {
// nop
}
strong_actor_ptr self; strong_actor_ptr self;
atom_value type; atom_value type;
uint64_t id; uint64_t id;
}; };
/// Request for a `sec::request_timeout` error. /// A delayed `sec::request_timeout` error that gets cancelled when the
struct request_timeout { /// request arrives in time.
struct request_timeout final: delayed_event {
static constexpr bool cancellable = true;
request_timeout(time_point due, strong_actor_ptr self, message_id id)
: delayed_event(request_timeout_type, due),
self(std::move(self)),
id(id) {
// nop
}
strong_actor_ptr self; strong_actor_ptr self;
message_id id; message_id id;
}; };
/// Request for sending a message to an actor at a later time. /// A delayed ::message to an actor.
struct actor_msg { struct actor_msg final : delayed_event {
static constexpr bool cancellable = false;
actor_msg(time_point due, strong_actor_ptr receiver,
mailbox_element_ptr content)
: delayed_event(actor_msg_type, due),
receiver(std::move(receiver)),
content(std::move(content)) {
// nop
}
strong_actor_ptr receiver; strong_actor_ptr receiver;
mailbox_element_ptr content; mailbox_element_ptr content;
}; };
/// Request for sending a message to a group at a later time. /// A delayed ::message to a group.
struct group_msg { struct group_msg final : delayed_event {
static constexpr bool cancellable = false;
group_msg(time_point due, group target, strong_actor_ptr sender,
message content)
: delayed_event(group_msg_type, due),
target(std::move(target)),
sender(std::move(sender)),
content(std::move(content)) {
// nop
}
group target; group target;
strong_actor_ptr sender; strong_actor_ptr sender;
message content; message content;
}; };
using value_type = variant<ordinary_timeout, multi_timeout, request_timeout, /// Cancels a delayed event.
actor_msg, group_msg>; struct cancellation : event {
cancellation(event_type t, actor_id aid) : event(t), aid(aid) {
//nop
}
using map_type = std::multimap<time_point, value_type>; actor_id aid;
};
using secondary_map = std::multimap<abstract_actor*, map_type::iterator>; /// Cancels matching ordinary timeouts.
struct ordinary_timeout_cancellation final : cancellation {
ordinary_timeout_cancellation(actor_id aid, atom_value type)
: cancellation(ordinary_timeout_cancellation_type, aid), type(type) {
// nop
}
struct ordinary_predicate {
atom_value type; atom_value type;
bool operator()(const secondary_map::value_type& x) const noexcept;
}; };
struct multi_predicate { /// Cancels the matching multi timeout.
struct multi_timeout_cancellation final : cancellation {
multi_timeout_cancellation(actor_id aid, atom_value type, uint64_t id)
: cancellation(ordinary_timeout_cancellation_type, aid),
type(type),
id(id) {
// nop
}
atom_value type; atom_value type;
bool operator()(const secondary_map::value_type& x) const noexcept; uint64_t id;
}; };
struct request_predicate { /// Cancels a `sec::request_timeout` error.
struct request_timeout_cancellation final : cancellation {
request_timeout_cancellation(actor_id aid, message_id id)
: cancellation(request_timeout_cancellation_type, aid), id(id) {
// nop
}
message_id id; message_id id;
bool operator()(const secondary_map::value_type& x) const noexcept;
}; };
struct visitor { /// Cancels all timeouts for an actor.
simple_actor_clock* thisptr; struct timeouts_cancellation final : cancellation {
timeouts_cancellation(actor_id aid)
: cancellation(timeouts_cancellation_type, aid) {
// nop
}
};
void operator()(ordinary_timeout& x); /// Cancels all timeouts and messages.
struct drop_all final : event {
drop_all() : event(drop_all_type) {
// nop
}
};
void operator()(multi_timeout& x); /// Shuts down the actor clock.
struct shutdown final : event {
shutdown() : event(shutdown_type) {
// nop
}
};
void operator()(request_timeout& x); // -- properties -------------------------------------------------------------
void operator()(actor_msg& x); const schedule_map& schedule() const {
return schedule_;
}
void operator()(group_msg& x); const actor_lookup_map& actor_lookup() const {
}; return actor_lookup_;
}
// -- convenience functions --------------------------------------------------
/// Triggers all timeouts with timestamp <= now.
/// @returns The number of triggered timeouts.
/// @private
size_t trigger_expired_timeouts();
// -- overridden member functions --------------------------------------------
void set_ordinary_timeout(time_point t, abstract_actor* self, void set_ordinary_timeout(time_point t, abstract_actor* self,
atom_value type, uint64_t id) override; atom_value type, uint64_t id) override;
...@@ -126,20 +290,13 @@ public: ...@@ -126,20 +290,13 @@ public:
void cancel_all() override; void cancel_all() override;
inline const map_type& schedule() const {
return schedule_;
}
inline const secondary_map& actor_lookup() const {
return actor_lookup_;
}
protected: protected:
// -- helper functions -------------------------------------------------------
template <class Predicate> template <class Predicate>
secondary_map::iterator lookup(abstract_actor* self, actor_lookup_map::iterator lookup(actor_id aid, Predicate pred) {
Predicate pred) {
auto e = actor_lookup_.end(); auto e = actor_lookup_.end();
auto range = actor_lookup_.equal_range(self); auto range = actor_lookup_.equal_range(aid);
if (range.first == range.second) if (range.first == range.second)
return e; return e;
auto i = std::find_if(range.first, range.second, pred); auto i = std::find_if(range.first, range.second, pred);
...@@ -147,8 +304,8 @@ protected: ...@@ -147,8 +304,8 @@ protected:
} }
template <class Predicate> template <class Predicate>
void cancel(abstract_actor* self, Predicate pred) { void cancel(actor_id aid, Predicate pred) {
auto i = lookup(self, pred); auto i = lookup(aid, pred);
if (i != actor_lookup_.end()) { if (i != actor_lookup_.end()) {
schedule_.erase(i->second); schedule_.erase(i->second);
actor_lookup_.erase(i); actor_lookup_.erase(i);
...@@ -156,19 +313,58 @@ protected: ...@@ -156,19 +313,58 @@ protected:
} }
template <class Predicate> template <class Predicate>
void drop_lookup(abstract_actor* self, Predicate pred) { void drop_lookup(actor_id aid, Predicate pred) {
auto i = lookup(self, pred); auto i = lookup(aid, pred);
if (i != actor_lookup_.end()) if (i != actor_lookup_.end())
actor_lookup_.erase(i); actor_lookup_.erase(i);
} }
void handle(const ordinary_timeout_cancellation& x);
void handle(const multi_timeout_cancellation& x);
void handle(const request_timeout_cancellation& x);
void handle(const timeouts_cancellation& x);
void ship(delayed_event& x);
template <class T>
detail::enable_if_t<T::cancellable>
add_schedule_entry(time_point t, std::unique_ptr<T> x) {
auto id = x->self->id();
auto i = schedule_.emplace(t, std::move(x));
i->second->backlink = actor_lookup_.emplace(id, i);
}
template <class T>
detail::enable_if_t<!T::cancellable>
add_schedule_entry(time_point t, std::unique_ptr<T> x) {
auto i = schedule_.emplace(t, std::move(x));
i->second->backlink = actor_lookup_.end();
}
void add_schedule_entry(time_point t, std::unique_ptr<ordinary_timeout> x);
template <class T>
void add_schedule_entry(std::unique_ptr<T> x) {
auto due = x->due;
add_schedule_entry(due, std::move(x));
}
template <class T, class... Ts>
void new_schedule_entry(time_point t, Ts&&... xs) {
add_schedule_entry(t, detail::make_unique<T>(t, std::forward<Ts>(xs)...));
}
// -- member variables -------------------------------------------------------
/// Timeout schedule. /// Timeout schedule.
map_type schedule_; schedule_map schedule_;
/// Secondary index for accessing timeouts by actor. /// Secondary index for accessing timeouts by actor.
secondary_map actor_lookup_; actor_lookup_map actor_lookup_;
}; };
} // namespace detail } // namespace detail
} // namespace caf } // namespace caf
...@@ -51,10 +51,6 @@ public: ...@@ -51,10 +51,6 @@ public:
/// @returns The number of triggered timeouts. /// @returns The number of triggered timeouts.
size_t trigger_timeouts(); size_t trigger_timeouts();
/// Triggers all timeouts with timestamp <= now.
/// @returns The number of triggered timeouts.
size_t trigger_expired_timeouts();
/// Advances the time by `x` and dispatches timeouts and delayed messages. /// Advances the time by `x` and dispatches timeouts and delayed messages.
/// @returns The number of triggered timeouts. /// @returns The number of triggered timeouts.
size_t advance_time(duration_type x); size_t advance_time(duration_type x);
......
...@@ -18,10 +18,14 @@ ...@@ -18,10 +18,14 @@
#pragma once #pragma once
#include <mutex> #include <array>
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
#include <memory>
#include <mutex>
#include "caf/abstract_actor.hpp"
#include "caf/detail/ringbuffer.hpp"
#include "caf/detail/simple_actor_clock.hpp" #include "caf/detail/simple_actor_clock.hpp"
namespace caf { namespace caf {
...@@ -29,9 +33,15 @@ namespace detail { ...@@ -29,9 +33,15 @@ namespace detail {
class thread_safe_actor_clock : public simple_actor_clock { class thread_safe_actor_clock : public simple_actor_clock {
public: public:
// -- constants --------------------------------------------------------------
static constexpr size_t buffer_size = 64;
// -- member types -----------------------------------------------------------
using super = simple_actor_clock; using super = simple_actor_clock;
thread_safe_actor_clock(); // -- member functions -------------------------------------------------------
void set_ordinary_timeout(time_point t, abstract_actor* self, void set_ordinary_timeout(time_point t, abstract_actor* self,
atom_value type, uint64_t id) override; atom_value type, uint64_t id) override;
...@@ -61,11 +71,14 @@ public: ...@@ -61,11 +71,14 @@ public:
void cancel_dispatch_loop(); void cancel_dispatch_loop();
private: private:
std::recursive_mutex mx_; void push(event* ptr);
std::condition_variable_any cv_;
std::atomic<bool> done_; /// Receives timer events from other threads.
detail::ringbuffer<unique_event_ptr, buffer_size> queue_;
/// Locally caches events for processing.
std::array<unique_event_ptr, buffer_size> events_;
}; };
} // namespace detail } // namespace detail
} // namespace caf } // namespace caf
...@@ -25,108 +25,40 @@ ...@@ -25,108 +25,40 @@
namespace caf { namespace caf {
namespace detail { namespace detail {
bool simple_actor_clock::ordinary_predicate:: simple_actor_clock::event::~event() {
operator()(const secondary_map::value_type& x) const noexcept { // nop
auto ptr = get_if<ordinary_timeout>(&x.second->second);
return ptr != nullptr ? ptr->type == type : false;
} }
bool simple_actor_clock::multi_predicate:: void simple_actor_clock::set_ordinary_timeout(time_point t,
operator()(const secondary_map::value_type& x) const noexcept { abstract_actor* self,
auto ptr = get_if<multi_timeout>(&x.second->second);
return ptr != nullptr ? ptr->type == type : false;
}
bool simple_actor_clock::request_predicate::
operator()(const secondary_map::value_type& x) const noexcept {
auto ptr = get_if<request_timeout>(&x.second->second);
return ptr != nullptr ? ptr->id == id : false;
}
void simple_actor_clock::visitor::operator()(ordinary_timeout& x) {
CAF_ASSERT(x.self != nullptr);
x.self->get()->eq_impl(make_message_id(), x.self, nullptr,
timeout_msg{x.type, x.id});
ordinary_predicate pred{x.type};
thisptr->drop_lookup(x.self->get(), pred);
}
void simple_actor_clock::visitor::operator()(multi_timeout& x) {
CAF_ASSERT(x.self != nullptr);
x.self->get()->eq_impl(make_message_id(), x.self, nullptr,
timeout_msg{x.type, x.id});
multi_predicate pred{x.type};
thisptr->drop_lookup(x.self->get(), pred);
}
void simple_actor_clock::visitor::operator()(request_timeout& x) {
CAF_ASSERT(x.self != nullptr);
x.self->get()->eq_impl(x.id, x.self, nullptr, sec::request_timeout);
request_predicate pred{x.id};
thisptr->drop_lookup(x.self->get(), pred);
}
void simple_actor_clock::visitor::operator()(actor_msg& x) {
x.receiver->enqueue(std::move(x.content), nullptr);
}
void simple_actor_clock::visitor::operator()(group_msg& x) {
x.target->eq_impl(make_message_id(), std::move(x.sender), nullptr,
std::move(x.content));
}
void simple_actor_clock::set_ordinary_timeout(time_point t, abstract_actor* self,
atom_value type, uint64_t id) { atom_value type, uint64_t id) {
ordinary_predicate pred{type}; new_schedule_entry<ordinary_timeout>(t, self->ctrl(), type, id);
auto i = lookup(self, pred);
auto sptr = actor_cast<strong_actor_ptr>(self);
ordinary_timeout tmp{std::move(sptr), type, id};
if (i != actor_lookup_.end()) {
schedule_.erase(i->second);
i->second = schedule_.emplace(t, std::move(tmp));
} else {
auto j = schedule_.emplace(t, std::move(tmp));
actor_lookup_.emplace(self, j);
}
} }
void simple_actor_clock::set_multi_timeout(time_point t, abstract_actor* self, void simple_actor_clock::set_multi_timeout(time_point t, abstract_actor* self,
atom_value type, uint64_t id) { atom_value type, uint64_t id) {
auto sptr = actor_cast<strong_actor_ptr>(self); new_schedule_entry<multi_timeout>(t, self->ctrl(), type, id);
multi_timeout tmp{std::move(sptr), type, id};
auto j = schedule_.emplace(t, std::move(tmp));
actor_lookup_.emplace(self, j);
} }
void simple_actor_clock::set_request_timeout(time_point t, abstract_actor* self, void simple_actor_clock::set_request_timeout(time_point t, abstract_actor* self,
message_id id) { message_id id) {
request_predicate pred{id}; new_schedule_entry<request_timeout>(t, self->ctrl(), id);
auto i = lookup(self, pred);
auto sptr = actor_cast<strong_actor_ptr>(self);
request_timeout tmp{std::move(sptr), id};
if (i != actor_lookup_.end()) {
schedule_.erase(i->second);
i->second = schedule_.emplace(t, std::move(tmp));
} else {
auto j = schedule_.emplace(t, std::move(tmp));
actor_lookup_.emplace(self, j);
}
} }
void simple_actor_clock::cancel_ordinary_timeout(abstract_actor* self, void simple_actor_clock::cancel_ordinary_timeout(abstract_actor* self,
atom_value type) { atom_value type) {
ordinary_predicate pred{type}; ordinary_timeout_cancellation tmp{self->id(), type};
cancel(self, pred); handle(tmp);
} }
void simple_actor_clock::cancel_request_timeout(abstract_actor* self, void simple_actor_clock::cancel_request_timeout(abstract_actor* self,
message_id id) { message_id id) {
request_predicate pred{id}; request_timeout_cancellation tmp{self->id(), id};
cancel(self, pred); handle(tmp);
} }
void simple_actor_clock::cancel_timeouts(abstract_actor* self) { void simple_actor_clock::cancel_timeouts(abstract_actor* self) {
auto range = actor_lookup_.equal_range(self); auto range = actor_lookup_.equal_range(self->id());
if (range.first == range.second) if (range.first == range.second)
return; return;
for (auto i = range.first; i != range.second; ++i) for (auto i = range.first; i != range.second; ++i)
...@@ -137,14 +69,14 @@ void simple_actor_clock::cancel_timeouts(abstract_actor* self) { ...@@ -137,14 +69,14 @@ void simple_actor_clock::cancel_timeouts(abstract_actor* self) {
void simple_actor_clock::schedule_message(time_point t, void simple_actor_clock::schedule_message(time_point t,
strong_actor_ptr receiver, strong_actor_ptr receiver,
mailbox_element_ptr content) { mailbox_element_ptr content) {
schedule_.emplace(t, actor_msg{std::move(receiver), std::move(content)}); new_schedule_entry<actor_msg>(t, std::move(receiver), std::move(content));
} }
void simple_actor_clock::schedule_message(time_point t, group target, void simple_actor_clock::schedule_message(time_point t, group target,
strong_actor_ptr sender, strong_actor_ptr sender,
message content) { message content) {
schedule_.emplace( new_schedule_entry<group_msg>(t, std::move(target), std::move(sender),
t, group_msg{std::move(target), std::move(sender), std::move(content)}); std::move(content));
} }
void simple_actor_clock::cancel_all() { void simple_actor_clock::cancel_all() {
...@@ -152,5 +84,119 @@ void simple_actor_clock::cancel_all() { ...@@ -152,5 +84,119 @@ void simple_actor_clock::cancel_all() {
schedule_.clear(); schedule_.clear();
} }
void simple_actor_clock::ship(delayed_event& x) {
switch (x.subtype) {
case ordinary_timeout_type: {
auto& dref = static_cast<ordinary_timeout&>(x);
auto& self = dref.self;
self->get()->eq_impl(make_message_id(), self, nullptr,
timeout_msg{dref.type, dref.id});
break;
}
case multi_timeout_type: {
auto& dref = static_cast<ordinary_timeout&>(x);
auto& self = dref.self;
self->get()->eq_impl(make_message_id(), self, nullptr,
timeout_msg{dref.type, dref.id});
break;
}
case request_timeout_type: {
auto& dref = static_cast<request_timeout&>(x);
auto& self = dref.self;
self->get()->eq_impl(dref.id, self, nullptr, sec::request_timeout);
break;
}
case actor_msg_type: {
auto& dref = static_cast<actor_msg&>(x);
dref.receiver->enqueue(std::move(dref.content), nullptr);
break;
}
case group_msg_type: {
auto& dref = static_cast<group_msg&>(x);
dref.target->eq_impl(make_message_id(), std::move(dref.sender), nullptr,
std::move(dref.content));
break;
}
default:
break;
}
}
void simple_actor_clock::handle(const ordinary_timeout_cancellation& x) {
auto pred = [&](const actor_lookup_map::value_type& kvp) {
auto& y = *kvp.second->second;
return y.subtype == ordinary_timeout_type
&& x.type == static_cast<const ordinary_timeout&>(y).type;
};
cancel(x.aid, pred);
}
void simple_actor_clock::handle(const multi_timeout_cancellation& x) {
auto pred = [&](const actor_lookup_map::value_type& kvp) {
auto& y = *kvp.second->second;
if (y.subtype != ordinary_timeout_type)
return false;
auto& dref = static_cast<const multi_timeout&>(y);
return x.type == dref.type && x.id == dref.id;
};
cancel(x.aid, pred);
}
void simple_actor_clock::handle(const request_timeout_cancellation& x) {
auto pred = [&](const actor_lookup_map::value_type& kvp) {
auto& y = *kvp.second->second;
return y.subtype == request_timeout_type
&& x.id == static_cast<const request_timeout&>(y).id;
};
cancel(x.aid, pred);
}
void simple_actor_clock::handle(const timeouts_cancellation& x) {
auto range = actor_lookup_.equal_range(x.aid);
if (range.first == range.second)
return;
for (auto i = range.first; i != range.second; ++i)
schedule_.erase(i->second);
actor_lookup_.erase(range.first, range.second);
}
size_t simple_actor_clock::trigger_expired_timeouts() {
size_t result = 0;
auto t = now();
auto i = schedule_.begin();
auto e = schedule_.end();
while (i != e && i->first <= t) {
auto ptr = std::move(i->second);
auto backlink = ptr->backlink;
if (backlink != actor_lookup_.end())
actor_lookup_.erase(backlink);
i = schedule_.erase(i);
ship(*ptr);
++result;
}
return result;
}
void
simple_actor_clock::add_schedule_entry(time_point t,
std::unique_ptr<ordinary_timeout> x) {
auto aid = x->self->id();
auto type = x->type;
auto pred = [&](const actor_lookup_map::value_type& kvp) {
auto& y = *kvp.second->second;
return y.subtype == ordinary_timeout_type
&& static_cast<const ordinary_timeout&>(y).type == type;
};
auto i = lookup(aid, pred);
if (i != actor_lookup_.end()) {
schedule_.erase(i->second);
i->second = schedule_.emplace(t, std::move(x));
} else {
auto j = schedule_.emplace(t, std::move(x));
i = actor_lookup_.emplace(aid, j);
}
i->second->second->backlink = i;
}
} // namespace detail } // namespace detail
} // namespace caf } // namespace caf
...@@ -46,13 +46,16 @@ bool test_actor_clock::trigger_timeout() { ...@@ -46,13 +46,16 @@ bool test_actor_clock::trigger_timeout() {
CAF_LOG_TRACE(CAF_ARG2("schedule.size", schedule_.size())); CAF_LOG_TRACE(CAF_ARG2("schedule.size", schedule_.size()));
if (schedule_.empty()) if (schedule_.empty())
return false; return false;
visitor f{this};
auto i = schedule_.begin(); auto i = schedule_.begin();
auto tout = i->first; auto tout = i->first;
if (tout > current_time) if (tout > current_time)
current_time = tout; current_time = tout;
visit(f, i->second); auto ptr = std::move(i->second);
schedule_.erase(i); schedule_.erase(i);
auto backlink = ptr->backlink;
if (backlink != actor_lookup_.end())
actor_lookup_.erase(backlink);
ship(*ptr);
return true; return true;
} }
...@@ -60,28 +63,9 @@ size_t test_actor_clock::trigger_timeouts() { ...@@ -60,28 +63,9 @@ size_t test_actor_clock::trigger_timeouts() {
CAF_LOG_TRACE(CAF_ARG2("schedule.size", schedule_.size())); CAF_LOG_TRACE(CAF_ARG2("schedule.size", schedule_.size()));
if (schedule_.empty()) if (schedule_.empty())
return 0u; return 0u;
visitor f{this};
auto result = schedule_.size();
for (auto& kvp : schedule_) {
auto tout = kvp.first;
if (tout > current_time)
current_time = tout;
visit(f, kvp.second);
}
schedule_.clear();
return result;
}
size_t test_actor_clock::trigger_expired_timeouts() {
CAF_LOG_TRACE(CAF_ARG2("schedule.size", schedule_.size()));
visitor f{this};
size_t result = 0; size_t result = 0;
auto i = schedule_.begin(); while (trigger_timeout())
while (i != schedule_.end() && i->first <= current_time) {
++result; ++result;
visit(f, i->second);
i = schedule_.erase(i);
}
return result; return result;
} }
......
...@@ -18,133 +18,148 @@ ...@@ -18,133 +18,148 @@
#include "caf/detail/thread_safe_actor_clock.hpp" #include "caf/detail/thread_safe_actor_clock.hpp"
#include "caf/actor_control_block.hpp"
#include "caf/logger.hpp"
#include "caf/sec.hpp"
#include "caf/system_messages.hpp"
namespace caf { namespace caf {
namespace detail { namespace detail {
namespace {
using guard_type = std::unique_lock<std::recursive_mutex>;
} // namespace <anonymous>
thread_safe_actor_clock::thread_safe_actor_clock() : done_(false) {
// nop
}
void thread_safe_actor_clock::set_ordinary_timeout(time_point t, void thread_safe_actor_clock::set_ordinary_timeout(time_point t,
abstract_actor* self, abstract_actor* self,
atom_value type, atom_value type,
uint64_t id) { uint64_t id) {
guard_type guard{mx_}; push(new ordinary_timeout(t, self->ctrl(), type, id));
if (!done_) {
super::set_ordinary_timeout(t, self, type, id);
cv_.notify_all();
}
} }
void thread_safe_actor_clock::set_request_timeout(time_point t, void thread_safe_actor_clock::set_request_timeout(time_point t,
abstract_actor* self, abstract_actor* self,
message_id id) { message_id id) {
guard_type guard{mx_}; push(new request_timeout(t, self->ctrl(), id));
if (!done_) {
super::set_request_timeout(t, self, id);
cv_.notify_all();
}
} }
void thread_safe_actor_clock::set_multi_timeout(time_point t, abstract_actor* self, void thread_safe_actor_clock::set_multi_timeout(time_point t,
atom_value type, uint64_t id) { abstract_actor* self,
guard_type guard{mx_}; atom_value type, uint64_t id) {
if (!done_) { push(new multi_timeout(t, self->ctrl(), type, id));
super::set_multi_timeout(t, self, type, id);
cv_.notify_all();
}
} }
void thread_safe_actor_clock::cancel_ordinary_timeout(abstract_actor* self, void thread_safe_actor_clock::cancel_ordinary_timeout(abstract_actor* self,
atom_value type) { atom_value type) {
guard_type guard{mx_}; push(new ordinary_timeout_cancellation(self->id(), type));
if (!done_) {
super::cancel_ordinary_timeout(self, type);
cv_.notify_all();
}
} }
void thread_safe_actor_clock::cancel_request_timeout(abstract_actor* self, void thread_safe_actor_clock::cancel_request_timeout(abstract_actor* self,
message_id id) { message_id id) {
guard_type guard{mx_}; push(new request_timeout_cancellation(self->id(), id));
if (!done_) {
super::cancel_request_timeout(self, id);
cv_.notify_all();
}
} }
void thread_safe_actor_clock::cancel_timeouts(abstract_actor* self) { void thread_safe_actor_clock::cancel_timeouts(abstract_actor* self) {
guard_type guard{mx_}; push(new timeouts_cancellation(self->id()));
if (!done_) {
super::cancel_timeouts(self);
cv_.notify_all();
}
} }
void thread_safe_actor_clock::schedule_message(time_point t, void thread_safe_actor_clock::schedule_message(time_point t,
strong_actor_ptr receiver, strong_actor_ptr receiver,
mailbox_element_ptr content) { mailbox_element_ptr content) {
guard_type guard{mx_}; push(new actor_msg(t, std::move(receiver), std::move(content)));
if (!done_) {
super::schedule_message(t, std::move(receiver), std::move(content));
cv_.notify_all();
}
} }
void thread_safe_actor_clock::schedule_message(time_point t, group target, void thread_safe_actor_clock::schedule_message(time_point t, group target,
strong_actor_ptr sender, strong_actor_ptr sender,
message content) { message content) {
guard_type guard{mx_}; push(new group_msg(t, std::move(target), std::move(sender),
if (!done_) { std::move(content)));
super::schedule_message(t, std::move(target), std::move(sender),
std::move(content));
cv_.notify_all();
}
} }
void thread_safe_actor_clock::cancel_all() { void thread_safe_actor_clock::cancel_all() {
guard_type guard{mx_}; push(new drop_all);
super::cancel_all();
cv_.notify_all();
} }
void thread_safe_actor_clock::run_dispatch_loop() { void thread_safe_actor_clock::run_dispatch_loop() {
visitor f{this}; for (;;) {
guard_type guard{mx_}; // Wait until queue is non-empty.
while (done_ == false) {
// Wait for non-empty schedule.
// Note: The thread calling run_dispatch_loop() is guaranteed not to lock
// the mutex recursively. Otherwise, cv_.wait() or cv_.wait_until()
// would be unsafe, because wait operations call unlock() only once.
if (schedule_.empty()) { if (schedule_.empty()) {
cv_.wait(guard); queue_.wait_nonempty();
} else { } else {
auto tout = schedule_.begin()->first; auto t = schedule_.begin()->second->due;
cv_.wait_until(guard, tout); if (!queue_.wait_nonempty(t)) {
// Handle timeout by shipping timed-out events and starting anew.
trigger_expired_timeouts();
continue;
}
} }
// Double-check whether schedule is non-empty and execute it. // Push all elements from the queue to the events buffer.
if (!schedule_.empty()) { auto i = events_.begin();
auto t = now(); auto e = queue_.get_all(i);
auto i = schedule_.begin(); for (; i != e; ++i) {
while (i != schedule_.end() && i->first <= t) { auto& x = *i;
visit(f, i->second); CAF_ASSERT(x != nullptr);
i = schedule_.erase(i); switch (x->subtype) {
case ordinary_timeout_cancellation_type: {
handle(static_cast<ordinary_timeout_cancellation&>(*x));
break;
}
case request_timeout_cancellation_type: {
handle(static_cast<request_timeout_cancellation&>(*x));
break;
}
case timeouts_cancellation_type: {
handle(static_cast<timeouts_cancellation&>(*x));
break;
}
case drop_all_type: {
schedule_.clear();
actor_lookup_.clear();
break;
}
case shutdown_type: {
schedule_.clear();
actor_lookup_.clear();
// Call it a day.
return;
}
case ordinary_timeout_type: {
auto dptr = static_cast<ordinary_timeout*>(x.release());
add_schedule_entry(std::unique_ptr<ordinary_timeout>{dptr});
break;
}
case multi_timeout_type: {
auto dptr = static_cast<multi_timeout*>(x.release());
add_schedule_entry(std::unique_ptr<multi_timeout>{dptr});
break;
}
case request_timeout_type: {
auto dptr = static_cast<request_timeout*>(x.release());
add_schedule_entry(std::unique_ptr<request_timeout>{dptr});
break;
}
case actor_msg_type: {
auto dptr = static_cast<actor_msg*>(x.release());
add_schedule_entry(std::unique_ptr<actor_msg>{dptr});
break;
}
case group_msg_type: {
auto dptr = static_cast<group_msg*>(x.release());
add_schedule_entry(std::unique_ptr<group_msg>{dptr});
break;
}
default: {
CAF_LOG_ERROR("unexpected event type");
break;
}
} }
x.reset();
} }
} }
schedule_.clear();
} }
void thread_safe_actor_clock::cancel_dispatch_loop() { void thread_safe_actor_clock::cancel_dispatch_loop() {
guard_type guard{mx_}; push(new shutdown);
done_ = true; }
cv_.notify_all();
void thread_safe_actor_clock::push(event* ptr) {
queue_.push_back(unique_event_ptr{ptr});
} }
} // namespace detail } // namespace detail
......
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