Commit dd49a57d authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/remoting-overhaul' into develop

parents 456e7d04 3cb67a3c
No preview for this file type
doc/basp_header.png

39.3 KB | W: | H:

doc/basp_header.png

76.8 KB | W: | H:

doc/basp_header.png
doc/basp_header.png
doc/basp_header.png
doc/basp_header.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -35,7 +35,6 @@ using plus_atom = atom_constant<atom("plus")>; ...@@ -35,7 +35,6 @@ using plus_atom = atom_constant<atom("plus")>;
using minus_atom = atom_constant<atom("minus")>; using minus_atom = atom_constant<atom("minus")>;
using result_atom = atom_constant<atom("result")>; using result_atom = atom_constant<atom("result")>;
using rebind_atom = atom_constant<atom("rebind")>; using rebind_atom = atom_constant<atom("rebind")>;
using connect_atom = atom_constant<atom("connect")>;
using reconnect_atom = atom_constant<atom("reconnect")>; using reconnect_atom = atom_constant<atom("reconnect")>;
// our "service" // our "service"
...@@ -105,9 +104,13 @@ private: ...@@ -105,9 +104,13 @@ private:
behavior reconnecting(std::function<void()> continuation = nullptr) { behavior reconnecting(std::function<void()> continuation = nullptr) {
using std::chrono::seconds; using std::chrono::seconds;
auto mm = io::get_middleman_actor(); auto mm = io::get_middleman_actor();
send(mm, get_atom::value, host_, port_); send(mm, connect_atom::value, host_, port_);
return { return {
[=](ok_atom, const actor_addr& new_server) { [=](ok_atom, node_id&, actor_addr& new_server, std::set<std::string>&) {
if (new_server == invalid_actor_addr) {
aout(this) << "*** received invalid remote actor" << endl;
return;
}
aout(this) << "*** connection succeeded, awaiting tasks" << endl; aout(this) << "*** connection succeeded, awaiting tasks" << endl;
server_ = actor_cast<actor>(new_server); server_ = actor_cast<actor>(new_server);
// return to previous behavior // return to previous behavior
...@@ -122,7 +125,7 @@ private: ...@@ -122,7 +125,7 @@ private:
<< ": " << errstr << ": " << errstr
<< " [try again in 3s]" << " [try again in 3s]"
<< endl; << endl;
delayed_send(mm, seconds(3), get_atom::value, host_, port_); delayed_send(mm, seconds(3), connect_atom::value, host_, port_);
}, },
[=](rebind_atom, string& nhost, uint16_t nport) { [=](rebind_atom, string& nhost, uint16_t nport) {
aout(this) << "*** rebind to " << nhost << ":" << nport << endl; aout(this) << "*** rebind to " << nhost << ":" << nport << endl;
...@@ -131,13 +134,17 @@ private: ...@@ -131,13 +134,17 @@ private:
swap(port_, nport); swap(port_, nport);
auto send_mm = [=] { auto send_mm = [=] {
unbecome(); unbecome();
send(mm, get_atom::value, host_, port_); send(mm, connect_atom::value, host_, port_);
}; };
// await pending ok/error message first, then send new request to MM // await pending ok/error message first, then send new request to MM
become( become(
keep_behavior, keep_behavior,
[=](ok_atom&, actor_addr&) { send_mm(); }, [=](ok_atom&, actor_addr&) {
[=](error_atom&, string&) { send_mm(); } send_mm();
},
[=](error_atom&, string&) {
send_mm();
}
); );
}, },
// simply ignore all requests until we have a connection // simply ignore all requests until we have a connection
......
...@@ -24,6 +24,7 @@ set (LIBCAF_CORE_SRCS ...@@ -24,6 +24,7 @@ set (LIBCAF_CORE_SRCS
src/actor_proxy.cpp src/actor_proxy.cpp
src/actor_registry.cpp src/actor_registry.cpp
src/attachable.cpp src/attachable.cpp
src/announce_actor_type.cpp
src/behavior.cpp src/behavior.cpp
src/behavior_stack.cpp src/behavior_stack.cpp
src/behavior_impl.cpp src/behavior_impl.cpp
...@@ -77,7 +78,8 @@ set (LIBCAF_CORE_SRCS ...@@ -77,7 +78,8 @@ set (LIBCAF_CORE_SRCS
src/sync_request_bouncer.cpp src/sync_request_bouncer.cpp
src/try_match.cpp src/try_match.cpp
src/uniform_type_info.cpp src/uniform_type_info.cpp
src/uniform_type_info_map.cpp) src/uniform_type_info_map.cpp
src/whereis.cpp)
add_custom_target(libcaf_core) add_custom_target(libcaf_core)
......
...@@ -156,6 +156,8 @@ protected: ...@@ -156,6 +156,8 @@ protected:
public: public:
/// @cond PRIVATE /// @cond PRIVATE
static actor_id latest_actor_id();
enum linking_operation { enum linking_operation {
establish_link_op, establish_link_op,
establish_backlink_op, establish_backlink_op,
...@@ -171,6 +173,8 @@ public: ...@@ -171,6 +173,8 @@ public:
static constexpr int is_blocking_flag = 0x10; // blocking_actor static constexpr int is_blocking_flag = 0x10; // blocking_actor
static constexpr int is_detached_flag = 0x20; // local_actor static constexpr int is_detached_flag = 0x20; // local_actor
static constexpr int is_priority_aware_flag = 0x40; // local_actor static constexpr int is_priority_aware_flag = 0x40; // local_actor
static constexpr int is_serializable_flag = 0x40; // local_actor
static constexpr int is_migrated_from_flag = 0x80; // local_actor
inline void set_flag(bool enable_flag, int mask) { inline void set_flag(bool enable_flag, int mask) {
auto x = flags(); auto x = flags();
...@@ -227,6 +231,22 @@ public: ...@@ -227,6 +231,22 @@ public:
set_flag(value, is_priority_aware_flag); set_flag(value, is_priority_aware_flag);
} }
inline bool is_serializable() const {
return get_flag(is_serializable_flag);
}
inline void is_serializable(bool value) {
set_flag(value, is_serializable_flag);
}
inline bool is_migrated_from() const {
return get_flag(is_migrated_from_flag);
}
inline void is_migrated_from(bool value) {
set_flag(value, is_migrated_from_flag);
}
// Tries to run a custom exception handler for `eptr`. // Tries to run a custom exception handler for `eptr`.
optional<uint32_t> handle(const std::exception_ptr& eptr); optional<uint32_t> handle(const std::exception_ptr& eptr);
......
...@@ -135,6 +135,11 @@ public: ...@@ -135,6 +135,11 @@ public:
/// Exchange content of `*this` and `other`. /// Exchange content of `*this` and `other`.
void swap(actor& other) noexcept; void swap(actor& other) noexcept;
/// Returns the interface definition for this actor handle.
static std::set<std::string> message_types() {
return std::set<std::string>{};
}
/// @cond PRIVATE /// @cond PRIVATE
inline abstract_actor* operator->() const noexcept { inline abstract_actor* operator->() const noexcept {
......
...@@ -20,9 +20,9 @@ ...@@ -20,9 +20,9 @@
#ifndef CAF_ACTOR_NAMESPACE_HPP #ifndef CAF_ACTOR_NAMESPACE_HPP
#define CAF_ACTOR_NAMESPACE_HPP #define CAF_ACTOR_NAMESPACE_HPP
#include <map>
#include <utility> #include <utility>
#include <functional> #include <functional>
#include <unordered_map>
#include "caf/node_id.hpp" #include "caf/node_id.hpp"
#include "caf/actor_cast.hpp" #include "caf/actor_cast.hpp"
...@@ -56,7 +56,7 @@ public: ...@@ -56,7 +56,7 @@ public:
/// Writes an actor address to `sink` and adds the actor /// Writes an actor address to `sink` and adds the actor
/// to the list of known actors for a later deserialization. /// to the list of known actors for a later deserialization.
void write(serializer* sink, const actor_addr& ptr); void write(serializer* sink, const actor_addr& ptr) const;
/// Reads an actor address from `source,` creating /// Reads an actor address from `source,` creating
/// addresses for remote actors on the fly if needed. /// addresses for remote actors on the fly if needed.
...@@ -120,7 +120,7 @@ public: ...@@ -120,7 +120,7 @@ public:
private: private:
backend& backend_; backend& backend_;
std::map<key_type, proxy_map> proxies_; std::unordered_map<key_type, proxy_map> proxies_;
}; };
} // namespace caf } // namespace caf
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#define CAF_ATOM_HPP #define CAF_ATOM_HPP
#include <string> #include <string>
#include <functional>
#include <type_traits> #include <type_traits>
#include "caf/detail/atom_val.hpp" #include "caf/detail/atom_val.hpp"
...@@ -68,6 +69,9 @@ using get_atom = atom_constant<atom("GET")>; ...@@ -68,6 +69,9 @@ using get_atom = atom_constant<atom("GET")>;
/// Generic 'PUT' atom for request operations. /// Generic 'PUT' atom for request operations.
using put_atom = atom_constant<atom("PUT")>; using put_atom = atom_constant<atom("PUT")>;
/// Generic 'UPDATE' atom, e.g., or signalizing updates in a key-value store.
using update_atom = atom_constant<atom("UPDATE")>;
/// Generic 'DELETE' atom for request operations. /// Generic 'DELETE' atom for request operations.
using delete_atom = atom_constant<atom("DELETE")>; using delete_atom = atom_constant<atom("DELETE")>;
...@@ -104,6 +108,45 @@ using link_atom = atom_constant<atom("LINK")>; ...@@ -104,6 +108,45 @@ using link_atom = atom_constant<atom("LINK")>;
/// Generic 'UNLINK' atom for removing networked links. /// Generic 'UNLINK' atom for removing networked links.
using unlink_atom = atom_constant<atom("UNLINK")>; using unlink_atom = atom_constant<atom("UNLINK")>;
/// Generic 'PUBLISH' atom, e.g., for publishing actors at a given port.
using publish_atom = atom_constant<atom("PUBLISH")>;
/// Generic 'UNPUBLISH' atom, e.g., for removing an actor/port mapping.
using unpublish_atom = atom_constant<atom("UNPUBLISH")>;
/// Generic 'PUBLISH' atom, e.g., for publishing actors at a given port.
using subscribe_atom = atom_constant<atom("SUBSCRIBE")>;
/// Generic 'UNPUBLISH' atom, e.g., for removing an actor/port mapping.
using unsubscribe_atom = atom_constant<atom("UNSUBSCRIB")>;
/// Generic 'CONNECT' atom, e.g., for connecting to remote CAF instances.
using connect_atom = atom_constant<atom("CONNECT")>;
/// Generic 'OPEN' atom, e.g., for opening a port or file.
using open_atom = atom_constant<atom("OPEN")>;
/// Generic 'CLOSE' atom, e.g., for closing a port or file.
using close_atom = atom_constant<atom("CLOSE")>;
/// Generic 'SPAWN' atom, e.g., for spawning remote actors.
using spawn_atom = atom_constant<atom("SPAWN")>;
/// Atom to signalize an actor to migrate its state to another actor.
using migrate_atom = atom_constant<atom("MIGRATE")>;
} // namespace caf } // namespace caf
namespace std {
template <>
struct hash<caf::atom_value> {
size_t operator()(caf::atom_value x) const {
hash<uint64_t> f;
return f(static_cast<uint64_t>(x));
}
};
} // namespace std
#endif // CAF_ATOM_HPP #endif // CAF_ATOM_HPP
...@@ -46,6 +46,8 @@ class blocking_actor ...@@ -46,6 +46,8 @@ class blocking_actor
: public extend<local_actor, blocking_actor>:: : public extend<local_actor, blocking_actor>::
with<mixin::sync_sender<blocking_response_handle_tag>::impl> { with<mixin::sync_sender<blocking_response_handle_tag>::impl> {
public: public:
using behavior_type = behavior;
blocking_actor(); blocking_actor();
~blocking_actor(); ~blocking_actor();
......
...@@ -127,6 +127,11 @@ operator>>(deserializer& source, T& value) { ...@@ -127,6 +127,11 @@ operator>>(deserializer& source, T& value) {
return source.read(value, uniform_typeid<T>()); return source.read(value, uniform_typeid<T>());
} }
template <class T>
void operator&(deserializer& source, T& value) {
source >> value;
}
} // namespace caf } // namespace caf
#endif // CAF_DESERIALIZER_HPP #endif // CAF_DESERIALIZER_HPP
...@@ -20,13 +20,14 @@ ...@@ -20,13 +20,14 @@
#ifndef CAF_DETAIL_ACTOR_REGISTRY_HPP #ifndef CAF_DETAIL_ACTOR_REGISTRY_HPP
#define CAF_DETAIL_ACTOR_REGISTRY_HPP #define CAF_DETAIL_ACTOR_REGISTRY_HPP
#include <map>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include <unordered_map>
#include <condition_variable> #include <condition_variable>
#include "caf/actor.hpp"
#include "caf/actor_addr.hpp" #include "caf/actor_addr.hpp"
#include "caf/abstract_actor.hpp" #include "caf/abstract_actor.hpp"
...@@ -38,10 +39,8 @@ namespace detail { ...@@ -38,10 +39,8 @@ namespace detail {
class singletons; class singletons;
class actor_registry : public singleton_mixin<actor_registry> { class actor_registry {
public: public:
friend class singleton_mixin<actor_registry>;
~actor_registry(); ~actor_registry();
/// A registry entry consists of a pointer to the actor and an /// A registry entry consists of a pointer to the actor and an
...@@ -82,19 +81,37 @@ public: ...@@ -82,19 +81,37 @@ public:
// blocks the caller until running-actors-count becomes `expected` // blocks the caller until running-actors-count becomes `expected`
void await_running_count_equal(size_t expected); void await_running_count_equal(size_t expected);
actor get_named(atom_value key) const;
void put_named(atom_value key, actor value);
using named_entries = std::unordered_map<atom_value, actor>;
named_entries named_actors() const;
static actor_registry* create_singleton();
void dispose();
void stop();
void initialize();
private: private:
using entries = std::map<actor_id, value_type>; using entries = std::unordered_map<actor_id, value_type>;
actor_registry(); actor_registry();
std::atomic<size_t> running_; std::atomic<size_t> running_;
std::atomic<actor_id> ids_;
std::mutex running_mtx_; std::mutex running_mtx_;
std::condition_variable running_cv_; std::condition_variable running_cv_;
mutable detail::shared_spinlock instances_mtx_; mutable detail::shared_spinlock instances_mtx_;
entries entries_; entries entries_;
named_entries named_entries_;
mutable detail::shared_spinlock named_entries_mtx_;
}; };
} // namespace detail } // namespace detail
......
...@@ -493,6 +493,26 @@ public: ...@@ -493,6 +493,26 @@ public:
static constexpr bool value = false; static constexpr bool value = false;
}; };
// checks whether T is serializable using a free function `serialize` taking
// either a `caf::serializer` or `caf::deserializer` as first argument
template <class T>
struct is_serializable {
private:
template <class U>
static int8_t fun(U*, decltype(serialize(std::declval<serializer&>(),
std::declval<U&>(), 0))* = nullptr,
decltype(serialize(std::declval<deserializer&>(),
std::declval<U&>(), 0))* = nullptr);
static int16_t fun(...);
public:
static constexpr bool value = sizeof(fun(static_cast<T*>(nullptr))) == 1;
};
template <class T>
constexpr bool is_serializable<T>::value;
} // namespace detail } // namespace detail
} // namespace caf } // namespace caf
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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_EXPERIMENTAL_ANNOUNCE_ACTOR_HPP
#define CAF_EXPERIMENTAL_ANNOUNCE_ACTOR_HPP
#include <type_traits>
#include "caf/actor.hpp"
#include "caf/actor_addr.hpp"
#include "caf/infer_handle.hpp"
#include "caf/detail/singletons.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/detail/actor_registry.hpp"
namespace caf {
namespace experimental {
using spawn_result = std::pair<actor_addr, std::set<std::string>>;
using spawn_fun = std::function<spawn_result (message)>;
template <class Trait, class F>
spawn_result dyn_spawn_impl(F ibf) {
using impl = typename Trait::impl;
using behavior_t = typename Trait::behavior_type;
auto ptr = make_counted<impl>();
ptr->initial_behavior_fac([=](local_actor* self) -> behavior {
auto res = ibf(self);
if (res && res->size() > 0 && res->template match_element<behavior_t>(0)) {
return std::move(res->template get_as_mutable<behavior_t>(0).unbox());
}
return {};
});
ptr->launch(nullptr, false, false);
return {ptr->address(), Trait::type::message_types()};
}
template <class Trait, class F>
spawn_result dyn_spawn(F fun, message& msg,
spawn_mode_token<spawn_mode::function>) {
return dyn_spawn_impl<Trait>([=](local_actor*) -> optional<message> {
return const_cast<message&>(msg).apply(fun);
});
}
template <class Trait, class F>
spawn_result dyn_spawn(F fun, message& msg,
spawn_mode_token<spawn_mode::function_with_selfptr>) {
return dyn_spawn_impl<Trait>([=](local_actor* self) -> optional<message> {
// we can't use make_message here because of the implicit conversions
using storage = detail::tuple_vals<typename Trait::impl*>;
auto ptr = make_counted<storage>(static_cast<typename Trait::impl*>(self));
auto m = message{detail::message_data::cow_ptr{std::move(ptr)}} + msg;
return m.apply(fun);
});
}
template <class F>
spawn_fun make_spawn_fun(F fun) {
return [fun](message msg) -> spawn_result {
using trait = infer_handle_from_fun<F>;
spawn_mode_token<trait::mode> tk;
return dyn_spawn<trait>(fun, msg, tk);
};
}
template <class T, class... Ts>
spawn_fun make_spawn_fun() {
static_assert(std::is_same<T*, decltype(new T(std::declval<Ts>()...))>::value,
"no constructor for T(Ts...) exists");
static_assert(detail::conjunction<
std::is_lvalue_reference<Ts>::value...
>::value,
"all Ts must be lvalue references");
static_assert(std::is_base_of<local_actor, T>::value,
"T is not derived from local_actor");
using handle = typename infer_handle_from_class<T>::type;
using pointer = intrusive_ptr<T>;
auto factory = &make_counted<T, Ts...>;
return [=](message msg) -> spawn_result {
pointer ptr;
auto res = msg.apply(factory);
if (! res || res->empty() || ! res->template match_element<pointer>(0))
return {};
ptr = std::move(res->template get_as_mutable<pointer>(0));
ptr->launch(nullptr, false, false);
return {ptr->address(), handle::message_types()};
};
}
actor spawn_announce_actor_type_server();
void announce_actor_type_impl(std::string&& name, spawn_fun f);
template <class F>
void announce_actor_type(std::string name, F fun) {
announce_actor_type_impl(std::move(name), make_spawn_fun(fun));
}
template <class T, class... Ts>
void announce_actor_type(std::string name) {
announce_actor_type_impl(std::move(name), make_spawn_fun<T, Ts...>());
}
} // namespace experimental
} // namespace caf
#endif // CAF_EXPERIMENTAL_ANNOUNCE_ACTOR_HPP
...@@ -29,6 +29,18 @@ ...@@ -29,6 +29,18 @@
namespace caf { namespace caf {
namespace experimental { namespace experimental {
template <class Archive, class U>
typename std::enable_if<detail::is_serializable<U>::value>::type
serialize_state(Archive& ar, U& st, const unsigned int version) {
serialize(ar, st, version);
}
template <class Archive, class U>
typename std::enable_if<! detail::is_serializable<U>::value>::type
serialize_state(Archive&, U&, const unsigned int) {
throw std::logic_error("serialize_state with unserializable type called");
}
/// An event-based actor with managed state. The state is constructed /// An event-based actor with managed state. The state is constructed
/// before `make_behavior` will get called and destroyed after the /// before `make_behavior` will get called and destroyed after the
/// actor called `quit`. This state management brakes cycles and /// actor called `quit`. This state management brakes cycles and
...@@ -39,7 +51,8 @@ class stateful_actor : public Base { ...@@ -39,7 +51,8 @@ class stateful_actor : public Base {
public: public:
template <class... Ts> template <class... Ts>
stateful_actor(Ts&&... xs) : Base(std::forward<Ts>(xs)...), state(state_) { stateful_actor(Ts&&... xs) : Base(std::forward<Ts>(xs)...), state(state_) {
// nop if (detail::is_serializable<State>::value)
this->is_serializable(true);
} }
~stateful_actor() { ~stateful_actor() {
...@@ -55,6 +68,14 @@ public: ...@@ -55,6 +68,14 @@ public:
return get_name(state_); return get_name(state_);
} }
void save(serializer& sink, const unsigned int version) override {
serialize_state(sink, state, version);
}
void load(deserializer& source, const unsigned int version) override {
serialize_state(source, state, version);
}
/// A reference to the actor's state. /// A reference to the actor's state.
State& state; State& state;
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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_EXPERIMENTAL_WHEREIS_HPP
#define CAF_EXPERIMENTAL_WHEREIS_HPP
#include "caf/fwd.hpp"
#include "caf/atom.hpp"
namespace caf {
namespace experimental {
actor whereis(atom_value registered_name);
} // namespace experimental
} // namespace caf
#endif // CAF_EXPERIMENTAL_WHEREIS_HPP
...@@ -56,6 +56,8 @@ class mailbox_element; ...@@ -56,6 +56,8 @@ class mailbox_element;
class message_handler; class message_handler;
class uniform_type_info; class uniform_type_info;
class event_based_actor; class event_based_actor;
class binary_serializer;
class binary_deserializer;
class forwarding_actor_proxy; class forwarding_actor_proxy;
// structs // structs
...@@ -87,8 +89,16 @@ template <class T, typename U> ...@@ -87,8 +89,16 @@ template <class T, typename U>
T actor_cast(const U&); T actor_cast(const U&);
namespace io { namespace io {
class broker;
class middleman; class broker;
class middleman;
namespace basp {
struct header;
} // namespace basp
} // namespace io } // namespace io
namespace scheduler { namespace scheduler {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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_INFER_HANDLE_HPP
#define CAF_INFER_HANDLE_HPP
#include "caf/actor.hpp"
#include "caf/actor_addr.hpp"
#include "caf/typed_behavior.hpp"
#include "caf/typed_event_based_actor.hpp"
namespace caf {
enum class spawn_mode {
function,
function_with_selfptr,
clazz
};
template <spawn_mode X>
using spawn_mode_token = std::integral_constant<spawn_mode, X>;
// default: dynamically typed actor without self pointer
template <class Result,
class FirstArg,
bool FirstArgValid =
std::is_base_of<
local_actor,
typename std::remove_pointer<FirstArg>::type
>::value>
struct infer_handle_from_fun_impl {
using type = actor;
using impl = event_based_actor;
using behavior_type = behavior;
static constexpr spawn_mode mode = spawn_mode::function;
};
// dynamically typed actor returning a behavior
template <class Impl>
struct infer_handle_from_fun_impl<void, Impl*, true> {
using type = actor;
using impl = Impl;
using behavior_type = behavior;
static constexpr spawn_mode mode = spawn_mode::function_with_selfptr;
};
// dynamically typed actor with self pointer
template <class Impl>
struct infer_handle_from_fun_impl<behavior, Impl*, true> {
using type = actor;
using impl = Impl;
using behavior_type = behavior;
static constexpr spawn_mode mode = spawn_mode::function_with_selfptr;
};
// statically typed actor returning a behavior
template <class... Sigs, class FirstArg>
struct infer_handle_from_fun_impl<typed_behavior<Sigs...>, FirstArg, false> {
using type = typed_actor<Sigs...>;
using impl = typed_event_based_actor<Sigs...>;
using behavior_type = typed_behavior<Sigs...>;
static constexpr spawn_mode mode = spawn_mode::function;
};
// statically typed actor with self pointer
template <class Result, class... Sigs>
struct infer_handle_from_fun_impl<Result, typed_event_based_actor<Sigs...>*,
true> {
using type = typed_actor<Sigs...>;
using impl = typed_event_based_actor<Sigs...>;
using behavior_type = typed_behavior<Sigs...>;
static constexpr spawn_mode mode = spawn_mode::function_with_selfptr;
};
template <class F, class Trait = typename detail::get_callable_trait<F>::type>
struct infer_handle_from_fun {
using result_type = typename Trait::result_type;
using arg_types = typename Trait::arg_types;
using first_arg = typename detail::tl_head<arg_types>::type;
using delegate = infer_handle_from_fun_impl<result_type, first_arg>;
using type = typename delegate::type;
using impl = typename delegate::impl;
using behavior_type = typename delegate::behavior_type;
using fun_type = typename Trait::fun_type;
static constexpr spawn_mode mode = delegate::mode;
};
template <class T>
struct infer_handle_from_behavior {
using type = actor;
};
template <class... Sigs>
struct infer_handle_from_behavior<typed_behavior<Sigs...>> {
using type = typed_actor<Sigs...>;
};
template <class T>
struct infer_handle_from_class {
using type =
typename infer_handle_from_behavior<
typename T::behavior_type
>::type;
static constexpr spawn_mode mode = spawn_mode::clazz;
};
} // namespace caf
#endif // CAF_INFER_HANDLE_HPP
...@@ -392,6 +392,16 @@ public: ...@@ -392,6 +392,16 @@ public:
/// implementation simply returns "actor". /// implementation simply returns "actor".
virtual const char* name() const; virtual const char* name() const;
/// Serializes the state of this actor to `sink`. This function is
/// only called if this actor has set the `is_serializable` flag.
/// The default implementation throws a `std::logic_error`.
virtual void save(serializer& sink, const unsigned int version);
/// Deserializes the state of this actor from `source`. This function is
/// only called if this actor has set the `is_serializable` flag.
/// The default implementation throws a `std::logic_error`.
virtual void load(deserializer& source, const unsigned int version);
/**************************************************************************** /****************************************************************************
* deprecated member functions * * deprecated member functions *
****************************************************************************/ ****************************************************************************/
...@@ -463,7 +473,9 @@ public: ...@@ -463,7 +473,9 @@ public:
return (mid.is_request()) ? mid.response_id() : message_id(); return (mid.is_request()) ? mid.response_id() : message_id();
} }
void forward_message(const actor& dest, message_priority mp); void forward_current_message(const actor& dest);
void forward_current_message(const actor& dest, message_priority mp);
template <class... Ts> template <class... Ts>
void delegate(message_priority mp, const actor& dest, Ts&&... xs) { void delegate(message_priority mp, const actor& dest, Ts&&... xs) {
...@@ -611,9 +623,9 @@ public: ...@@ -611,9 +623,9 @@ public:
initial_behavior_fac_ = std::move(fun); initial_behavior_fac_ = std::move(fun);
} }
protected:
void do_become(behavior bhvr, bool discard_old); void do_become(behavior bhvr, bool discard_old);
protected:
// used only in thread-mapped actors // used only in thread-mapped actors
void await_data(); void await_data();
......
...@@ -390,6 +390,12 @@ inline message make_message(message other) { ...@@ -390,6 +390,12 @@ inline message make_message(message other) {
return std::move(other); return std::move(other);
} }
/// Returns an empty `message`.
/// @relates message
inline message make_message() {
return message{};
}
/****************************************************************************** /******************************************************************************
* template member function implementations * * template member function implementations *
******************************************************************************/ ******************************************************************************/
......
...@@ -49,9 +49,13 @@ public: ...@@ -49,9 +49,13 @@ public:
} }
/// Sends `response_message` and invalidates this handle afterwards. /// Sends `response_message` and invalidates this handle afterwards.
void deliver(message response_message) const; template <class... Ts>
void deliver(Ts&&... xs) const {
deliver_impl(make_message(std::forward<Ts>(xs)...));
}
private: private:
void deliver_impl(message response_message) const;
actor_addr from_; actor_addr from_;
actor_addr to_; actor_addr to_;
message_id id_; message_id id_;
......
...@@ -107,6 +107,11 @@ operator<<(serializer& sink, const T& value) { ...@@ -107,6 +107,11 @@ operator<<(serializer& sink, const T& value) {
return sink.write(value, uniform_typeid<T>()); return sink.write(value, uniform_typeid<T>());
} }
template <class T>
void operator&(serializer& sink, const T& value) {
sink << value;
}
} // namespace caf } // namespace caf
#endif // CAF_SERIALIZER_HPP #endif // CAF_SERIALIZER_HPP
...@@ -222,7 +222,7 @@ struct infer_typed_actor_base<Result, T*, true> { ...@@ -222,7 +222,7 @@ struct infer_typed_actor_base<Result, T*, true> {
/// constructor arguments. /// constructor arguments.
template <class C, spawn_options Os = no_spawn_options, class... Ts> template <class C, spawn_options Os = no_spawn_options, class... Ts>
typename actor_handle_from_signature_list<typename C::signatures>::type typename actor_handle_from_signature_list<typename C::signatures>::type
spawn_typed(Ts&&... xs) { CAF_DEPRECATED spawn_typed(Ts&&... xs) {
return spawn_class<C, Os>(nullptr, empty_before_launch_callback{}, return spawn_class<C, Os>(nullptr, empty_before_launch_callback{},
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
} }
......
...@@ -228,6 +228,12 @@ private: ...@@ -228,6 +228,12 @@ private:
behavior bhvr_; behavior bhvr_;
}; };
template <class T>
struct is_typed_behavior : std::false_type { };
template <class... Sigs>
struct is_typed_behavior<typed_behavior<Sigs...>> : std::true_type { };
} // namespace caf } // namespace caf
#endif // CAF_TYPED_BEHAVIOR_HPP #endif // CAF_TYPED_BEHAVIOR_HPP
...@@ -41,9 +41,16 @@ ...@@ -41,9 +41,16 @@
namespace caf { namespace caf {
namespace { namespace {
using guard_type = std::unique_lock<std::mutex>; using guard_type = std::unique_lock<std::mutex>;
std::atomic<actor_id> ids_;
} // namespace <anonymous> } // namespace <anonymous>
actor_id abstract_actor::latest_actor_id() {
return ids_.load();
}
// exit_reason_ is guaranteed to be set to 0, i.e., exit_reason::not_exited, // exit_reason_ is guaranteed to be set to 0, i.e., exit_reason::not_exited,
// by std::atomic<> constructor // by std::atomic<> constructor
...@@ -59,7 +66,7 @@ abstract_actor::abstract_actor(actor_id aid, node_id nid) ...@@ -59,7 +66,7 @@ abstract_actor::abstract_actor(actor_id aid, node_id nid)
abstract_actor::abstract_actor() abstract_actor::abstract_actor()
: abstract_channel(abstract_channel::is_abstract_actor_flag, : abstract_channel(abstract_channel::is_abstract_actor_flag,
detail::singletons::get_node_id()), detail::singletons::get_node_id()),
id_(detail::singletons::get_actor_registry()->next_id()), id_(++ids_),
exit_reason_(exit_reason::not_exited), exit_reason_(exit_reason::not_exited),
host_(nullptr) { host_(nullptr) {
// nop // nop
......
...@@ -62,7 +62,7 @@ actor_namespace::actor_namespace(backend& be) : backend_(be) { ...@@ -62,7 +62,7 @@ actor_namespace::actor_namespace(backend& be) : backend_(be) {
// nop // nop
} }
void actor_namespace::write(serializer* sink, const actor_addr& addr) { void actor_namespace::write(serializer* sink, const actor_addr& addr) const {
CAF_ASSERT(sink != nullptr); CAF_ASSERT(sink != nullptr);
if (! addr) { if (! addr) {
node_id::host_id_type zero; node_id::host_id_type zero;
......
...@@ -22,11 +22,19 @@ ...@@ -22,11 +22,19 @@
#include <mutex> #include <mutex>
#include <limits> #include <limits>
#include <stdexcept> #include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include "caf/spawn.hpp"
#include "caf/locks.hpp" #include "caf/locks.hpp"
#include "caf/actor_cast.hpp" #include "caf/actor_cast.hpp"
#include "caf/attachable.hpp" #include "caf/attachable.hpp"
#include "caf/exit_reason.hpp" #include "caf/exit_reason.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/experimental/stateful_actor.hpp"
#include "caf/experimental/announce_actor_type.hpp"
#include "caf/scheduler/detached_threads.hpp" #include "caf/scheduler/detached_threads.hpp"
...@@ -47,7 +55,7 @@ actor_registry::~actor_registry() { ...@@ -47,7 +55,7 @@ actor_registry::~actor_registry() {
// nop // nop
} }
actor_registry::actor_registry() : running_(0), ids_(1) { actor_registry::actor_registry() : running_(0) {
// nop // nop
} }
...@@ -95,10 +103,6 @@ void actor_registry::erase(actor_id key, uint32_t reason) { ...@@ -95,10 +103,6 @@ void actor_registry::erase(actor_id key, uint32_t reason) {
} }
} }
uint32_t actor_registry::next_id() {
return ++ids_;
}
void actor_registry::inc_running() { void actor_registry::inc_running() {
# if defined(CAF_LOG_LEVEL) && CAF_LOG_LEVEL >= CAF_DEBUG # if defined(CAF_LOG_LEVEL) && CAF_LOG_LEVEL >= CAF_DEBUG
CAF_LOG_DEBUG("new value = " << ++running_); CAF_LOG_DEBUG("new value = " << ++running_);
...@@ -130,5 +134,119 @@ void actor_registry::await_running_count_equal(size_t expected) { ...@@ -130,5 +134,119 @@ void actor_registry::await_running_count_equal(size_t expected) {
} }
} }
actor actor_registry::get_named(atom_value key) const {
shared_guard guard{named_entries_mtx_};
auto i = named_entries_.find(key);
if (i == named_entries_.end())
return invalid_actor;
return i->second;
}
void actor_registry::put_named(atom_value key, actor value) {
if (value)
value->attach_functor([=](uint32_t) {
detail::singletons::get_actor_registry()->put_named(key, invalid_actor);
});
exclusive_guard guard{named_entries_mtx_};
named_entries_.emplace(key, std::move(value));
}
auto actor_registry::named_actors() const -> named_entries {
shared_guard guard{named_entries_mtx_};
return named_entries_;
}
actor_registry* actor_registry::create_singleton() {
return new actor_registry;
}
void actor_registry::dispose() {
delete this;
}
void actor_registry::stop() {
scoped_actor self{true};
for (auto& kvp : named_entries_) {
self->monitor(kvp.second);
self->send_exit(kvp.second, exit_reason::kill);
self->receive(
[](const down_msg&) {
// nop
}
);
}
named_entries_.clear();
}
void actor_registry::initialize() {
using namespace experimental;
struct kvstate {
using key_type = std::string;
using mapped_type = message;
using subscriber_set = std::unordered_set<actor>;
using topic_set = std::unordered_set<std::string>;
std::unordered_map<key_type, std::pair<mapped_type, subscriber_set>> data;
std::unordered_map<actor,topic_set> subscribers;
const char* name = "caf.config_server";
};
auto kvstore = [](stateful_actor<kvstate>* self) -> behavior {
return {
[=](put_atom, const std::string& key, message& msg) {
auto& vp = self->state.data[key];
if (vp.first == msg)
return;
vp.first = std::move(msg);
for (auto& subscriber : vp.second)
if (subscriber != self->current_sender())
self->send(subscriber, update_atom::value, key, vp.second);
},
[=](get_atom, std::string& key) -> message {
auto i = self->state.data.find(key);
return make_message(ok_atom::value, std::move(key),
i != self->state.data.end() ? i->second.first
: make_message());
},
[=](subscribe_atom, const std::string& key) {
auto subscriber = actor_cast<actor>(self->current_sender());
if (! subscriber)
return;
self->state.data[key].second.insert(subscriber);
auto& subscribers = self->state.subscribers;
auto i = subscribers.find(subscriber);
if (i != subscribers.end()) {
i->second.insert(key);
} else {
self->monitor(subscriber);
subscribers.emplace(subscriber, kvstate::topic_set{key});
}
},
[=](unsubscribe_atom, const std::string& key) {
auto subscriber = actor_cast<actor>(self->current_sender());
if (! subscriber)
return;
self->state.subscribers[subscriber].erase(key);
self->state.data[key].second.erase(subscriber);
},
[=](const down_msg& dm) {
auto subscriber = actor_cast<actor>(dm.source);
if (! subscriber)
return;
auto& subscribers = self->state.subscribers;
auto i = subscribers.find(subscriber);
if (i == subscribers.end())
return;
for (auto& key : i->second)
self->state.data[key].second.erase(subscriber);
subscribers.erase(i);
},
others >> [] {
return make_message(error_atom::value, "unsupported operation");
}
};
};
named_entries_.emplace(atom("SpawnServ"), spawn_announce_actor_type_server());
named_entries_.emplace(atom("ConfigServ"), spawn<hidden>(kvstore));
}
} // namespace detail } // namespace detail
} // namespace caf } // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/experimental/announce_actor_type.hpp"
#include "caf/atom.hpp"
#include "caf/send.hpp"
#include "caf/spawn.hpp"
#include "caf/to_string.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/experimental/stateful_actor.hpp"
#include "caf/detail/logging.hpp"
#include "caf/detail/singletons.hpp"
#include "caf/detail/actor_registry.hpp"
namespace caf {
namespace experimental {
namespace {
struct spawner_state {
std::unordered_map<std::string, spawn_fun> funs_;
};
behavior announce_actor_type_server(stateful_actor<spawner_state>* self) {
return {
[=](add_atom, std::string& name, spawn_fun& f) {
self->state.funs_.emplace(std::move(name), std::move(f));
},
[=](get_atom, const std::string& name, message& args)
-> either<ok_atom, actor_addr, std::set<std::string>>
::or_else<error_atom, std::string> {
auto i = self->state.funs_.find(name);
if (i == self->state.funs_.end())
return {error_atom::value, "no actor type found named " + name};
auto f = i->second;
auto res = f(args);
if (res.first == invalid_actor_addr)
return {error_atom::value, "cannot initialize an actor type " + name
+ " using the provided arguments"};
return {ok_atom::value, res.first, res.second};
},
others >> [=] {
CAF_LOGF_WARNING("Unexpected message: "
<< to_string(self->current_message()));
}
};
}
} // namespace <anonymous>
actor spawn_announce_actor_type_server() {
return spawn<hidden + lazy_init>(announce_actor_type_server);
}
void announce_actor_type_impl(std::string&& name, spawn_fun f) {
auto registry = detail::singletons::get_actor_registry();
auto server = registry->get_named(atom("SpawnServ"));
anon_send(server, add_atom::value, std::move(name), std::move(f));
}
} // namespace experimental
} // namespace caf
...@@ -30,7 +30,7 @@ event_based_actor::~event_based_actor() { ...@@ -30,7 +30,7 @@ event_based_actor::~event_based_actor() {
void event_based_actor::forward_to(const actor& whom, void event_based_actor::forward_to(const actor& whom,
message_priority prio) { message_priority prio) {
forward_message(whom, prio); forward_current_message(whom, prio);
} }
void event_based_actor::initialize() { void event_based_actor::initialize() {
......
...@@ -108,10 +108,16 @@ std::vector<group> local_actor::joined_groups() const { ...@@ -108,10 +108,16 @@ std::vector<group> local_actor::joined_groups() const {
return result; return result;
} }
void local_actor::forward_message(const actor& dest, message_priority prio) { void local_actor::forward_current_message(const actor& dest) {
if (! dest) { if (! dest)
return;
dest->enqueue(std::move(current_element_), host());
}
void local_actor::forward_current_message(const actor& dest,
message_priority prio) {
if (! dest)
return; return;
}
auto mid = current_element_->mid; auto mid = current_element_->mid;
current_element_->mid = prio == message_priority::high current_element_->mid = prio == message_priority::high
? mid.with_high_priority() ? mid.with_high_priority()
...@@ -180,27 +186,117 @@ enum class msg_type { ...@@ -180,27 +186,117 @@ enum class msg_type {
expired_sync_response, // a sync response that already timed out expired_sync_response, // a sync response that already timed out
timeout, // triggers currently active timeout timeout, // triggers currently active timeout
ordinary, // an asynchronous message or sync. request ordinary, // an asynchronous message or sync. request
sync_response // a synchronous response sync_response, // a synchronous response
sys_message // a system message, e.g., signalizing migration
}; };
msg_type filter_msg(local_actor* self, mailbox_element& node) { msg_type filter_msg(local_actor* self, mailbox_element& node) {
const message& msg = node.msg; message& msg = node.msg;
auto mid = node.mid; auto mid = node.mid;
if (mid.is_response()) { if (mid.is_response())
return self->awaits(mid) ? msg_type::sync_response return self->awaits(mid) ? msg_type::sync_response
: msg_type::expired_sync_response; : msg_type::expired_sync_response;
// intercept system messages, e.g., signalizing migration
if (msg.size() > 1 && msg.match_element<sys_atom>(0) && node.sender) {
bool mismatch = false;
msg.apply({
[&](sys_atom, migrate_atom, const actor& mm) {
// migrate this actor to `target`
if (! self->is_serializable()) {
node.sender->enqueue(
mailbox_element::make_joint(self->address(), node.mid.response_id(),
error_atom::value, "not serializable"),
self->host());
return;
}
std::vector<char> buf;
binary_serializer bs{std::back_inserter(buf)};
self->save(bs, 0);
auto sender = node.sender;
auto mid = node.mid;
// sync_send(...)
auto req = self->sync_send_impl(message_priority::normal, mm,
migrate_atom::value, self->name(),
std::move(buf));
self->set_response_handler(req, behavior{
[=](ok_atom, const actor_addr& dest) {
// respond to original message with {'OK', dest}
sender->enqueue(mailbox_element::make_joint(self->address(),
mid.response_id(),
ok_atom::value, dest),
self->host());
// "decay" into a proxy for `dest`
auto dest_hdl = actor_cast<actor>(dest);
self->do_become(behavior{
others >> [=] {
self->forward_current_message(dest_hdl);
}
}, false);
self->is_migrated_from(true);
},
[=](error_atom, std::string& errmsg) {
// respond to original message with {'ERROR', errmsg}
sender->enqueue(mailbox_element::make_joint(self->address(),
mid.response_id(),
error_atom::value,
std::move(errmsg)),
self->host());
}
});
},
[&](sys_atom, migrate_atom, std::vector<char>& buf) {
// "replace" this actor with the content of `buf`
if (! self->is_serializable()) {
node.sender->enqueue(
mailbox_element::make_joint(self->address(), node.mid.response_id(),
error_atom::value, "not serializable"),
self->host());
return;
}
if (self->is_migrated_from()) {
// undo the `do_become` we did when migrating away from this object
self->bhvr_stack().pop_back();
self->is_migrated_from(false);
}
binary_deserializer bd{buf.data(), buf.size()};
self->load(bd, 0);
node.sender->enqueue(
mailbox_element::make_joint(self->address(), node.mid.response_id(),
ok_atom::value, self->address()),
self->host());
},
[&](sys_atom, get_atom, std::string& what) {
CAF_LOGF_TRACE(CAF_ARG(what));
if (what == "info") {
CAF_LOGF_DEBUG("reply to 'info' message");
node.sender->enqueue(
mailbox_element::make_joint(self->address(), node.mid.response_id(),
ok_atom::value, std::move(what),
self->address(), self->name()),
self->host());
return;
}
node.sender->enqueue(
mailbox_element::make_joint(self->address(), node.mid.response_id(),
error_atom::value,
"unknown key: " + std::move(what)),
self->host());
},
others >> [&] {
mismatch = true;
}
});
return mismatch ? msg_type::ordinary : msg_type::sys_message;
} }
if (msg.size() != 1) { // all other system messages always consist of one element
if (msg.size() != 1)
return msg_type::ordinary; return msg_type::ordinary;
}
if (msg.match_element<timeout_msg>(0)) { if (msg.match_element<timeout_msg>(0)) {
auto& tm = msg.get_as<timeout_msg>(0); auto& tm = msg.get_as<timeout_msg>(0);
auto tid = tm.timeout_id; auto tid = tm.timeout_id;
CAF_ASSERT(! mid.valid()); CAF_ASSERT(! mid.valid());
if (self->is_active_timeout(tid)) { return self->is_active_timeout(tid) ? msg_type::timeout
return msg_type::timeout; : msg_type::expired_timeout;
}
return msg_type::expired_timeout;
} }
if (msg.match_element<exit_msg>(0)) { if (msg.match_element<exit_msg>(0)) {
auto& em = msg.get_as<exit_msg>(0); auto& em = msg.get_as<exit_msg>(0);
...@@ -316,6 +412,9 @@ invoke_message_result local_actor::invoke_message(mailbox_element_ptr& ptr, ...@@ -316,6 +412,9 @@ invoke_message_result local_actor::invoke_message(mailbox_element_ptr& ptr,
case msg_type::expired_timeout: case msg_type::expired_timeout:
CAF_LOG_DEBUG("dropped expired timeout message"); CAF_LOG_DEBUG("dropped expired timeout message");
return im_dropped; return im_dropped;
case msg_type::sys_message:
CAF_LOG_DEBUG("handled system message");
return im_dropped;
case msg_type::non_normal_exit: case msg_type::non_normal_exit:
CAF_LOG_DEBUG("handled non-normal exit signal"); CAF_LOG_DEBUG("handled non-normal exit signal");
// this message was handled // this message was handled
...@@ -348,10 +447,9 @@ invoke_message_result local_actor::invoke_message(mailbox_element_ptr& ptr, ...@@ -348,10 +447,9 @@ invoke_message_result local_actor::invoke_message(mailbox_element_ptr& ptr,
} else { } else {
auto res = post_process_invoke_res(this, mid, auto res = post_process_invoke_res(this, mid,
fun(current_mailbox_element()->msg)); fun(current_mailbox_element()->msg));
ptr.swap(current_mailbox_element());
if (! res) { if (! res) {
CAF_LOG_WARNING("sync failure occured in actor " CAF_LOG_WARNING("sync failure occured in actor "
<< "with ID " << id()); << "with ID " << id());
handle_sync_failure(); handle_sync_failure();
} }
} }
...@@ -824,6 +922,14 @@ const char* local_actor::name() const { ...@@ -824,6 +922,14 @@ const char* local_actor::name() const {
return "actor"; return "actor";
} }
void local_actor::save(serializer&, const unsigned int) {
throw std::logic_error("local_actor::serialize called");
}
void local_actor::load(deserializer&, const unsigned int) {
throw std::logic_error("local_actor::deserialize called");
}
behavior& local_actor::get_behavior() { behavior& local_actor::get_behavior() {
return pending_responses_.empty() ? bhvr_stack_.back() return pending_responses_.empty() ? bhvr_stack_.back()
: pending_responses_.front().second; : pending_responses_.front().second;
......
...@@ -101,8 +101,9 @@ const char* message::uniform_name_at(size_t pos) const { ...@@ -101,8 +101,9 @@ const char* message::uniform_name_at(size_t pos) const {
} }
bool message::equals(const message& other) const { bool message::equals(const message& other) const {
CAF_ASSERT(vals_); if (empty())
return vals_->equals(*other.vals()); return other.empty();
return other.empty() ? false : vals_->equals(*other.vals());
} }
message message::drop(size_t n) const { message message::drop(size_t n) const {
......
...@@ -30,10 +30,9 @@ response_promise::response_promise(const actor_addr& from, const actor_addr& to, ...@@ -30,10 +30,9 @@ response_promise::response_promise(const actor_addr& from, const actor_addr& to,
CAF_ASSERT(id.is_response() || ! id.valid()); CAF_ASSERT(id.is_response() || ! id.valid());
} }
void response_promise::deliver(message msg) const { void response_promise::deliver_impl(message msg) const {
if (! to_) { if (! to_)
return; return;
}
auto to = actor_cast<abstract_actor_ptr>(to_); auto to = actor_cast<abstract_actor_ptr>(to_);
auto from = actor_cast<abstract_actor_ptr>(from_); auto from = actor_cast<abstract_actor_ptr>(from_);
to->enqueue(from_, id_, std::move(msg), from->host()); to->enqueue(from_, id_, std::move(msg), from->host());
......
...@@ -44,6 +44,11 @@ std::thread run_program_impl(actor rc, const char* cpath, ...@@ -44,6 +44,11 @@ std::thread run_program_impl(actor rc, const char* cpath,
oss << " 2>&1"; oss << " 2>&1";
string cmdstr = oss.str(); string cmdstr = oss.str();
return std::thread{ [cmdstr, rc] { return std::thread{ [cmdstr, rc] {
// on FreeBSD, popen() hangs indefinitely in some cases
# ifdef CAF_BSD
system(cmdstr.c_str());
anon_send(rc, "");
# else
string output; string output;
auto fp = popen(cmdstr.c_str(), "r"); auto fp = popen(cmdstr.c_str(), "r");
if (! fp) { if (! fp) {
...@@ -57,6 +62,7 @@ std::thread run_program_impl(actor rc, const char* cpath, ...@@ -57,6 +62,7 @@ std::thread run_program_impl(actor rc, const char* cpath,
} }
pclose(fp); pclose(fp);
anon_send(rc, output); anon_send(rc, output);
# endif
}}; }};
} }
#else #else
......
...@@ -76,12 +76,12 @@ void singletons::stop_singletons() { ...@@ -76,12 +76,12 @@ void singletons::stop_singletons() {
} }
CAF_LOGF_DEBUG("stop group manager"); CAF_LOGF_DEBUG("stop group manager");
stop(s_group_manager); stop(s_group_manager);
CAF_LOGF_DEBUG("stop actor registry");
stop(s_actor_registry);
CAF_LOGF_DEBUG("stop scheduler"); CAF_LOGF_DEBUG("stop scheduler");
stop(s_scheduling_coordinator); stop(s_scheduling_coordinator);
CAF_LOGF_DEBUG("wait for all detached threads"); CAF_LOGF_DEBUG("wait for all detached threads");
scheduler::await_detached_threads(); scheduler::await_detached_threads();
CAF_LOGF_DEBUG("stop actor registry");
stop(s_actor_registry);
// dispose singletons, i.e., release memory // dispose singletons, i.e., release memory
CAF_LOGF_DEBUG("dispose plugins"); CAF_LOGF_DEBUG("dispose plugins");
for (auto& plugin : s_plugins) { for (auto& plugin : s_plugins) {
......
...@@ -95,18 +95,6 @@ const char* numbered_type_names[] = { ...@@ -95,18 +95,6 @@ const char* numbered_type_names[] = {
namespace { namespace {
// might become part of serializer's public API at some point
template <class T>
void operator&(serializer& sink, const T& value) {
sink << value;
}
// might become part of deserializer's public API at some point
template <class T>
void operator&(deserializer& source, T& value) {
source >> value;
}
// primitive types are handled by serializer/deserializer directly // primitive types are handled by serializer/deserializer directly
template <class T, class U> template <class T, class U>
typename std::enable_if< typename std::enable_if<
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/experimental/whereis.hpp"
#include "caf/actor.hpp"
#include "caf/detail/singletons.hpp"
#include "caf/detail/actor_registry.hpp"
namespace caf {
namespace experimental {
actor whereis(atom_value registered_name) {
return detail::singletons::get_actor_registry()->get_named(registered_name);
}
} // namespace experimental
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE announce_actor_type
#include "caf/test/unit_test.hpp"
#include "caf/all.hpp"
#include "caf/experimental/announce_actor_type.hpp"
#include "caf/detail/actor_registry.hpp"
using namespace caf;
using namespace caf::experimental;
using std::endl;
namespace {
struct fixture {
actor aut;
actor spawner;
fixture() {
auto registry = detail::singletons::get_actor_registry();
spawner = registry->get_named(atom("SpawnServ"));
}
void set_aut(message args, bool expect_fail = false) {
CAF_MESSAGE("set aut");
scoped_actor self;
self->on_sync_failure([&] {
CAF_TEST_ERROR("received unexpeced sync. response: "
<< to_string(self->current_message()));
});
if (expect_fail) {
self->sync_send(spawner, get_atom::value, "test_actor", std::move(args)).await(
[&](error_atom, const std::string&) {
CAF_TEST_VERBOSE("received error_atom (expected)");
}
);
} else {
self->sync_send(spawner, get_atom::value, "test_actor", std::move(args)).await(
[&](ok_atom, actor_addr res, const std::set<std::string>& ifs) {
CAF_REQUIRE(res != invalid_actor_addr);
aut = actor_cast<actor>(res);
CAF_CHECK(ifs.empty());
}
);
}
}
~fixture() {
if (aut != invalid_actor) {
scoped_actor self;
self->monitor(aut);
self->receive(
[](const down_msg& dm) {
CAF_CHECK(dm.reason == exit_reason::normal);
}
);
}
await_all_actors_done();
shutdown();
}
};
} // namespace <anonymous>
CAF_TEST_FIXTURE_SCOPE(announce_actor_type_tests, fixture)
CAF_TEST(fun_no_args) {
auto test_actor = [] {
CAF_MESSAGE("inside test_actor");
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message());
}
CAF_TEST(fun_no_args_selfptr) {
auto test_actor = [](event_based_actor*) {
CAF_MESSAGE("inside test_actor");
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message());
}
CAF_TEST(fun_one_arg) {
auto test_actor = [](int i) {
CAF_CHECK_EQUAL(i, 42);
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message(42));
}
CAF_TEST(fun_one_arg_selfptr) {
auto test_actor = [](event_based_actor*, int i) {
CAF_CHECK_EQUAL(i, 42);
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message(42));
}
CAF_TEST(class_no_arg) {
struct test_actor : event_based_actor {
behavior make_behavior() override {
return {};
}
};
announce_actor_type<test_actor>("test_actor");
set_aut(make_message(42), true);
set_aut(make_message());
}
CAF_TEST(class_one_arg) {
struct test_actor : event_based_actor {
test_actor(int value) {
CAF_CHECK_EQUAL(value, 42);
}
behavior make_behavior() override {
return {};
}
};
announce_actor_type<test_actor, const int&>("test_actor");
set_aut(make_message(), true);
set_aut(make_message(42));
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE announce_actor_type
#include "caf/test/unit_test.hpp"
#include "caf/all.hpp"
#include "caf/experimental/announce_actor_type.hpp"
#include "caf/detail/actor_registry.hpp"
using namespace caf;
using namespace caf::experimental;
using std::endl;
namespace {
struct fixture {
actor aut;
actor spawner;
fixture() {
auto registry = detail::singletons::get_actor_registry();
spawner = registry->get_named(atom("spawner"));
}
void set_aut(message args, bool expect_fail = false) {
CAF_MESSAGE("set aut");
scoped_actor self;
self->on_sync_failure([&] {
CAF_TEST_ERROR("received unexpeced sync. response: "
<< to_string(self->current_message()));
});
if (expect_fail) {
self->sync_send(spawner, get_atom::value, "test_actor", std::move(args)).await(
[&](error_atom, const std::string&) {
CAF_TEST_VERBOSE("received error_atom (expected)");
}
);
} else {
self->sync_send(spawner, get_atom::value, "test_actor", std::move(args)).await(
[&](ok_atom, actor_addr res, const std::set<std::string>& ifs) {
CAF_REQUIRE(res != invalid_actor_addr);
aut = actor_cast<actor>(res);
CAF_CHECK(ifs.empty());
}
);
}
}
~fixture() {
if (aut != invalid_actor) {
scoped_actor self;
self->monitor(aut);
self->receive(
[](const down_msg& dm) {
CAF_CHECK(dm.reason == exit_reason::normal);
}
);
}
await_all_actors_done();
shutdown();
}
};
using detail::is_serializable;
// not serializable
class testee1 {
};
// serializable via member function
class testee2 {
public:
template <class Archive>
void serialize(Archive&, const unsigned int) {
// nop
}
};
// serializable via free function
class testee3 {
// nop
};
template<class Archive>
void serialize(Archive&, testee3&, const unsigned int) {
// nop
}
template <class Archive, class T>
void serialize(Archive& ar, T& x, const unsigned int version,
decltype(x.serialize(ar, version))* = nullptr) {
x.serialize(ar, version);
}
struct migratable_state {
int value = 0;
};
template <class Archive>
void serialize(Archive& ar, migratable_state& x, const unsigned int) {
ar & x.value;
}
struct migratable_actor : stateful_actor<migratable_state> {
behavior make_behavior() override {
return {
[=](get_atom) {
return state.value;
},
[=](put_atom, int value) {
state.value = value;
},
[=](sys_atom, migrate_atom, actor_addr destination) {
std::vector<char> buf;
binary_serializer bs{std::back_inserter(buf)};
save(bs, 0);
actor dest = actor_cast<actor>(destination);
link_to(dest);
sync_send(dest, sys_atom::value, migrate_atom::value, std::move(buf)).then(
[=](ok_atom) {
// "decay" into a proxy for `dest`
become(
[=](sys_atom, migrate_atom, std::vector<char> buf) {
unlink_from(dest);
become(make_behavior());
binary_deserializer bd{buf.data(), buf.size()};
deserialize(bd, 0);
return make_message(ok_atom::value);
},
others >> [=] {
forward_to(dest);
}
);
},
others >> [=] {
// do nothing, i.e., process further messages as usual
}
);
},
[=](sys_atom, migrate_atom, std::vector<char> buf) {
binary_deserializer bd{buf.data(), buf.size()};
deserialize(bd, 0);
return make_message(ok_atom::value);
}
};
}
};
} // namespace <anonymous>
CAF_TEST(serializable_stuff) {
CAF_CHECK(is_serializable<testee1>::value == false);
CAF_CHECK(is_serializable<testee2>::value == true);
CAF_CHECK(is_serializable<testee3>::value == true);
}
CAF_TEST(migratable_stuff) {
auto a = spawn<migratable_actor>();
auto b = spawn<migratable_actor>();
scoped_actor self;
self->send(a, put_atom::value, 42);
self->send(a, sys_atom::value, migrate_atom::value, b.address());
self->sync_send(a, get_atom::value).await(
[&](int result) {
CAF_CHECK(result == 42);
CAF_CHECK(self->current_sender() == b.address());
}
);
self->send_exit(b, exit_reason::kill);
self->await_all_other_actors_done();
}
CAF_TEST_FIXTURE_SCOPE(announce_actor_type_tests, fixture)
CAF_TEST(fun_no_args) {
auto test_actor = [] {
CAF_MESSAGE("inside test_actor");
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message());
}
CAF_TEST(fun_no_args_selfptr) {
auto test_actor = [](event_based_actor*) {
CAF_MESSAGE("inside test_actor");
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message());
}
CAF_TEST(fun_one_arg) {
auto test_actor = [](int i) {
CAF_CHECK_EQUAL(i, 42);
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message(42));
}
CAF_TEST(fun_one_arg_selfptr) {
auto test_actor = [](event_based_actor*, int i) {
CAF_CHECK_EQUAL(i, 42);
};
announce_actor_type("test_actor", test_actor);
set_aut(make_message(42));
}
CAF_TEST(class_no_arg) {
struct test_actor : event_based_actor {
behavior make_behavior() override {
return {};
}
};
announce_actor_type<test_actor>("test_actor");
set_aut(make_message(42), true);
set_aut(make_message());
}
CAF_TEST(class_one_arg) {
struct test_actor : event_based_actor {
test_actor(int value) {
CAF_CHECK_EQUAL(value, 42);
}
behavior make_behavior() override {
return {};
}
};
announce_actor_type<test_actor, const int&>("test_actor");
set_aut(make_message(), true);
set_aut(make_message(42));
}
CAF_TEST_FIXTURE_SCOPE_END()
...@@ -30,6 +30,67 @@ using std::endl; ...@@ -30,6 +30,67 @@ using std::endl;
namespace { namespace {
struct meta_info {
constexpr meta_info(const char* x, size_t y, uint32_t z)
: name(x), hash(y), version(z) {
// nop
}
constexpr meta_info(const meta_info& other)
: name(other.name),
hash(other.hash),
version(other.version) {
// nop
}
const char* name;
size_t hash;
uint32_t version;
};
constexpr size_t str_hash(const char* cstr, size_t interim = 0) {
return (*cstr == '\0') ? interim : str_hash(cstr + 1, interim * 101 + *cstr);
}
constexpr meta_info make_meta_info(const char* name, uint32_t version) {
return meta_info{name, str_hash(name), version};
}
template <class T>
struct meta_information;
#define CAF_META_INFORMATION(class_name, version) \
template <> \
struct meta_information<class_name> { \
static constexpr meta_info value = make_meta_info(#class_name, version); \
}; \
constexpr meta_info meta_information<class_name>::value;
#define DUMMY(name) \
class name {}; \
CAF_META_INFORMATION(name, 0)
DUMMY(foo1)
DUMMY(foo2)
DUMMY(foo3)
DUMMY(foo4)
DUMMY(foo5)
DUMMY(foo6)
DUMMY(foo7)
DUMMY(foo8)
DUMMY(foo9)
DUMMY(foo10)
DUMMY(foo11)
DUMMY(foo12)
DUMMY(foo13)
DUMMY(foo14)
DUMMY(foo15)
DUMMY(foo16)
DUMMY(foo17)
DUMMY(foo18)
DUMMY(foo19)
DUMMY(foo20)
struct fixture { struct fixture {
~fixture() { ~fixture() {
await_all_actors_done(); await_all_actors_done();
...@@ -37,6 +98,7 @@ struct fixture { ...@@ -37,6 +98,7 @@ struct fixture {
} }
}; };
constexpr const char* global_redirect = ":test"; constexpr const char* global_redirect = ":test";
constexpr const char* local_redirect = ":test2"; constexpr const char* local_redirect = ":test2";
...@@ -57,6 +119,133 @@ void chattier_actor(event_based_actor* self, const std::string& fn) { ...@@ -57,6 +119,133 @@ void chattier_actor(event_based_actor* self, const std::string& fn) {
CAF_TEST_FIXTURE_SCOPE(aout_tests, fixture) CAF_TEST_FIXTURE_SCOPE(aout_tests, fixture)
const meta_info* lookup(const std::vector<std::pair<size_t, const meta_info*>>& haystack,
const meta_info* needle) {
/*
auto h = needle->hash;
auto e = haystack.end();
for (auto i = haystack.begin(); i != e; ++i) {
if (i->first == h) {
auto n = i + 1;
if (n == e || n->second->hash != h)
return i->second;
for (auto j = i; j != e; ++j)
if (strcmp(j->second->name, needle->name) == 0)
return j->second;
}
}
*/
auto h = needle->hash;
auto e = haystack.end();
//auto i = std::find_if(haystack.begin(), e,
// [h](const std::pair<size_t, const meta_info*>& x ) { return x.first == h; });
auto i = std::lower_bound(haystack.begin(), e, h,
[](const std::pair<size_t, const meta_info*>& x, size_t y) { return x.first < y; });
if (i == e || i->first != h)
return nullptr;
// check for collision
auto n = i + 1;
if (n == e || n->first != h)
return i->second;
i = std::find_if(i, e, [needle](const std::pair<size_t, const meta_info*>& x ) { return strcmp(x.second->name, needle->name) == 0; });
if (i != e)
return i->second;
return nullptr;
}
const meta_info* lookup(std::unordered_multimap<size_t, const meta_info*>& haystack,
const meta_info* needle) {
auto i = haystack.find(needle->hash);
auto e = haystack.end();
if (i == e)
return nullptr;
auto n = std::next(i);
if (n == e || n->second->hash != i->second->hash)
return i->second;
for (; i != e; ++i)
if (strcmp(i->second->name, needle->name) == 0)
return i->second;
return nullptr;
}
const meta_info* lookup(std::multimap<size_t, const meta_info*>& haystack,
const meta_info* needle) {
auto i = haystack.find(needle->hash);
auto e = haystack.end();
if (i == e)
return nullptr;
auto n = std::next(i);
if (n == e || n->second->hash != i->second->hash)
return i->second;
for (; i != e; ++i)
if (strcmp(i->second->name, needle->name) == 0)
return i->second;
return nullptr;
}
CAF_TEST(foobar) {
using std::make_pair;
std::vector<std::pair<size_t, const meta_info*>> map1;
std::unordered_multimap<size_t, const meta_info*> map2;
std::multimap<size_t, const meta_info*> map3;
const meta_info* arr[] = {
&meta_information<foo1>::value,
&meta_information<foo2>::value,
&meta_information<foo3>::value,
&meta_information<foo4>::value,
&meta_information<foo5>::value,
&meta_information<foo6>::value,
&meta_information<foo7>::value,
&meta_information<foo8>::value,
&meta_information<foo9>::value,
&meta_information<foo10>::value,
&meta_information<foo11>::value,
&meta_information<foo12>::value,
&meta_information<foo13>::value,
&meta_information<foo14>::value,
&meta_information<foo15>::value,
&meta_information<foo16>::value,
&meta_information<foo17>::value,
&meta_information<foo18>::value,
&meta_information<foo19>::value,
&meta_information<foo20>::value
};
for (auto i = std::begin(arr); i != std::end(arr); ++i) {
map1.emplace_back((*i)->hash, *i);
map2.emplace((*i)->hash, *i);
map3.emplace((*i)->hash, *i);
}
std::sort(map1.begin(), map1.end());
std::array<const meta_info*, 20> dummy;
{
auto t0 = std::chrono::high_resolution_clock::now();
for (auto i = 0; i < 10000; ++i)
dummy[i % 20] = lookup(map1, arr[i % 20]);
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << "vector: " << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << std::endl;
std::cout << "check: " << std::equal(dummy.begin(), dummy.end(), std::begin(arr)) << std::endl;
}
{
auto t0 = std::chrono::high_resolution_clock::now();
for (auto i = 0; i < 10000; ++i)
dummy[i % 20] = lookup(map2, arr[i % 20]);
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << "hash map: " << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << std::endl;
std::cout << "check: " << std::equal(dummy.begin(), dummy.end(), std::begin(arr)) << std::endl;
}
{
auto t0 = std::chrono::high_resolution_clock::now();
for (auto i = 0; i < 10000; ++i)
dummy[i % 20] = lookup(map3, arr[i % 20]);
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << "map: " << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << std::endl;
std::cout << "check: " << std::equal(dummy.begin(), dummy.end(), std::begin(arr)) << std::endl;
}
CAF_CHECK(meta_information<foo1>::value.name == "foo1");
CAF_CHECK(meta_information<foo1>::value.hash == str_hash("foo1"));
CAF_CHECK(meta_information<foo1>::value.version == 0);
}
CAF_TEST(global_redirect) { CAF_TEST(global_redirect) {
scoped_actor self; scoped_actor self;
self->join(group::get("local", global_redirect)); self->join(group::get("local", global_redirect));
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE local_migration
#include "caf/test/unit_test.hpp"
#include "caf/all.hpp"
#include "caf/detail/actor_registry.hpp"
using namespace caf;
using namespace caf::experimental;
using std::endl;
namespace {
struct migratable_state {
int value = 0;
static const char* name;
};
const char* migratable_state::name = "migratable_actor";
template <class Archive>
void serialize(Archive& ar, migratable_state& x, const unsigned int) {
ar & x.value;
}
struct migratable_actor : stateful_actor<migratable_state> {
behavior make_behavior() override {
return {
[=](get_atom) {
return state.value;
},
[=](put_atom, int value) {
state.value = value;
}
};
}
};
// always migrates to `dest`
behavior pseudo_mm(event_based_actor* self, const actor& dest) {
return {
[=](migrate_atom, const std::string& name, std::vector<char>& buf) {
CAF_CHECK(name == "migratable_actor");
self->delegate(dest, sys_atom::value, migrate_atom::value,
std::move(buf));
}
};
}
} // namespace <anonymous>
CAF_TEST(migrate_locally) {
auto a = spawn<migratable_actor>();
auto b = spawn<migratable_actor>();
auto mm1 = spawn(pseudo_mm, b);
scoped_actor self;
self->send(a, put_atom::value, 42);
// migrate from a to b
self->sync_send(a, sys_atom::value, migrate_atom::value, mm1).await(
[&](ok_atom, const actor_addr& dest) {
CAF_CHECK(dest == b);
}
);
self->sync_send(a, get_atom::value).await(
[&](int result) {
CAF_CHECK(result == 42);
CAF_CHECK(self->current_sender() == b.address());
}
);
auto mm2 = spawn(pseudo_mm, a);
self->send(b, put_atom::value, 23);
// migrate back from b to a
self->sync_send(b, sys_atom::value, migrate_atom::value, mm2).await(
[&](ok_atom, const actor_addr& dest) {
CAF_CHECK(dest == a);
}
);
self->sync_send(b, get_atom::value).await(
[&](int result) {
CAF_CHECK(result == 23);
CAF_CHECK(self->current_sender() == a.address());
}
);
self->send_exit(a, exit_reason::kill);
self->send_exit(b, exit_reason::kill);
self->send_exit(mm1, exit_reason::kill);
self->send_exit(mm2, exit_reason::kill);
self->await_all_other_actors_done();
}
...@@ -27,6 +27,18 @@ ...@@ -27,6 +27,18 @@
using namespace caf; using namespace caf;
CAF_TEST(apply) {
auto f1 = [] {
CAF_TEST_ERROR("f1 invoked!");
};
auto f2 = [](int i) {
CAF_CHECK_EQUAL(i, 42);
};
auto m = make_message(42);
m.apply(f1);
m.apply(f2);
}
CAF_TEST(drop) { CAF_TEST(drop) {
auto m1 = make_message(1, 2, 3, 4, 5); auto m1 = make_message(1, 2, 3, 4, 5);
std::vector<message> messages{ std::vector<message> messages{
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "caf/io/remote_group.hpp" #include "caf/io/remote_group.hpp"
#include "caf/io/set_middleman.hpp" #include "caf/io/set_middleman.hpp"
#include "caf/io/receive_policy.hpp" #include "caf/io/receive_policy.hpp"
#include "caf/io/middleman_actor.hpp"
#include "caf/io/system_messages.hpp" #include "caf/io/system_messages.hpp"
#include "caf/io/publish_local_groups.hpp" #include "caf/io/publish_local_groups.hpp"
......
...@@ -27,14 +27,16 @@ ...@@ -27,14 +27,16 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include "caf/fwd.hpp"
#include "caf/node_id.hpp" #include "caf/node_id.hpp"
#include "caf/callback.hpp"
#include "caf/actor_addr.hpp" #include "caf/actor_addr.hpp"
#include "caf/serializer.hpp" #include "caf/serializer.hpp"
#include "caf/deserializer.hpp" #include "caf/deserializer.hpp"
#include "caf/abstract_actor.hpp" #include "caf/abstract_actor.hpp"
#include "caf/callback.hpp"
#include "caf/actor_namespace.hpp" #include "caf/actor_namespace.hpp"
#include "caf/io/middleman.hpp"
#include "caf/io/receive_policy.hpp" #include "caf/io/receive_policy.hpp"
#include "caf/io/abstract_broker.hpp" #include "caf/io/abstract_broker.hpp"
#include "caf/io/system_messages.hpp" #include "caf/io/system_messages.hpp"
...@@ -307,6 +309,11 @@ public: ...@@ -307,6 +309,11 @@ public:
/// `invalid_connection_handle` if no direct connection to `nid` exists. /// `invalid_connection_handle` if no direct connection to `nid` exists.
connection_handle lookup_direct(const node_id& nid) const; connection_handle lookup_direct(const node_id& nid) const;
/// Returns the next hop that would be chosen for `nid`
/// or `invalid_node_id` if there's no indirect route to `nid`.
node_id lookup_indirect(const node_id& nid) const;
/// Flush output buffer for `r`. /// Flush output buffer for `r`.
void flush(const route& r); void flush(const route& r);
...@@ -315,7 +322,7 @@ public: ...@@ -315,7 +322,7 @@ public:
void add_direct(const connection_handle& hdl, const node_id& dest); void add_direct(const connection_handle& hdl, const node_id& dest);
/// Adds a new indirect route to the table. /// Adds a new indirect route to the table.
void add_indirect(const node_id& hop, const node_id& dest); bool add_indirect(const node_id& hop, const node_id& dest);
/// Blacklist the route to `dest` via `hop`. /// Blacklist the route to `dest` via `hop`.
void blacklist(const node_id& hop, const node_id& dest); void blacklist(const node_id& hop, const node_id& dest);
...@@ -325,6 +332,10 @@ public: ...@@ -325,6 +332,10 @@ public:
/// including the node that is assigned as direct path for `hdl`. /// including the node that is assigned as direct path for `hdl`.
void erase_direct(const connection_handle& hdl, erase_callback& cb); void erase_direct(const connection_handle& hdl, erase_callback& cb);
/// Removes any entry for indirect connection to `dest` and returns
/// `true` if `dest` had an indirect route, otherwise `false`.
bool erase_indirect(const node_id& dest);
/// Queries whether `dest` is reachable. /// Queries whether `dest` is reachable.
bool reachable(const node_id& dest); bool reachable(const node_id& dest);
...@@ -362,14 +373,14 @@ public: ...@@ -362,14 +373,14 @@ public:
/// Provides a callback-based interface for certain BASP events. /// Provides a callback-based interface for certain BASP events.
class callee { class callee {
public: public:
explicit callee(actor_namespace::backend& mgm); explicit callee(actor_namespace::backend& mgm, middleman& mm);
virtual ~callee(); virtual ~callee();
/// Called if a server handshake was received and /// Called if a server handshake was received and
/// the connection to `nid` is established. /// the connection to `nid` is established.
virtual void finalize_handshake(const node_id& nid, actor_id aid, virtual void finalize_handshake(const node_id& nid, actor_id aid,
const std::set<std::string>& sigs) = 0; std::set<std::string>& sigs) = 0;
/// Called whenever a direct connection was closed or a /// Called whenever a direct connection was closed or a
/// node became unrechable for other reasons *before* /// node became unrechable for other reasons *before*
...@@ -378,8 +389,12 @@ public: ...@@ -378,8 +389,12 @@ public:
/// routing table from this callback. /// routing table from this callback.
virtual void purge_state(const node_id& nid) = 0; virtual void purge_state(const node_id& nid) = 0;
/// Called whenever a remote node created a proxy
/// for one of our local actors.
virtual void proxy_announced(const node_id& nid, actor_id aid) = 0; virtual void proxy_announced(const node_id& nid, actor_id aid) = 0;
/// Called whenever a remote actor died to destroy
/// the proxy instance on our end.
virtual void kill_proxy(const node_id& nid, actor_id aid, uint32_t rsn) = 0; virtual void kill_proxy(const node_id& nid, actor_id aid, uint32_t rsn) = 0;
/// Called whenever a `dispatch_message` arrived for a local actor. /// Called whenever a `dispatch_message` arrived for a local actor.
...@@ -387,13 +402,27 @@ public: ...@@ -387,13 +402,27 @@ public:
const node_id& dest_node, actor_id dest_actor, const node_id& dest_node, actor_id dest_actor,
message& msg, message_id mid) = 0; message& msg, message_id mid) = 0;
/// Called whenever BASP learns the ID of a remote node
/// to which it does not have a direct connection.
virtual void learned_new_node_directly(const node_id& nid,
bool was_known_indirectly) = 0;
/// Called whenever BASP learns the ID of a remote node
/// to which it does not have a direct connection.
virtual void learned_new_node_indirectly(const node_id& nid) = 0;
/// Returns the actor namespace associated to this BASP protocol instance. /// Returns the actor namespace associated to this BASP protocol instance.
actor_namespace& get_namespace() { inline actor_namespace& get_namespace() {
return namespace_; return namespace_;
} }
public: inline middleman& get_middleman() {
return middleman_;
}
protected:
actor_namespace namespace_; actor_namespace namespace_;
middleman& middleman_;
}; };
/// Describes a function object responsible for writing /// Describes a function object responsible for writing
...@@ -490,6 +519,9 @@ public: ...@@ -490,6 +519,9 @@ public:
/// written (e.g. used when establishing direct connections on-the-fly). /// written (e.g. used when establishing direct connections on-the-fly).
void write_server_handshake(buffer_type& buf, optional<uint16_t> port); void write_server_handshake(buffer_type& buf, optional<uint16_t> port);
/// Writes the client handshake to `buf`.
void write_client_handshake(buffer_type& buf, const node_id& remote_side);
/// Writes a `dispatch_error` to `buf`. /// Writes a `dispatch_error` to `buf`.
void write_dispatch_error(buffer_type& buf, void write_dispatch_error(buffer_type& buf,
const node_id& source_node, const node_id& source_node,
...@@ -504,10 +536,16 @@ public: ...@@ -504,10 +536,16 @@ public:
actor_id aid, actor_id aid,
uint32_t rsn); uint32_t rsn);
const node_id& this_node() const { inline const node_id& this_node() const {
return this_node_; return this_node_;
} }
/// Invokes the callback(s) associated with given event.
template <hook::event_type Event, typename... Ts>
void notify(Ts&&... xs) {
callee_.get_middleman().template notify<Event>(std::forward<Ts>(xs)...);
}
private: private:
routing_table tbl_; routing_table tbl_;
published_actor_map published_actors_; published_actor_map published_actors_;
......
...@@ -49,7 +49,7 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee { ...@@ -49,7 +49,7 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee {
// inherited from basp::instance::listener // inherited from basp::instance::listener
void finalize_handshake(const node_id& nid, actor_id aid, void finalize_handshake(const node_id& nid, actor_id aid,
const std::set<std::string>& sigs) override; std::set<std::string>& sigs) override;
// inherited from basp::instance::listener // inherited from basp::instance::listener
void purge_state(const node_id& id) override; void purge_state(const node_id& id) override;
...@@ -60,10 +60,21 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee { ...@@ -60,10 +60,21 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee {
// inherited from basp::instance::listener // inherited from basp::instance::listener
void kill_proxy(const node_id& nid, actor_id aid, uint32_t rsn) override; void kill_proxy(const node_id& nid, actor_id aid, uint32_t rsn) override;
// inherited from basp::instance::listener
void deliver(const node_id& source_node, actor_id source_actor, void deliver(const node_id& source_node, actor_id source_actor,
const node_id& dest_node, actor_id dest_actor, const node_id& dest_node, actor_id dest_actor,
message& msg, message_id mid) override; message& msg, message_id mid) override;
// performs bookkeeping such as managing `spawn_servers`
void learned_new_node(const node_id& nid);
// inherited from basp::instance::listener
void learned_new_node_directly(const node_id& nid,
bool was_known_indirectly_before) override;
// inherited from basp::instance::listener
void learned_new_node_indirectly(const node_id& nid) override;
struct connection_context { struct connection_context {
basp::connection_state cstate; basp::connection_state cstate;
basp::header hdr; basp::header hdr;
...@@ -71,7 +82,6 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee { ...@@ -71,7 +82,6 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee {
node_id id; node_id id;
uint16_t remote_port; uint16_t remote_port;
optional<response_promise> callback; optional<response_promise> callback;
std::set<std::string> expected_sigs;
}; };
void set_context(connection_handle hdl); void set_context(connection_handle hdl);
...@@ -94,6 +104,16 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee { ...@@ -94,6 +104,16 @@ struct basp_broker_state : actor_namespace::backend, basp::instance::callee {
// keeps the associated proxies alive to work around subtle bugs // keeps the associated proxies alive to work around subtle bugs
std::unordered_map<node_id, std::pair<uint16_t, actor_addr>> known_remotes; std::unordered_map<node_id, std::pair<uint16_t, actor_addr>> known_remotes;
// stores handles to spawn servers for other nodes; these servers
// are spawned whenever the broker learns a new node ID and try to
// get a 'SpawnServ' instance on the remote side
std::unordered_map<node_id, actor> spawn_servers;
// can be enabled by the user to let CAF automatically try
// to establish new connections at runtime to optimize
// routing paths by forming a mesh between all nodes
bool enable_automatic_connections = false;
const node_id& this_node() const { const node_id& this_node() const {
return instance.this_node(); return instance.this_node();
} }
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#ifndef CAF_IO_HOOK_HPP #ifndef CAF_IO_HOOK_HPP
#define CAF_IO_HOOK_HPP #define CAF_IO_HOOK_HPP
#include <set>
#include <memory> #include <memory>
#include <vector> #include <vector>
...@@ -71,16 +72,17 @@ public: ...@@ -71,16 +72,17 @@ public:
const message& payload); const message& payload);
/// Called whenever a message is forwarded to a different node. /// Called whenever a message is forwarded to a different node.
virtual void message_forwarded_cb(const node_id& from, const node_id& dest, virtual void message_forwarded_cb(const basp::header& hdr,
const std::vector<char>* payload); const std::vector<char>* payload);
/// Called whenever no route for a forwarding request exists. /// Called whenever no route for a forwarding request exists.
virtual void message_forwarding_failed_cb(const node_id& from, virtual void message_forwarding_failed_cb(const basp::header& hdr,
const node_id& to,
const std::vector<char>* payload); const std::vector<char>* payload);
/// Called whenever an actor has been published. /// Called whenever an actor has been published.
virtual void actor_published_cb(const actor_addr& addr, uint16_t port); virtual void actor_published_cb(const actor_addr& addr,
const std::set<std::string>& ifs,
uint16_t port);
/// Called whenever a new remote actor appeared. /// Called whenever a new remote actor appeared.
virtual void new_remote_actor_cb(const actor_addr& addr); virtual void new_remote_actor_cb(const actor_addr& addr);
...@@ -93,6 +95,15 @@ public: ...@@ -93,6 +95,15 @@ public:
/// @param node The newly added entry to the routing table. /// @param node The newly added entry to the routing table.
virtual void new_route_added_cb(const node_id& via, const node_id& node); virtual void new_route_added_cb(const node_id& via, const node_id& node);
/// Called whenever a direct connection was lost.
virtual void connection_lost_cb(const node_id& dest);
/// Called whenever a route became unavailable.
/// @param hop The node that was either disconnected
/// or lost a connection itself.
/// @param dest The node that is no longer reachable via `hop`.
virtual void route_lost_cb(const node_id& hop, const node_id& dest);
/// Called whenever a message was discarded because a remote node /// Called whenever a message was discarded because a remote node
/// tried to send a message to an actor ID that could not be found /// tried to send a message to an actor ID that could not be found
/// in the registry. /// in the registry.
...@@ -115,6 +126,8 @@ public: ...@@ -115,6 +126,8 @@ public:
new_remote_actor, new_remote_actor,
new_connection_established, new_connection_established,
new_route_added, new_route_added,
connection_lost,
route_lost,
invalid_message_received, invalid_message_received,
before_shutdown before_shutdown
}; };
...@@ -150,6 +163,8 @@ private: ...@@ -150,6 +163,8 @@ private:
CAF_IO_HOOK_DISPATCH(new_remote_actor) CAF_IO_HOOK_DISPATCH(new_remote_actor)
CAF_IO_HOOK_DISPATCH(new_connection_established) CAF_IO_HOOK_DISPATCH(new_connection_established)
CAF_IO_HOOK_DISPATCH(new_route_added) CAF_IO_HOOK_DISPATCH(new_route_added)
CAF_IO_HOOK_DISPATCH(connection_lost)
CAF_IO_HOOK_DISPATCH(route_lost)
CAF_IO_HOOK_DISPATCH(invalid_message_received) CAF_IO_HOOK_DISPATCH(invalid_message_received)
CAF_IO_HOOK_DISPATCH(before_shutdown) CAF_IO_HOOK_DISPATCH(before_shutdown)
}; };
......
...@@ -99,6 +99,10 @@ public: ...@@ -99,6 +99,10 @@ public:
}); });
} }
inline bool has_hook() const {
return hooks_ != nullptr;
}
template <class F> template <class F>
void add_shutdown_cb(F fun) { void add_shutdown_cb(F fun) {
struct impl : hook { struct impl : hook {
......
...@@ -30,100 +30,94 @@ namespace io { ...@@ -30,100 +30,94 @@ namespace io {
/// ///
/// The interface implements the following pseudo code. /// The interface implements the following pseudo code.
/// ~~~ /// ~~~
/// using put_result =
/// either (ok_atom, uint16_t port)
/// or (error_atom, string error_string);
///
/// using get_result =
/// either (ok_atom, actor_addr remote_address)
/// or (error_atom, string error_string);
///
/// using delete_result =
/// either (ok_atom)
/// or (error_atom, string error_string);
///
/// interface middleman_actor { /// interface middleman_actor {
/// (put_atom, actor_addr whom, uint16_t port, string addr, bool reuse_addr)
/// -> put_result;
/// ///
/// (put_atom, actor_addr whom, uint16_t port, string addr) /// // Establishes a new `port <-> actor` mapping and returns the actual
/// -> put_result; /// // port in use on success. Passing 0 as port instructs the OS to choose
/// // the next high-level port available for binding.
/// // @param port: Unused TCP port or 0 for any.
/// // @param whom: Actor that should be published at given port.
/// // @param ifs: Interface of given actor.
/// // @param addr: IP address to listen to or empty for any.
/// // @param reuse_addr: Enables or disables SO_REUSEPORT option.
/// (publish_atom, uint16_t port, actor_addr whom,
/// set<string> ifs, string addr, bool reuse_addr)
/// -> either (ok_atom, uint16_t port)
/// or (error_atom, string error_string)
/// ///
/// (put_atom, actor_addr whom, uint16_t port, bool reuse_addr) /// // Opens a new port other CAF instances can connect to. The
/// -> put_result; /// // difference between `PUBLISH` and `OPEN` is that no actor is mapped to
/// // this port, meaning that connecting nodes only get a valid `node_id`
/// // handle when connecting.
/// // @param port: Unused TCP port or 0 for any.
/// // @param addr: IP address to listen to or empty for any.
/// // @param reuse_addr: Enables or disables SO_REUSEPORT option.
/// (open_atom, uint16_t port, string addr, bool reuse_addr)
/// -> either (ok_atom, uint16_t port)
/// or (error_atom, string error_string)
/// ///
/// (put_atom, actor_addr whom, uint16_t port) /// // Queries a remote node and returns an ID to this node as well as
/// -> put_result; /// // an `actor_addr` to a remote actor if an actor was published at this
/// // port. The actor address must be cast to either `actor` or
/// // `typed_actor` using `actor_cast` after validating `ifs`.
/// // @param hostname IP address or DNS hostname.
/// // @param port TCP port.
/// (connect_atom, string hostname, uint16_t port)
/// -> either (ok_atom, node_id nid, actor_addr remote_actor, set<string> ifs)
/// or (error_atom, string error_string)
/// ///
/// (get_atom, string hostname, uint16_t port) /// // Closes `port` if it is mapped to `whom`.
/// -> get_result; /// // @param whom A published actor.
/// // @param port Used TCP port.
/// (unpublish_atom, actor_addr whom, uint16_t port)
/// -> either (ok_atom)
/// or (error_atom, string error_string)
/// ///
/// (get_atom, string hostname, uint16_t port, set<string> expected_ifs) /// // Unconditionally closes `port`, removing any actor
/// -> get_result; /// // published at this port.
/// // @param port Used TCP port.
/// (close_atom, uint16_t port)
/// -> either (ok_atom)
/// or (error_atom, string error_string)
/// ///
/// (delete_atom, actor_addr whom) /// // Spawns an actor on a remote node, initializing it
/// -> delete_result; /// // using the arguments stored in `msg`.
/// // @param nid ID of the remote node that should spawn the actor.
/// // @param name Announced type name of the actor.
/// // @param args Initialization arguments for the actor.
/// // @returns The address of the spawned actor and its interface
/// // description on success; an error string otherwise.
/// (spawn_atom, node_id nid, string name, message args)
/// -> either (ok_atom, actor_addr, set<string>
/// or (error_atom, string error_string)
/// ///
/// (delete_atom, actor_addr whom, uint16_t port)
/// -> delete_result;
/// } /// }
/// ~~~ /// ~~~
///
/// The `middleman_actor` actor offers the following operations:
/// - `PUT` establishes a new `port <-> actor`
/// mapping and returns the actual port in use on success.
/// Passing 0 as port instructs the OS to choose the next high-level port
/// available for binding.
/// Type | Name | Parameter Description
/// -----------|------------|--------------------------------------------------
/// put_atom | | Identifies `PUT` operations.
/// actor_addr | whom | Actor that should be published at given port.
/// uint16_t | port | Unused TCP port or 0 for any.
/// string | addr | Optional; IP address to listen to or `INADDR_ANY`
/// bool | reuse_addr | Optional; enable SO_REUSEPORT option
///
/// - `GET` queries a remote node and returns an `actor_addr` to the remote actor
/// on success. This handle must be cast to either `actor` or `typed_actor`
/// using `actor_cast`.
/// Type | Name | Parameter Description
/// ------------|-------------|------------------------------------------------
/// get_atom | | Identifies `GET` operations.
/// string | hostname | Valid hostname or IP address.
/// uint16_t | port | TCP port.
/// set<string> | expected_ifs | Optional; Interface of typed remote actor.
///
/// - `DELETE` removes either all `port <-> actor` mappings for an actor or only
/// a single one if the optional `port` parameter is set.
/// Type | Name | Parameter Description
/// ------------|-------------|------------------------------------------------
/// delete_atom | | Identifies `DELETE` operations.
/// actor_addr | whom | Published actor.
/// uint16_t | port | Optional; remove only a single mapping.
using middleman_actor = using middleman_actor =
typed_actor< typed_actor<
replies_to<put_atom, uint16_t, actor_addr, std::set<std::string>, std::string, bool> replies_to<publish_atom, uint16_t, actor_addr,
::with_either<ok_atom, uint16_t> std::set<std::string>, std::string, bool>
::or_else<error_atom, std::string>,
replies_to<put_atom, uint16_t, actor_addr, std::set<std::string>, std::string>
::with_either<ok_atom, uint16_t>
::or_else<error_atom, std::string>,
replies_to<put_atom, uint16_t, actor_addr, std::set<std::string>, bool>
::with_either<ok_atom, uint16_t> ::with_either<ok_atom, uint16_t>
::or_else<error_atom, std::string>, ::or_else<error_atom, std::string>,
replies_to<put_atom, uint16_t, actor_addr, std::set<std::string>>
replies_to<open_atom, uint16_t, std::string, bool>
::with_either<ok_atom, uint16_t> ::with_either<ok_atom, uint16_t>
::or_else<error_atom, std::string>, ::or_else<error_atom, std::string>,
replies_to<get_atom, std::string, uint16_t>
::with_either<ok_atom, actor_addr> replies_to<connect_atom, std::string, uint16_t>
::or_else<error_atom, std::string>, ::with_either<ok_atom, node_id, actor_addr, std::set<std::string>>
replies_to<get_atom, std::string, uint16_t, std::set<std::string>>
::with_either<ok_atom, actor_addr>
::or_else<error_atom, std::string>, ::or_else<error_atom, std::string>,
replies_to<delete_atom, actor_addr>
replies_to<unpublish_atom, actor_addr, uint16_t>
::with_either<ok_atom> ::with_either<ok_atom>
::or_else<error_atom, std::string>, ::or_else<error_atom, std::string>,
replies_to<delete_atom, actor_addr, uint16_t>
replies_to<close_atom, uint16_t>
::with_either<ok_atom> ::with_either<ok_atom>
::or_else<error_atom, std::string>,
replies_to<spawn_atom, node_id, std::string, message>
::with_either<ok_atom, actor_addr, std::set<std::string>>
::or_else<error_atom, std::string>>; ::or_else<error_atom, std::string>>;
/// Returns a handle for asynchronous networking operations. /// Returns a handle for asynchronous networking operations.
...@@ -133,4 +127,3 @@ middleman_actor get_middleman_actor(); // implemented in middleman.cpp ...@@ -133,4 +127,3 @@ middleman_actor get_middleman_actor(); // implemented in middleman.cpp
} // namespace caf } // namespace caf
#endif // CAF_IO_MIDDLEMAN_ACTOR_HPP #endif // CAF_IO_MIDDLEMAN_ACTOR_HPP
...@@ -33,10 +33,11 @@ namespace caf { ...@@ -33,10 +33,11 @@ namespace caf {
namespace io { namespace io {
namespace network { namespace network {
// {protocol => address}
using address_listing = std::map<protocol, std::vector<std::string>>;
// {interface_name => {protocol => address}} // {interface_name => {protocol => address}}
using interfaces_map = std::map<std::string, using interfaces_map = std::map<std::string, address_listing>;
std::map<protocol,
std::vector<std::string>>>;
/// Utility class bundling access to network interface names and addresses. /// Utility class bundling access to network interface names and addresses.
class interfaces { class interfaces {
...@@ -45,8 +46,7 @@ public: ...@@ -45,8 +46,7 @@ public:
static interfaces_map list_all(bool include_localhost = true); static interfaces_map list_all(bool include_localhost = true);
/// Returns all addresses for all devices for all protocols. /// Returns all addresses for all devices for all protocols.
static std::map<protocol, std::vector<std::string>> static address_listing list_addresses(bool include_localhost = true);
list_addresses(bool include_localhost = true);
/// Returns all addresses for all devices for given protocol. /// Returns all addresses for all devices for given protocol.
static std::vector<std::string> list_addresses(protocol proc, static std::vector<std::string> list_addresses(protocol proc,
......
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
namespace caf { namespace caf {
namespace io { namespace io {
abstract_actor_ptr remote_actor_impl(std::set<std::string> ifs, actor_addr remote_actor_impl(std::set<std::string> ifs,
const std::string& host, uint16_t port); std::string host, uint16_t port);
/// Establish a new connection to the actor at `host` on given `port`. /// Establish a new connection to the actor at `host` on given `port`.
/// @param host Valid hostname or IP address. /// @param host Valid hostname or IP address.
...@@ -41,8 +41,8 @@ abstract_actor_ptr remote_actor_impl(std::set<std::string> ifs, ...@@ -41,8 +41,8 @@ abstract_actor_ptr remote_actor_impl(std::set<std::string> ifs,
/// representing a remote actor. /// representing a remote actor.
/// @throws network_error Thrown on connection error or /// @throws network_error Thrown on connection error or
/// when connecting to a typed actor. /// when connecting to a typed actor.
inline actor remote_actor(const std::string& host, uint16_t port) { inline actor remote_actor(std::string host, uint16_t port) {
auto res = remote_actor_impl(std::set<std::string>{}, host, port); auto res = remote_actor_impl(std::set<std::string>{}, std::move(host), port);
return actor_cast<actor>(res); return actor_cast<actor>(res);
} }
...@@ -54,10 +54,10 @@ inline actor remote_actor(const std::string& host, uint16_t port) { ...@@ -54,10 +54,10 @@ inline actor remote_actor(const std::string& host, uint16_t port) {
/// @throws network_error Thrown on connection error or when connecting /// @throws network_error Thrown on connection error or when connecting
/// to an untyped otherwise unexpected actor. /// to an untyped otherwise unexpected actor.
template <class ActorHandle> template <class ActorHandle>
ActorHandle typed_remote_actor(const std::string& host, uint16_t port) { ActorHandle typed_remote_actor(std::string host, uint16_t port) {
auto iface = ActorHandle::message_types(); auto res = remote_actor_impl(ActorHandle::message_types(),
return actor_cast<ActorHandle>(remote_actor_impl(std::move(iface), std::move(host), port);
host, port)); return actor_cast<ActorHandle>(res);
} }
} // namespace io } // namespace io
......
...@@ -39,15 +39,15 @@ namespace basp { ...@@ -39,15 +39,15 @@ namespace basp {
std::string to_string(message_type x) { std::string to_string(message_type x) {
switch (x) { switch (x) {
case basp::message_type::server_handshake: case message_type::server_handshake:
return "server_handshake"; return "server_handshake";
case basp::message_type::client_handshake: case message_type::client_handshake:
return "client_handshake"; return "client_handshake";
case basp::message_type::dispatch_message: case message_type::dispatch_message:
return "dispatch_message"; return "dispatch_message";
case basp::message_type::announce_proxy_instance: case message_type::announce_proxy_instance:
return "announce_proxy_instance"; return "announce_proxy_instance";
case basp::message_type::kill_proxy_instance: case message_type::kill_proxy_instance:
return "kill_proxy_instance"; return "kill_proxy_instance";
default: default:
return "???"; return "???";
...@@ -146,7 +146,6 @@ bool announce_proxy_instance_valid(const header& hdr) { ...@@ -146,7 +146,6 @@ bool announce_proxy_instance_valid(const header& hdr) {
&& zero(hdr.operation_data); && zero(hdr.operation_data);
} }
bool kill_proxy_instance_valid(const header& hdr) { bool kill_proxy_instance_valid(const header& hdr) {
return valid(hdr.source_node) return valid(hdr.source_node)
&& valid(hdr.dest_node) && valid(hdr.dest_node)
...@@ -192,6 +191,19 @@ optional<routing_table::route> routing_table::lookup(const node_id& target) { ...@@ -192,6 +191,19 @@ optional<routing_table::route> routing_table::lookup(const node_id& target) {
auto hdl = lookup_direct(target); auto hdl = lookup_direct(target);
if (hdl != invalid_connection_handle) if (hdl != invalid_connection_handle)
return route{parent_->wr_buf(hdl), target, hdl}; return route{parent_->wr_buf(hdl), target, hdl};
// pick first available indirect route
auto i = indirect_.find(target);
if (i != indirect_.end()) {
auto& hops = i->second;
while (! hops.empty()) {
auto& hop = *hops.begin();
auto hdl = lookup_direct(hop);
if (hdl != invalid_connection_handle)
return route{parent_->wr_buf(hdl), hop, hdl};
else
hops.erase(hops.begin());
}
}
return none; return none;
} }
...@@ -209,6 +221,15 @@ routing_table::lookup_direct(const node_id& nid) const { ...@@ -209,6 +221,15 @@ routing_table::lookup_direct(const node_id& nid) const {
return get_opt(direct_by_nid_, nid, invalid_connection_handle); return get_opt(direct_by_nid_, nid, invalid_connection_handle);
} }
node_id routing_table::lookup_indirect(const node_id& nid) const {
auto i = indirect_.find(nid);
if (i == indirect_.end())
return invalid_node_id;
if (i->second.empty())
return invalid_node_id;
return *i->second.begin();
}
void routing_table::blacklist(const node_id& hop, const node_id& dest) { void routing_table::blacklist(const node_id& hop, const node_id& dest) {
blacklist_[dest].emplace(hop); blacklist_[dest].emplace(hop);
auto i = indirect_.find(dest); auto i = indirect_.find(dest);
...@@ -225,22 +246,41 @@ void routing_table::erase_direct(const connection_handle& hdl, ...@@ -225,22 +246,41 @@ void routing_table::erase_direct(const connection_handle& hdl,
if (i == direct_by_hdl_.end()) if (i == direct_by_hdl_.end())
return; return;
cb(i->second); cb(i->second);
parent_->parent().notify<hook::connection_lost>(i->second);
direct_by_nid_.erase(i->second); direct_by_nid_.erase(i->second);
direct_by_hdl_.erase(i); direct_by_hdl_.erase(i);
} }
bool routing_table::erase_indirect(const node_id& dest) {
auto i = indirect_.find(dest);
if (i == indirect_.end())
return false;
if (parent_->parent().has_hook())
for (auto& nid : i->second)
parent_->parent().notify<hook::route_lost>(nid, dest);
indirect_.erase(i);
return true;
}
void routing_table::add_direct(const connection_handle& hdl, void routing_table::add_direct(const connection_handle& hdl,
const node_id& nid) { const node_id& nid) {
CAF_ASSERT(direct_by_hdl_.count(hdl) == 0); CAF_ASSERT(direct_by_hdl_.count(hdl) == 0);
CAF_ASSERT(direct_by_nid_.count(nid) == 0); CAF_ASSERT(direct_by_nid_.count(nid) == 0);
direct_by_hdl_.emplace(hdl, nid); direct_by_hdl_.emplace(hdl, nid);
direct_by_nid_.emplace(nid, hdl); direct_by_nid_.emplace(nid, hdl);
parent_->parent().notify<hook::new_connection_established>(nid);
} }
void routing_table::add_indirect(const node_id& hop, const node_id& dest) { bool routing_table::add_indirect(const node_id& hop, const node_id& dest) {
auto i = blacklist_.find(dest); auto i = blacklist_.find(dest);
if (i == blacklist_.end() || i->second.count(hop) == 0) if (i == blacklist_.end() || i->second.count(hop) == 0) {
indirect_[dest].emplace(hop); auto& hops = indirect_[dest];
auto added_first = hops.empty();
hops.emplace(hop);
parent_->parent().notify<hook::new_route_added>(hop, dest);
return added_first;
}
return false; // blacklisted
} }
bool routing_table::reachable(const node_id& dest) { bool routing_table::reachable(const node_id& dest) {
...@@ -253,14 +293,17 @@ size_t routing_table::erase(const node_id& dest, erase_callback& cb) { ...@@ -253,14 +293,17 @@ size_t routing_table::erase(const node_id& dest, erase_callback& cb) {
auto i = indirect_.find(dest); auto i = indirect_.find(dest);
if (i != indirect_.end()) { if (i != indirect_.end()) {
res = i->second.size(); res = i->second.size();
for (auto& nid : i->second) for (auto& nid : i->second) {
cb(nid); cb(nid);
parent_->parent().notify<hook::route_lost>(nid, dest);
}
indirect_.erase(i); indirect_.erase(i);
} }
auto hdl = lookup_direct(dest); auto hdl = lookup_direct(dest);
if (hdl != invalid_connection_handle) { if (hdl != invalid_connection_handle) {
direct_by_hdl_.erase(hdl); direct_by_hdl_.erase(hdl);
direct_by_nid_.erase(dest); direct_by_nid_.erase(dest);
parent_->parent().notify<hook::connection_lost>(dest);
++res; ++res;
} }
return res; return res;
...@@ -270,7 +313,9 @@ size_t routing_table::erase(const node_id& dest, erase_callback& cb) { ...@@ -270,7 +313,9 @@ size_t routing_table::erase(const node_id& dest, erase_callback& cb) {
* callee * * callee *
******************************************************************************/ ******************************************************************************/
instance::callee::callee(actor_namespace::backend& mgm) : namespace_(mgm) { instance::callee::callee(actor_namespace::backend& mgm, middleman& mm)
: namespace_(mgm),
middleman_(mm) {
// nop // nop
} }
...@@ -328,6 +373,7 @@ connection_state instance::handle(const new_data_msg& dm, header& hdr, ...@@ -328,6 +373,7 @@ connection_state instance::handle(const new_data_msg& dm, header& hdr,
if (payload) if (payload)
bs.write_raw(payload->size(), payload->data()); bs.write_raw(payload->size(), payload->data());
tbl_.flush(*path); tbl_.flush(*path);
notify<hook::message_forwarded>(hdr, payload);
} else { } else {
CAF_LOG_INFO("cannot forward message, no route to destination"); CAF_LOG_INFO("cannot forward message, no route to destination");
if (hdr.source_node != this_node_) { if (hdr.source_node != this_node_) {
...@@ -345,6 +391,7 @@ connection_state instance::handle(const new_data_msg& dm, header& hdr, ...@@ -345,6 +391,7 @@ connection_state instance::handle(const new_data_msg& dm, header& hdr,
} else { } else {
CAF_LOG_WARNING("lost packet with probably spoofed source"); CAF_LOG_WARNING("lost packet with probably spoofed source");
} }
notify<hook::message_forwarding_failed>(hdr, payload);
} }
return await_header; return await_header;
} }
...@@ -375,36 +422,47 @@ connection_state instance::handle(const new_data_msg& dm, header& hdr, ...@@ -375,36 +422,47 @@ connection_state instance::handle(const new_data_msg& dm, header& hdr,
callee_.finalize_handshake(hdr.source_node, aid, sigs); callee_.finalize_handshake(hdr.source_node, aid, sigs);
return err(); return err();
} }
// add entry to routing table and call client // add direct route to this node and remove any indirect entry
CAF_LOG_INFO("new direct connection: " << to_string(hdr.source_node)); CAF_LOG_INFO("new direct connection: " << to_string(hdr.source_node));
tbl_.add_direct(dm.handle, hdr.source_node); tbl_.add_direct(dm.handle, hdr.source_node);
auto was_indirect = tbl_.erase_indirect(hdr.source_node);
// write handshake as client in response // write handshake as client in response
auto path = tbl_.lookup(hdr.source_node); auto path = tbl_.lookup(hdr.source_node);
if (!path) { if (!path) {
CAF_LOG_ERROR("no route to host after server handshake"); CAF_LOG_ERROR("no route to host after server handshake");
return err(); return err();
} }
write(path->wr_buf, write_client_handshake(path->wr_buf, hdr.source_node);
message_type::client_handshake, nullptr, 0, callee_.learned_new_node_directly(hdr.source_node, was_indirect);
this_node_, hdr.source_node,
invalid_actor_id, invalid_actor_id);
// tell client to create proxy etc.
callee_.finalize_handshake(hdr.source_node, aid, sigs); callee_.finalize_handshake(hdr.source_node, aid, sigs);
flush(*path); flush(*path);
break; break;
} }
case message_type::client_handshake: case message_type::client_handshake: {
if (tbl_.lookup_direct(hdr.source_node) != invalid_connection_handle) { if (tbl_.lookup_direct(hdr.source_node) != invalid_connection_handle) {
CAF_LOG_INFO("received second client handshake for " CAF_LOG_INFO("received second client handshake for "
<< to_string(hdr.source_node) << ", close connection"); << to_string(hdr.source_node) << " (ignored)");
return err(); break;
} }
// add direct route to this node // add direct route to this node and remove any indirect entry
CAF_LOG_INFO("new direct connection: " << to_string(hdr.source_node));
tbl_.add_direct(dm.handle, hdr.source_node); tbl_.add_direct(dm.handle, hdr.source_node);
auto was_indirect = tbl_.erase_indirect(hdr.source_node);
callee_.learned_new_node_directly(hdr.source_node, was_indirect);
break; break;
}
case message_type::dispatch_message: { case message_type::dispatch_message: {
if (! payload_valid()) if (! payload_valid())
return err(); return err();
// in case the sender of this message was received via a third node,
// we assume that that node to offers a route to the original source
auto last_hop = tbl_.lookup_direct(dm.handle);
if (hdr.source_node != invalid_node_id
&& hdr.source_node != this_node_
&& last_hop != hdr.source_node
&& tbl_.lookup_direct(hdr.source_node) == invalid_connection_handle
&& tbl_.add_indirect(last_hop, hdr.source_node))
callee_.learned_new_node_indirectly(hdr.source_node);
binary_deserializer bd{payload->data(), payload->size(), binary_deserializer bd{payload->data(), payload->size(),
&get_namespace()}; &get_namespace()};
message msg; message msg;
...@@ -468,6 +526,7 @@ void instance::add_published_actor(uint16_t port, ...@@ -468,6 +526,7 @@ void instance::add_published_actor(uint16_t port,
auto& entry = published_actors_[port]; auto& entry = published_actors_[port];
swap(entry.first, published_actor); swap(entry.first, published_actor);
swap(entry.second, published_interface); swap(entry.second, published_interface);
notify<hook::actor_published>(entry.first, entry.second, port);
} }
size_t instance::remove_published_actor(uint16_t port, size_t instance::remove_published_actor(uint16_t port,
...@@ -511,11 +570,12 @@ size_t instance::remove_published_actor(const actor_addr& whom, uint16_t port, ...@@ -511,11 +570,12 @@ size_t instance::remove_published_actor(const actor_addr& whom, uint16_t port,
bool instance::dispatch(const actor_addr& sender, const actor_addr& receiver, bool instance::dispatch(const actor_addr& sender, const actor_addr& receiver,
message_id mid, const message& msg) { message_id mid, const message& msg) {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
if (! receiver.is_remote()) CAF_ASSERT(receiver.is_remote());
return false;
auto path = lookup(receiver->node()); auto path = lookup(receiver->node());
if (! path) if (! path) {
notify<hook::message_sending_failed>(sender, receiver, mid, msg);
return false; return false;
}
auto writer = make_callback([&](serializer& sink) { auto writer = make_callback([&](serializer& sink) {
msg.serialize(sink); msg.serialize(sink);
}); });
...@@ -524,6 +584,7 @@ bool instance::dispatch(const actor_addr& sender, const actor_addr& receiver, ...@@ -524,6 +584,7 @@ bool instance::dispatch(const actor_addr& sender, const actor_addr& receiver,
sender ? sender->id() : invalid_actor_id, receiver->id()}; sender ? sender->id() : invalid_actor_id, receiver->id()};
write(path->wr_buf, hdr, &writer); write(path->wr_buf, hdr, &writer);
flush(*path); flush(*path);
notify<hook::message_sent>(sender, path->next_hop, receiver, mid, msg);
return true; return true;
} }
...@@ -563,7 +624,7 @@ void instance::write(buffer_type& buf, ...@@ -563,7 +624,7 @@ void instance::write(buffer_type& buf,
bs2.write(source_actor) bs2.write(source_actor)
.write(dest_actor) .write(dest_actor)
.write(plen) .write(plen)
.write(static_cast<uint32_t>(operation)) .write(static_cast<uint32_t>(operation))
.write(operation_data); .write(operation_data);
if (payload_len) if (payload_len)
*payload_len = plen; *payload_len = plen;
...@@ -577,6 +638,7 @@ void instance::write(buffer_type& buf, header& hdr, payload_writer* pw) { ...@@ -577,6 +638,7 @@ void instance::write(buffer_type& buf, header& hdr, payload_writer* pw) {
void instance::write_server_handshake(buffer_type& out_buf, void instance::write_server_handshake(buffer_type& out_buf,
optional<uint16_t> port) { optional<uint16_t> port) {
using namespace detail;
published_actor* pa = nullptr; published_actor* pa = nullptr;
if (port) { if (port) {
auto i = published_actors_.find(*port); auto i = published_actors_.find(*port);
...@@ -584,12 +646,8 @@ void instance::write_server_handshake(buffer_type& out_buf, ...@@ -584,12 +646,8 @@ void instance::write_server_handshake(buffer_type& out_buf,
pa = &i->second; pa = &i->second;
} }
auto writer = make_callback([&](serializer& sink) { auto writer = make_callback([&](serializer& sink) {
if (! pa) { if (pa)
return; sink << pa->first.id() << pa->second;
}
sink << pa->first.id() << static_cast<uint32_t>(pa->second.size());
for (auto& sig : pa->second)
sink << sig;
}); });
header hdr{message_type::server_handshake, 0, version, header hdr{message_type::server_handshake, 0, version,
this_node_, invalid_node_id, this_node_, invalid_node_id,
...@@ -597,6 +655,14 @@ void instance::write_server_handshake(buffer_type& out_buf, ...@@ -597,6 +655,14 @@ void instance::write_server_handshake(buffer_type& out_buf,
write(out_buf, hdr, &writer); write(out_buf, hdr, &writer);
} }
void instance::write_client_handshake(buffer_type& buf,
const node_id& remote_side) {
write(buf,
message_type::client_handshake, nullptr, 0,
this_node_, remote_side,
invalid_actor_id, invalid_actor_id);
}
void instance::write_dispatch_error(buffer_type& buf, void instance::write_dispatch_error(buffer_type& buf,
const node_id& source_node, const node_id& source_node,
const node_id& dest_node, const node_id& dest_node,
......
...@@ -19,10 +19,15 @@ ...@@ -19,10 +19,15 @@
#include "caf/io/basp_broker.hpp" #include "caf/io/basp_broker.hpp"
#include <limits>
#include "caf/exception.hpp" #include "caf/exception.hpp"
#include "caf/make_counted.hpp" #include "caf/make_counted.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/forwarding_actor_proxy.hpp" #include "caf/forwarding_actor_proxy.hpp"
#include "caf/experimental/whereis.hpp"
#include "caf/detail/singletons.hpp" #include "caf/detail/singletons.hpp"
#include "caf/detail/actor_registry.hpp" #include "caf/detail/actor_registry.hpp"
#include "caf/detail/sync_request_bouncer.hpp" #include "caf/detail/sync_request_bouncer.hpp"
...@@ -31,7 +36,9 @@ ...@@ -31,7 +36,9 @@
#include "caf/io/middleman.hpp" #include "caf/io/middleman.hpp"
#include "caf/io/unpublish.hpp" #include "caf/io/unpublish.hpp"
using std::string; #include "caf/io/network/interfaces.hpp"
using namespace caf::experimental;
namespace caf { namespace caf {
namespace io { namespace io {
...@@ -41,7 +48,8 @@ namespace io { ...@@ -41,7 +48,8 @@ namespace io {
******************************************************************************/ ******************************************************************************/
basp_broker_state::basp_broker_state(broker* selfptr) basp_broker_state::basp_broker_state(broker* selfptr)
: basp::instance::callee(static_cast<actor_namespace::backend&>(*this)), : basp::instance::callee(static_cast<actor_namespace::backend&>(*this),
selfptr->parent()),
self(selfptr), self(selfptr),
instance(selfptr, *this) { instance(selfptr, *this) {
CAF_ASSERT(this_node() != invalid_node_id); CAF_ASSERT(this_node() != invalid_node_id);
...@@ -56,8 +64,10 @@ actor_proxy_ptr basp_broker_state::make_proxy(const node_id& nid, ...@@ -56,8 +64,10 @@ actor_proxy_ptr basp_broker_state::make_proxy(const node_id& nid,
// this member function is being called whenever we deserialize a // this member function is being called whenever we deserialize a
// payload received from a remote node; if a remote node A sends // payload received from a remote node; if a remote node A sends
// us a handle to a third node B, then we assume that A offers a route to B // us a handle to a third node B, then we assume that A offers a route to B
if (nid != this_context->id) if (nid != this_context->id
instance.tbl().add_indirect(this_context->id, nid); && instance.tbl().lookup_direct(nid) == invalid_connection_handle
&& instance.tbl().add_indirect(this_context->id, nid))
learned_new_node_indirectly(nid);
// we need to tell remote side we are watching this actor now; // we need to tell remote side we are watching this actor now;
// use a direct route if possible, i.e., when talking to a third node // use a direct route if possible, i.e., when talking to a third node
auto path = instance.tbl().lookup(nid); auto path = instance.tbl().lookup(nid);
...@@ -88,12 +98,12 @@ actor_proxy_ptr basp_broker_state::make_proxy(const node_id& nid, ...@@ -88,12 +98,12 @@ actor_proxy_ptr basp_broker_state::make_proxy(const node_id& nid,
this_node(), nid, this_node(), nid,
invalid_actor_id, aid); invalid_actor_id, aid);
instance.tbl().flush(*path); instance.tbl().flush(*path);
self->parent().notify<hook::new_remote_actor>(res->address()); middleman_.notify<hook::new_remote_actor>(res->address());
return res; return res;
} }
void basp_broker_state::finalize_handshake(const node_id& nid, actor_id aid, void basp_broker_state::finalize_handshake(const node_id& nid, actor_id aid,
const std::set<std::string>& sigs) { std::set<std::string>& sigs) {
CAF_LOG_TRACE(CAF_TSARG(nid) CAF_LOG_TRACE(CAF_TSARG(nid)
<< ", " << CAF_ARG(aid) << ", " << CAF_ARG(aid)
<< ", " << CAF_TSARG(make_message(sigs))); << ", " << CAF_TSARG(make_message(sigs)));
...@@ -102,20 +112,15 @@ void basp_broker_state::finalize_handshake(const node_id& nid, actor_id aid, ...@@ -102,20 +112,15 @@ void basp_broker_state::finalize_handshake(const node_id& nid, actor_id aid,
auto& cb = this_context->callback; auto& cb = this_context->callback;
if (! cb) if (! cb)
return; return;
auto& exp = this_context->expected_sigs;
auto cleanup = detail::make_scope_guard([&] { auto cleanup = detail::make_scope_guard([&] {
cb = none; cb = none;
exp.clear();
}); });
if (! std::includes(sigs.begin(), sigs.end(), exp.begin(), exp.end())) {
cb->deliver(make_message(error_atom::value,
"expected signature does not "
"comply to found signature"));
return;
}
if (aid == invalid_actor_id) { if (aid == invalid_actor_id) {
// can occur when connecting to the default port of a node // can occur when connecting to the default port of a node
cb->deliver(make_message(ok_atom::value, actor_addr{invalid_actor_addr})); cb->deliver(make_message(ok_atom::value,
nid,
actor_addr{invalid_actor_addr},
std::move(sigs)));
return; return;
} }
abstract_actor_ptr ptr; abstract_actor_ptr ptr;
...@@ -132,9 +137,8 @@ void basp_broker_state::finalize_handshake(const node_id& nid, actor_id aid, ...@@ -132,9 +137,8 @@ void basp_broker_state::finalize_handshake(const node_id& nid, actor_id aid,
actor_addr addr = ptr ? ptr->address() : invalid_actor_addr; actor_addr addr = ptr ? ptr->address() : invalid_actor_addr;
if (addr.is_remote()) if (addr.is_remote())
known_remotes.emplace(nid, std::make_pair(this_context->remote_port, addr)); known_remotes.emplace(nid, std::make_pair(this_context->remote_port, addr));
cb->deliver(make_message(ok_atom::value, addr)); cb->deliver(make_message(ok_atom::value, nid, addr, std::move(sigs)));
this_context->callback = none; this_context->callback = none;
this_context->expected_sigs.clear();
} }
void basp_broker_state::purge_state(const node_id& nid) { void basp_broker_state::purge_state(const node_id& nid) {
...@@ -207,21 +211,179 @@ void basp_broker_state::deliver(const node_id& source_node, ...@@ -207,21 +211,179 @@ void basp_broker_state::deliver(const node_id& source_node,
} }
abstract_actor_ptr dest; abstract_actor_ptr dest;
uint32_t rsn = exit_reason::remote_link_unreachable; uint32_t rsn = exit_reason::remote_link_unreachable;
if (dest_node == this_node()) if (dest_node != this_node())
std::tie(dest, rsn) = registry->get_entry(dest_actor);
else
dest = get_namespace().get_or_put(dest_node, dest_actor); dest = get_namespace().get_or_put(dest_node, dest_actor);
else
if (dest_actor == std::numeric_limits<actor_id>::max()) {
// this hack allows CAF to talk to older CAF versions; CAF <= 0.14 will
// discard this message silently as the receiver is not found, while
// CAF >= 0.14.1 will use the operation data to identify named actors
auto dest_name = static_cast<atom_value>(mid.integer_value());
CAF_LOG_DEBUG("dest_name = " << to_string(dest_name));
mid = message_id::make(); // override this since the message is async
dest = actor_cast<abstract_actor_ptr>(registry->get_named(dest_name));
} else {
std::tie(dest, rsn) = registry->get_entry(dest_actor);
}
if (! dest) { if (! dest) {
CAF_LOG_INFO("cannot deliver message, destination not found"); CAF_LOG_INFO("cannot deliver message, destination not found");
self->parent().notify<hook::invalid_message_received>(source_node, src,
dest_actor, mid, msg);
if (mid.valid() && src != invalid_actor_addr) { if (mid.valid() && src != invalid_actor_addr) {
detail::sync_request_bouncer srb{rsn}; detail::sync_request_bouncer srb{rsn};
srb(src, mid); srb(src, mid);
} }
return; return;
} }
self->parent().notify<hook::message_received>(source_node, src,
dest->address(), mid, msg);
dest->enqueue(src, mid, std::move(msg), nullptr); dest->enqueue(src, mid, std::move(msg), nullptr);
} }
void basp_broker_state::learned_new_node(const node_id& nid) {
CAF_LOG_TRACE(CAF_TSARG(nid));
auto& tmp = spawn_servers[nid];
tmp = spawn<hidden>([=](event_based_actor* this_actor) -> behavior {
return {
[=](ok_atom, const std::string& /* key == "info" */,
const actor_addr& config_serv_addr, const std::string& /* name */) {
auto config_serv = actor_cast<actor>(config_serv_addr);
this_actor->monitor(config_serv);
this_actor->become(
[=](spawn_atom, std::string& type, message& args)
-> delegated<either<ok_atom, actor_addr, std::set<std::string>>
::or_else<error_atom, std::string>> {
this_actor->delegate(config_serv, get_atom::value,
std::move(type), std::move(args));
return {};
},
[=](const down_msg& dm) {
this_actor->quit(dm.reason);
},
others >> [=] {
CAF_LOGF_ERROR("spawn server has received unexpected message: "
<< to_string(this_actor->current_message()));
}
);
},
after(std::chrono::minutes(5)) >> [=] {
CAF_LOGF_INFO("no spawn server on node " << to_string(nid));
this_actor->quit();
}
};
});
using namespace detail;
singletons::get_actor_registry()->put(tmp.id(),
actor_cast<abstract_actor_ptr>(tmp));
auto writer = make_callback([](serializer& sink) {
auto msg = make_message(sys_atom::value, get_atom::value, "info");
msg.serialize(sink);
});
auto path = instance.tbl().lookup(nid);
if (! path) {
CAF_LOG_ERROR("learned_new_node called, but no route to nid");
return;
}
// writing std::numeric_limits<actor_id>::max() is a hack to get
// this send-to-named-actor feature working with older CAF releases
instance.write(path->wr_buf,
basp::message_type::dispatch_message,
nullptr, static_cast<uint64_t>(atom("SpawnServ")),
this_node(), nid,
tmp.id(), std::numeric_limits<actor_id>::max(),
&writer);
instance.flush(*path);
}
void basp_broker_state::learned_new_node_directly(const node_id& nid,
bool was_indirectly_before) {
CAF_ASSERT(this_context != nullptr);
CAF_LOG_TRACE(CAF_TSARG(nid));
if (! was_indirectly_before)
learned_new_node(nid);
}
void basp_broker_state::learned_new_node_indirectly(const node_id& nid) {
CAF_ASSERT(this_context != nullptr);
CAF_LOG_TRACE(CAF_TSARG(nid));
learned_new_node(nid);
if (! enable_automatic_connections)
return;
actor bb = self; // a handle for our helper back to this BASP broker
// this member function gets only called once, after adding a new
// indirect connection to the routing table; hence, spawning
// our helper here exactly once and there is no need to track
// in-flight connection requests
auto connection_helper = [=](event_based_actor* helper, actor s) -> behavior {
helper->monitor(s);
return {
// this config is send from the remote `ConfigServ`
[=](ok_atom, const std::string&, message& msg) {
CAF_LOGF_DEBUG("received requested config: " << to_string(msg));
// whatever happens, we are done afterwards
helper->quit();
msg.apply({
[&](uint16_t port, network::address_listing& addresses) {
auto& mx = middleman::instance()->backend();
for (auto& kvp : addresses)
if (kvp.first != network::protocol::ethernet)
for (auto& addr : kvp.second) {
try {
auto hdl = mx.new_tcp_scribe(addr, port);
// gotcha! send scribe to our BASP broker
// to initiate handshake etc.
CAF_LOGF_INFO("connected directly via " << addr);
helper->send(bb, connect_atom::value, hdl, port);
return;
}
catch (...) {
// simply try next address
}
}
CAF_LOGF_INFO("could not connect to node directly, nid = "
<< to_string(nid));
}
});
},
[=](const down_msg& dm) {
helper->quit(dm.reason);
},
after(std::chrono::minutes(10)) >> [=] {
// nothing heard in about 10 minutes... just a call it a day, then
CAF_LOGF_INFO("aborted direct connection attempt after 10min, nid = "
<< to_string(nid));
helper->quit(exit_reason::user_shutdown);
}
};
};
auto path = instance.tbl().lookup(nid);
if (! path) {
CAF_LOG_ERROR("learned_new_node_indirectly called, but no route to nid");
return;
}
if (path->next_hop == nid) {
CAF_LOG_ERROR("learned_new_node_indirectly called with direct connection");
return;
}
using namespace detail;
auto tmp = spawn<detached + hidden>(connection_helper, self);
singletons::get_actor_registry()->put(tmp.id(),
actor_cast<abstract_actor_ptr>(tmp));
auto writer = make_callback([](serializer& sink) {
auto msg = make_message(get_atom::value, "basp.default-connectivity");
msg.serialize(sink);
});
// writing std::numeric_limits<actor_id>::max() is a hack to get
// this send-to-named-actor feature working with older CAF releases
instance.write(path->wr_buf,
basp::message_type::dispatch_message,
nullptr, static_cast<uint64_t>(atom("ConfigServ")),
this_node(), nid,
tmp.id(), std::numeric_limits<actor_id>::max(),
&writer);
instance.flush(*path);
}
void basp_broker_state::set_context(connection_handle hdl) { void basp_broker_state::set_context(connection_handle hdl) {
CAF_LOG_TRACE(CAF_MARG(hdl, id)); CAF_LOG_TRACE(CAF_MARG(hdl, id));
auto i = ctx.find(hdl); auto i = ctx.find(hdl);
...@@ -236,8 +398,7 @@ void basp_broker_state::set_context(connection_handle hdl) { ...@@ -236,8 +398,7 @@ void basp_broker_state::set_context(connection_handle hdl) {
hdl, hdl,
invalid_node_id, invalid_node_id,
0, 0,
none, none}).first;
std::set<std::string>{}}).first;
} }
this_context = &i->second; this_context = &i->second;
} }
...@@ -262,11 +423,14 @@ bool basp_broker_state::erase_context(connection_handle hdl) { ...@@ -262,11 +423,14 @@ bool basp_broker_state::erase_context(connection_handle hdl) {
******************************************************************************/ ******************************************************************************/
basp_broker::basp_broker(middleman& mm) basp_broker::basp_broker(middleman& mm)
: caf::experimental::stateful_actor<basp_broker_state, broker>(mm) { : stateful_actor<basp_broker_state, broker>(mm) {
// nop // nop
} }
behavior basp_broker::make_behavior() { behavior basp_broker::make_behavior() {
// ask the configuration server whether we should open a default port
auto config_server = whereis(atom("ConfigServ"));
send(config_server, get_atom::value, "global.enable-automatic-connections");
return { return {
// received from underlying broker implementation // received from underlying broker implementation
[=](new_data_msg& msg) { [=](new_data_msg& msg) {
...@@ -339,7 +503,7 @@ behavior basp_broker::make_behavior() { ...@@ -339,7 +503,7 @@ behavior basp_broker::make_behavior() {
state.instance.remove_published_actor(port); state.instance.remove_published_actor(port);
}, },
// received from middleman actor // received from middleman actor
[=](put_atom, accept_handle hdl, uint16_t port, const actor_addr& whom, [=](publish_atom, accept_handle hdl, uint16_t port, const actor_addr& whom,
std::set<std::string>& sigs) { std::set<std::string>& sigs) {
CAF_LOG_TRACE(CAF_ARG(hdl.id()) << ", "<< CAF_TSARG(whom) CAF_LOG_TRACE(CAF_ARG(hdl.id()) << ", "<< CAF_TSARG(whom)
<< ", " << CAF_ARG(port)); << ", " << CAF_ARG(port));
...@@ -354,11 +518,9 @@ behavior basp_broker::make_behavior() { ...@@ -354,11 +518,9 @@ behavior basp_broker::make_behavior() {
} }
detail::singletons::get_actor_registry()->put(whom->id(), whom); detail::singletons::get_actor_registry()->put(whom->id(), whom);
state.instance.add_published_actor(port, whom, std::move(sigs)); state.instance.add_published_actor(port, whom, std::move(sigs));
parent().notify<hook::actor_published>(whom, port);
}, },
// received from middleman actor (delegated) // received from middleman actor (delegated)
[=](get_atom, connection_handle hdl, uint16_t port, [=](connect_atom, connection_handle hdl, uint16_t port) {
std::set<std::string>& expected_ifs) {
CAF_LOG_TRACE(CAF_ARG(hdl.id())); CAF_LOG_TRACE(CAF_ARG(hdl.id()));
auto rp = make_response_promise(); auto rp = make_response_promise();
try { try {
...@@ -376,7 +538,6 @@ behavior basp_broker::make_behavior() { ...@@ -376,7 +538,6 @@ behavior basp_broker::make_behavior() {
ctx.remote_port = port; ctx.remote_port = port;
ctx.cstate = basp::await_header; ctx.cstate = basp::await_header;
ctx.callback = rp; ctx.callback = rp;
ctx.expected_sigs = std::move(expected_ifs);
// await server handshake // await server handshake
configure_read(hdl, receive_policy::exactly(basp::header_size)); configure_read(hdl, receive_policy::exactly(basp::header_size));
}, },
...@@ -384,7 +545,7 @@ behavior basp_broker::make_behavior() { ...@@ -384,7 +545,7 @@ behavior basp_broker::make_behavior() {
CAF_LOG_TRACE(CAF_TSARG(nid) << ", " << CAF_ARG(aid)); CAF_LOG_TRACE(CAF_TSARG(nid) << ", " << CAF_ARG(aid));
state.get_namespace().erase(nid, aid); state.get_namespace().erase(nid, aid);
}, },
[=](delete_atom, const actor_addr& whom, uint16_t port) -> message { [=](unpublish_atom, const actor_addr& whom, uint16_t port) -> message {
CAF_LOG_TRACE(CAF_TSARG(whom) << ", " << CAF_ARG(port)); CAF_LOG_TRACE(CAF_TSARG(whom) << ", " << CAF_ARG(port));
if (whom == invalid_actor_addr) if (whom == invalid_actor_addr)
return make_message(error_atom::value, "whom == invalid_actor_addr"); return make_message(error_atom::value, "whom == invalid_actor_addr");
...@@ -397,6 +558,50 @@ behavior basp_broker::make_behavior() { ...@@ -397,6 +558,50 @@ behavior basp_broker::make_behavior() {
? make_message(error_atom::value, "no mapping found") ? make_message(error_atom::value, "no mapping found")
: make_message(ok_atom::value); : make_message(ok_atom::value);
}, },
[=](close_atom, uint16_t port) -> message {
if (port == 0)
return make_message(error_atom::value, "port == 0");
// it is well-defined behavior to not have an actor published here,
// hence the result can be ignored safely
state.instance.remove_published_actor(port, nullptr);
auto acceptor = hdl_by_port(port);
if (acceptor) {
close(*acceptor);
return make_message(ok_atom::value);
}
return make_message(error_atom::value, "no doorman for given port found");
},
[=](spawn_atom, const node_id& nid, std::string& type, message& args)
-> delegated<either<ok_atom, actor_addr, std::set<std::string>>
::or_else<error_atom, std::string>> {
auto err = [=](std::string errmsg) {
auto rp = make_response_promise();
rp.deliver(error_atom::value, std::move(errmsg));
};
auto i = state.spawn_servers.find(nid);
if (i == state.spawn_servers.end()) {
err("no connection to requested node");
return {};
}
delegate(i->second, spawn_atom::value, std::move(type), std::move(args));
return {};
},
[=](ok_atom, const std::string& key, message& value) {
if (key == "global.enable-automatic-connections") {
value.apply([&](bool enabled) {
if (! enabled)
return;
CAF_LOG_INFO("enable automatic connection");
// open a random port and store a record for others
// how to connect to this port in the configuration server
auto port = add_tcp_doorman(uint16_t{0}).second;
auto addrs = network::interfaces::list_addresses(false);
send(config_server, put_atom::value, "basp.default-connectivity",
make_message(port, std::move(addrs)));
state.enable_automatic_connections = true;
});
}
},
// catch-all error handler // catch-all error handler
others >> others >>
[=] { [=] {
......
...@@ -40,9 +40,14 @@ void hook::message_sent_cb(const actor_addr& from, const node_id& dest_node, ...@@ -40,9 +40,14 @@ void hook::message_sent_cb(const actor_addr& from, const node_id& dest_node,
call_next<message_sent>(from, dest_node, dest, mid, payload); call_next<message_sent>(from, dest_node, dest, mid, payload);
} }
void hook::message_forwarded_cb(const node_id& from, const node_id& dest, void hook::message_forwarded_cb(const basp::header& hdr,
const std::vector<char>* payload) { const std::vector<char>* payload) {
call_next<message_forwarded>(from, dest, payload); call_next<message_forwarded>(hdr, payload);
}
void hook::message_forwarding_failed_cb(const basp::header& hdr,
const std::vector<char>* payload) {
call_next<message_forwarding_failed>(hdr, payload);
} }
void hook::message_sending_failed_cb(const actor_addr& from, void hook::message_sending_failed_cb(const actor_addr& from,
...@@ -52,13 +57,10 @@ void hook::message_sending_failed_cb(const actor_addr& from, ...@@ -52,13 +57,10 @@ void hook::message_sending_failed_cb(const actor_addr& from,
call_next<message_sending_failed>(from, dest, mid, payload); call_next<message_sending_failed>(from, dest, mid, payload);
} }
void hook::message_forwarding_failed_cb(const node_id& from, const node_id& to, void hook::actor_published_cb(const actor_addr& addr,
const std::vector<char>* payload) { const std::set<std::string>& ifs,
call_next<message_forwarding_failed>(from, to, payload); uint16_t port) {
} call_next<actor_published>(addr, ifs, port);
void hook::actor_published_cb(const actor_addr& addr, uint16_t port) {
call_next<actor_published>(addr, port);
} }
void hook::new_remote_actor_cb(const actor_addr& addr) { void hook::new_remote_actor_cb(const actor_addr& addr) {
...@@ -73,6 +75,14 @@ void hook::new_route_added_cb(const node_id& via, const node_id& node) { ...@@ -73,6 +75,14 @@ void hook::new_route_added_cb(const node_id& via, const node_id& node) {
call_next<new_route_added>(via, node); call_next<new_route_added>(via, node);
} }
void hook::connection_lost_cb(const node_id& dest) {
call_next<connection_lost>(dest);
}
void hook::route_lost_cb(const node_id& hop, const node_id& dest) {
call_next<route_lost>(hop, dest);
}
void hook::invalid_message_received_cb(const node_id& source, void hook::invalid_message_received_cb(const node_id& source,
const actor_addr& sender, const actor_addr& sender,
actor_id invalid_dest, actor_id invalid_dest,
......
...@@ -40,6 +40,8 @@ ...@@ -40,6 +40,8 @@
#include "caf/io/basp_broker.hpp" #include "caf/io/basp_broker.hpp"
#include "caf/io/system_messages.hpp" #include "caf/io/system_messages.hpp"
#include "caf/io/network/interfaces.hpp"
#include "caf/detail/logging.hpp" #include "caf/detail/logging.hpp"
#include "caf/detail/ripemd_160.hpp" #include "caf/detail/ripemd_160.hpp"
#include "caf/detail/safe_equal.hpp" #include "caf/detail/safe_equal.hpp"
...@@ -58,62 +60,48 @@ namespace io { ...@@ -58,62 +60,48 @@ namespace io {
namespace { namespace {
template <class Subtype, class I> void serialize(serializer& sink, std::vector<char>& buf) {
inline void serialize_impl(const handle<Subtype, I>& hdl, serializer* sink) { sink << static_cast<uint32_t>(buf.size());
sink->write_value(hdl.id()); sink.write_raw(buf.size(), buf.data());
} }
template <class Subtype, class I> void serialize(deserializer& source, std::vector<char>& buf) {
inline void deserialize_impl(handle<Subtype, I>& hdl, deserializer* source) { auto bs = source.read<uint32_t>();
hdl.set_id(source->read<int64_t>()); buf.resize(bs);
source.read_raw(buf.size(), buf.data());
} }
inline void serialize_impl(const new_connection_msg& msg, serializer* sink) { template <class Subtype, class I>
serialize_impl(msg.source, sink); void serialize(serializer& sink, handle<Subtype, I>& hdl) {
serialize_impl(msg.handle, sink); sink.write_value(hdl.id());
} }
inline void deserialize_impl(new_connection_msg& msg, deserializer* source) { template <class Subtype, class I>
deserialize_impl(msg.source, source); void serialize(deserializer& source, handle<Subtype, I>& hdl) {
deserialize_impl(msg.handle, source); hdl.set_id(source.read<int64_t>());
} }
inline void serialize_impl(const new_data_msg& msg, serializer* sink) { template <class Archive>
serialize_impl(msg.handle, sink); void serialize(Archive& ar, new_connection_msg& msg) {
auto buf_size = static_cast<uint32_t>(msg.buf.size()); serialize(ar, msg.source);
if (buf_size != msg.buf.size()) { // narrowing error serialize(ar, msg.handle);
std::ostringstream oss;
oss << "attempted to send more than "
<< std::numeric_limits<uint32_t>::max() << " bytes";
auto errstr = oss.str();
CAF_LOGF_INFO(errstr);
throw std::ios_base::failure(std::move(errstr));
}
sink->write_value(buf_size);
sink->write_raw(msg.buf.size(), msg.buf.data());
} }
inline void deserialize_impl(new_data_msg& msg, deserializer* source) { template <class Archive>
deserialize_impl(msg.handle, source); void serialize(Archive& ar, new_data_msg& msg) {
auto buf_size = source->read<uint32_t>(); serialize(ar, msg.handle);
msg.buf.resize(buf_size); serialize(ar, msg.buf);
source->read_raw(msg.buf.size(), msg.buf.data());
} }
// connection_closed_msg & acceptor_closed_msg have the same fields
template <class T>
typename std::enable_if<std::is_same<T, connection_closed_msg>::value
|| std::is_same<T, acceptor_closed_msg>::value>::type
serialize_impl(const T& dm, serializer* sink) {
serialize_impl(dm.handle, sink);
}
// connection_closed_msg & acceptor_closed_msg have the same fields // connection_closed_msg & acceptor_closed_msg have the same fields
template <class T> template <class Archive, class T>
typename std::enable_if<std::is_same<T, connection_closed_msg>::value typename std::enable_if<
|| std::is_same<T, acceptor_closed_msg>::value>::type std::is_same<T, connection_closed_msg>::value
deserialize_impl(T& dm, deserializer* source) { || std::is_same<T, acceptor_closed_msg>::value
deserialize_impl(dm.handle, source); >::type
serialize(Archive& ar, T& dm) {
serialize(ar, dm.handle);
} }
template <class T> template <class T>
...@@ -126,11 +114,11 @@ public: ...@@ -126,11 +114,11 @@ public:
} }
void serialize(const void* instance, serializer* sink) const { void serialize(const void* instance, serializer* sink) const {
serialize_impl(super::deref(instance), sink); io::serialize(*sink, super::deref(const_cast<void*>(instance)));
} }
void deserialize(void* instance, deserializer* source) const { void deserialize(void* instance, deserializer* source) const {
deserialize_impl(super::deref(instance), source); io::serialize(*source, super::deref(instance));
} }
}; };
...@@ -139,8 +127,6 @@ void do_announce(const char* tname) { ...@@ -139,8 +127,6 @@ void do_announce(const char* tname) {
announce(typeid(T), uniform_type_info_ptr{new uti_impl<T>(tname)}); announce(typeid(T), uniform_type_info_ptr{new uti_impl<T>(tname)});
} }
} // namespace <anonymous>
class middleman_actor_impl : public middleman_actor::base { class middleman_actor_impl : public middleman_actor::base {
public: public:
middleman_actor_impl(middleman& mref, actor default_broker) middleman_actor_impl(middleman& mref, actor default_broker)
...@@ -149,65 +135,68 @@ public: ...@@ -149,65 +135,68 @@ public:
// nop // nop
} }
~middleman_actor_impl();
void on_exit() { void on_exit() {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
broker_ = invalid_actor; broker_ = invalid_actor;
} }
using get_op_result = either<ok_atom, actor_addr> using put_res = either<ok_atom, uint16_t>::or_else<error_atom, std::string>;
::or_else<error_atom, std::string>;
using get_op_promise = typed_response_promise<get_op_result>; using get_res = delegated<either<ok_atom, node_id, actor_addr,
std::set<std::string>>
::or_else<error_atom, std::string>>;
using del_op_result = either<ok_atom>::or_else<error_atom, std::string>; using del_res = delegated<either<ok_atom>::or_else<error_atom, std::string>>;
using del_op_promise = delegated<del_op_result>;
using map_type = std::map<int64_t, response_promise>;
behavior_type make_behavior() override { behavior_type make_behavior() override {
return { return {
[=](put_atom, uint16_t port, const actor_addr& whom, [=](publish_atom, uint16_t port, actor_addr& whom,
std::set<std::string>& sigs, const std::string& addr, std::set<std::string>& sigs, std::string& addr, bool reuse) {
bool reuse_addr) { return put(port, whom, sigs, addr.c_str(), reuse);
return put(port, whom, sigs, addr.c_str(), reuse_addr);
},
[=](put_atom, uint16_t port, const actor_addr& whom,
std::set<std::string>& sigs, const std::string& addr) {
return put(port, whom, sigs, addr.c_str());
},
[=](put_atom, uint16_t port, const actor_addr& whom,
std::set<std::string>& sigs, bool reuse_addr) {
return put(port, whom, sigs, nullptr, reuse_addr);
}, },
[=](put_atom, uint16_t port, const actor_addr& whom, [=](open_atom, uint16_t port, std::string& addr, bool reuse) -> put_res {
std::set<std::string>& sigs) { actor_addr whom = invalid_actor_addr;
return put(port, whom, sigs); std::set<std::string> sigs;
return put(port, whom, sigs, addr.c_str(), reuse);
}, },
[=](get_atom, const std::string& hostname, uint16_t port, [=](connect_atom, const std::string& hostname, uint16_t port) -> get_res {
std::set<std::string>& expected_ifs) { CAF_LOG_TRACE(CAF_ARG(hostname) << ", " << CAF_ARG(port));
return get(hostname, port, std::move(expected_ifs)); try {
auto hdl = parent_.backend().new_tcp_scribe(hostname, port);
delegate(broker_, connect_atom::value, hdl, port);
}
catch (network_error& err) {
// fullfil promise immediately
std::string msg = "network_error: ";
msg += err.what();
auto rp = make_response_promise();
rp.deliver(make_message(error_atom::value, std::move(msg)));
}
return {};
}, },
[=](get_atom, const std::string& hostname, uint16_t port) { [=](unpublish_atom, const actor_addr&, uint16_t) -> del_res {
return get(hostname, port, std::set<std::string>()); forward_current_message(broker_);
return {};
}, },
[=](delete_atom, const actor_addr& whom) { [=](close_atom, uint16_t) -> del_res {
return del(whom); forward_current_message(broker_);
return {};
}, },
[=](delete_atom, const actor_addr& whom, uint16_t port) { [=](spawn_atom, const node_id&, const std::string&, const message&)
return del(whom, port); -> delegated<either<ok_atom, actor_addr, std::set<std::string>>
::or_else<error_atom, std::string>> {
forward_current_message(broker_);
return {};
} }
}; };
} }
private: private:
either<ok_atom, uint16_t>::or_else<error_atom, std::string> put_res put(uint16_t port, actor_addr& whom,
put(uint16_t port, const actor_addr& whom, std::set<std::string>& sigs, std::set<std::string>& sigs, const char* in = nullptr,
const char* in = nullptr, bool reuse_addr = false) { bool reuse_addr = false) {
CAF_LOG_TRACE(CAF_TSARG(whom) << ", " << CAF_ARG(port) CAF_LOG_TRACE(CAF_TSARG(whom) << ", " << CAF_ARG(port) << ", "
<< ", " << CAF_ARG(reuse_addr)); << CAF_ARG(reuse_addr));
accept_handle hdl; accept_handle hdl;
uint16_t actual_port; uint16_t actual_port;
try { try {
...@@ -225,66 +214,16 @@ private: ...@@ -225,66 +214,16 @@ private:
catch (network_error& err) { catch (network_error& err) {
return {error_atom::value, std::string("network_error: ") + err.what()}; return {error_atom::value, std::string("network_error: ") + err.what()};
} }
send(broker_, put_atom::value, hdl, actual_port, whom, std::move(sigs)); send(broker_, publish_atom::value, hdl, actual_port,
std::move(whom), std::move(sigs));
return {ok_atom::value, actual_port}; return {ok_atom::value, actual_port};
} }
get_op_promise get(const std::string& hostname, uint16_t port,
std::set<std::string> expected_ifs) {
CAF_LOG_TRACE(CAF_ARG(hostname) << ", " << CAF_ARG(port));
get_op_promise result;
try {
auto hdl = parent_.backend().new_tcp_scribe(hostname, port);
delegate(broker_, get_atom::value, hdl, port, std::move(expected_ifs));
}
catch (network_error& err) {
// fullfil promise immediately
std::string msg = "network_error: ";
msg += err.what();
result = make_response_promise();
result.deliver(get_op_result{error_atom::value, std::move(msg)});
}
return result;
}
del_op_promise del(const actor_addr& whom, uint16_t port = 0) {
CAF_LOG_TRACE(CAF_TSARG(whom) << ", " << CAF_ARG(port));
delegate(broker_, delete_atom::value, whom, port);
return {};
}
template <class T, class... Ts>
void handle_ok(map_type& storage, int64_t request_id, Ts&&... xs) {
CAF_LOG_TRACE(CAF_ARG(request_id));
auto i = storage.find(request_id);
if (i == storage.end()) {
CAF_LOG_ERROR("request id not found: " << request_id);
return;
}
i->second.deliver(T{ok_atom::value, std::forward<Ts>(xs)...}.value);
storage.erase(i);
}
template <class F>
bool finalize_request(map_type& storage, int64_t req_id, F fun) {
CAF_LOG_TRACE(CAF_ARG(req_id));
auto i = storage.find(req_id);
if (i == storage.end()) {
CAF_LOG_INFO("request ID not found in storage");
return false;
}
fun(i->second);
storage.erase(i);
return true;
}
actor broker_; actor broker_;
middleman& parent_; middleman& parent_;
}; };
middleman_actor_impl::~middleman_actor_impl() { } // namespace <anonymous>
CAF_LOG_TRACE("");
}
middleman* middleman::instance() { middleman* middleman::instance() {
CAF_LOGF_TRACE(""); CAF_LOGF_TRACE("");
...@@ -319,6 +258,7 @@ void middleman::initialize() { ...@@ -319,6 +258,7 @@ void middleman::initialize() {
} }
// announce io-related types // announce io-related types
announce<network::protocol>("caf::io::network::protocol"); announce<network::protocol>("caf::io::network::protocol");
announce<network::address_listing>("caf::io::network::address_listing");
do_announce<new_data_msg>("caf::io::new_data_msg"); do_announce<new_data_msg>("caf::io::new_data_msg");
do_announce<new_connection_msg>("caf::io::new_connection_msg"); do_announce<new_connection_msg>("caf::io::new_connection_msg");
do_announce<acceptor_closed_msg>("caf::io::acceptor_closed_msg"); do_announce<acceptor_closed_msg>("caf::io::acceptor_closed_msg");
......
...@@ -53,7 +53,7 @@ uint16_t publish_impl(uint16_t port, actor_addr whom, ...@@ -53,7 +53,7 @@ uint16_t publish_impl(uint16_t port, actor_addr whom,
uint16_t result; uint16_t result;
std::string error_msg; std::string error_msg;
try { try {
self->sync_send(mm, put_atom::value, port, self->sync_send(mm, publish_atom::value, port,
std::move(whom), std::move(sigs), str, ru).await( std::move(whom), std::move(sigs), str, ru).await(
[&](ok_atom, uint16_t res) { [&](ok_atom, uint16_t res) {
result = res; result = res;
......
...@@ -42,14 +42,20 @@ ...@@ -42,14 +42,20 @@
namespace caf { namespace caf {
namespace io { namespace io {
abstract_actor_ptr remote_actor_impl(std::set<std::string> ifs, actor_addr remote_actor_impl(std::set<std::string> ifs,
const std::string& host, uint16_t port) { std::string host, uint16_t port) {
auto mm = get_middleman_actor(); auto mm = get_middleman_actor();
actor_addr result;
scoped_actor self; scoped_actor self;
abstract_actor_ptr result; self->sync_send(mm, connect_atom::value, std::move(host), port).await(
self->sync_send(mm, get_atom{}, std::move(host), port, std::move(ifs)).await( [&](ok_atom, const node_id&, actor_addr res, std::set<std::string>& xs) {
[&](ok_atom, actor_addr res) { if (!res)
result = actor_cast<abstract_actor_ptr>(res); throw network_error("no actor published at given port");
if (! (xs.empty() && ifs.empty())
&& ! std::includes(xs.begin(), xs.end(), ifs.begin(), ifs.end()))
throw network_error("expected signature does not "
"comply to found signature");
result = std::move(res);
}, },
[&](error_atom, std::string& msg) { [&](error_atom, std::string& msg) {
throw network_error(std::move(msg)); throw network_error(std::move(msg));
...@@ -60,4 +66,3 @@ abstract_actor_ptr remote_actor_impl(std::set<std::string> ifs, ...@@ -60,4 +66,3 @@ abstract_actor_ptr remote_actor_impl(std::set<std::string> ifs,
} // namespace io } // namespace io
} // namespace caf } // namespace caf
...@@ -61,9 +61,11 @@ void scribe::consume(const void*, size_t num_bytes) { ...@@ -61,9 +61,11 @@ void scribe::consume(const void*, size_t num_bytes) {
buf.resize(num_bytes); buf.resize(num_bytes);
read_msg().buf.swap(buf); read_msg().buf.swap(buf);
parent()->invoke_message(invalid_actor_addr, invalid_message_id, read_msg_); parent()->invoke_message(invalid_actor_addr, invalid_message_id, read_msg_);
// swap buffer back to stream and implicitly flush wr_buf() if (! read_msg_.empty()) {
read_msg().buf.swap(buf); // swap buffer back to stream and implicitly flush wr_buf()
flush(); read_msg().buf.swap(buf);
flush();
}
} }
void scribe::io_failure(network::operation op) { void scribe::io_failure(network::operation op) {
......
...@@ -34,7 +34,7 @@ void unpublish_impl(const actor_addr& whom, uint16_t port, bool blocking) { ...@@ -34,7 +34,7 @@ void unpublish_impl(const actor_addr& whom, uint16_t port, bool blocking) {
auto mm = get_middleman_actor(); auto mm = get_middleman_actor();
if (blocking) { if (blocking) {
scoped_actor self; scoped_actor self;
self->sync_send(mm, delete_atom::value, whom, port).await( self->sync_send(mm, unpublish_atom::value, whom, port).await(
[](ok_atom) { [](ok_atom) {
// ok, basp_broker is done // ok, basp_broker is done
}, },
...@@ -43,7 +43,7 @@ void unpublish_impl(const actor_addr& whom, uint16_t port, bool blocking) { ...@@ -43,7 +43,7 @@ void unpublish_impl(const actor_addr& whom, uint16_t port, bool blocking) {
} }
); );
} else { } else {
anon_send(mm, delete_atom::value, whom, port); anon_send(mm, unpublish_atom::value, whom, port);
} }
} }
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE io_automatic_connection
#include "caf/test/unit_test.hpp"
#include <set>
#include <thread>
#include <vector>
#include "caf/all.hpp"
#include "caf/io/all.hpp"
#include "caf/experimental/whereis.hpp"
#include "caf/io/network/interfaces.hpp"
#include "caf/io/network/test_multiplexer.hpp"
#include "caf/detail/run_program.hpp"
using namespace caf;
using namespace caf::io;
using namespace caf::experimental;
using std::string;
using ping_atom = atom_constant<atom("ping")>;
using pong_atom = atom_constant<atom("pong")>;
/*
This test checks whether automatic connections work as expected
by first connecting three nodes "in line". In step 2, we send a
message across the line, forcing the nodes to build a mesh. In step 3,
we disconnect the node that originally connected the other two and expect
that the other two nodes communicate uninterrupted.
1) Initial setup:
Earth ---- Mars ---- Jupiter
2) After Jupiter has send a message to Earth:
Earth ---- Mars
\ /
\ /
\ /
Jupiter
3) After Earth has received the message and disconnected Mars:
Earth ---- Jupiter
*/
std::thread run_prog(const char* arg, uint16_t port, bool use_asio) {
return detail::run_program(invalid_actor, caf::test::engine::path(), "-n",
"-s", CAF_XSTR(CAF_SUITE), "--", arg, "-p", port,
(use_asio ? "--use-asio" : ""));
}
// we run the same code on all three nodes, a simple ping-pong client
struct testee_state {
std::set<actor> buddies;
uint16_t port = 0;
const char* name = "testee";
};
behavior testee(stateful_actor<testee_state>* self) {
return {
[self](ping_atom, actor buddy, bool please_broadcast) -> message {
if (please_broadcast)
for (auto& x : self->state.buddies)
if (x != buddy)
send_as(buddy, x, ping_atom::value, buddy, false);
self->state.buddies.emplace(std::move(buddy));
return make_message(pong_atom::value, self);
},
[self](pong_atom, actor buddy) {
self->state.buddies.emplace(std::move(buddy));
},
[self](put_atom, uint16_t new_port) {
self->state.port = new_port;
},
[self](get_atom) {
return self->state.port;
}
};
}
void run_earth(bool use_asio, bool as_server, uint16_t pub_port) {
scoped_actor self;
struct captain : hook {
public:
captain(actor parent) : parent_(std::move(parent)) {
// nop
}
void new_connection_established_cb(const node_id& node) override {
anon_send(parent_, put_atom::value, node);
call_next<hook::new_connection_established>(node);
}
void new_remote_actor_cb(const actor_addr& addr) override {
anon_send(parent_, put_atom::value, addr);
call_next<hook::new_remote_actor>(addr);
}
void connection_lost_cb(const node_id& dest) override {
anon_send(parent_, delete_atom::value, dest);
}
private:
actor parent_;
};
middleman::instance()->add_hook<captain>(self);
auto aut = spawn(testee);
auto port = publish(aut, pub_port);
CAF_TEST_VERBOSE("published testee at port " << port);
std::thread mars_process;
std::thread jupiter_process;
// launch process for Mars
if (! as_server) {
CAF_TEST_VERBOSE("launch process for Mars");
mars_process = run_prog("--mars", port, use_asio);
}
CAF_TEST_VERBOSE("wait for Mars to connect");
node_id mars;
self->receive(
[&](put_atom, const node_id& nid) {
mars = nid;
CAF_TEST_VERBOSE(CAF_TSARG(mars));
}
);
actor_addr mars_addr;
uint16_t mars_port;
self->receive_while([&] { return mars_addr == invalid_actor_addr; })(
[&](put_atom, const actor_addr& addr) {
auto hdl = actor_cast<actor>(addr);
self->sync_send(hdl, sys_atom::value, get_atom::value, "info").await(
[&](ok_atom, const string&, const actor_addr&, const string& name) {
if (name != "testee")
return;
mars_addr = addr;
CAF_TEST_VERBOSE(CAF_TSARG(mars_addr));
self->sync_send(actor_cast<actor>(mars_addr), get_atom::value).await(
[&](uint16_t mp) {
CAF_TEST_VERBOSE("mars published its actor at port " << mp);
mars_port = mp;
}
);
}
);
}
);
// launch process for Jupiter
if (! as_server) {
CAF_TEST_VERBOSE("launch process for Jupiter");
jupiter_process = run_prog("--jupiter", mars_port, use_asio);
}
CAF_TEST_VERBOSE("wait for Jupiter to connect");
self->receive(
[](put_atom, const node_id& jupiter) {
CAF_TEST_VERBOSE(CAF_TSARG(jupiter));
}
);
actor_addr jupiter_addr;
self->receive_while([&] { return jupiter_addr == invalid_actor_addr; })(
[&](put_atom, const actor_addr& addr) {
auto hdl = actor_cast<actor>(addr);
self->sync_send(hdl, sys_atom::value, get_atom::value, "info").await(
[&](ok_atom, const string&, const actor_addr&, const string& name) {
if (name != "testee")
return;
jupiter_addr = addr;
CAF_TEST_VERBOSE(CAF_TSARG(jupiter_addr));
}
);
}
);
CAF_TEST_VERBOSE("shutdown Mars");
anon_send_exit(mars_addr, exit_reason::kill);
if (mars_process.joinable())
mars_process.join();
self->receive(
[&](delete_atom, const node_id& nid) {
CAF_CHECK_EQUAL(nid, mars);
}
);
CAF_TEST_VERBOSE("check whether we still can talk to Jupiter");
self->send(aut, ping_atom::value, self, true);
std::set<actor_addr> found;
int i = 0;
self->receive_for(i, 2)(
[&](pong_atom, const actor&) {
found.emplace(self->current_sender());
}
);
std::set<actor_addr> expected{aut.address(), jupiter_addr};
CAF_CHECK_EQUAL(found, expected);
CAF_TEST_VERBOSE("shutdown Jupiter");
anon_send_exit(jupiter_addr, exit_reason::kill);
if (jupiter_process.joinable())
jupiter_process.join();
anon_send_exit(aut, exit_reason::kill);
}
void run_mars(uint16_t port_to_earth, uint16_t pub_port) {
auto aut = spawn(testee);
auto port = publish(aut, pub_port);
anon_send(aut, put_atom::value, port);
CAF_TEST_VERBOSE("published testee at port " << port);
auto earth = remote_actor("localhost", port_to_earth);
send_as(aut, earth, ping_atom::value, aut, false);
}
void run_jupiter(uint16_t port_to_mars) {
auto aut = spawn(testee);
auto mars = remote_actor("localhost", port_to_mars);
send_as(aut, mars, ping_atom::value, aut, true);
}
CAF_TEST(triangle_setup) {
uint16_t port = 0;
uint16_t publish_port = 0;
auto argv = caf::test::engine::argv();
auto argc = caf::test::engine::argc();
auto r = message_builder(argv, argv + argc).extract_opts({
{"port,p", "port of remote side (when running mars or jupiter)", port},
{"mars", "run mars"},
{"jupiter", "run jupiter"},
{"use-asio", "use ASIO network backend (if available)"},
{"server,s", "run in server mode (don't run clients)", publish_port}
});
// check arguments
bool is_mars = r.opts.count("mars") > 0;
bool is_jupiter = r.opts.count("jupiter") > 0;
bool has_port = r.opts.count("port") > 0;
if (((is_mars || is_jupiter) && ! has_port) || (is_mars && is_jupiter)) {
CAF_TEST_ERROR("need a port when running Mars or Jupiter and cannot "
"both at the same time");
return;
}
auto use_asio = r.opts.count("use-asio") > 0;
# ifdef CAF_USE_ASIO
if (use_asio) {
CAF_MESSAGE("enable ASIO backend");
set_middleman<network::asio_multiplexer>();
}
# endif // CAF_USE_ASIO
// enable automatic connections
anon_send(whereis(atom("ConfigServ")), put_atom::value,
"global.enable-automatic-connections", make_message(true));
auto as_server = r.opts.count("server") > 0;
if (is_mars)
run_mars(port, publish_port);
else if (is_jupiter)
run_jupiter(port);
else
run_earth(use_asio, as_server, publish_port);
await_all_actors_done();
shutdown();
}
...@@ -25,17 +25,59 @@ ...@@ -25,17 +25,59 @@
#include <array> #include <array>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
#include <limits>
#include <iostream> #include <iostream>
#include <condition_variable> #include <condition_variable>
#include "caf/all.hpp" #include "caf/all.hpp"
#include "caf/io/all.hpp" #include "caf/io/all.hpp"
#include "caf/experimental/whereis.hpp"
#include "caf/io/network/interfaces.hpp"
#include "caf/io/network/test_multiplexer.hpp" #include "caf/io/network/test_multiplexer.hpp"
namespace std {
ostream& operator<<(ostream& out, const caf::io::basp::message_type& x) {
return out << to_string(x);
}
template <class T>
ostream& operator<<(ostream& out, const caf::variant<caf::anything, T>& x) {
using std::to_string;
using caf::to_string;
using caf::io::basp::to_string;
if (get<caf::anything>(&x) != nullptr)
return out << "*";
return out << to_string(get<T>(x));
}
} // namespace std
namespace caf {
template <class T>
bool operator==(const variant<anything, T>& x, const T& y) {
return get<anything>(&x) != nullptr || get<T>(x) == y;
}
template <class T>
bool operator==(const T& x, const variant<anything, T>& y) {
return (y == x);
}
template <class T>
std::string to_string(const variant<anything, T>& x) {
return get<anything>(&x) == nullptr ? std::string{"*"} : to_string(get<T>(x));
}
} // namespace caf
using namespace std; using namespace std;
using namespace caf; using namespace caf;
using namespace caf::io; using namespace caf::io;
using namespace caf::experimental;
namespace { namespace {
...@@ -71,9 +113,12 @@ public: ...@@ -71,9 +113,12 @@ public:
auto mm = middleman::instance(); auto mm = middleman::instance();
aut_ = mm->get_named_broker<basp_broker>(atom("_BASP")); aut_ = mm->get_named_broker<basp_broker>(atom("_BASP"));
this_node_ = detail::singletons::get_node_id(); this_node_ = detail::singletons::get_node_id();
CAF_TEST_VERBOSE("this node: " << to_string(this_node_));
self_.reset(new scoped_actor); self_.reset(new scoped_actor);
// run the initialization message of the BASP broker // run the initialization message of the BASP broker
mpx_->exec_runnable(); mpx_->exec_runnable();
// handle the message from the configuration server
mpx()->exec_runnable();
ahdl_ = accept_handle::from_int(1); ahdl_ = accept_handle::from_int(1);
mpx_->assign_tcp_doorman(aut_.get(), ahdl_); mpx_->assign_tcp_doorman(aut_.get(), ahdl_);
registry_ = detail::singletons::get_actor_registry(); registry_ = detail::singletons::get_actor_registry();
...@@ -179,6 +224,22 @@ public: ...@@ -179,6 +224,22 @@ public:
using payload_writer = basp::instance::payload_writer; using payload_writer = basp::instance::payload_writer;
void to_payload(binary_serializer&) {
// end of recursion
}
template <class T, class... Ts>
void to_payload(binary_serializer& bs, const T& x, const Ts&... xs) {
bs << x;
to_payload(bs, xs...);
}
template <class... Ts>
void to_payload(buffer& buf, const Ts&... xs) {
binary_serializer bs{std::back_inserter(buf), &get_namespace()};
to_payload(bs, xs...);
}
void to_buf(buffer& buf, basp::header& hdr, payload_writer* writer) { void to_buf(buffer& buf, basp::header& hdr, payload_writer* writer) {
instance().write(buf, hdr, writer); instance().write(buf, hdr, writer);
} }
...@@ -229,16 +290,24 @@ public: ...@@ -229,16 +290,24 @@ public:
invalid_actor_id, invalid_actor_id}); invalid_actor_id, invalid_actor_id});
if (published_actor_id != invalid_actor_id) if (published_actor_id != invalid_actor_id)
m.expect(hdl, m.expect(hdl,
{basp::message_type::server_handshake, 0, basp::version, basp::message_type::server_handshake, any_vals, basp::version,
this_node(), invalid_node_id, this_node(), node_id{invalid_node_id},
published_actor_id, invalid_actor_id}, published_actor_id, invalid_actor_id,
published_actor_id, published_actor_id,
published_actor_ifs); published_actor_ifs);
else else
m.expect(hdl, m.expect(hdl,
{basp::message_type::server_handshake, 0, basp::version, basp::message_type::server_handshake, any_vals, basp::version,
this_node(), invalid_node_id, this_node(), node_id{invalid_node_id},
invalid_actor_id, invalid_actor_id}); invalid_actor_id, invalid_actor_id);
// upon receiving our client handshake, BASP will check
// whether there is a SpawnServ actor on this node
m.expect(hdl,
basp::message_type::dispatch_message, any_vals,
static_cast<uint64_t>(atom("SpawnServ")),
this_node(), remote_node(i),
any_vals, std::numeric_limits<actor_id>::max(),
make_message(sys_atom::value, get_atom::value, "info"));
// test whether basp instance correctly updates the // test whether basp instance correctly updates the
// routing table upon receiving client handshakes // routing table upon receiving client handshakes
auto path = tbl().lookup(remote_node(i)); auto path = tbl().lookup(remote_node(i));
...@@ -289,29 +358,49 @@ public: ...@@ -289,29 +358,49 @@ public:
} }
template <class... Ts> template <class... Ts>
mock_t& expect(connection_handle hdl, basp::header hdr, const Ts&... xs) { mock_t& expect(connection_handle hdl,
CAF_MESSAGE("expect " << num++ << ". sent message to be a " variant<anything, basp::message_type> operation,
<< to_string(hdr.operation)); variant<anything, uint32_t> payload_len,
variant<anything, uint64_t> operation_data,
variant<anything, node_id> source_node,
variant<anything, node_id> dest_node,
variant<anything, actor_id> source_actor,
variant<anything, actor_id> dest_actor,
const Ts&... xs) {
CAF_MESSAGE("expect " << num << ". sent message to be a "
<< operation);
buffer buf; buffer buf;
this_->to_buf(buf, hdr, nullptr, xs...); this_->to_payload(buf, xs...);
buffer& ob = this_->mpx()->output_buffer(hdl); buffer& ob = this_->mpx()->output_buffer(hdl);
CAF_REQUIRE(buf.size() <= ob.size()); CAF_TEST_VERBOSE("output buffer has " << ob.size() << " bytes");
auto first = ob.begin(); CAF_REQUIRE(ob.size() >= basp::header_size);
auto last = ob.begin() + static_cast<ptrdiff_t>(buf.size()); basp::header hdr;
buffer cpy(first, last);
ob.erase(first, last);
basp::header tmp;
{ // lifetime scope of source { // lifetime scope of source
auto source = this_->make_deserializer(cpy); auto source = this_->make_deserializer(ob);
basp::read_hdr(source, tmp); basp::read_hdr(source, hdr);
}
buffer payload;
if (hdr.payload_len > 0) {
CAF_REQUIRE(ob.size() >= (basp::header_size + hdr.payload_len));
auto first = ob.begin() + basp::header_size;
auto end = first + hdr.payload_len;
payload.assign(first, end);
CAF_TEST_VERBOSE("erase " << std::distance(ob.begin(), end)
<< " bytes from output buffer");
ob.erase(ob.begin(), end);
} else {
ob.erase(ob.begin(), ob.begin() + basp::header_size);
} }
CAF_REQUIRE(to_string(hdr) == to_string(tmp)); CAF_CHECK_EQUAL(operation, hdr.operation);
CAF_REQUIRE(equal(buf.begin(), buf.begin() + basp::header_size, CAF_CHECK_EQUAL(payload_len, hdr.payload_len);
cpy.begin())); CAF_CHECK_EQUAL(operation_data, hdr.operation_data);
buf.erase(buf.begin(), buf.begin() + basp::header_size); CAF_CHECK_EQUAL(source_node, hdr.source_node);
cpy.erase(cpy.begin(), cpy.begin() + basp::header_size); CAF_CHECK_EQUAL(dest_node, hdr.dest_node);
CAF_REQUIRE(hdr.payload_len == buf.size() && cpy.size() == buf.size()); CAF_CHECK_EQUAL(source_actor, hdr.source_actor);
CAF_REQUIRE(hexstr(buf) == hexstr(cpy)); CAF_CHECK_EQUAL(dest_actor, hdr.dest_actor);
CAF_REQUIRE(payload.size() == buf.size());
CAF_REQUIRE(hexstr(payload) == hexstr(buf));
++num;
return *this; return *this;
} }
...@@ -358,14 +447,13 @@ CAF_TEST(empty_server_handshake) { ...@@ -358,14 +447,13 @@ CAF_TEST(empty_server_handshake) {
buffer payload; buffer payload;
std::tie(hdr, payload) = from_buf(buf); std::tie(hdr, payload) = from_buf(buf);
basp::header expected{basp::message_type::server_handshake, basp::header expected{basp::message_type::server_handshake,
0, static_cast<uint32_t>(payload.size()),
basp::version, basp::version,
this_node(), invalid_node_id, this_node(), invalid_node_id,
invalid_actor_id, invalid_actor_id}; invalid_actor_id, invalid_actor_id};
CAF_CHECK(basp::valid(hdr)); CAF_CHECK(basp::valid(hdr));
CAF_CHECK(basp::is_handshake(hdr)); CAF_CHECK(basp::is_handshake(hdr));
CAF_CHECK(hdr == expected); CAF_CHECK_EQUAL(to_string(hdr), to_string(expected));
CAF_CHECK(payload.empty());
} }
CAF_TEST(non_empty_server_handshake) { CAF_TEST(non_empty_server_handshake) {
...@@ -392,9 +480,9 @@ CAF_TEST(client_handshake_and_dispatch) { ...@@ -392,9 +480,9 @@ CAF_TEST(client_handshake_and_dispatch) {
remote_node(0), this_node(), pseudo_remote(0)->id(), self()->id()}, remote_node(0), this_node(), pseudo_remote(0)->id(), self()->id()},
make_message(1, 2, 3)) make_message(1, 2, 3))
.expect(remote_hdl(0), .expect(remote_hdl(0),
{basp::message_type::announce_proxy_instance, 0, 0, basp::message_type::announce_proxy_instance, uint32_t{0}, uint64_t{0},
this_node(), remote_node(0), this_node(), remote_node(0),
invalid_actor_id, pseudo_remote(0)->id()}); invalid_actor_id, pseudo_remote(0)->id());
// must've created a proxy for our remote actor // must've created a proxy for our remote actor
CAF_REQUIRE(get_namespace().count_proxies(remote_node(0)) == 1); CAF_REQUIRE(get_namespace().count_proxies(remote_node(0)) == 1);
// must've send remote node a message that this proxy is monitored now // must've send remote node a message that this proxy is monitored now
...@@ -408,8 +496,8 @@ CAF_TEST(client_handshake_and_dispatch) { ...@@ -408,8 +496,8 @@ CAF_TEST(client_handshake_and_dispatch) {
}, },
THROW_ON_UNEXPECTED(self()) THROW_ON_UNEXPECTED(self())
); );
// check for message forwarded by `forwarding_actor_proxy` CAF_TEST_VERBOSE("exec message of forwarding proxy");
mpx()->exec_runnable(); // exec the message of our forwarding proxy mpx()->exec_runnable();
dispatch_out_buf(remote_hdl(0)); // deserialize and send message from out buf dispatch_out_buf(remote_hdl(0)); // deserialize and send message from out buf
pseudo_remote(0)->receive( pseudo_remote(0)->receive(
[](int i) { [](int i) {
...@@ -431,9 +519,9 @@ CAF_TEST(message_forwarding) { ...@@ -431,9 +519,9 @@ CAF_TEST(message_forwarding) {
invalid_actor_id, pseudo_remote(1)->id()}, invalid_actor_id, pseudo_remote(1)->id()},
msg) msg)
.expect(remote_hdl(1), .expect(remote_hdl(1),
{basp::message_type::dispatch_message, 0, 0, basp::message_type::dispatch_message, any_vals, uint64_t{0},
remote_node(0), remote_node(1), remote_node(0), remote_node(1),
invalid_actor_id, pseudo_remote(1)->id()}, invalid_actor_id, pseudo_remote(1)->id(),
msg); msg);
} }
...@@ -449,13 +537,15 @@ CAF_TEST(remote_actor_and_send) { ...@@ -449,13 +537,15 @@ CAF_TEST(remote_actor_and_send) {
CAF_MESSAGE("self: " << to_string(self()->address())); CAF_MESSAGE("self: " << to_string(self()->address()));
mpx()->provide_scribe("localhost", 4242, remote_hdl(0)); mpx()->provide_scribe("localhost", 4242, remote_hdl(0));
CAF_CHECK(mpx()->pending_scribes().count(make_pair("localhost", 4242)) == 1); CAF_CHECK(mpx()->pending_scribes().count(make_pair("localhost", 4242)) == 1);
auto mm = get_middleman_actor(); auto mm1 = get_middleman_actor();
actor result; actor result;
auto f = self()->sync_send(mm, get_atom::value, "localhost", uint16_t{4242}); auto f = self()->sync_send(mm1, connect_atom::value,
"localhost", uint16_t{4242});
mpx()->exec_runnable(); // process message in basp_broker mpx()->exec_runnable(); // process message in basp_broker
CAF_CHECK(mpx()->pending_scribes().count(make_pair("localhost", 4242)) == 0); CAF_CHECK(mpx()->pending_scribes().count(make_pair("localhost", 4242)) == 0);
// build a fake server handshake containing the id of our first pseudo actor // build a fake server handshake containing the id of our first pseudo actor
CAF_TEST_VERBOSE("server handshake => client handshake + proxy announcement"); CAF_TEST_VERBOSE("server handshake => client handshake + proxy announcement");
auto na = registry()->named_actors();
mock(remote_hdl(0), mock(remote_hdl(0),
{basp::message_type::server_handshake, 0, basp::version, {basp::message_type::server_handshake, 0, basp::version,
remote_node(0), invalid_node_id, remote_node(0), invalid_node_id,
...@@ -463,22 +553,30 @@ CAF_TEST(remote_actor_and_send) { ...@@ -463,22 +553,30 @@ CAF_TEST(remote_actor_and_send) {
pseudo_remote(0)->id(), pseudo_remote(0)->id(),
uint32_t{0}) uint32_t{0})
.expect(remote_hdl(0), .expect(remote_hdl(0),
{basp::message_type::client_handshake, 0, 0, basp::message_type::client_handshake, uint32_t{0}, uint64_t{0},
this_node(), remote_node(0), this_node(), remote_node(0),
invalid_actor_id, invalid_actor_id}) invalid_actor_id, invalid_actor_id)
.expect(remote_hdl(0), .expect(remote_hdl(0),
{basp::message_type::announce_proxy_instance, 0, 0, basp::message_type::dispatch_message, any_vals,
this_node(), remote_node(0), static_cast<uint64_t>(atom("SpawnServ")),
invalid_actor_id, pseudo_remote(0)->id()}); this_node(), remote_node(0),
any_vals, std::numeric_limits<actor_id>::max(),
make_message(sys_atom::value, get_atom::value, "info"))
.expect(remote_hdl(0),
basp::message_type::announce_proxy_instance, uint32_t{0}, uint64_t{0},
this_node(), remote_node(0),
invalid_actor_id, pseudo_remote(0)->id());
// basp broker should've send the proxy // basp broker should've send the proxy
f.await( f.await(
[&](ok_atom, actor_addr res) { [&](ok_atom, node_id nid, actor_addr res, std::set<std::string> ifs) {
auto aptr = actor_cast<abstract_actor_ptr>(res); auto aptr = actor_cast<abstract_actor_ptr>(res);
CAF_REQUIRE(aptr.downcast<forwarding_actor_proxy>() != nullptr); CAF_REQUIRE(aptr.downcast<forwarding_actor_proxy>() != nullptr);
CAF_CHECK(get_namespace().get_all().size() == 1); CAF_CHECK(get_namespace().get_all().size() == 1);
CAF_CHECK(get_namespace().count_proxies(remote_node(0)) == 1); CAF_CHECK(get_namespace().count_proxies(remote_node(0)) == 1);
CAF_CHECK(nid == remote_node(0));
CAF_CHECK(res.node() == remote_node(0)); CAF_CHECK(res.node() == remote_node(0));
CAF_CHECK(res.id() == pseudo_remote(0)->id()); CAF_CHECK(res.id() == pseudo_remote(0)->id());
CAF_CHECK(ifs.empty());
auto proxy = get_namespace().get(remote_node(0), pseudo_remote(0)->id()); auto proxy = get_namespace().get(remote_node(0), pseudo_remote(0)->id());
CAF_REQUIRE(proxy != nullptr); CAF_REQUIRE(proxy != nullptr);
CAF_REQUIRE(proxy->address() == res); CAF_REQUIRE(proxy->address() == res);
...@@ -493,9 +591,9 @@ CAF_TEST(remote_actor_and_send) { ...@@ -493,9 +591,9 @@ CAF_TEST(remote_actor_and_send) {
mpx()->exec_runnable(); // process forwarded message in basp_broker mpx()->exec_runnable(); // process forwarded message in basp_broker
mock() mock()
.expect(remote_hdl(0), .expect(remote_hdl(0),
{basp::message_type::dispatch_message, 0, 0, basp::message_type::dispatch_message, any_vals, uint64_t{0},
this_node(), remote_node(0), this_node(), remote_node(0),
invalid_actor_id, pseudo_remote(0)->id()}, invalid_actor_id, pseudo_remote(0)->id(),
make_message(42)); make_message(42));
auto msg = make_message("hi there!"); auto msg = make_message("hi there!");
CAF_MESSAGE("send message via BASP (from proxy)"); CAF_MESSAGE("send message via BASP (from proxy)");
...@@ -527,9 +625,9 @@ CAF_TEST(actor_serialize_and_deserialize) { ...@@ -527,9 +625,9 @@ CAF_TEST(actor_serialize_and_deserialize) {
auto prx = get_namespace().get_or_put(remote_node(0), pseudo_remote(0)->id()); auto prx = get_namespace().get_or_put(remote_node(0), pseudo_remote(0)->id());
mock() mock()
.expect(remote_hdl(0), .expect(remote_hdl(0),
{basp::message_type::announce_proxy_instance, 0, 0, basp::message_type::announce_proxy_instance, uint32_t{0}, uint64_t{0},
this_node(), prx->node(), this_node(), prx->node(),
invalid_actor_id, prx->id()}); invalid_actor_id, prx->id());
CAF_CHECK(prx->node() == remote_node(0)); CAF_CHECK(prx->node() == remote_node(0));
CAF_CHECK(prx->id() == pseudo_remote(0)->id()); CAF_CHECK(prx->id() == pseudo_remote(0)->id());
auto testee = spawn(testee_impl); auto testee = spawn(testee_impl);
...@@ -547,10 +645,150 @@ CAF_TEST(actor_serialize_and_deserialize) { ...@@ -547,10 +645,150 @@ CAF_TEST(actor_serialize_and_deserialize) {
// output buffer must contain the reflected message // output buffer must contain the reflected message
mock() mock()
.expect(remote_hdl(0), .expect(remote_hdl(0),
{basp::message_type::dispatch_message, 0, 0, basp::message_type::dispatch_message, any_vals, uint64_t{0},
this_node(), prx->node(), this_node(), prx->node(),
testee->id(), prx->id()}, testee->id(), prx->id(),
msg); msg);
} }
CAF_TEST(indirect_connections) {
// jupiter [remote hdl 0] -> mars [remote hdl 1] -> earth [this_node]
// (this node receives a message from jupiter via mars and responds via mars)
CAF_MESSAGE("self: " << to_string(self()->address()));
auto ax = accept_handle::from_int(4242);
mpx()->provide_acceptor(4242, ax);
publish(self(), 4242);
mpx()->exec_runnable(); // process publish message in basp_broker
// connect to mars
connect_node(1, ax, self()->id());
// now, an actor from jupiter sends a message to us via mars
mock(remote_hdl(1),
{basp::message_type::dispatch_message, 0, 0,
remote_node(0), this_node(),
pseudo_remote(0)->id(), self()->id()},
make_message("hello from jupiter!"))
// this asks Jupiter if it has a 'SpawnServ'
.expect(remote_hdl(1),
basp::message_type::dispatch_message, any_vals,
static_cast<uint64_t>(atom("SpawnServ")),
this_node(), remote_node(0),
any_vals, std::numeric_limits<actor_id>::max(),
make_message(sys_atom::value, get_atom::value, "info"))
// this tells Jupiter that Earth learned the address of one its actors
.expect(remote_hdl(1),
basp::message_type::announce_proxy_instance, uint32_t{0}, uint64_t{0},
this_node(), remote_node(0),
invalid_actor_id, pseudo_remote(0)->id());
CAF_MESSAGE("receive message from jupiter");
self()->receive(
[](const std::string& str) -> std::string {
CAF_CHECK(str == "hello from jupiter!");
return "hello from earth!";
},
THROW_ON_UNEXPECTED(self())
);
mpx()->exec_runnable(); // process forwarded message in basp_broker
mock()
.expect(remote_hdl(1),
basp::message_type::dispatch_message, any_vals, uint64_t{0},
this_node(), remote_node(0),
self()->id(), pseudo_remote(0)->id(),
make_message("hello from earth!"));
}
CAF_TEST(automatic_connection) {
// this tells our BASP broker to enable the automatic connection feature
anon_send(aut(), ok_atom::value,
"global.enable-automatic-connections", make_message(true));
mpx()->exec_runnable(); // process publish message in basp_broker
// jupiter [remote hdl 0] -> mars [remote hdl 1] -> earth [this_node]
// (this node receives a message from jupiter via mars and responds via mars,
// but then also establishes a connection to jupiter directly)
mpx()->provide_scribe("jupiter", 8080, remote_hdl(0));
CAF_CHECK(mpx()->pending_scribes().count(make_pair("jupiter", 8080)) == 1);
CAF_MESSAGE("self: " << to_string(self()->address()));
auto ax = accept_handle::from_int(4242);
mpx()->provide_acceptor(4242, ax);
publish(self(), 4242);
mpx()->exec_runnable(); // process publish message in basp_broker
// connect to mars
connect_node(1, ax, self()->id());
CAF_CHECK_EQUAL(tbl().lookup_direct(remote_node(1)).id(), remote_hdl(1).id());
// now, an actor from jupiter sends a message to us via mars
mock(remote_hdl(1),
{basp::message_type::dispatch_message, 0, 0,
remote_node(0), this_node(),
pseudo_remote(0)->id(), self()->id()},
make_message("hello from jupiter!"))
.expect(remote_hdl(1),
basp::message_type::dispatch_message, any_vals,
static_cast<uint64_t>(atom("SpawnServ")),
this_node(), remote_node(0),
any_vals, std::numeric_limits<actor_id>::max(),
make_message(sys_atom::value, get_atom::value, "info"))
.expect(remote_hdl(1),
basp::message_type::dispatch_message, any_vals,
static_cast<uint64_t>(atom("ConfigServ")),
this_node(), remote_node(0),
any_vals, // actor ID of an actor spawned by the BASP broker
std::numeric_limits<actor_id>::max(),
make_message(get_atom::value, "basp.default-connectivity"))
.expect(remote_hdl(1),
basp::message_type::announce_proxy_instance, uint32_t{0}, uint64_t{0},
this_node(), remote_node(0),
invalid_actor_id, pseudo_remote(0)->id());
CAF_CHECK_EQUAL(mpx()->output_buffer(remote_hdl(1)).size(), 0);
CAF_CHECK_EQUAL(tbl().lookup_indirect(remote_node(0)), remote_node(1));
CAF_CHECK_EQUAL(tbl().lookup_indirect(remote_node(1)), invalid_node_id);
auto connection_helper = abstract_actor::latest_actor_id();
CAF_CHECK_EQUAL(mpx()->output_buffer(remote_hdl(1)).size(), 0);
// create a dummy config server and respond to the name lookup
CAF_MESSAGE("receive ConfigServ of jupiter");
network::address_listing res;
res[network::protocol::ipv4].push_back("jupiter");
mock(remote_hdl(1),
{basp::message_type::dispatch_message, 0, 0,
this_node(), this_node(),
invalid_actor_id, connection_helper},
make_message(ok_atom::value, "basp.default-connectivity",
make_message(uint16_t{8080}, std::move(res))));
// our connection helper should now connect to jupiter and
// send the scribe handle over to the BASP broker
mpx()->exec_runnable();
CAF_CHECK_EQUAL(mpx()->output_buffer(remote_hdl(1)).size(), 0);
CAF_CHECK(mpx()->pending_scribes().count(make_pair("jupiter", 8080)) == 0);
// send handshake from jupiter
mock(remote_hdl(0),
{basp::message_type::server_handshake, 0, basp::version,
remote_node(0), invalid_node_id,
pseudo_remote(0)->id(), invalid_actor_id},
pseudo_remote(0)->id(),
uint32_t{0})
.expect(remote_hdl(0),
basp::message_type::client_handshake, uint32_t{0}, uint64_t{0},
this_node(), remote_node(0),
invalid_actor_id, invalid_actor_id);
CAF_CHECK_EQUAL(tbl().lookup_indirect(remote_node(0)), invalid_node_id);
CAF_CHECK_EQUAL(tbl().lookup_indirect(remote_node(1)), invalid_node_id);
CAF_CHECK_EQUAL(tbl().lookup_direct(remote_node(0)).id(), remote_hdl(0).id());
CAF_CHECK_EQUAL(tbl().lookup_direct(remote_node(1)).id(), remote_hdl(1).id());
CAF_MESSAGE("receive message from jupiter");
self()->receive(
[](const std::string& str) -> std::string {
CAF_CHECK(str == "hello from jupiter!");
return "hello from earth!";
},
THROW_ON_UNEXPECTED(self())
);
mpx()->exec_runnable(); // process forwarded message in basp_broker
CAF_MESSAGE("response message must take direct route now");
mock()
.expect(remote_hdl(0),
basp::message_type::dispatch_message, any_vals, uint64_t{0},
this_node(), remote_node(0),
self()->id(), pseudo_remote(0)->id(),
make_message("hello from earth!"));
CAF_CHECK(mpx()->output_buffer(remote_hdl(1)).size() == 0);
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE io_remote_spawn
#include "caf/test/unit_test.hpp"
#include <thread>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <functional>
#include "caf/all.hpp"
#include "caf/io/all.hpp"
#include "caf/detail/run_program.hpp"
#include "caf/experimental/announce_actor_type.hpp"
#ifdef CAF_USE_ASIO
#include "caf/io/network/asio_multiplexer.hpp"
#endif // CAF_USE_ASIO
using namespace caf;
using namespace caf::experimental;
namespace {
behavior mirror(event_based_actor* self) {
return {
others >> [=] {
return self->current_message();
}
};
}
behavior client(event_based_actor* self, actor serv) {
self->send(serv, ok_atom::value);
return {
others >> [=] {
CAF_TEST_ERROR("unexpected message: "
<< to_string(self->current_message()));
}
};
}
struct server_state {
actor client; // the spawn we connect to the server in our main
actor aut; // our mirror
};
behavior server(stateful_actor<server_state>* self) {
self->on_sync_failure([=] {
CAF_TEST_ERROR("unexpected sync response: "
<< to_string(self->current_message()));
});
return {
[=](ok_atom) {
auto s = self->current_sender();
CAF_REQUIRE(s != invalid_actor_addr);
CAF_REQUIRE(s.is_remote());
self->state.client = actor_cast<actor>(s);
auto mm = io::get_middleman_actor();
self->sync_send(mm, spawn_atom::value,
s.node(), "mirror", make_message()).then(
[=](ok_atom, const actor_addr& addr, const std::set<std::string>& ifs) {
CAF_REQUIRE(addr != invalid_actor_addr);
CAF_CHECK(ifs.empty());
self->state.aut = actor_cast<actor>(addr);
self->send(self->state.aut, "hello mirror");
self->become(
[=](const std::string& str) {
CAF_CHECK(self->current_sender() == self->state.aut);
CAF_CHECK(str == "hello mirror");
self->send_exit(self->state.aut, exit_reason::kill);
self->send_exit(self->state.client, exit_reason::kill);
self->quit();
}
);
},
[=](error_atom, const std::string& errmsg) {
CAF_TEST_ERROR("could not spawn mirror: " << errmsg);
}
);
}
};
}
} // namespace <anonymous>
CAF_TEST(remote_spawn) {
announce_actor_type("mirror", mirror);
auto argv = caf::test::engine::argv();
auto argc = caf::test::engine::argc();
uint16_t port = 0;
auto r = message_builder(argv, argv + argc).extract_opts({
{"server,s", "run as server (don't run client"},
{"client,c", "add client port (two needed)", port},
{"port,p", "force a port in server mode", port},
{"use-asio", "use ASIO network backend (if available)"}
});
if (! r.error.empty() || r.opts.count("help") > 0 || ! r.remainder.empty()) {
std::cout << r.error << std::endl << std::endl << r.helptext << std::endl;
return;
}
auto use_asio = r.opts.count("use-asio") > 0;
if (use_asio) {
# ifdef CAF_USE_ASIO
CAF_MESSAGE("enable ASIO backend");
io::set_middleman<io::network::asio_multiplexer>();
# endif // CAF_USE_ASIO
}
if (r.opts.count("client") > 0) {
auto serv = io::remote_actor("localhost", port);
spawn(client, serv);
await_all_actors_done();
return;
}
auto serv = spawn(server);
port = io::publish(serv, port);
CAF_TEST_INFO("published server at port " << port);
if (r.opts.count("server") == 0) {
auto child = detail::run_program(invalid_actor, argv[0], "-n", "-s",
CAF_XSTR(CAF_SUITE), "--", "-c",
port, (use_asio ? "--use-asio" : ""));
child.join();
}
await_all_actors_done();
}
...@@ -219,7 +219,8 @@ CAF_TEST(test_uniform_type) { ...@@ -219,7 +219,8 @@ CAF_TEST(test_uniform_type) {
"caf::io::connection_closed_msg", "caf::io::connection_closed_msg",
"caf::io::network::protocol", "caf::io::network::protocol",
"caf::io::new_connection_msg", "caf::io::new_connection_msg",
"caf::io::new_data_msg")); "caf::io::new_data_msg",
"caf::io::network::address_listing"));
CAF_MESSAGE("io types checked"); CAF_MESSAGE("io types checked");
} }
// check whether enums can be announced as members // check whether enums can be announced as members
......
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