Commit 57ca0479 authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/message-lifetime' into develop

parents a376b31a e789e5f5
......@@ -173,8 +173,6 @@ class behavior_impl : public ref_counted {
virtual bhvr_invoke_result invoke(message&) = 0;
virtual bhvr_invoke_result invoke(const message&) = 0;
inline bhvr_invoke_result invoke(message&& arg) {
message tmp(std::move(arg));
return invoke(tmp);
......@@ -206,7 +204,7 @@ struct dummy_match_expr {
}
};
template <class MatchExpr, typename F>
template <class MatchExpr, class F = std::function<void()>>
class default_behavior_impl : public behavior_impl {
public:
template <class Expr>
......@@ -223,25 +221,18 @@ class default_behavior_impl : public behavior_impl {
// nop
}
bhvr_invoke_result invoke(message& tup) {
auto res = m_expr(tup);
optional_message_visitor omv;
return apply_visitor(omv, res);
}
bhvr_invoke_result invoke(const message& tup) {
auto res = m_expr(tup);
bhvr_invoke_result invoke(message& msg) override {
auto res = m_expr(msg);
optional_message_visitor omv;
return apply_visitor(omv, res);
}
typename behavior_impl::pointer
copy(const generic_timeout_definition& tdef) const {
return new default_behavior_impl<MatchExpr, std::function<void()>>(m_expr,
tdef);
copy(const generic_timeout_definition& tdef) const override {
return new default_behavior_impl<MatchExpr>(m_expr, tdef);
}
void handle_timeout() {
void handle_timeout() override {
m_fun();
}
......
......@@ -35,21 +35,16 @@
namespace caf {
namespace detail {
class lifted_fun_zipper {
public:
struct lifted_fun_zipper {
template <class F, typename T>
auto operator()(const F& fun, T& arg) -> decltype(fun(arg)) const {
return fun(arg);
}
// forward everything as reference if no guard/transformation is set
template <class T>
auto operator()(const unit_t&, T& arg) const -> decltype(std::ref(arg)) {
return std::ref(arg);
}
};
template <class T>
......@@ -67,62 +62,64 @@ inline bool has_none() {
}
template <class T, class... Ts>
inline bool has_none(const T&, const Ts&... vs) {
bool has_none(const T&, const Ts&... vs) {
return has_none(vs...);
}
template <class T, class... Ts>
inline bool has_none(const optional<T>& v, const Ts&... vs) {
bool has_none(const optional<T>& v, const Ts&... vs) {
return !v || has_none(vs...);
}
// allows F to have fewer arguments than the lifted_fun calling it
template <class R, typename F>
class lifted_fun_invoker {
public:
using arg_types = typename get_callable_trait<F>::arg_types;
static constexpr size_t num_args = tl_size<arg_types>::value;
public:
lifted_fun_invoker(F& fun) : f(fun) {}
lifted_fun_invoker(F& fun) : f(fun) {
// nop
}
template <class... Ts>
typename std::enable_if<sizeof...(Ts) == num_args, R>::type
operator()(Ts&... vs) const {
if (has_none(vs...)) return none;
if (has_none(vs...)) {
return none;
}
return f(unopt(vs)...);
}
template <class T, class... Ts>
typename std::enable_if<(sizeof...(Ts) + 1 > num_args), R>::type
operator()(T& v, Ts&... vs) const {
if (has_none(v)) return none;
if (has_none(v)) {
return none;
}
return (*this)(vs...);
}
private:
F& f;
};
template <class F>
class lifted_fun_invoker<bool, F> {
public:
using arg_types = typename get_callable_trait<F>::arg_types;
static constexpr size_t num_args = tl_size<arg_types>::value;
public:
lifted_fun_invoker(F& fun) : f(fun) {}
lifted_fun_invoker(F& fun) : f(fun) {
// nop
}
template <class... Ts>
typename std::enable_if<sizeof...(Ts) == num_args, bool>::type
operator()(Ts&&... vs) const {
if (has_none(vs...)) return false;
if (has_none(vs...)) {
return false;
}
f(unopt(vs)...);
return true;
}
......@@ -130,14 +127,14 @@ class lifted_fun_invoker<bool, F> {
template <class T, class... Ts>
typename std::enable_if<(sizeof...(Ts) + 1 > num_args), bool>::type
operator()(T&& arg, Ts&&... vs) const {
if (has_none(arg)) return false;
if (has_none(arg)) {
return false;
}
return (*this)(vs...);
}
private:
F& f;
};
/**
......@@ -181,7 +178,9 @@ class lifted_fun {
lifted_fun& operator=(const lifted_fun&) = default;
lifted_fun(F f) : m_fun(std::move(f)) {}
lifted_fun(F f) : m_fun(std::move(f)) {
// nop
}
lifted_fun(F f, projections ps) : m_fun(std::move(f)), m_ps(std::move(ps)) {
// nop
......
......@@ -251,10 +251,13 @@ struct is_legal_tuple_type {
* Checks wheter `T` is a non-const reference.
*/
template <class T>
struct is_mutable_ref {
static constexpr bool value = std::is_reference<T>::value
&& !std::is_const<T>::value;
};
struct is_mutable_ref : std::false_type { };
template <class T>
struct is_mutable_ref<const T&> : std::false_type { };
template <class T>
struct is_mutable_ref<T&> : std::true_type { };
/**
* Checks whether `T::static_type_name()` exists.
......
......@@ -43,13 +43,6 @@
namespace caf {
namespace detail {
template <long N>
struct long_constant {
static constexpr long value = N;
};
using minus1l = long_constant<-1l>;
template <class T1, typename T2>
T2& deduce_const(T1&, T2& rhs) {
return rhs;
......@@ -186,25 +179,10 @@ struct get_case<false, Expr, Trans, Pattern> {
>::type;
};
template <class Fun>
struct has_bool_result {
using result_type = typename Fun::result_type;
static constexpr bool value = std::is_same<bool, result_type>::value;
using token_type = std::integral_constant<bool, value>;
};
template <class T>
bool unroll_expr_result_valid(const T&) {
return true;
}
template <class T>
bool unroll_expr_result_valid(const optional<T>& opt) {
return static_cast<bool>(opt);
}
inline variant<none_t, unit_t> unroll_expr_result_unbox(bool& value) {
if (value) return unit;
inline variant<none_t, unit_t> unroll_expr_result_unbox(bool value) {
if (value) {
return unit;
}
return none;
}
......@@ -213,34 +191,28 @@ T& unroll_expr_result_unbox(optional<T>& opt) {
return *opt;
}
template <class Result, class PPFPs, class Msg>
Result unroll_expr(PPFPs&, minus1l, Msg&) {
// end of recursion
return none;
}
template <class Result, class PPFPs, long N, class Msg>
Result unroll_expr(PPFPs& fs, long_constant<N>, Msg& msg) {
{ // recursively evaluate sub expressions
Result res = unroll_expr<Result>(fs, long_constant<N - 1>{}, msg);
if (!get<none_t>(&res)) {
return res;
}
template <class Result>
struct unroll_expr {
Result operator()(message&) const {
// end of recursion
return none;
}
auto& f = get<N>(fs);
using ft = typename std::decay<decltype(f)>::type;
meta_elements<typename ft::pattern> ms;
typename ft::intermediate_tuple targs;
if ((ft::has_wildcard || ft::type_token == msg.type_token())
&& try_match(msg, ms.arr.data(), ms.arr.size(), targs.data)) {
auto is = detail::get_indices(targs);
auto res = detail::apply_args(f, is, deduce_const(msg, targs));
if (unroll_expr_result_valid(res)) {
return std::move(unroll_expr_result_unbox(res));
template <class F, class... Fs>
Result operator()(message& msg, F& f, Fs&... fs) const {
meta_elements<typename F::pattern> ms;
typename F::intermediate_tuple targs;
if ((F::has_wildcard || F::type_token == msg.type_token())
&& try_match(msg, ms.arr.data(), ms.arr.size(), targs.data)) {
auto is = detail::get_indices(targs);
auto res = detail::apply_args(f, is, deduce_const(msg, targs));
if (res) {
return std::move(unroll_expr_result_unbox(res));
}
}
return (*this)(msg, fs...);
}
return none;
}
};
template <bool IsManipulator, typename T0, typename T1>
struct mexpr_fwd_ {
......@@ -269,20 +241,12 @@ struct mexpr_fwd {
>::type;
};
// detach_if_needed(message tup, bool do_detach)
inline message& detach_if_needed(message& tup, std::true_type) {
tup.force_detach();
return tup;
}
inline message detach_if_needed(const message& tup, std::true_type) {
message cpy{tup};
cpy.force_detach();
return cpy;
inline void detach_if_needed(message& msg, std::true_type) {
msg.force_detach();
}
inline const message& detach_if_needed(const message& tup, std::false_type) {
return tup;
inline void detach_if_needed(const message&, std::false_type) {
// nop
}
inline void* fetch_native_data(message& msg, std::true_type) {
......@@ -298,14 +262,12 @@ struct is_manipulator_case {
// static constexpr bool value = T::second_type::manipulates_args;
using arg_types = typename T::arg_types;
static constexpr bool value = tl_exists<arg_types, is_mutable_ref>::value;
};
template <class T>
struct get_case_result {
// using type = typename T::second_type::result_type;
using type = typename T::result_type;
};
} // namespace detail
......@@ -319,7 +281,6 @@ struct match_result_from_type_list;
template <class... Ts>
struct match_result_from_type_list<detail::type_list<Ts...>> {
using type = variant<none_t, typename lift_void<Ts>::type...>;
};
/**
......@@ -343,11 +304,6 @@ class match_expr {
>::type
>::type;
static constexpr bool has_manipulator =
detail::tl_exists<cases_list, detail::is_manipulator_case>::value;
using idx_token_type = detail::long_constant<sizeof...(Cs) - 1l>;
template <class T, class... Ts>
match_expr(T v, Ts&&... vs) : m_cases(std::move(v), std::forward<Ts>(vs)...) {
// nop
......@@ -357,17 +313,16 @@ class match_expr {
match_expr(const match_expr&) = default;
result_type operator()(const message& tup) {
return apply(tup);
}
result_type operator()(message& tup) {
return apply(tup);
}
result_type operator()(message&& tup) {
message tmp{tup};
return apply(tmp);
result_type operator()(message& msg) {
using mutator_token = std::integral_constant<bool,
detail::tl_exists<
cases_list,
detail::is_manipulator_case
>::value>;
detail::detach_if_needed(msg, mutator_token{});
auto indices = detail::get_indices(m_cases);
detail::unroll_expr<result_type> f;
return detail::apply_args_prefixed(f, indices, m_cases, msg);
}
template <class... Ds>
......@@ -395,20 +350,6 @@ class match_expr {
// std::tuple<type_list<...>, ...>,
// ...>
std::tuple<Cs...> m_cases;
static constexpr size_t cache_size = 10;
using cache_element = std::pair<const std::type_info*, uint64_t>;
template <class Msg>
result_type apply(Msg& msg) {
idx_token_type idx_token;
std::integral_constant<bool, has_manipulator> mutator_token;
// returns either a reference or a new object
using detached = decltype(detail::detach_if_needed(msg, mutator_token));
detached mref = detail::detach_if_needed(msg, mutator_token);
return detail::unroll_expr<result_type>(m_cases, idx_token, mref);
}
};
template <class T>
......@@ -507,22 +448,26 @@ behavior_impl_ptr match_expr_concat(const T0& arg0, const T1& arg1,
// some more convenience functions
template <class F,
class E = typename std::enable_if<is_callable<F>::value>::type>
match_expr<typename get_case<false, F, type_list<>, type_list<>>::type>
lift_to_match_expr(F fun) {
template <class T,
class E = typename std::enable_if<
is_callable<T>::value && !is_match_expr<T>::value
>::type>
match_expr<typename get_case<false, T, type_list<>, type_list<>>::type>
lift_to_match_expr(T arg) {
using result_type =
typename get_case<
false,
F,
T,
detail::empty_type_list,
detail::empty_type_list
>::type;
return result_type{std::move(fun)};
return result_type{std::move(arg)};
}
template <class T,
class E = typename std::enable_if<!is_callable<T>::value>::type>
class E = typename std::enable_if<
!is_callable<T>::value || is_match_expr<T>::value
>::type>
T lift_to_match_expr(T arg) {
return arg;
}
......
......@@ -160,7 +160,7 @@ class invoke_policy {
if (ref_opt) {
auto fhdl = fetch_response_promise(self, hdl);
behavior inner = *ref_opt;
*ref_opt = behavior {
ref_opt->assign(
others() >> [=] {
// inner is const inside this lambda and mutable a C++14 feature
behavior cpy = inner;
......@@ -169,7 +169,7 @@ class invoke_policy {
fhdl.deliver(*inner_res);
}
}
};
);
}
} else {
// respond by using the result of 'fun'
......
......@@ -24,9 +24,10 @@
#include "caf/detail/type_traits.hpp"
#include "caf/detail/variant_data.hpp"
#define CAF_VARIANT_CASE(x) \
case x: return visitor(from.get(std::integral_constant<int, \
x < max_type_id ? x : max_type_id >()))
#define CAF_VARIANT_CASE(x) \
case x: \
return visitor(from.get( \
std::integral_constant<int, (x < max_type_id ? x : max_type_id)>()))
namespace caf {
......@@ -36,7 +37,7 @@ struct variant_assign_helper {
T& lhs;
variant_assign_helper(T& lhs_ref) : lhs(lhs_ref) { }
template <class U>
inline void operator()(const U& rhs) const {
void operator()(const U& rhs) const {
lhs = rhs;
}
};
......@@ -47,17 +48,18 @@ struct variant_move_helper {
T& lhs;
variant_move_helper(T& lhs_ref) : lhs(lhs_ref) { }
template <class U>
inline void operator()(U& rhs) const {
void operator()(U& rhs) const {
lhs = std::move(rhs);
}
};
template <class T, typename U,
bool Enable = std::is_integral<T>::value && std::is_integral<U>::value>
template <class T, class U,
bool Enable = std::is_integral<T>::value
&& std::is_integral<U>::value>
struct is_equal_int_type {
static constexpr bool value =
sizeof(T) == sizeof(U)
&& std::is_signed<T>::value == std::is_signed<U>::value;
static constexpr bool value = sizeof(T) == sizeof(U)
&& std::is_signed<T>::value
== std::is_signed<U>::value;
};
template <class T, typename U>
......@@ -70,26 +72,25 @@ struct is_equal_int_type<T, U, false> : std::false_type { };
* `uint8_t != unsigned char on some compilers.
*/
template <class T, typename U>
struct is_same_ish : std::conditional<
std::is_same<T, U>::value,
std::true_type,
is_equal_int_type<T, U>
>::type { };
struct is_same_ish
: std::conditional<
std::is_same<T, U>::value,
std::true_type,
is_equal_int_type<T, U>
>::type { };
/**
* A variant represents always a valid value of one of the types `Ts...`.
*/
template <class... Ts>
class variant {
public:
using types = detail::type_list<Ts...>;
static constexpr int max_type_id = sizeof...(Ts) - 1;
static_assert(!detail::tl_exists<types, std::is_reference>::value,
"Cannot create a variant of references");
"Cannot create a variant of references");
variant& operator=(const variant& other) {
variant_assign_helper<variant> helper{*this};
......@@ -134,20 +135,19 @@ class variant {
}
/** @cond PRIVATE */
template <int Pos>
inline bool is(std::integral_constant<int, Pos>) const {
bool is(std::integral_constant<int, Pos>) const {
return m_type == Pos;
}
template <int Pos>
inline const typename detail::tl_at<types, Pos>::type&
const typename detail::tl_at<types, Pos>::type&
get(std::integral_constant<int, Pos> token) const {
return m_data.get(token);
}
template <int Pos>
inline typename detail::tl_at<types, Pos>::type&
typename detail::tl_at<types, Pos>::type&
get(std::integral_constant<int, Pos> token) {
return m_data.get(token);
}
......@@ -161,11 +161,9 @@ class variant {
typename Visitor::result_type apply(Visitor& visitor) {
return apply_impl(*this, visitor);
}
/** @endcond */
private:
template <class Self, typename Visitor>
static typename Visitor::result_type apply_impl(Self& from, Visitor& visitor) {
switch (from.m_type) {
......@@ -222,30 +220,29 @@ class variant {
}
template <class... Us>
inline void set(const variant<Us...>& other) {
void set(const variant<Us...>& other) {
using namespace detail;
static_assert(tl_is_strict_subset<type_list<Us...>, types>::value,
"cannot set variant of type A to variant of type B "
"unless the element types of A are a strict subset of "
"the element types of B");
"cannot set variant of type A to variant of type B "
"unless the element types of A are a strict subset of "
"the element types of B");
variant_assign_helper<variant> helper{*this};
other.apply(helper);
}
template <class... Us>
inline void set(variant<Us...>&& other) {
void set(variant<Us...>&& other) {
using namespace detail;
static_assert(tl_is_strict_subset<type_list<Us...>, types>::value,
"cannot set variant of type A to variant of type B "
"unless the element types of A are a strict subset of "
"the element types of B");
"cannot set variant of type A to variant of type B "
"unless the element types of A are a strict subset of "
"the element types of B");
variant_move_helper<variant> helper{*this};
other.apply(helper);
}
int m_type;
detail::variant_data<typename lift_void<Ts>::type...> m_data;
};
/**
......@@ -254,9 +251,10 @@ class variant {
template <class T, class... Us>
T& get(variant<Us...>& value) {
using namespace detail;
constexpr int type_id = tl_find_if<type_list<Us...>,
tbind<is_same_ish, T>::template type
>::value;
constexpr int type_id = tl_find_if<
type_list<Us...>,
tbind<is_same_ish, T>::template type
>::value;
std::integral_constant<int, type_id> token;
// silence compiler error about "binding to unrelated types" such as
// 'signed char' to 'char' (which is obvious bullshit)
......@@ -278,11 +276,14 @@ const T& get(const variant<Us...>& value) {
template <class T, class... Us>
T* get(variant<Us...>* value) {
using namespace detail;
constexpr int type_id = tl_find_if<type_list<Us...>,
tbind<is_same_ish, T>::template type
>::value;
constexpr int type_id = tl_find_if<
type_list<Us...>,
tbind<is_same_ish, T>::template type
>::value;
std::integral_constant<int, type_id> token;
if (value->is(token)) return &get<T>(*value);
if (value->is(token)) {
return &get<T>(*value);
}
return nullptr;
}
......
......@@ -30,14 +30,7 @@ class combinator : public behavior_impl {
public:
bhvr_invoke_result invoke(message& arg) {
auto res = first->invoke(arg);
if (!res) return second->invoke(arg);
return res;
}
bhvr_invoke_result invoke(const message& arg) {
auto res = first->invoke(arg);
if (!res) return second->invoke(arg);
return res;
return res ? res : second->invoke(arg);
}
void handle_timeout() {
......@@ -78,9 +71,8 @@ behavior_impl::pointer behavior_impl::or_else(const pointer& other) {
}
behavior_impl* new_default_behavior(duration d, std::function<void()> fun) {
using impl = default_behavior_impl<dummy_match_expr, std::function<void()>>;
dummy_match_expr nop;
return new impl(nop, d, std::move(fun));
return new default_behavior_impl<dummy_match_expr>(nop, d, std::move(fun));
}
} // namespace detail
......
......@@ -74,6 +74,7 @@ message_data* message_data::ptr::get_detached() {
if (!p->unique()) {
auto np = p->copy();
m_ptr.reset(np);
CAF_REQUIRE(np->unique());
return np;
}
return p;
......
......@@ -25,7 +25,7 @@ ref_counted::~ref_counted() {
// nop
}
ref_counted::ref_counted(const ref_counted&) {
ref_counted::ref_counted(const ref_counted&) : m_rc(0) {
// nop; don't copy reference count
}
......
......@@ -35,6 +35,7 @@ add_unit_test(constructor_attach)
add_unit_test(custom_exception_handler)
add_unit_test(typed_spawn)
add_unit_test(actor_lifetime)
add_unit_test(message_lifetime)
add_unit_test(local_group)
add_unit_test(sync_send)
add_unit_test(broker)
......
#include <atomic>
#include <iostream>
#include "test.hpp"
#include "caf/all.hpp"
using std::cout;
using std::endl;
using namespace caf;
class testee : public event_based_actor {
public:
testee();
~testee();
behavior make_behavior() override;
};
testee::testee() {
// nop
}
testee::~testee() {
// nop
}
behavior testee::make_behavior() {
return {
others() >> [=] {
CAF_CHECK_EQUAL(last_dequeued().cvals()->get_reference_count(), 2);
quit();
return last_dequeued();
}
};
}
class tester : public event_based_actor {
public:
tester(actor aut);
~tester();
behavior make_behavior() override;
private:
actor m_aut;
message m_msg;
};
tester::tester(actor aut) :
m_aut(std::move(aut)),
m_msg(make_message(1, 2, 3)) {
// nop
}
tester::~tester() {
// nop
}
behavior tester::make_behavior() {
monitor(m_aut);
send(m_aut, m_msg);
return {
on(1, 2, 3) >> [=] {
CAF_CHECK_EQUAL(last_dequeued().cvals()->get_reference_count(), 2);
CAF_CHECK(last_dequeued().cvals().get() == m_msg.cvals().get());
},
[=](const down_msg& dm) {
CAF_CHECK(dm.source == m_aut);
CAF_CHECK_EQUAL(dm.reason, exit_reason::normal);
CAF_CHECK_EQUAL(last_dequeued().cvals()->get_reference_count(), 1);
quit();
},
others() >> CAF_UNEXPECTED_MSG_CB(this)
};
}
void test_message_lifetime_in_scoped_actor() {
auto msg = make_message(1, 2, 3);
scoped_actor self;
self->send(self, msg);
self->receive(
on(1, 2, 3) >> [&] {
CAF_CHECK_EQUAL(msg.cvals()->get_reference_count(), 2);
CAF_CHECK_EQUAL(self->last_dequeued().cvals()->get_reference_count(), 2);
CAF_CHECK(self->last_dequeued().cvals().get() == msg.cvals().get());
}
);
CAF_CHECK_EQUAL(msg.cvals()->get_reference_count(), 1);
msg = make_message(42);
self->send(self, msg);
self->receive(
[&](int& value) {
CAF_CHECK_EQUAL(msg.cvals()->get_reference_count(), 1);
CAF_CHECK_EQUAL(self->last_dequeued().cvals()->get_reference_count(), 1);
CAF_CHECK(self->last_dequeued().cvals().get() != msg.cvals().get());
value = 10;
}
);
CAF_CHECK_EQUAL(msg.get_as<int>(0), 42);
}
void test_message_lifetime() {
test_message_lifetime_in_scoped_actor();
if (CAF_TEST_RESULT() != 0) {
return;
}
// put some preassure on the scheduler (check for thread safety)
for (size_t i = 0; i < 100; ++i) {
spawn<tester>(spawn<testee>());
}
}
int main() {
CAF_TEST(test_message_lifetime);
test_message_lifetime();
await_all_actors_done();
shutdown();
return CAF_TEST_RESULT();
}
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