Commit 930d232a authored by Jakob Otto's avatar Jakob Otto

Merge branch master

parents df629f20 b156634c
......@@ -5,23 +5,43 @@ project(caf_net C CXX)
# e.g., for creating proper Xcode projects
file(GLOB_RECURSE LIBCAF_NET_HDRS "caf/*.hpp")
enum_to_string("caf/net/basp/connection_state.hpp" "basp_conn_strings.cpp")
enum_to_string("caf/net/basp/ec.hpp" "basp_ec_strings.cpp")
enum_to_string("caf/net/basp/message_type.hpp" "basp_message_type_strings.cpp")
enum_to_string("caf/net/operation.hpp" "operation_strings.cpp")
# list cpp files excluding platform-dependent files
set(LIBCAF_NET_SRCS
"${CMAKE_CURRENT_BINARY_DIR}/basp_conn_strings.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/basp_ec_strings.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/basp_message_type_strings.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/operation_strings.cpp"
src/actor_proxy_impl.cpp
src/application.cpp
src/convert_ip_endpoint.cpp
src/datagram_socket.cpp
src/defaults.cpp
src/ec.cpp
src/endpoint_manager.cpp
src/header.cpp
src/host.cpp
src/ip.cpp
src/multiplexer.cpp
src/net/backend/test.cpp
src/net/endpoint_manager_queue.cpp
src/net/middleman.cpp
src/net/middleman_backend.cpp
src/net/packet_writer.cpp
src/network_socket.cpp
src/pipe_socket.cpp
src/pollset_updater.cpp
src/socket.cpp
src/socket_manager.cpp
src/stream_socket.cpp
src/scribe.cpp
src/tcp_accept_socket.cpp
src/tcp_stream_socket.cpp
src/udp_datagram_socket.cpp
src/defaults.cpp
src/message_queue.cpp
src/worker.cpp
)
add_custom_target(libcaf_net)
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/fwd.hpp"
namespace caf::detail {
void convert(const ip_endpoint& src, sockaddr_storage& dst);
error convert(const sockaddr_storage& src, ip_endpoint& dst);
} // namespace caf::detail
......@@ -20,8 +20,7 @@
#include "caf/detail/socket_sys_includes.hpp"
namespace caf {
namespace detail {
namespace caf::detail {
inline auto addr_of(sockaddr_in& what) -> decltype(what.sin_addr)& {
return what.sin_addr;
......@@ -47,5 +46,4 @@ inline auto port_of(sockaddr_in6& what) -> decltype(what.sin6_port)& {
return what.sin6_port;
}
} // namespace detail
} // namespace caf
} // namespace caf::detail
......@@ -20,8 +20,7 @@
#include "caf/config.hpp"
namespace caf {
namespace net {
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -41,5 +40,4 @@ using socket_size_type = unsigned;
#endif // CAF_WINDOWS
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -52,5 +52,6 @@
# include <netinet/tcp.h>
# include <sys/socket.h>
# include <unistd.h>
# include <sys/uio.h>
#endif
// clang-format on
......@@ -21,8 +21,7 @@
#include "caf/actor_proxy.hpp"
#include "caf/net/endpoint_manager.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// Implements a simple proxy forwarding all operations to a manager.
class actor_proxy_impl : public actor_proxy {
......@@ -35,10 +34,6 @@ public:
void enqueue(mailbox_element_ptr what, execution_unit* context) override;
bool add_backlink(abstract_actor* x) override;
bool remove_backlink(abstract_actor* x) override;
void kill_proxy(execution_unit* ctx, error rsn) override;
private:
......@@ -46,5 +41,4 @@ private:
endpoint_manager_ptr dst_;
};
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/datagram_socket.hpp"
#include "caf/net/datagram_transport.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/host.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/net/packet_writer_decorator.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/pollset_updater.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_id.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/transport_base.hpp"
#include "caf/net/transport_worker.hpp"
#include "caf/net/transport_worker_dispatcher.hpp"
#include "caf/net/udp_datagram_socket.hpp"
/******************************************************************************_opt
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <map>
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/node_id.hpp"
namespace caf::net::backend {
/// Minimal backend for unit testing.
/// @warning this backend is *not* thread safe.
class test : public middleman_backend {
public:
// -- member types -----------------------------------------------------------
using peer_entry = std::pair<stream_socket, endpoint_manager_ptr>;
// -- constructors, destructors, and assignment operators --------------------
test(middleman& mm);
~test() override;
// -- interface functions ----------------------------------------------------
error init() override;
endpoint_manager_ptr peer(const node_id& id) override;
void resolve(const uri& locator, const actor& listener) override;
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override;
void set_last_hop(node_id*) override;
// -- properties -------------------------------------------------------------
stream_socket socket(const node_id& peer_id) {
return get_peer(peer_id).first;
}
peer_entry& emplace(const node_id& peer_id, stream_socket first,
stream_socket second);
private:
peer_entry& get_peer(const node_id& id);
middleman& mm_;
std::map<node_id, peer_entry> peers_;
proxy_registry proxies_;
};
} // namespace caf::net::backend
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "caf/actor_addr.hpp"
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/byte.hpp"
#include "caf/callback.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/worker_hub.hpp"
#include "caf/error.hpp"
#include "caf/net/basp/connection_state.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/header.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/net/basp/message_type.hpp"
#include "caf/net/basp/worker.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/node_id.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/response_promise.hpp"
#include "caf/scoped_execution_unit.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/span.hpp"
#include "caf/unit.hpp"
namespace caf::net::basp {
/// An implementation of BASP as an application layer protocol.
class application {
public:
// -- member types -----------------------------------------------------------
using buffer_type = std::vector<byte>;
using byte_span = span<const byte>;
using hub_type = detail::worker_hub<worker>;
struct test_tag {};
// -- constructors, destructors, and assignment operators --------------------
explicit application(proxy_registry& proxies);
// -- interface functions ----------------------------------------------------
template <class Parent>
error init(Parent& parent) {
// Initialize member variables.
system_ = &parent.system();
executor_.system_ptr(system_);
executor_.proxy_registry_ptr(&proxies_);
// TODO: use `if constexpr` when switching to C++17.
// Allow unit tests to run the application without endpoint manager.
if (!std::is_base_of<test_tag, Parent>::value)
manager_ = &parent.manager();
auto workers = get_or(system_->config(), "middleman.workers",
defaults::middleman::workers);
for (size_t i = 0; i < workers; ++i)
hub_->add_new_worker(*queue_, proxies_);
// Write handshake.
auto hdr = parent.next_header_buffer();
auto payload = parent.next_payload_buffer();
if (auto err = generate_handshake(payload))
return err;
to_bytes(header{message_type::handshake,
static_cast<uint32_t>(payload.size()), version},
hdr);
parent.write_packet(hdr, payload);
parent.transport().configure_read(receive_policy::exactly(header_size));
return none;
}
error write_message(packet_writer& writer,
std::unique_ptr<endpoint_manager_queue::message> ptr);
template <class Parent>
error handle_data(Parent& parent, byte_span bytes) {
static_assert(std::is_base_of<packet_writer, Parent>::value,
"parent must implement packet_writer");
size_t next_read_size = header_size;
if (auto err = handle(next_read_size, parent, bytes))
return err;
parent.transport().configure_read(receive_policy::exactly(next_read_size));
return none;
}
void resolve(packet_writer& writer, string_view path, const actor& listener);
static void new_proxy(packet_writer& writer, actor_id id);
void local_actor_down(packet_writer& writer, actor_id id, error reason);
template <class Parent>
void timeout(Parent&, atom_value, uint64_t) {
// nop
}
void handle_error(sec) {
// nop
}
static expected<buffer_type> serialize(actor_system& sys,
const type_erased_tuple& x);
// -- utility functions ------------------------------------------------------
strong_actor_ptr resolve_local_path(string_view path);
// -- properties -------------------------------------------------------------
connection_state state() const noexcept {
return state_;
}
actor_system& system() const noexcept {
return *system_;
}
private:
// -- handling of incoming messages ------------------------------------------
error handle(size_t& next_read_size, packet_writer& writer, byte_span bytes);
error handle(packet_writer& writer, header hdr, byte_span payload);
error handle_handshake(packet_writer& writer, header hdr, byte_span payload);
error handle_actor_message(packet_writer& writer, header hdr,
byte_span payload);
error handle_resolve_request(packet_writer& writer, header rec_hdr,
byte_span received);
error handle_resolve_response(packet_writer& writer, header received_hdr,
byte_span received);
error handle_monitor_message(packet_writer& writer, header received_hdr,
byte_span received);
error handle_down_message(packet_writer& writer, header received_hdr,
byte_span received);
/// Writes the handshake payload to `buf_`.
error generate_handshake(buffer_type& buf);
// -- member variables -------------------------------------------------------
/// Stores a pointer to the parent actor system.
actor_system* system_ = nullptr;
/// Stores the expected type of the next incoming message.
connection_state state_ = connection_state::await_handshake_header;
/// Caches the last header while waiting for the matching payload.
header hdr_;
/// Stores the ID of our peer.
node_id peer_id_;
/// Tracks which local actors our peer monitors.
std::unordered_set<actor_addr> monitored_actors_; // TODO: this is unused
/// Caches actor handles obtained via `resolve`.
std::unordered_map<uint64_t, actor> pending_resolves_;
/// Ascending ID generator for requests to our peer.
uint64_t next_request_id_ = 1;
/// Points to the factory object for generating proxies.
proxy_registry& proxies_;
/// Points to the endpoint manager that owns this applications.
endpoint_manager* manager_ = nullptr;
/// Provides pointers to the actor system as well as the registry,
/// serializers and deserializer.
scoped_execution_unit executor_;
std::unique_ptr<message_queue> queue_;
std::unique_ptr<hub_type> hub_;
};
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <string>
namespace caf::net::basp {
/// @addtogroup BASP
/// Stores the state of a connection in a `basp::application`.
enum class connection_state {
/// Initial state for any connection to wait for the peer's handshake.
await_handshake_header,
/// Indicates that the header for the peer's handshake arrived and BASP
/// requires the payload next.
await_handshake_payload,
/// Indicates that a connection is established and this node is waiting for
/// the next BASP header.
await_header,
/// Indicates that this node has received a header with non-zero payload and
/// is waiting for the data.
await_payload,
/// Indicates that the connection is about to shut down.
shutdown,
};
/// @relates connection_state
std::string to_string(connection_state x);
/// @}
} // namespace caf::net::basp
......@@ -19,51 +19,19 @@
#pragma once
#include <cstddef>
#include <limits>
#include <cstdint>
#include "caf/net/socket_id.hpp"
namespace caf::net::basp {
namespace caf {
namespace net {
/// @addtogroup BASP
template <class Derived>
struct abstract_socket {
socket_id id;
/// The current BASP version.
/// @note BASP is not backwards compatible.
constexpr uint64_t version = 1;
constexpr abstract_socket() : id(invalid_socket_id) {
// nop
}
/// Size of a BASP header in serialized form.
constexpr size_t header_size = 13;
constexpr abstract_socket(socket_id id) : id(id) {
// nop
}
/// @}
constexpr abstract_socket(const Derived& other) : id(other.id) {
// nop
}
abstract_socket& operator=(const Derived& other) {
id = other.id;
return *this;
}
template <class Inspector>
friend typename Inspector::result_type inspect(Inspector& f, Derived& x) {
return f(x.id);
}
friend constexpr bool operator==(Derived x, Derived y) {
return x.id == y.id;
}
friend constexpr bool operator!=(Derived x, Derived y) {
return x.id != y.id;
}
friend constexpr bool operator<(Derived x, Derived y) {
return x.id < y.id;
}
};
} // namespace net
} // namespace caf
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <string>
#include "caf/fwd.hpp"
namespace caf::net::basp {
/// BASP-specific error codes.
enum class ec : uint8_t {
invalid_magic_number = 1,
unexpected_number_of_bytes,
unexpected_payload,
missing_payload,
illegal_state,
invalid_handshake,
missing_handshake,
unexpected_handshake,
version_mismatch,
unimplemented = 10,
app_identifiers_mismatch,
invalid_payload,
invalid_scheme,
invalid_locator,
};
/// @relates ec
std::string to_string(ec x);
/// @relates ec
error make_error(ec x);
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <array>
#include <cstdint>
#include "caf/detail/comparable.hpp"
#include "caf/fwd.hpp"
#include "caf/meta/hex_formatted.hpp"
#include "caf/meta/type_name.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/message_type.hpp"
namespace caf::net::basp {
/// @addtogroup BASP
/// The header of a Binary Actor System Protocol (BASP) message.
struct header : detail::comparable<header> {
// -- constructors, destructors, and assignment operators --------------------
constexpr header() noexcept
: type(message_type::handshake), payload_len(0), operation_data(0) {
// nop
}
constexpr header(message_type type, uint32_t payload_len,
uint64_t operation_data) noexcept
: type(type), payload_len(payload_len), operation_data(operation_data) {
// nop
}
header(const header&) noexcept = default;
header& operator=(const header&) noexcept = default;
// -- factory functions ------------------------------------------------------
/// @pre `bytes.size() == header_size`
static header from_bytes(span<const byte> bytes);
// -- comparison -------------------------------------------------------------
int compare(header other) const noexcept;
// -- member variables -------------------------------------------------------
/// Denotes the BASP operation and how `operation_data` gets interpreted.
message_type type;
/// Stores the size in bytes for the payload that follows this header.
uint32_t payload_len;
/// Stores type-specific information such as the BASP version in handshakes.
uint64_t operation_data;
};
/// Serializes a header to a byte representation.
/// @relates header
std::array<byte, header_size> to_bytes(header x);
/// Serializes a header to a byte representation.
/// @relates header
void to_bytes(header x, std::vector<byte>& buf);
/// @relates header
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, header& x) {
return f(meta::type_name("basp::header"), x.type, x.payload_len,
x.operation_data);
}
/// @}
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <mutex>
#include <vector>
#include "caf/actor_control_block.hpp"
#include "caf/fwd.hpp"
#include "caf/mailbox_element.hpp"
namespace caf::net::basp {
/// Enforces strict order of message delivery, i.e., deliver messages in the
/// same order as if they were deserialized by a single thread.
class message_queue {
public:
// -- member types -----------------------------------------------------------
/// Request for sending a message to an actor at a later time.
struct actor_msg {
uint64_t id;
strong_actor_ptr receiver;
mailbox_element_ptr content;
};
// -- constructors, destructors, and assignment operators --------------------
message_queue();
// -- mutators ---------------------------------------------------------------
/// Adds a new message to the queue or deliver it immediately if possible.
void push(execution_unit* ctx, uint64_t id, strong_actor_ptr receiver,
mailbox_element_ptr content);
/// Marks given ID as dropped, effectively skipping it without effect.
void drop(execution_unit* ctx, uint64_t id);
/// Returns the next ascending ID.
uint64_t new_id();
// -- member variables -------------------------------------------------------
/// Protects all other properties.
std::mutex lock;
/// The next available ascending ID. The counter is large enough to overflow
/// after roughly 600 years if we dispatch a message every microsecond.
uint64_t next_id;
/// The next ID that we can ship.
uint64_t next_undelivered;
/// Keeps messages in sorted order in case a message other than
/// `next_undelivered` gets ready first.
std::vector<actor_msg> pending;
};
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <string>
namespace caf::net::basp {
/// @addtogroup BASP
/// Describes the first header field of a BASP message and determines the
/// interpretation of the other header fields.
enum class message_type : uint8_t {
/// Sends supported BASP version and node information to the server.
///
/// ![](client_handshake.png)
handshake = 0,
/// Transmits an actor-to-actor messages.
///
/// ![](direct_message.png)
actor_message = 1,
/// Tries to resolve a path on the receiving node.
///
/// ![](resolve_request.png)
resolve_request = 2,
/// Transmits the result of a path lookup.
///
/// ![](resolve_response.png)
resolve_response = 3,
/// Informs the receiving node that the sending node has created a proxy
/// instance for one of its actors. Causes the receiving node to attach a
/// functor to the actor that triggers a down_message on termination.
///
/// ![](monitor_message.png)
monitor_message = 4,
/// Informs the receiving node that it has a proxy for an actor that has been
/// terminated.
///
/// ![](down_message.png)
down_message = 5,
/// Used to generate periodic traffic between two nodes in order to detect
/// disconnects.
///
/// ![](heartbeat.png)
heartbeat = 6,
};
/// @relates message_type
std::string to_string(message_type);
/// @}
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <vector>
#include "caf/actor_control_block.hpp"
#include "caf/actor_proxy.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/config.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/sync_request_bouncer.hpp"
#include "caf/execution_unit.hpp"
#include "caf/logger.hpp"
#include "caf/message.hpp"
#include "caf/message_id.hpp"
#include "caf/net/basp/header.hpp"
#include "caf/node_id.hpp"
namespace caf::net::basp {
template <class Subtype>
class remote_message_handler {
public:
void handle_remote_message(execution_unit* ctx) {
// Local variables.
auto& dref = static_cast<Subtype&>(*this);
auto& payload = dref.payload_;
auto& hdr = dref.hdr_;
auto& registry = dref.system_->registry();
auto& proxies = *dref.proxies_;
CAF_LOG_TRACE(CAF_ARG(hdr) << CAF_ARG2("payload.size", payload.size()));
// Deserialize payload.
actor_id src_id = 0;
node_id src_node;
actor_id dst_id = 0;
std::vector<strong_actor_ptr> fwd_stack;
message content;
binary_deserializer source{ctx, payload};
if (auto err = source(src_node, src_id, dst_id, fwd_stack, content)) {
CAF_LOG_ERROR("could not deserialize payload: " << CAF_ARG(err));
return;
}
// Sanity checks.
if (dst_id == 0)
return;
// Try to fetch the receiver.
auto dst_hdl = registry.get(dst_id);
if (dst_hdl == nullptr) {
CAF_LOG_DEBUG("no actor found for given ID, drop message");
return;
}
// Try to fetch the sender.
strong_actor_ptr src_hdl;
if (src_node != none && src_id != 0)
src_hdl = proxies.get_or_put(src_node, src_id);
// Ship the message.
auto ptr = make_mailbox_element(std::move(src_hdl),
make_message_id(hdr.operation_data),
std::move(fwd_stack), std::move(content));
dst_hdl->get()->enqueue(std::move(ptr), nullptr);
}
};
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <atomic>
#include <cstdint>
#include <vector>
#include "caf/config.hpp"
#include "caf/detail/abstract_worker.hpp"
#include "caf/detail/worker_hub.hpp"
#include "caf/net/basp/header.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/net/basp/remote_message_handler.hpp"
#include "caf/net/fwd.hpp"
#include "caf/node_id.hpp"
#include "caf/resumable.hpp"
namespace caf::net::basp {
/// Deserializes payloads for BASP messages asynchronously.
class worker : public detail::abstract_worker,
public remote_message_handler<worker> {
public:
// -- friends ----------------------------------------------------------------
friend remote_message_handler<worker>;
// -- member types -----------------------------------------------------------
using super = detail::abstract_worker;
using scheduler_type = scheduler::abstract_coordinator;
using buffer_type = std::vector<byte>;
using hub_type = detail::worker_hub<worker>;
// -- constructors, destructors, and assignment operators --------------------
/// Only the ::worker_hub has access to the construtor.
worker(hub_type& hub, message_queue& queue, proxy_registry& proxies);
~worker() override;
// -- management -------------------------------------------------------------
void launch(const node_id& last_hop, const basp::header& hdr,
span<const byte> payload);
// -- implementation of resumable --------------------------------------------
resume_result resume(execution_unit* ctx, size_t) override;
private:
// -- constants and assertions -----------------------------------------------
/// Stores how many bytes the "first half" of this object requires.
static constexpr size_t pointer_members_size = sizeof(hub_type*)
+ sizeof(message_queue*)
+ sizeof(proxy_registry*)
+ sizeof(actor_system*);
static_assert(CAF_CACHE_LINE_SIZE > pointer_members_size,
"invalid cache line size");
// -- member variables -------------------------------------------------------
/// Points to our home hub.
hub_type* hub_;
/// Points to the queue for establishing strict ordering.
message_queue* queue_;
/// Points to our proxy registry / factory.
proxy_registry* proxies_;
/// Points to the parent system.
actor_system* system_;
/// Prevents false sharing when writing to `next`.
char pad_[CAF_CACHE_LINE_SIZE - pointer_members_size];
/// ID for local ordering.
uint64_t msg_id_;
/// Identifies the node that sent us `hdr_` and `payload_`.
node_id last_hop_;
/// The header for the next message. Either a direct_message or a
/// routed_message.
header hdr_;
/// Contains whatever this worker deserializes next.
buffer_type payload_;
};
} // namespace caf::net::basp
......@@ -21,22 +21,13 @@
#include "caf/net/network_socket.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// A datagram-oriented network communication endpoint.
struct datagram_socket : abstract_socket<datagram_socket> {
using super = abstract_socket<datagram_socket>;
struct datagram_socket : network_socket {
using super = network_socket;
using super::super;
constexpr operator socket() const noexcept {
return socket{id};
}
constexpr operator network_socket() const noexcept {
return network_socket{id};
}
};
/// Enables or disables `SIO_UDP_CONNRESET` error on `x`.
......@@ -49,5 +40,4 @@ error allow_connreset(datagram_socket x, bool new_value);
variant<size_t, sec>
check_datagram_socket_io_res(std::make_signed<size_t>::type res);
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <deque>
#include <vector>
#include "caf/fwd.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/logger.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/transport_base.hpp"
#include "caf/net/transport_worker_dispatcher.hpp"
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
namespace caf::net {
template <class Factory>
using datagram_transport_base = transport_base<
datagram_transport<Factory>,
transport_worker_dispatcher<Factory, ip_endpoint>, udp_datagram_socket,
Factory, ip_endpoint>;
/// Implements a udp_transport policy that manages a datagram socket.
template <class Factory>
class datagram_transport : public datagram_transport_base<Factory> {
public:
// Maximal UDP-packet size
static constexpr size_t max_datagram_size = std::numeric_limits<
uint16_t>::max();
// -- member types -----------------------------------------------------------
using factory_type = Factory;
using id_type = ip_endpoint;
using application_type = typename factory_type::application_type;
using super = datagram_transport_base<factory_type>;
using buffer_type = typename super::buffer_type;
using buffer_cache_type = typename super::buffer_cache_type;
// -- constructors, destructors, and assignment operators --------------------
datagram_transport(udp_datagram_socket handle, factory_type factory)
: super(handle, std::move(factory)) {
// nop
}
// -- public member functions ------------------------------------------------
error init(endpoint_manager& manager) override {
CAF_LOG_TRACE("");
if (auto err = super::init(manager))
return err;
prepare_next_read();
return none;
}
bool handle_read_event(endpoint_manager&) override {
CAF_LOG_TRACE(CAF_ARG(this->handle_.id));
auto ret = read(this->handle_, make_span(this->read_buf_));
if (auto res = get_if<std::pair<size_t, ip_endpoint>>(&ret)) {
auto num_bytes = res->first;
CAF_LOG_DEBUG("received " << num_bytes << " bytes");
auto ep = res->second;
this->read_buf_.resize(num_bytes);
this->next_layer_.handle_data(*this, make_span(this->read_buf_),
std::move(ep));
prepare_next_read();
} else {
auto err = get<sec>(ret);
CAF_LOG_DEBUG("send failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
return false;
}
return true;
}
bool handle_write_event(endpoint_manager& manager) override {
CAF_LOG_TRACE(CAF_ARG(this->handle_.id)
<< CAF_ARG2("queue-size", packet_queue_.size()));
// Try to write leftover data.
write_some();
// Get new data from parent.
for (auto msg = manager.next_message(); msg != nullptr;
msg = manager.next_message()) {
this->next_layer_.write_message(*this, std::move(msg));
}
// Write prepared data.
return write_some();
}
// TODO: remove this function. `resolve` should add workers when needed.
error add_new_worker(node_id node, id_type id) {
auto worker = this->next_layer_.add_new_worker(*this, node, id);
if (!worker)
return worker.error();
return none;
}
void write_packet(id_type id, span<buffer_type*> buffers) override {
CAF_LOG_TRACE("");
CAF_ASSERT(!buffers.empty());
if (packet_queue_.empty())
this->manager().register_writing();
// By convention, the first buffer is a header buffer. Every other buffer is
// a payload buffer.
packet_queue_.emplace_back(id, buffers);
}
/// Helper struct for managing outgoing packets
struct packet {
id_type id;
buffer_cache_type bytes;
size_t size;
packet(id_type id, span<buffer_type*> bufs) : id(id) {
size = 0;
for (auto buf : bufs) {
size += buf->size();
bytes.emplace_back(std::move(*buf));
}
}
std::vector<std::vector<byte>*> get_buffer_ptrs() {
std::vector<std::vector<byte>*> ptrs;
for (auto& buf : bytes)
ptrs.emplace_back(&buf);
return ptrs;
}
};
private:
// -- utility functions ------------------------------------------------------
void prepare_next_read() {
this->read_buf_.resize(max_datagram_size);
}
bool write_some() {
CAF_LOG_TRACE(CAF_ARG2("handle", this->handle_.id));
// Helper function to sort empty buffers back into the right caches.
auto recycle = [&]() {
auto& front = packet_queue_.front();
auto& bufs = front.bytes;
auto it = bufs.begin();
if (this->header_bufs_.size() < this->header_bufs_.capacity()) {
it->clear();
this->header_bufs_.emplace_back(std::move(*it++));
}
for (; it != bufs.end()
&& this->payload_bufs_.size() < this->payload_bufs_.capacity();
++it) {
it->clear();
this->payload_bufs_.emplace_back(std::move(*it));
}
packet_queue_.pop_front();
};
// Write as many bytes as possible.
while (!packet_queue_.empty()) {
auto& packet = packet_queue_.front();
auto ptrs = packet.get_buffer_ptrs();
auto write_ret = write(this->handle_, make_span(ptrs), packet.id);
if (auto num_bytes = get_if<size_t>(&write_ret)) {
CAF_LOG_DEBUG(CAF_ARG(this->handle_.id) << CAF_ARG(*num_bytes));
CAF_LOG_WARNING_IF(*num_bytes < packet.size,
"packet was not sent completely");
recycle();
} else {
auto err = get<sec>(write_ret);
if (err != sec::unavailable_or_would_block) {
CAF_LOG_ERROR("write failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
return false;
}
CAF_LOG_DEBUG("write returned `unavailable_or_would_block`");
return true;
}
}
return false;
}
std::deque<packet> packet_queue_;
};
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstddef>
// -- hard-coded default values for various CAF options ------------------------
namespace caf::defaults::middleman {
/// Maximum number of cached buffers for sending payloads.
extern const size_t max_payload_buffers;
/// Maximum number of cached buffers for sending headers.
extern const size_t max_header_buffers;
} // namespace caf::defaults::middleman
......@@ -25,13 +25,13 @@
#include "caf/net/stream_socket.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/policy/scribe.hpp"
#include "caf/send.hpp"
#include "caf/stream_transport.hpp"
namespace caf {
namespace policy {
/// A doorman accepts TCP connections and creates scribes to handle them.
/// A doorman accepts TCP connections and creates stream_transports to handle
/// them.
class doorman {
public:
doorman(net::tcp_accept_socket acceptor) : acceptor_(acceptor) {
......@@ -96,5 +96,4 @@ public:
}
};
} // namespace policy
} // namespace caf
......@@ -23,17 +23,18 @@
#include <memory>
#include "caf/actor.hpp"
#include "caf/actor_clock.hpp"
#include "caf/byte.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive/drr_queue.hpp"
#include "caf/intrusive/fifo_inbox.hpp"
#include "caf/intrusive/singly_linked.hpp"
#include "caf/mailbox_element.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// Manages a communication endpoint.
class endpoint_manager : public socket_manager {
......@@ -49,71 +50,6 @@ public:
using serialize_fun_type = maybe_buffer (*)(actor_system&,
const type_erased_tuple&);
struct event : intrusive::singly_linked<event> {
struct resolve_request {
std::string path;
actor listener;
};
struct timeout {
atom_value type;
uint64_t id;
};
event(std::string path, actor listener);
event(atom_value type, uint64_t id);
/// Either contains a string for `resolve` requests or an `atom_value`
variant<resolve_request, timeout> value;
};
struct event_policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = event;
using unique_pointer = std::unique_ptr<event>;
using queue_type = intrusive::drr_queue<event_policy>;
task_size_type task_size(const event&) const noexcept {
return 1;
}
};
using event_queue_type = intrusive::fifo_inbox<event_policy>;
struct message : intrusive::singly_linked<message> {
/// Original message to a remote actor.
mailbox_element_ptr msg;
/// Serialized representation of of `msg->content()`.
std::vector<byte> payload;
message(mailbox_element_ptr msg, std::vector<byte> payload);
};
struct message_policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = message;
using unique_pointer = std::unique_ptr<message>;
using queue_type = intrusive::drr_queue<message_policy>;
task_size_type task_size(const message& x) const noexcept {
return x.payload.size();
}
};
using message_queue_type = intrusive::fifo_inbox<message_policy>;
// -- constructors, destructors, and assignment operators --------------------
endpoint_manager(socket handle, const multiplexer_ptr& parent,
......@@ -127,15 +63,22 @@ public:
return sys_;
}
std::unique_ptr<message> next_message();
endpoint_manager_queue::message_ptr next_message();
// -- event management -------------------------------------------------------
/// Resolves a path to a remote actor.
void resolve(std::string path, actor listener);
void resolve(uri locator, actor listener);
/// Enqueues a message to the endpoint.
void enqueue(mailbox_element_ptr msg, std::vector<byte> payload);
void enqueue(mailbox_element_ptr msg, strong_actor_ptr receiver,
std::vector<byte> payload);
/// Enqueues an event to the endpoint.
template <class... Ts>
void enqueue_event(Ts&&... xs) {
enqueue(new endpoint_manager_queue::event(std::forward<Ts>(xs)...));
}
// -- pure virtual member functions ------------------------------------------
......@@ -146,17 +89,18 @@ public:
virtual serialize_fun_type serialize_fun() const noexcept = 0;
protected:
bool enqueue(endpoint_manager_queue::element* ptr);
/// Points to the hosting actor system.
actor_system& sys_;
/// Stores control events.
event_queue_type events_;
/// Stores control events and outbound messages.
endpoint_manager_queue::type queue_;
/// Stores outbound messages.
message_queue_type messages_;
/// Stores a proxy for interacting with the actor clock.
actor timeout_proxy_;
};
using endpoint_manager_ptr = intrusive_ptr<endpoint_manager>;
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -18,12 +18,16 @@
#pragma once
#include "caf/abstract_actor.hpp"
#include "caf/actor_cast.hpp"
#include "caf/actor_system.hpp"
#include "caf/atom.hpp"
#include "caf/detail/overload.hpp"
#include "caf/net/endpoint_manager.hpp"
namespace caf {
namespace net {
namespace caf::net {
template <class Transport, class Application>
template <class Transport>
class endpoint_manager_impl : public endpoint_manager {
public:
// -- member types -----------------------------------------------------------
......@@ -32,15 +36,13 @@ public:
using transport_type = Transport;
using application_type = Application;
using application_type = typename transport_type::application_type;
// -- constructors, destructors, and assignment operators --------------------
endpoint_manager_impl(const multiplexer_ptr& parent, actor_system& sys,
Transport trans, Application app)
: super(trans.handle(), parent, sys),
transport_(std::move(trans)),
application_(std::move(app)) {
Transport trans)
: super(trans.handle(), parent, sys), transport_(std::move(trans)) {
// nop
}
......@@ -54,13 +56,26 @@ public:
return transport_;
}
application_type& application() {
return application_;
endpoint_manager_impl& manager() {
return *this;
}
// -- timeout management -----------------------------------------------------
template <class... Ts>
uint64_t set_timeout(actor_clock::time_point tp, atom_value type,
Ts&&... xs) {
auto act = actor_cast<abstract_actor*>(timeout_proxy_);
CAF_ASSERT(act != nullptr);
sys_.clock().set_multi_timeout(tp, act, type, next_timeout_id_);
transport_.set_timeout(next_timeout_id_, std::forward<Ts>(xs)...);
return next_timeout_id_++;
}
// -- interface functions ----------------------------------------------------
error init() override {
this->register_reading();
return transport_.init(*this);
}
......@@ -69,29 +84,40 @@ public:
}
bool handle_write_event() override {
if (!this->events_.blocked() && !this->events_.empty()) {
if (!this->queue_.blocked()) {
this->queue_.fetch_more();
auto& q = std::get<0>(this->queue_.queue().queues());
do {
this->events_.fetch_more();
auto& q = this->events_.queue();
q.inc_deficit(q.total_task_size());
for (auto ptr = q.next(); ptr != nullptr; ptr = q.next()) {
using timeout = endpoint_manager::event::timeout;
using resolve_request = endpoint_manager::event::resolve_request;
if (auto rr = get_if<resolve_request>(&ptr->value)) {
transport_.resolve(*this, std::move(rr->path),
std::move(rr->listener));
} else {
auto& t = get<timeout>(ptr->value);
transport_.timeout(*this, t.type, t.id);
}
auto f = detail::make_overload(
[&](endpoint_manager_queue::event::resolve_request& x) {
transport_.resolve(*this, x.locator, x.listener);
},
[&](endpoint_manager_queue::event::new_proxy& x) {
transport_.new_proxy(*this, x.peer, x.id);
},
[&](endpoint_manager_queue::event::local_actor_down& x) {
transport_.local_actor_down(*this, x.observing_peer, x.id,
std::move(x.reason));
},
[&](endpoint_manager_queue::event::timeout& x) {
transport_.timeout(*this, x.type, x.id);
});
visit(f, ptr->value);
}
} while (!this->events_.try_block());
} while (!q.empty());
}
return transport_.handle_write_event(*this);
if (!transport_.handle_write_event(*this)) {
if (this->queue_.blocked())
return false;
return !(this->queue_.empty() && this->queue_.try_block());
}
return true;
}
void handle_error(sec code) override {
transport_.handle_error(application_, code);
transport_.handle_error(code);
}
serialize_fun_type serialize_fun() const noexcept override {
......@@ -100,8 +126,9 @@ public:
private:
transport_type transport_;
application_type application_;
/// Stores the id for the next timeout.
uint64_t next_timeout_id_;
};
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/actor.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive/drr_queue.hpp"
#include "caf/intrusive/fifo_inbox.hpp"
#include "caf/intrusive/singly_linked.hpp"
#include "caf/intrusive/wdrr_fixed_multiplexed_queue.hpp"
#include "caf/mailbox_element.hpp"
#include "caf/unit.hpp"
#include "caf/variant.hpp"
namespace caf::net {
class endpoint_manager_queue {
public:
enum class element_type { event, message };
class element : public intrusive::singly_linked<element> {
public:
explicit element(element_type tag) : tag_(tag) {
// nop
}
virtual ~element();
virtual size_t task_size() const noexcept = 0;
element_type tag() const noexcept {
return tag_;
}
private:
element_type tag_;
};
using element_ptr = std::unique_ptr<element>;
class event final : public element {
public:
struct resolve_request {
uri locator;
actor listener;
};
struct new_proxy {
node_id peer;
actor_id id;
};
struct local_actor_down {
node_id observing_peer;
actor_id id;
error reason;
};
struct timeout {
atom_value type;
uint64_t id;
};
event(uri locator, actor listener);
event(node_id peer, actor_id proxy_id);
event(node_id observing_peer, actor_id local_actor_id, error reason);
event(atom_value type, uint64_t id);
~event() override;
size_t task_size() const noexcept override;
/// Holds the event data.
variant<resolve_request, new_proxy, local_actor_down, timeout> value;
};
using event_ptr = std::unique_ptr<event>;
struct event_policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = event;
using unique_pointer = event_ptr;
using queue_type = intrusive::drr_queue<event_policy>;
constexpr event_policy(unit_t) {
// nop
}
task_size_type task_size(const event&) const noexcept {
return 1;
}
};
class message : public element {
public:
/// Original message to a remote actor.
mailbox_element_ptr msg;
/// ID of the receiving actor.
strong_actor_ptr receiver;
/// Serialized representation of of `msg->content()`.
std::vector<byte> payload;
message(mailbox_element_ptr msg, strong_actor_ptr receiver,
std::vector<byte> payload);
~message() override;
size_t task_size() const noexcept override;
};
using message_ptr = std::unique_ptr<message>;
struct message_policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = message;
using unique_pointer = message_ptr;
using queue_type = intrusive::drr_queue<message_policy>;
constexpr message_policy(unit_t) {
// nop
}
static task_size_type task_size(const message& x) noexcept {
// Return at least 1 if the payload is empty.
return x.payload.size() + static_cast<task_size_type>(x.payload.empty());
}
};
struct categorized {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = element;
using unique_pointer = element_ptr;
constexpr categorized(unit_t) {
// nop
}
template <class Queue>
deficit_type quantum(const Queue&, deficit_type x) const noexcept {
return x;
}
size_t id_of(const element& x) const noexcept {
return static_cast<size_t>(x.tag());
}
};
struct policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = element;
using unique_pointer = std::unique_ptr<element>;
using queue_type = intrusive::wdrr_fixed_multiplexed_queue<
categorized, event_policy::queue_type, message_policy::queue_type>;
task_size_type task_size(const message& x) const noexcept {
return x.task_size();
}
};
using type = intrusive::fifo_inbox<policy>;
};
} // namespace caf::net
......@@ -22,22 +22,47 @@
#include "caf/intrusive_ptr.hpp"
namespace caf {
namespace net {
namespace caf::net {
// -- templates ----------------------------------------------------------------
template <class Application>
class stream_transport;
template <class Factory>
class datagram_transport;
template <class Application, class IdType = unit_t>
class transport_worker;
template <class Transport, class IdType = unit_t>
class transport_worker_dispatcher;
// -- classes ------------------------------------------------------------------
class endpoint_manager;
class middleman;
class middleman_backend;
class multiplexer;
class socket_manager;
// -- structs ------------------------------------------------------------------
struct network_socket;
struct pipe_socket;
struct socket;
struct stream_socket;
struct tcp_accept_socket;
struct tcp_stream_socket;
struct datagram_socket;
struct udp_datagram_socket;
using socket_manager_ptr = intrusive_ptr<socket_manager>;
// -- smart pointers -----------------------------------------------------------
using endpoint_manager_ptr = intrusive_ptr<endpoint_manager>;
using middleman_backend_ptr = std::unique_ptr<middleman_backend>;
using multiplexer_ptr = std::shared_ptr<multiplexer>;
using socket_manager_ptr = intrusive_ptr<socket_manager>;
using weak_multiplexer_ptr = std::weak_ptr<multiplexer>;
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -20,8 +20,7 @@
#include "caf/fwd.hpp"
namespace caf {
namespace net {
namespace caf::net {
struct this_host {
/// Initializes the network subsystem.
......@@ -31,5 +30,4 @@ struct this_host {
static void cleanup();
};
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -5,7 +5,7 @@
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
......@@ -23,16 +23,22 @@
#include "caf/fwd.hpp"
namespace caf {
namespace net {
namespace ip {
namespace caf::net::ip {
/// Returns all IP addresses of to `host` (if any).
/// Returns all IP addresses of `host` (if any).
std::vector<ip_address> resolve(string_view host);
/// Returns all IP addresses of `host` (if any).
std::vector<ip_address> resolve(ip_address host);
/// Returns the IP addresses for a local endpoint, which is either an address,
/// an interface name, or the string "localhost".
std::vector<ip_address> local_addresses(string_view host);
/// Returns the IP addresses for a local endpoint address.
std::vector<ip_address> local_addresses(ip_address host);
/// Returns the hostname of this device.
std::string hostname();
} // namespace ip
} // namespace net
} // namespace caf
} // namespace caf::net::ip
......@@ -22,16 +22,13 @@
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
namespace caf {
namespace net {
namespace caf::net {
template <class Transport, class Application>
template <class Transport>
endpoint_manager_ptr make_endpoint_manager(const multiplexer_ptr& mpx,
actor_system& sys, Transport trans,
Application app) {
using impl = endpoint_manager_impl<Transport, Application>;
return make_counted<impl>(mpx, sys, std::move(trans), std::move(app));
actor_system& sys, Transport trans) {
using impl = endpoint_manager_impl<Transport>;
return make_counted<impl>(mpx, sys, std::move(trans));
}
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <thread>
#include "caf/actor_system.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
namespace caf::net {
class middleman : public actor_system::module {
public:
// -- member types -----------------------------------------------------------
using module = actor_system::module;
using module_ptr = actor_system::module_ptr;
using middleman_backend_list = std::vector<middleman_backend_ptr>;
// -- constructors, destructors, and assignment operators --------------------
~middleman() override;
// -- interface functions ----------------------------------------------------
void start() override;
void stop() override;
void init(actor_system_config&) override;
id_t id() const override;
void* subtype_ptr() override;
// -- factory functions ------------------------------------------------------
template <class... Ts>
static module* make(actor_system& sys, detail::type_list<Ts...> token) {
std::unique_ptr<middleman> result{new middleman(sys)};
if (sizeof...(Ts) > 0) {
result->backends_.reserve(sizeof...(Ts));
create_backends(*result, token);
}
return result.release();
}
// -- remoting ---------------------------------------------------------------
/// Resolves a path to a remote actor.
void resolve(const uri& locator, const actor& listener);
// -- properties -------------------------------------------------------------
actor_system& system() {
return sys_;
}
const actor_system_config& config() const noexcept {
return sys_.config();
}
const multiplexer_ptr& mpx() const noexcept {
return mpx_;
}
middleman_backend* backend(string_view scheme) const noexcept;
private:
// -- constructors, destructors, and assignment operators --------------------
explicit middleman(actor_system& sys);
// -- utility functions ------------------------------------------------------
static void create_backends(middleman&, detail::type_list<>) {
// End of recursion.
}
template <class T, class... Ts>
static void create_backends(middleman& mm, detail::type_list<T, Ts...>) {
mm.backends_.emplace_back(new T(mm));
create_backends(mm, detail::type_list<Ts...>{});
}
// -- member variables -------------------------------------------------------
/// Points to the parent system.
actor_system& sys_;
/// Stores the global socket I/O multiplexer.
multiplexer_ptr mpx_;
/// Stores all available backends for managing peers.
middleman_backend_list backends_;
/// Runs the multiplexer's event loop
std::thread mpx_thread_;
};
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <string>
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/proxy_registry.hpp"
namespace caf::net {
/// Technology-specific backend for connecting to and managing peer
/// connections.
/// @relates middleman
class middleman_backend : public proxy_registry::backend {
public:
// -- constructors, destructors, and assignment operators --------------------
middleman_backend(std::string id);
virtual ~middleman_backend();
// -- interface functions ----------------------------------------------------
/// Initializes the backend.
virtual error init() = 0;
/// @returns The endpoint manager for `peer` on success, `nullptr` otherwise.
virtual endpoint_manager_ptr peer(const node_id& id) = 0;
/// Resolves a path to a remote actor.
virtual void resolve(const uri& locator, const actor& listener) = 0;
// -- properties -------------------------------------------------------------
const std::string& id() const noexcept {
return id_;
}
private:
/// Stores the technology-specific identifier.
std::string id_;
};
/// @relates middleman_backend
using middleman_backend_ptr = std::unique_ptr<middleman_backend>;
} // namespace caf::net
......@@ -34,8 +34,7 @@ struct pollfd;
} // extern "C"
namespace caf {
namespace net {
namespace caf::net {
/// Multiplexes any number of ::socket_manager objects with a ::socket.
class multiplexer : public std::enable_shared_from_this<multiplexer> {
......@@ -64,9 +63,13 @@ public:
// -- thread-safe signaling --------------------------------------------------
/// Causes the multiplexer to update its event bitmask for `mgr`.
/// Registers `mgr` for read events.
/// @thread-safe
void update(const socket_manager_ptr& mgr);
void register_reading(const socket_manager_ptr& mgr);
/// Registers `mgr` for write events.
/// @thread-safe
void register_writing(const socket_manager_ptr& mgr);
/// Closes the pipe for signaling updates to the multiplexer. After closing
/// the pipe, calls to `update` no longer have any effect.
......@@ -79,21 +82,25 @@ public:
/// ready as a result.
bool poll_once(bool blocking);
/// Sets the thread ID to `std::this_thread::id()`.
void set_thread_id();
/// Polls until no socket event handler remains.
void run();
/// Processes all updates on the socket managers.
void handle_updates();
protected:
// -- utility functions ------------------------------------------------------
/// Handles an I/O event on given manager.
void handle(const socket_manager_ptr& mgr, int mask);
short handle(const socket_manager_ptr& mgr, short events, short revents);
/// Adds a new socket manager to the pollset.
void add(socket_manager_ptr mgr);
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater.
void write_to_pipe(uint8_t opcode, const socket_manager_ptr& mgr);
// -- member variables -------------------------------------------------------
/// Bookkeeping data for managed sockets.
......@@ -103,9 +110,6 @@ protected:
/// order as their sockets appear in `pollset_`.
manager_list managers_;
/// Managers that updated their event mask and need updating.
manager_list dirty_managers_;
/// Stores the ID of the thread this multiplexer is running in. Set when
/// calling `init()`.
std::thread::id tid_;
......@@ -123,5 +127,4 @@ using multiplexer_ptr = std::shared_ptr<multiplexer>;
/// @relates multiplexer
using weak_multiplexer_ptr = std::weak_ptr<multiplexer>;
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -25,22 +25,16 @@
#include "caf/config.hpp"
#include "caf/fwd.hpp"
#include "caf/net/abstract_socket.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_id.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// A bidirectional network communication endpoint.
struct network_socket : abstract_socket<network_socket> {
using super = abstract_socket<network_socket>;
struct network_socket : socket {
using super = socket;
using super::super;
constexpr operator socket() const noexcept {
return socket{id};
}
};
/// Enables or disables `SIGPIPE` events from `x`.
......@@ -88,5 +82,4 @@ void shutdown_write(network_socket x);
/// @relates network_socket
void shutdown(network_socket x);
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -18,8 +18,9 @@
#pragma once
namespace caf {
namespace net {
#include <string>
namespace caf::net {
/// Values for representing bitmask of I/O operations.
enum class operation {
......@@ -45,5 +46,6 @@ constexpr operation operator~(operation x) {
return static_cast<operation>(~static_cast<int>(x));
}
} // namespace net
} // namespace caf
std::string to_string(operation x);
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <vector>
#include "caf/byte.hpp"
#include "caf/net/fwd.hpp"
#include "caf/span.hpp"
namespace caf::net {
/// Implements an interface for packet writing in application-layers.
class packet_writer {
public:
using buffer_type = std::vector<byte>;
virtual ~packet_writer();
/// Returns a buffer for writing header information.
virtual buffer_type next_header_buffer() = 0;
/// Returns a buffer for writing payload content.
virtual buffer_type next_payload_buffer() = 0;
/// Convenience function to write a packet consisting of multiple buffers.
/// @param buffers all buffers for the packet. The first buffer is a header
/// buffer, the other buffers are payload buffer.
/// @warning this function takes ownership of `buffers`.
template <class... Ts>
void write_packet(Ts&... buffers) {
buffer_type* bufs[] = {&buffers...};
write_impl(make_span(bufs, sizeof...(Ts)));
}
protected:
/// Implementing function for `write_packet`.
/// @param buffers a `span` containing all buffers of a packet.
virtual void write_impl(span<buffer_type*> buffers) = 0;
};
} // namespace caf::net
......@@ -18,42 +18,67 @@
#pragma once
namespace caf {
namespace net {
#include "caf/net/packet_writer.hpp"
#include "caf/byte.hpp"
#include "caf/span.hpp"
namespace caf::net {
/// Implements the interface for transport and application policies and
/// dispatches member functions either to `decorator` or `parent`.
/// dispatches member functions either to `object` or `parent`.
template <class Object, class Parent>
class write_packet_decorator {
class packet_writer_decorator final : public packet_writer {
public:
dispatcher(Object& object, Parent& parent)
// -- member types -----------------------------------------------------------
using transport_type = typename Parent::transport_type;
using application_type = typename Parent::application_type;
// -- constructors, destructors, and assignment operators --------------------
packet_writer_decorator(Object& object, Parent& parent)
: object_(object), parent_(parent) {
// nop
}
template <class Header>
void write_packet(const Header& header, span<const byte> payload) {
object_.write_packet(parent_, header, payload);
}
// -- properties -------------------------------------------------------------
actor_system& system() {
parent_.system();
return parent_.system();
}
void cancel_timeout(atom_value type, uint64_t id) {
parent_.cancel_timeout(type, id);
transport_type& transport() {
return parent_.transport();
}
void set_timeout(timestamp tout, atom_value type, uint64_t id) {
parent_.set_timeout(tout, type, id);
endpoint_manager& manager() {
return parent_.manager();
}
buffer_type next_header_buffer() override {
return transport().next_header_buffer();
}
buffer_type next_payload_buffer() override {
return transport().next_payload_buffer();
}
// -- member functions -------------------------------------------------------
void cancel_timeout(atom_value type, uint64_t id) {
parent_.cancel_timeout(type, id);
}
typename parent::transport_type& transport() {
return parent_.transport_;
template <class... Ts>
uint64_t set_timeout(timestamp tout, atom_value type, Ts&&... xs) {
return parent_.set_timeout(tout, type, std::forward<Ts>(xs)...);
}
typename parent::application_type& application() {
return parent_.application_;
protected:
void write_impl(span<buffer_type*> buffers) override {
parent_.write_packet(object_.id(), buffers);
}
private:
......@@ -62,10 +87,9 @@ private:
};
template <class Object, class Parent>
write_packet_decorator<Object, Parent>
make_write_packet_decorator(Object& object, Parent& parent) {
packet_writer_decorator<Object, Parent>
make_packet_writer_decorator(Object& object, Parent& parent) {
return {object, parent};
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -23,22 +23,16 @@
#include <utility>
#include "caf/fwd.hpp"
#include "caf/net/abstract_socket.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_id.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// A unidirectional communication endpoint for inter-process communication.
struct pipe_socket : abstract_socket<pipe_socket> {
using super = abstract_socket<pipe_socket>;
struct pipe_socket : socket {
using super = socket;
using super::super;
constexpr operator socket() const noexcept {
return socket{id};
}
};
/// Creates two connected sockets. The first socket is the read handle and the
......@@ -68,5 +62,4 @@ variant<size_t, sec> read(pipe_socket x, span<byte>);
variant<size_t, sec>
check_pipe_socket_io_res(std::make_signed<size_t>::type res);
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -20,13 +20,13 @@
#include <array>
#include <cstdint>
#include <mutex>
#include "caf/byte.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/socket_manager.hpp"
namespace caf {
namespace net {
namespace caf::net {
class pollset_updater : public socket_manager {
public:
......@@ -34,9 +34,11 @@ public:
using super = socket_manager;
using msg_buf = std::array<byte, sizeof(intptr_t) + 1>;
// -- constructors, destructors, and assignment operators --------------------
pollset_updater(pipe_socket read_handle, multiplexer_ptr parent);
pollset_updater(pipe_socket read_handle, const multiplexer_ptr& parent);
~pollset_updater() override;
......@@ -56,9 +58,8 @@ public:
void handle_error(sec code) override;
private:
std::array<byte, sizeof(intptr_t)> buf_;
msg_buf buf_;
size_t buf_size_;
};
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -24,8 +24,7 @@
#include "caf/config.hpp"
namespace caf {
namespace net {
namespace caf::net {
enum class receive_policy_flag : unsigned { at_least, at_most, exactly };
......@@ -57,5 +56,4 @@ public:
}
};
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -23,21 +23,41 @@
#include <type_traits>
#include "caf/config.hpp"
#include "caf/detail/comparable.hpp"
#include "caf/fwd.hpp"
#include "caf/net/abstract_socket.hpp"
#include "caf/net/socket_id.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// An internal endpoint for sending or receiving data. Can be either a
/// ::network_socket, ::pipe_socket, ::stream_socket, or ::datagram_socket.
struct socket : abstract_socket<socket> {
using super = abstract_socket<socket>;
struct socket : detail::comparable<socket> {
socket_id id;
using super::super;
constexpr socket() noexcept : id(invalid_socket_id) {
// nop
}
constexpr explicit socket(socket_id id) noexcept : id(id) {
// nop
}
constexpr socket(const socket& other) noexcept = default;
socket& operator=(const socket& other) noexcept = default;
constexpr signed_socket_id compare(socket other) const noexcept {
return static_cast<signed_socket_id>(id)
- static_cast<signed_socket_id>(other.id);
}
};
/// @relates socket
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, socket& x) {
return f(x.id);
}
/// Denotes the invalid socket.
constexpr auto invalid_socket = socket{invalid_socket_id};
......@@ -68,5 +88,4 @@ error child_process_inherit(socket x, bool new_value);
/// @relates socket
error nonblocking(socket x, bool new_value);
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -18,32 +18,56 @@
#pragma once
#include "caf/net/socket.hpp"
#include "caf/net/socket_id.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// Closes the guarded socket when destroyed.
template <class Socket>
class socket_guard {
public:
explicit socket_guard(Socket sock) : sock_(sock) {
socket_guard() noexcept : sock_(invalid_socket_id) {
// nop
}
explicit socket_guard(Socket sock) noexcept : sock_(sock) {
// nop
}
socket_guard(socket_guard&& other) noexcept : sock_(other.release()) {
// nop
}
socket_guard(const socket_guard&) = delete;
socket_guard& operator=(socket_guard&& other) noexcept {
reset(other.release());
return *this;
}
socket_guard& operator=(const socket_guard&) = delete;
~socket_guard() {
if (sock_.id != invalid_socket_id) {
net::close(sock_);
sock_.id = invalid_socket_id;
}
if (sock_.id != invalid_socket_id)
close(sock_);
}
Socket release() {
void reset(Socket x) noexcept {
if (sock_.id != invalid_socket_id)
close(sock_);
sock_ = x;
}
Socket release() noexcept {
auto sock = sock_;
sock_.id = invalid_socket_id;
return sock;
}
Socket socket() const noexcept {
return sock_;
}
private:
Socket sock_;
};
......@@ -53,5 +77,4 @@ socket_guard<Socket> make_socket_guard(Socket sock) {
return socket_guard<Socket>{sock};
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -20,11 +20,11 @@
#include <cstddef>
#include <limits>
#include <type_traits>
#include "caf/config.hpp"
namespace caf {
namespace net {
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -46,5 +46,8 @@ constexpr socket_id invalid_socket_id = -1;
#endif // CAF_WINDOWS
} // namespace net
} // namespace caf
/// Signed counterpart of `socket_id`.
/// @relates socket
using signed_socket_id = std::make_signed<socket_id>::type;
} // namespace caf::net
......@@ -18,8 +18,6 @@
#pragma once
#include <atomic>
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
......@@ -27,8 +25,7 @@
#include "caf/net/socket.hpp"
#include "caf/ref_counted.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// Manages the lifetime of a single socket and handles any I/O events on it.
class socket_manager : public ref_counted {
......@@ -59,21 +56,26 @@ public:
}
/// Returns registered operations (read, write, or both).
operation mask() const noexcept;
operation mask() const noexcept {
return mask_;
}
/// Adds given flag(s) to the event mask and updates the parent on success.
/// Adds given flag(s) to the event mask.
/// @returns `false` if `mask() | flag == mask()`, `true` otherwise.
/// @pre `has_parent()`
/// @pre `flag != operation::none`
bool mask_add(operation flag) noexcept;
/// Deletes given flag(s) from the event mask and updates the parent on
/// success.
/// Tries to clear given flag(s) from the event mask.
/// @returns `false` if `mask() & ~flag == mask()`, `true` otherwise.
/// @pre `has_parent()`
/// @pre `flag != operation::none`
bool mask_del(operation flag) noexcept;
// -- event loop management --------------------------------------------------
void register_reading();
void register_writing();
// -- pure virtual member functions ------------------------------------------
/// Called whenever the socket received new data.
......@@ -91,12 +93,11 @@ protected:
socket handle_;
std::atomic<operation> mask_;
operation mask_;
weak_multiplexer_ptr parent_;
};
using socket_manager_ptr = intrusive_ptr<socket_manager>;
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -21,23 +21,14 @@
#include "caf/fwd.hpp"
#include "caf/net/network_socket.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// A connection-oriented network communication endpoint for bidirectional byte
/// streams.
struct stream_socket : abstract_socket<stream_socket> {
using super = abstract_socket<stream_socket>;
struct stream_socket : network_socket {
using super = network_socket;
using super::super;
constexpr operator socket() const noexcept {
return socket{id};
}
constexpr operator network_socket() const noexcept {
return network_socket{id};
}
};
/// Creates two connected sockets to mimic network communication (usually for
......@@ -56,7 +47,6 @@ error nodelay(stream_socket x, bool new_value);
/// Receives data from `x`.
/// @param x Connected endpoint.
/// @param buf Points to destination buffer.
/// @param buf_size Specifies the maximum size of the buffer in bytes.
/// @returns The number of received bytes on success, an error code otherwise.
/// @relates stream_socket
/// @post either the result is a `sec` or a positive (non-zero) integer
......@@ -65,17 +55,26 @@ variant<size_t, sec> read(stream_socket x, span<byte> buf);
/// Transmits data from `x` to its peer.
/// @param x Connected endpoint.
/// @param buf Points to the message to send.
/// @param buf_size Specifies the size of the buffer in bytes.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates stream_socket
/// @post either the result is a `sec` or a positive (non-zero) integer
variant<size_t, sec> write(stream_socket x, span<const byte> buf);
/// Transmits data from `x` to its peer.
/// @param x Connected endpoint.
/// @param bufs Points to the message to send, scattered across up to 10
/// buffers.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates stream_socket
/// @post either the result is a `sec` or a positive (non-zero) integer
/// @pre `bufs.size() < 10`
variant<size_t, sec> write(stream_socket x,
std::initializer_list<span<const byte>> bufs);
/// Converts the result from I/O operation on a ::stream_socket to either an
/// error code or a non-zero positive integer.
/// @relates stream_socket
variant<size_t, sec>
check_stream_socket_io_res(std::make_signed<size_t>::type res);
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <deque>
#include <vector>
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/transport_base.hpp"
#include "caf/net/transport_worker.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
namespace caf::net {
template <class Application>
using stream_transport_base = transport_base<
stream_transport<Application>, transport_worker<Application>, stream_socket,
Application, unit_t>;
/// Implements a stream_transport that manages a stream socket.
template <class Application>
class stream_transport : public stream_transport_base<Application> {
public:
// -- member types -----------------------------------------------------------
using application_type = Application;
using worker_type = transport_worker<application_type>;
using super = stream_transport_base<application_type>;
using id_type = typename super::id_type;
using buffer_type = typename super::buffer_type;
using write_queue_type = std::deque<std::pair<bool, buffer_type>>;
// -- constructors, destructors, and assignment operators --------------------
stream_transport(stream_socket handle, application_type application)
: super(handle, std::move(application)),
written_(0),
read_threshold_(1024),
collected_(0),
max_(1024),
rd_flag_(net::receive_policy_flag::exactly) {
// nop
}
// -- member functions -------------------------------------------------------
bool handle_read_event(endpoint_manager&) override {
auto buf = this->read_buf_.data() + this->collected_;
size_t len = this->read_threshold_ - this->collected_;
CAF_LOG_TRACE(CAF_ARG2("handle", this->handle().id)
<< CAF_ARG2("missing", len));
auto ret = read(this->handle_, make_span(buf, len));
// Update state.
if (auto num_bytes = get_if<size_t>(&ret)) {
CAF_LOG_DEBUG(CAF_ARG(len)
<< CAF_ARG(this->handle_.id) << CAF_ARG(*num_bytes));
this->collected_ += *num_bytes;
if (this->collected_ >= this->read_threshold_) {
if (auto err = this->next_layer_.handle_data(*this, this->read_buf_)) {
CAF_LOG_ERROR("handle_data failed: " << CAF_ARG(err));
return false;
}
this->prepare_next_read();
}
} else {
auto err = get<sec>(ret);
if (err != sec::unavailable_or_would_block) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
return false;
}
}
return true;
}
bool handle_write_event(endpoint_manager& parent) override {
CAF_LOG_TRACE(CAF_ARG2("handle", this->handle().id));
// Try to write leftover data.
write_some();
// Get new data from parent.
// TODO: dont read all messages at once - get one by one.
for (auto msg = parent.next_message(); msg != nullptr;
msg = parent.next_message()) {
this->next_layer_.write_message(*this, std::move(msg));
}
// Write prepared data.
return write_some();
}
void write_packet(id_type, span<buffer_type*> buffers) override {
CAF_LOG_TRACE("");
CAF_ASSERT(!buffers.empty());
if (this->write_queue_.empty())
this->manager().register_writing();
// By convention, the first buffer is a header buffer. Every other buffer is
// a payload buffer.
auto i = buffers.begin();
this->write_queue_.emplace_back(true, std::move(*(*i++)));
while (i != buffers.end())
this->write_queue_.emplace_back(false, std::move(*(*i++)));
}
void configure_read(receive_policy::config cfg) override {
rd_flag_ = cfg.first;
max_ = cfg.second;
prepare_next_read();
}
private:
// -- utility functions ------------------------------------------------------
void prepare_next_read() {
collected_ = 0;
switch (rd_flag_) {
case net::receive_policy_flag::exactly:
if (this->read_buf_.size() != max_)
this->read_buf_.resize(max_);
read_threshold_ = max_;
break;
case net::receive_policy_flag::at_most:
if (this->read_buf_.size() != max_)
this->read_buf_.resize(max_);
read_threshold_ = 1;
break;
case net::receive_policy_flag::at_least: {
// read up to 10% more, but at least allow 100 bytes more
auto max_size = max_ + std::max<size_t>(100, max_ / 10);
if (this->read_buf_.size() != max_size)
this->read_buf_.resize(max_size);
read_threshold_ = max_;
break;
}
}
}
bool write_some() {
CAF_LOG_TRACE(CAF_ARG2("handle", this->handle_.id));
// Helper function to sort empty buffers back into the right caches.
auto recycle = [&]() {
auto& front = this->write_queue_.front();
auto& is_header = front.first;
auto& buf = front.second;
written_ = 0;
buf.clear();
if (is_header) {
if (this->header_bufs_.size() < this->header_bufs_.capacity())
this->header_bufs_.emplace_back(std::move(buf));
} else if (this->payload_bufs_.size() < this->payload_bufs_.capacity()) {
this->payload_bufs_.emplace_back(std::move(buf));
}
write_queue_.pop_front();
};
// Write buffers from the write_queue_ for as long as possible.
while (!write_queue_.empty()) {
auto& buf = write_queue_.front().second;
CAF_ASSERT(!buf.empty());
auto data = buf.data() + written_;
auto len = buf.size() - written_;
auto write_ret = write(this->handle(), make_span(data, len));
if (auto num_bytes = get_if<size_t>(&write_ret)) {
CAF_LOG_DEBUG(CAF_ARG(this->handle_.id) << CAF_ARG(*num_bytes));
written_ += *num_bytes;
if (written_ >= buf.size()) {
recycle();
written_ = 0;
}
} else {
auto err = get<sec>(write_ret);
if (err != sec::unavailable_or_would_block) {
CAF_LOG_DEBUG("send failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
return false;
}
return true;
}
}
return false;
}
write_queue_type write_queue_;
size_t written_;
size_t read_threshold_;
size_t collected_;
size_t max_;
receive_policy_flag rd_flag_;
// TODO implement retries using this member!
// size_t max_consecutive_reads_;
};
} // namespace caf::net
......@@ -18,35 +18,24 @@
#pragma once
#include "caf/ip_endpoint.hpp"
#include "caf/net/abstract_socket.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/uri.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// Represents a TCP acceptr in listening mode.
struct tcp_accept_socket : abstract_socket<tcp_accept_socket> {
using super = abstract_socket<tcp_accept_socket>;
/// Represents a TCP acceptor in listening mode.
struct tcp_accept_socket : network_socket {
using super = network_socket;
using super::super;
constexpr operator socket() const noexcept {
return socket{id};
}
constexpr operator network_socket() const noexcept {
return network_socket{id};
}
};
/// Creates a new TCP socket to accept connections on a given port.
/// @param node The endpoint to listen on and the filter for incoming addresses.
/// Passing the address `0.0.0.0` will accept incoming connection from any host.
/// Passing port 0 lets the OS choose the port.
/// @relates tcp_accept_socket
expected<tcp_accept_socket> make_tcp_accept_socket(ip_endpoint node,
bool reuse_addr = false);
......@@ -54,6 +43,7 @@ expected<tcp_accept_socket> make_tcp_accept_socket(ip_endpoint node,
/// Creates a new TCP socket to accept connections on a given port.
/// @param node The endpoint to listen on and the filter for incoming addresses.
/// Passing the address `0.0.0.0` will accept incoming connection from any host.
/// Passing port 0 lets the OS choose the port.
/// @param reuse_addr Optionally sets the SO_REUSEADDR option on the socket.
/// @relates tcp_accept_socket
expected<tcp_accept_socket>
......@@ -67,5 +57,4 @@ make_tcp_accept_socket(const uri::authority_type& node,
/// @relates tcp_accept_socket
expected<tcp_stream_socket> accept(tcp_accept_socket x);
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -19,46 +19,30 @@
#pragma once
#include "caf/ip_endpoint.hpp"
#include "caf/net/abstract_socket.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/uri.hpp"
namespace caf {
namespace net {
namespace caf::net {
/// Represents a TCP connection.
struct tcp_stream_socket : abstract_socket<tcp_stream_socket> {
using super = abstract_socket<tcp_stream_socket>;
struct tcp_stream_socket : stream_socket {
using super = stream_socket;
using super::super;
constexpr operator socket() const noexcept {
return socket{id};
}
constexpr operator network_socket() const noexcept {
return network_socket{id};
}
constexpr operator stream_socket() const noexcept {
return stream_socket{id};
}
};
/// Creates a `tcp_stream_socket` connected to given remote node.
/// @param node Host and port of the remote node
/// @param node Host and port of the remote node.
/// @returns The connected socket or an error.
/// @relates tcp_stream_socket
expected<tcp_stream_socket> make_connected_tcp_stream_socket(ip_endpoint node);
/// Create a `tcp_stream_socket` connected to `auth`.
/// @param node Host and port of the remote node
/// @param node Host and port of the remote node.
/// @returns The connected socket or an error.
/// @relates tcp_stream_socket
expected<tcp_stream_socket>
make_connected_tcp_stream_socket(const uri::authority_type& node);
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/detail/overload.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/receive_policy.hpp"
namespace caf::net {
/// Implements a base class for transports.
/// @tparam Transport The derived type of the transport implementation.
/// @tparam NextLayer The Following Layer. Either `transport_worker` or
/// `transport_worker_dispatcher`
/// @tparam Handle The type of the related socket_handle.
/// @tparam Application The type of the application used in this stack.
/// @tparam IdType The id type of the derived transport, must match the IdType
/// of `NextLayer`.
template <class Transport, class NextLayer, class Handle, class Application,
class IdType>
class transport_base {
public:
// -- member types -----------------------------------------------------------
using next_layer_type = NextLayer;
using handle_type = Handle;
using transport_type = Transport;
using application_type = Application;
using id_type = IdType;
using buffer_type = std::vector<byte>;
using buffer_cache_type = std::vector<buffer_type>;
// -- constructors, destructors, and assignment operators --------------------
transport_base(handle_type handle, application_type application)
: next_layer_(std::move(application)), handle_(handle), manager_(nullptr) {
// nop
}
// -- properties -------------------------------------------------------------
/// Returns the socket_handle of this transport.
handle_type handle() const noexcept {
return handle_;
}
/// Returns a reference to the `actor_system` of this transport.
/// @pre `init` must be called before calling this getter.
actor_system& system() {
return manager().system();
}
/// Returns a reference to the application of this transport.
application_type& application() {
return next_layer_.application();
}
/// Returns a reference to this transport.
transport_type& transport() {
return *reinterpret_cast<transport_type*>(this);
}
/// Returns a reference to the `endpoint_manager` of this transport.
/// @pre `init` must be called before calling this getter.
endpoint_manager& manager() {
CAF_ASSERT(manager_);
return *manager_;
}
// -- transport member functions ---------------------------------------------
/// Initializes this transport.
/// @param parent The `endpoint_manager` that manages this transport.
/// @returns `error` on failure, none on success.
virtual error init(endpoint_manager& parent) {
CAF_LOG_TRACE("");
manager_ = &parent;
auto& cfg = system().config();
auto max_header_bufs = get_or(cfg, "middleman.max-header-buffers",
defaults::middleman::max_header_buffers);
header_bufs_.reserve(max_header_bufs);
auto max_payload_bufs = get_or(cfg, "middleman.max-payload-buffers",
defaults::middleman::max_payload_buffers);
payload_bufs_.reserve(max_payload_bufs);
if (auto err = next_layer_.init(*this))
return err;
return none;
}
/// Resolves a remote actor using `locator` and sends the resolved actor to
/// listener on success - an `error` otherwise.
/// @param locator The `uri` of the remote actor.
/// @param listener The `actor_handle` which the result should be sent to.
auto resolve(endpoint_manager&, const uri& locator, const actor& listener) {
CAF_LOG_TRACE(CAF_ARG(locator) << CAF_ARG(listener));
auto f = detail::make_overload(
[&](auto& layer) -> decltype(layer.resolve(*this, locator, listener)) {
return layer.resolve(*this, locator, listener);
},
[&](auto& layer) -> decltype(
layer.resolve(*this, locator.path(), listener)) {
return layer.resolve(*this, locator.path(), listener);
});
f(next_layer_);
}
/// Gets called by an actor proxy after creation.
/// @param peer The `node_id` of the remote node.
/// @param id The id of the remote actor.
void new_proxy(endpoint_manager&, const node_id& peer, actor_id id) {
next_layer_.new_proxy(*this, peer, id);
}
/// Notifies the remote endpoint that the local actor is down.
/// @param peer The `node_id` of the remote endpoint.
/// @param id The `actor_id` of the remote actor.
/// @param reason The reason why the local actor has shut down.
void local_actor_down(endpoint_manager&, const node_id& peer, actor_id id,
error reason) {
next_layer_.local_actor_down(*this, peer, id, std::move(reason));
}
/// Notifies the transport that the timeout identified by `value` plus `id`
/// was triggered.
/// @param value The `atom_value` of the timeout.
/// @param id The timeout id of the timeout.
void timeout(endpoint_manager&, atom_value value, uint64_t id) {
next_layer_.timeout(*this, value, id);
}
/// Callback for setting a timeout. Will be called after setting a timeout to
/// get the timeout id for local use.
/// @param timeout_id The id of the previously set timeout.
/// @param ts Any further information that was passed when setting the
/// timeout.
template <class... Ts>
void set_timeout(uint64_t timeout_id, Ts&&... ts) {
next_layer_.set_timeout(timeout_id, std::forward<Ts>(ts)...);
}
/// Callback for when an error occurs.
/// @param code The error code to handle.
void handle_error(sec code) {
next_layer_.handle_error(code);
}
// -- (pure) virtual functions -----------------------------------------------
/// Configures this transport for the next read event.
virtual void configure_read(receive_policy::config) {
// nop
}
/// Called by the endpoint manager when the transport can read data from its
/// socket.
virtual bool handle_read_event(endpoint_manager&) = 0;
/// Called by the endpoint manager when the transport can write data to its
/// socket.
virtual bool handle_write_event(endpoint_manager& parent) = 0;
/// Queues a packet scattered across multiple buffers to be sent via this
/// transport.
/// @param id The id of the destination endpoint.
/// @param buffers Pointers to the buffers that make up the packet content.
virtual void write_packet(id_type id, span<buffer_type*> buffers) = 0;
// -- buffer management ------------------------------------------------------
/// Returns the next cached header buffer or creates a new one if no buffers
/// are cached.
buffer_type next_header_buffer() {
return next_buffer_impl(header_bufs_);
}
/// Returns the next cached payload buffer or creates a new one if no buffers
/// are cached.
buffer_type next_payload_buffer() {
return next_buffer_impl(payload_bufs_);
}
private:
// -- utility functions ------------------------------------------------------
static buffer_type next_buffer_impl(buffer_cache_type cache) {
if (cache.empty())
return {};
auto buf = std::move(cache.back());
cache.pop_back();
return buf;
}
protected:
next_layer_type next_layer_;
handle_type handle_;
buffer_cache_type header_bufs_;
buffer_cache_type payload_bufs_;
buffer_type read_buf_;
endpoint_manager* manager_;
};
} // namespace caf::net
......@@ -5,7 +5,7 @@
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
......@@ -18,135 +18,102 @@
#pragma once
#include "caf/byte.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace policy {
/// Implements a scribe policy that manages a stream socket.
class scribe {
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/packet_writer_decorator.hpp"
#include "caf/unit.hpp"
namespace caf::net {
/// Implements a worker for transport protocols.
template <class Application, class IdType>
class transport_worker {
public:
explicit scribe(net::stream_socket handle);
// -- member types -----------------------------------------------------------
using id_type = IdType;
using application_type = Application;
// -- constructors, destructors, and assignment operators --------------------
explicit transport_worker(application_type application,
id_type id = id_type{})
: application_(std::move(application)), id_(std::move(id)) {
// nop
}
// -- properties -------------------------------------------------------------
application_type& application() noexcept {
return application_;
}
const application_type& application() const noexcept {
return application_;
}
net::stream_socket handle() const noexcept {
return handle_;
const id_type& id() const noexcept {
return id_;
}
// -- member functions -------------------------------------------------------
template <class Parent>
error init(Parent& parent) {
if (auto err = parent.application().init(parent))
return err;
parent.mask_add(net::operation::read);
return none;
auto writer = make_packet_writer_decorator(*this, parent);
return application_.init(writer);
}
template <class Parent>
bool handle_read_event(Parent& parent) {
auto buf = read_buf_.data() + collected_;
size_t len = read_threshold_ - collected_;
CAF_LOG_TRACE(CAF_ARG(handle_.id) << CAF_ARG(len));
auto ret = read(handle_, make_span(buf, len));
// Update state.
if (auto num_bytes = get_if<size_t>(&ret)) {
CAF_LOG_DEBUG(CAF_ARG(len) << CAF_ARG(handle_.id) << CAF_ARG(*num_bytes));
collected_ += *num_bytes;
if (collected_ >= read_threshold_) {
parent.application().handle_data(*this, read_buf_);
prepare_next_read();
}
return true;
} else {
auto err = get<sec>(ret);
CAF_LOG_DEBUG("receive failed" << CAF_ARG(err));
parent.application().handle_error(err);
return false;
}
error handle_data(Parent& parent, span<const byte> data) {
auto writer = make_packet_writer_decorator(*this, parent);
return application_.handle_data(writer, data);
}
template <class Parent>
bool handle_write_event(Parent& parent) {
// Try to write leftover data.
write_some(parent);
// Get new data from parent.
// TODO: dont read all messages at once - get one by one.
for (auto msg = parent.next_message(); msg != nullptr;
msg = parent.next_message()) {
parent.application().write_message(*this, std::move(msg));
}
// Write prepared data.
return write_some(parent);
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.write_message(writer, std::move(msg));
}
template <class Parent>
bool write_some(Parent& parent) {
if (write_buf_.empty())
return false;
auto len = write_buf_.size() - written_;
auto buf = write_buf_.data() + written_;
CAF_LOG_TRACE(CAF_ARG(handle_.id) << CAF_ARG(len));
auto ret = net::write(handle_, make_span(buf, len));
if (auto num_bytes = get_if<size_t>(&ret)) {
CAF_LOG_DEBUG(CAF_ARG(len) << CAF_ARG(handle_.id) << CAF_ARG(*num_bytes));
// Update state.
written_ += *num_bytes;
if (written_ >= write_buf_.size()) {
written_ = 0;
write_buf_.clear();
return false;
}
} else {
auto err = get<sec>(ret);
CAF_LOG_DEBUG("send failed" << CAF_ARG(err));
parent.application().handle_error(err);
return false;
}
return true;
void resolve(Parent& parent, string_view path, const actor& listener) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.resolve(writer, path, listener);
}
template <class Parent>
void resolve(Parent& parent, const std::string& path, actor listener) {
parent.application().resolve(parent, path, listener);
void new_proxy(Parent& parent, const node_id&, actor_id id) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.new_proxy(writer, id);
}
template <class Parent>
void timeout(Parent& parent, atom_value value, uint64_t id) {
parent.application().timeout(*this, value, id);
void local_actor_down(Parent& parent, const node_id&, actor_id id,
error reason) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.local_actor_down(writer, id, std::move(reason));
}
template <class Application>
void handle_error(Application& application, sec code) {
application.handle_error(code);
template <class Parent>
void timeout(Parent& parent, atom_value value, uint64_t id) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.timeout(writer, value, id);
}
void prepare_next_read();
void configure_read(net::receive_policy::config cfg);
void write_packet(span<const byte> buf);
void handle_error(sec error) {
application_.handle_error(error);
}
private:
net::stream_socket handle_;
std::vector<byte> read_buf_;
std::vector<byte> write_buf_;
size_t max_consecutive_reads_; // TODO use this field!
size_t read_threshold_;
size_t collected_;
size_t max_;
net::receive_policy_flag rd_flag_;
size_t written_;
application_type application_;
id_type id_;
};
} // namespace policy
} // namespace caf
template <class Application, class IdType = unit_t>
using transport_worker_ptr = std::shared_ptr<
transport_worker<Application, IdType>>;
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <unordered_map>
#include "caf/logger.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/packet_writer_decorator.hpp"
#include "caf/net/transport_worker.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
namespace caf::net {
/// Implements a dispatcher that dispatches between transport and workers.
template <class Factory, class IdType>
class transport_worker_dispatcher {
public:
// -- member types -----------------------------------------------------------
using id_type = IdType;
using factory_type = Factory;
using application_type = typename factory_type::application_type;
using worker_type = transport_worker<application_type, id_type>;
using worker_ptr = transport_worker_ptr<application_type, id_type>;
// -- constructors, destructors, and assignment operators --------------------
explicit transport_worker_dispatcher(factory_type factory)
: factory_(std::move(factory)) {
// nop
}
// -- member functions -------------------------------------------------------
template <class Parent>
error init(Parent&) {
CAF_ASSERT(workers_by_id_.empty());
return none;
}
template <class Parent>
error handle_data(Parent& parent, span<byte> data, id_type id) {
if (auto worker = find_worker(id))
return worker->handle_data(parent, data);
// TODO: Where to get node_id from here?
auto worker = add_new_worker(parent, node_id{}, id);
if (worker)
return (*worker)->handle_data(parent, data);
else
return std::move(worker.error());
}
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto receiver = msg->receiver;
if (!receiver)
return;
auto nid = receiver->node();
if (auto worker = find_worker(nid)) {
worker->write_message(parent, std::move(msg));
return;
}
// TODO: where to get id_type from here?
if (auto worker = add_new_worker(parent, nid, id_type{}))
(*worker)->write_message(parent, std::move(msg));
}
template <class Parent>
void resolve(Parent& parent, const uri& locator, const actor& listener) {
if (auto worker = find_worker(make_node_id(locator)))
worker->resolve(parent, locator.path(), listener);
else
anon_send(listener,
make_error(sec::runtime_error, "could not resolve node"));
}
template <class Parent>
void new_proxy(Parent& parent, const node_id& nid, actor_id id) {
if (auto worker = find_worker(nid))
worker->new_proxy(parent, nid, id);
}
template <class Parent>
void local_actor_down(Parent& parent, const node_id& nid, actor_id id,
error reason) {
if (auto worker = find_worker(nid))
worker->local_actor_down(parent, nid, id, std::move(reason));
}
template <class... Ts>
void set_timeout(uint64_t timeout_id, id_type id, Ts&&...) {
workers_by_timeout_id_.emplace(timeout_id, workers_by_id_.at(id));
}
template <class Parent>
void timeout(Parent& parent, atom_value value, uint64_t id) {
if (auto worker = workers_by_timeout_id_.at(id)) {
worker->timeout(parent, value, id);
workers_by_timeout_id_.erase(id);
}
}
void handle_error(sec error) {
for (const auto& p : workers_by_id_) {
auto worker = p.second;
worker->handle_error(error);
}
}
template <class Parent>
expected<worker_ptr> add_new_worker(Parent& parent, node_id node,
id_type id) {
CAF_LOG_TRACE(CAF_ARG(node) << CAF_ARG(id));
auto application = factory_.make();
auto worker = std::make_shared<worker_type>(std::move(application), id);
if (auto err = worker->init(parent))
return err;
workers_by_id_.emplace(std::move(id), worker);
workers_by_node_.emplace(std::move(node), worker);
return worker;
}
private:
worker_ptr find_worker(const node_id& nid) {
return find_worker_impl(workers_by_node_, nid);
}
worker_ptr find_worker(const id_type& id) {
return find_worker_impl(workers_by_id_, id);
}
template <class Key>
worker_ptr find_worker_impl(const std::unordered_map<Key, worker_ptr>& map,
const Key& key) {
if (map.count(key) == 0) {
CAF_LOG_DEBUG("could not find worker: " << CAF_ARG(key));
return nullptr;
}
return map.at(key);
}
// -- worker lookups ---------------------------------------------------------
std::unordered_map<id_type, worker_ptr> workers_by_id_;
std::unordered_map<node_id, worker_ptr> workers_by_node_;
std::unordered_map<uint64_t, worker_ptr> workers_by_timeout_id_;
factory_type factory_;
};
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/fwd.hpp"
#include "caf/net/network_socket.hpp"
namespace caf::net {
/// A datagram-oriented network communication endpoint for bidirectional
/// byte transmission.
struct udp_datagram_socket : network_socket {
using super = network_socket;
using super::super;
};
/// Creates a `udp_datagram_socket` bound to given port.
/// @param node ip_endpoint that contains the port to bind to. Pass port '0' to
/// bind to any unused port - The endpoint will be updated with the specific
/// port that was bound.
/// @returns The connected socket or an error.
/// @relates udp_datagram_socket
expected<std::pair<udp_datagram_socket, uint16_t>>
make_udp_datagram_socket(ip_endpoint ep, bool reuse_addr = false);
/// Enables or disables `SIO_UDP_CONNRESET` error on `x`.
/// @relates udp_datagram_socket
error allow_connreset(udp_datagram_socket x, bool new_value);
/// Receives the next datagram on socket `x`.
/// @param x The UDP socket for receiving datagrams.
/// @param buf Writable output buffer.
/// @returns The number of received bytes and the sender as `ip_endpoint` on
/// success, an error code otherwise.
/// @relates udp_datagram_socket
/// @post buf was modified and the resulting integer represents the length of
/// the received datagram, even if it did not fit into the given buffer.
variant<std::pair<size_t, ip_endpoint>, sec> read(udp_datagram_socket x,
span<byte> buf);
/// Sends the content of `bufs` as a datagram to the endpoint `ep` on socket
/// `x`.
/// @param x The UDP socket for sending datagrams.
/// @param bufs Points to the datagram to send, scattered across up to 10
/// buffers.
/// @param ep The enpoint to send the datagram to.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates udp_datagram_socket
/// @pre `bufs.size() < 10`
variant<size_t, sec> write(udp_datagram_socket x, span<std::vector<byte>*> bufs,
ip_endpoint ep);
/// Sends the content of `buf` as a datagram to the endpoint `ep` on socket `x`.
/// @param x The UDP socket for sending datagrams.
/// @param buf The buffer to send.
/// @param ep The enpoint to send the datagram to.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates udp_datagram_socket
variant<size_t, sec> write(udp_datagram_socket x, span<const byte> buf,
ip_endpoint ep);
/// Converts the result from I/O operation on a ::udp_datagram_socket to either
/// an error code or a non-zero positive integer.
/// @relates udp_datagram_socket
variant<size_t, sec>
check_udp_datagram_socket_io_res(std::make_signed<size_t>::type res);
} // namespace caf::net
......@@ -22,51 +22,31 @@
#include "caf/expected.hpp"
#include "caf/logger.hpp"
namespace caf {
namespace net {
namespace caf::net {
actor_proxy_impl::actor_proxy_impl(actor_config& cfg, endpoint_manager_ptr dst)
: super(cfg), sf_(dst->serialize_fun()), dst_(std::move(dst)) {
// anon_send(broker_, monitor_atom::value, ctrl());
CAF_ASSERT(dst_ != nullptr);
dst_->enqueue_event(node(), id());
}
actor_proxy_impl::~actor_proxy_impl() {
// anon_send(broker_, make_message(delete_atom::value, node(), id()));
// nop
}
void actor_proxy_impl::enqueue(mailbox_element_ptr what, execution_unit*) {
CAF_PUSH_AID(0);
CAF_ASSERT(what != nullptr);
CAF_LOG_SEND_EVENT(what);
if (auto payload = sf_(home_system(), what->content()))
dst_->enqueue(std::move(what), std::move(*payload));
dst_->enqueue(std::move(what), ctrl(), std::move(*payload));
else
CAF_LOG_ERROR(
"unable to serialize payload: " << home_system().render(payload.error()));
}
bool actor_proxy_impl::add_backlink(abstract_actor* x) {
if (monitorable_actor::add_backlink(x)) {
enqueue(make_mailbox_element(ctrl(), make_message_id(), {},
link_atom::value, x->ctrl()),
nullptr);
return true;
}
return false;
}
bool actor_proxy_impl::remove_backlink(abstract_actor* x) {
if (monitorable_actor::remove_backlink(x)) {
enqueue(make_mailbox_element(ctrl(), make_message_id(), {},
unlink_atom::value, x->ctrl()),
nullptr);
return true;
}
return false;
}
void actor_proxy_impl::kill_proxy(execution_unit* ctx, error rsn) {
cleanup(std::move(rsn), ctx);
}
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/basp/application.hpp"
#include <vector>
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/byte.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/detail/parse.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/no_stages.hpp"
#include "caf/none.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/type_erased_tuple.hpp"
namespace caf::net::basp {
application::application(proxy_registry& proxies)
: proxies_(proxies), queue_{new message_queue}, hub_{new hub_type} {
// nop
}
error application::write_message(
packet_writer& writer, std::unique_ptr<endpoint_manager_queue::message> ptr) {
CAF_ASSERT(ptr != nullptr);
CAF_ASSERT(ptr->msg != nullptr);
CAF_LOG_TRACE(CAF_ARG2("content", ptr->msg->content()));
auto payload_prefix = writer.next_payload_buffer();
serializer_impl<buffer_type> sink{system(), payload_prefix};
const auto& src = ptr->msg->sender;
const auto& dst = ptr->receiver;
if (dst == nullptr) {
// TODO: valid?
return none;
}
if (src != nullptr) {
auto src_id = src->id();
system().registry().put(src_id, src);
if (auto err = sink(src->node(), src_id, dst->id(), ptr->msg->stages))
return err;
} else {
if (auto err = sink(node_id{}, actor_id{0}, dst->id(), ptr->msg->stages))
return err;
}
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::actor_message,
static_cast<uint32_t>(payload_prefix.size()
+ ptr->payload.size()),
ptr->msg->mid.integer_value()},
hdr);
writer.write_packet(hdr, payload_prefix, ptr->payload);
return none;
}
void application::resolve(packet_writer& writer, string_view path,
const actor& listener) {
CAF_LOG_TRACE(CAF_ARG(path) << CAF_ARG(listener));
auto payload = writer.next_payload_buffer();
serializer_impl<buffer_type> sink{&executor_, payload};
if (auto err = sink(path)) {
CAF_LOG_ERROR("unable to serialize path" << CAF_ARG(err));
return;
}
auto req_id = next_request_id_++;
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::resolve_request,
static_cast<uint32_t>(payload.size()), req_id},
hdr);
writer.write_packet(hdr, payload);
pending_resolves_.emplace(req_id, listener);
}
void application::new_proxy(packet_writer& writer, actor_id id) {
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::monitor_message, 0, static_cast<uint64_t>(id)},
hdr);
writer.write_packet(hdr);
}
void application::local_actor_down(packet_writer& writer, actor_id id,
error reason) {
auto payload = writer.next_payload_buffer();
serializer_impl<buffer_type> sink{system(), payload};
if (auto err = sink(reason))
CAF_RAISE_ERROR("unable to serialize an error");
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::down_message,
static_cast<uint32_t>(payload.size()),
static_cast<uint64_t>(id)},
hdr);
writer.write_packet(hdr, payload);
}
expected<std::vector<byte>> application::serialize(actor_system& sys,
const type_erased_tuple& x) {
std::vector<byte> result;
serializer_impl<std::vector<byte>> sink{sys, result};
if (auto err = message::save(sink, x))
return err;
return result;
}
strong_actor_ptr application::resolve_local_path(string_view path) {
CAF_LOG_TRACE(CAF_ARG(path));
// We currently support two path formats: `id/<actor_id>` and `name/<atom>`.
static constexpr string_view id_prefix = "id/";
if (starts_with(path, id_prefix)) {
path.remove_prefix(id_prefix.size());
actor_id aid;
if (auto err = detail::parse(path, aid))
return nullptr;
return system().registry().get(aid);
}
static constexpr string_view name_prefix = "name/";
if (starts_with(path, name_prefix)) {
path.remove_prefix(name_prefix.size());
atom_value name;
if (auto err = detail::parse(path, name))
return nullptr;
return system().registry().get(name);
}
return nullptr;
}
error application::handle(size_t& next_read_size, packet_writer& writer,
byte_span bytes) {
CAF_LOG_TRACE(CAF_ARG(state_) << CAF_ARG2("bytes.size", bytes.size()));
switch (state_) {
case connection_state::await_handshake_header: {
if (bytes.size() != header_size)
return ec::unexpected_number_of_bytes;
hdr_ = header::from_bytes(bytes);
if (hdr_.type != message_type::handshake)
return ec::missing_handshake;
if (hdr_.operation_data != version)
return ec::version_mismatch;
if (hdr_.payload_len == 0)
return ec::missing_payload;
state_ = connection_state::await_handshake_payload;
next_read_size = hdr_.payload_len;
return none;
}
case connection_state::await_handshake_payload: {
if (auto err = handle_handshake(writer, hdr_, bytes))
return err;
state_ = connection_state::await_header;
return none;
}
case connection_state::await_header: {
if (bytes.size() != header_size)
return ec::unexpected_number_of_bytes;
hdr_ = header::from_bytes(bytes);
if (hdr_.payload_len == 0)
return handle(writer, hdr_, byte_span{});
next_read_size = hdr_.payload_len;
state_ = connection_state::await_payload;
return none;
}
case connection_state::await_payload: {
if (bytes.size() != hdr_.payload_len)
return ec::unexpected_number_of_bytes;
state_ = connection_state::await_header;
return handle(writer, hdr_, bytes);
}
default:
return ec::illegal_state;
}
}
error application::handle(packet_writer& writer, header hdr,
byte_span payload) {
CAF_LOG_TRACE(CAF_ARG(hdr) << CAF_ARG2("payload.size", payload.size()));
switch (hdr.type) {
case message_type::handshake:
return ec::unexpected_handshake;
case message_type::actor_message:
return handle_actor_message(writer, hdr, payload);
case message_type::resolve_request:
return handle_resolve_request(writer, hdr, payload);
case message_type::resolve_response:
return handle_resolve_response(writer, hdr, payload);
case message_type::monitor_message:
return handle_monitor_message(writer, hdr, payload);
case message_type::down_message:
return handle_down_message(writer, hdr, payload);
case message_type::heartbeat:
return none;
default:
return ec::unimplemented;
}
}
error application::handle_handshake(packet_writer&, header hdr,
byte_span payload) {
CAF_LOG_TRACE(CAF_ARG(hdr) << CAF_ARG2("payload.size", payload.size()));
if (hdr.type != message_type::handshake)
return ec::missing_handshake;
if (hdr.operation_data != version)
return ec::version_mismatch;
node_id peer_id;
std::vector<std::string> app_ids;
binary_deserializer source{&executor_, payload};
if (auto err = source(peer_id, app_ids))
return err;
if (!peer_id || app_ids.empty())
return ec::invalid_handshake;
auto ids = get_or(system().config(), "middleman.app-identifiers",
defaults::middleman::app_identifiers);
auto predicate = [=](const std::string& x) {
return std::find(ids.begin(), ids.end(), x) != ids.end();
};
if (std::none_of(app_ids.begin(), app_ids.end(), predicate))
return ec::app_identifiers_mismatch;
peer_id_ = std::move(peer_id);
state_ = connection_state::await_header;
return none;
}
error application::handle_actor_message(packet_writer&, header hdr,
byte_span payload) {
auto worker = hub_->pop();
if (worker != nullptr) {
CAF_LOG_DEBUG("launch BASP worker for deserializing an actor_message");
worker->launch(node_id{}, hdr, payload);
} else {
CAF_LOG_DEBUG(
"out of BASP workers, continue deserializing an actor_message");
// If no worker is available then we have no other choice than to take
// the performance hit and deserialize in this thread.
struct handler : remote_message_handler<handler> {
handler(message_queue* queue, proxy_registry* proxies,
actor_system* system, node_id last_hop, basp::header& hdr,
byte_span payload)
: queue_(queue),
proxies_(proxies),
system_(system),
last_hop_(std::move(last_hop)),
hdr_(hdr),
payload_(payload) {
msg_id_ = queue_->new_id();
}
message_queue* queue_;
proxy_registry* proxies_;
actor_system* system_;
node_id last_hop_;
basp::header& hdr_;
byte_span payload_;
uint64_t msg_id_;
};
handler f{queue_.get(), &proxies_, system_, node_id{}, hdr, payload};
f.handle_remote_message(&executor_);
}
return none;
}
error application::handle_resolve_request(packet_writer& writer, header rec_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(rec_hdr) << CAF_ARG2("received.size", received.size()));
CAF_ASSERT(rec_hdr.type == message_type::resolve_request);
size_t path_size = 0;
binary_deserializer source{&executor_, received};
if (auto err = source.begin_sequence(path_size))
return err;
// We expect the received buffer to contain the path only.
if (path_size != source.remaining())
return ec::invalid_payload;
auto remainder = source.remainder();
string_view path{reinterpret_cast<const char*>(remainder.data()),
remainder.size()};
// Write result.
auto result = resolve_local_path(path);
actor_id aid;
std::set<std::string> ifs;
if (result) {
aid = result->id();
system().registry().put(aid, result);
} else {
aid = 0;
}
// TODO: figure out how to obtain messaging interface.
auto payload = writer.next_payload_buffer();
serializer_impl<buffer_type> sink{&executor_, payload};
if (auto err = sink(aid, ifs))
return err;
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::resolve_response,
static_cast<uint32_t>(payload.size()),
rec_hdr.operation_data},
hdr);
writer.write_packet(hdr, payload);
return none;
}
error application::handle_resolve_response(packet_writer&, header received_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(received_hdr)
<< CAF_ARG2("received.size", received.size()));
CAF_ASSERT(received_hdr.type == message_type::resolve_response);
auto i = pending_resolves_.find(received_hdr.operation_data);
if (i == pending_resolves_.end()) {
CAF_LOG_ERROR("received unknown ID in resolve_response message");
return none;
}
auto guard = detail::make_scope_guard([&] { pending_resolves_.erase(i); });
actor_id aid;
std::set<std::string> ifs;
binary_deserializer source{&executor_, received};
if (auto err = source(aid, ifs)) {
anon_send(i->second, sec::remote_lookup_failed);
return err;
}
if (aid == 0) {
anon_send(i->second, strong_actor_ptr{nullptr}, std::move(ifs));
return none;
}
anon_send(i->second, proxies_.get_or_put(peer_id_, aid), std::move(ifs));
return none;
}
error application::handle_monitor_message(packet_writer& writer,
header received_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(received_hdr)
<< CAF_ARG2("received.size", received.size()));
if (!received.empty())
return ec::unexpected_payload;
auto aid = static_cast<actor_id>(received_hdr.operation_data);
auto hdl = system().registry().get(aid);
if (hdl != nullptr) {
endpoint_manager_ptr mgr = manager_;
auto nid = peer_id_;
hdl->get()->attach_functor([mgr, nid, aid](error reason) mutable {
mgr->enqueue_event(std::move(nid), aid, std::move(reason));
});
} else {
error reason = exit_reason::unknown;
auto payload = writer.next_payload_buffer();
serializer_impl<buffer_type> sink{&executor_, payload};
if (auto err = sink(reason))
return err;
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::down_message,
static_cast<uint32_t>(payload.size()),
received_hdr.operation_data},
hdr);
writer.write_packet(hdr, payload);
}
return none;
}
error application::handle_down_message(packet_writer&, header received_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(received_hdr)
<< CAF_ARG2("received.size", received.size()));
error reason;
binary_deserializer source{&executor_, received};
if (auto err = source(reason))
return err;
proxies_.erase(peer_id_, received_hdr.operation_data, std::move(reason));
return none;
}
error application::generate_handshake(std::vector<byte>& buf) {
serializer_impl<buffer_type> sink{&executor_, buf};
return sink(system().node(),
get_or(system().config(), "middleman.app-identifiers",
defaults::middleman::app_identifiers));
}
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/detail/convert_ip_endpoint.hpp"
#include "caf/error.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv6_endpoint.hpp"
#include "caf/sec.hpp"
namespace caf::detail {
void convert(const ip_endpoint& src, sockaddr_storage& dst) {
memset(&dst, 0, sizeof(sockaddr_storage));
if (src.address().embeds_v4()) {
auto sockaddr4 = reinterpret_cast<sockaddr_in*>(&dst);
sockaddr4->sin_family = AF_INET;
sockaddr4->sin_port = ntohs(src.port());
sockaddr4->sin_addr.s_addr = src.address().embedded_v4().bits();
} else {
auto sockaddr6 = reinterpret_cast<sockaddr_in6*>(&dst);
sockaddr6->sin6_family = AF_INET6;
sockaddr6->sin6_port = ntohs(src.port());
memcpy(&sockaddr6->sin6_addr, src.address().bytes().data(),
src.address().bytes().size());
}
}
error convert(const sockaddr_storage& src, ip_endpoint& dst) {
if (src.ss_family == AF_INET) {
auto sockaddr4 = reinterpret_cast<const sockaddr_in&>(src);
ipv4_address ipv4_addr;
memcpy(ipv4_addr.data().data(), &sockaddr4.sin_addr, ipv4_addr.size());
dst = ip_endpoint{ipv4_addr, htons(sockaddr4.sin_port)};
} else if (src.ss_family == AF_INET6) {
auto sockaddr6 = reinterpret_cast<const sockaddr_in6&>(src);
ipv6_address ipv6_addr;
memcpy(ipv6_addr.bytes().data(), &sockaddr6.sin6_addr,
ipv6_addr.bytes().size());
dst = ip_endpoint{ipv6_addr, htons(sockaddr6.sin6_port)};
} else {
return sec::invalid_argument;
}
return none;
}
} // namespace caf::detail
......@@ -22,8 +22,7 @@
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/logger.hpp"
namespace caf {
namespace net {
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -60,5 +59,4 @@ check_datagram_socket_io_res(std::make_signed<size_t>::type res) {
return static_cast<size_t>(res);
}
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/defaults.hpp"
namespace caf::defaults::middleman {
const size_t max_payload_buffers = 100;
const size_t max_header_buffers = 10;
} // namespace caf::defaults::middleman
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/basp/ec.hpp"
#include "caf/atom.hpp"
#include "caf/error.hpp"
#include "caf/string_view.hpp"
namespace caf::net::basp {
error make_error(ec x) {
return {static_cast<uint8_t>(x), atom("basp")};
}
} // namespace caf::net::basp
......@@ -20,78 +20,70 @@
#include "caf/byte.hpp"
#include "caf/intrusive/inbox_result.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
namespace caf {
namespace net {
endpoint_manager::event::event(std::string path, actor listener)
: value(resolve_request{std::move(path), std::move(listener)}) {
// nop
}
endpoint_manager::event::event(atom_value type, uint64_t id)
: value(timeout{type, id}) {
// nop
}
endpoint_manager::message::message(mailbox_element_ptr msg,
std::vector<byte> payload)
: msg(std::move(msg)), payload(std::move(payload)) {
// nop
}
namespace caf::net {
endpoint_manager::endpoint_manager(socket handle, const multiplexer_ptr& parent,
actor_system& sys)
: super(handle, parent),
sys_(sys),
events_(event_policy{}),
messages_(message_policy{}) {
events_.try_block();
messages_.try_block();
: super(handle, parent), sys_(sys), queue_(unit, unit, unit) {
queue_.try_block();
}
endpoint_manager::~endpoint_manager() {
// nop
}
std::unique_ptr<endpoint_manager::message> endpoint_manager::next_message() {
if (messages_.blocked())
endpoint_manager_queue::message_ptr endpoint_manager::next_message() {
if (queue_.blocked())
return nullptr;
messages_.fetch_more();
auto& q = messages_.queue();
queue_.fetch_more();
auto& q = std::get<1>(queue_.queue().queues());
auto ts = q.next_task_size();
if (ts == 0)
return nullptr;
q.inc_deficit(ts);
auto result = q.next();
if (q.empty())
messages_.try_block();
if (queue_.empty())
queue_.try_block();
return result;
}
void endpoint_manager::resolve(std::string path, actor listener) {
void endpoint_manager::resolve(uri locator, actor listener) {
using intrusive::inbox_result;
auto ptr = new event(std::move(path), std::move(listener));
switch (events_.push_back(ptr)) {
default:
break;
case inbox_result::unblocked_reader:
mask_add(operation::write);
break;
case inbox_result::queue_closed:
anon_send(listener, resolve_atom::value,
make_error(sec::request_receiver_down));
}
using event_type = endpoint_manager_queue::event;
auto ptr = new event_type(std::move(locator), listener);
if (!enqueue(ptr))
anon_send(listener, resolve_atom::value,
make_error(sec::request_receiver_down));
}
void endpoint_manager::enqueue(mailbox_element_ptr msg,
strong_actor_ptr receiver,
std::vector<byte> payload) {
auto ptr = new message(std::move(msg), std::move(payload));
if (messages_.push_back(ptr) == intrusive::inbox_result::unblocked_reader)
mask_add(operation::write);
using message_type = endpoint_manager_queue::message;
auto ptr = new message_type(std::move(msg), std::move(receiver),
std::move(payload));
enqueue(ptr);
}
bool endpoint_manager::enqueue(endpoint_manager_queue::element* ptr) {
switch (queue_.push_back(ptr)) {
case intrusive::inbox_result::success:
return true;
case intrusive::inbox_result::unblocked_reader: {
auto mpx = parent_.lock();
if (mpx) {
mpx->register_writing(this);
return true;
}
return false;
}
default:
return false;
}
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -16,64 +16,55 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/policy/scribe.hpp"
#include "caf/net/basp/header.hpp"
#include <system_error>
#include <cstring>
#include "caf/byte.hpp"
#include "caf/config.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/span.hpp"
namespace caf {
namespace policy {
namespace caf::net::basp {
scribe::scribe(caf::net::stream_socket handle)
: handle_(handle),
max_consecutive_reads_(0),
read_threshold_(1024),
collected_(0),
max_(1024),
rd_flag_(net::receive_policy_flag::exactly),
written_(0) {
// nop
namespace {
void to_bytes_impl(const header& x, byte* ptr) {
*ptr = static_cast<byte>(x.type);
auto payload_len = detail::to_network_order(x.payload_len);
memcpy(ptr + 1, &payload_len, sizeof(payload_len));
auto operation_data = detail::to_network_order(x.operation_data);
memcpy(ptr + 5, &operation_data, sizeof(operation_data));
}
} // namespace
int header::compare(header other) const noexcept {
auto x = to_bytes(*this);
auto y = to_bytes(other);
return memcmp(x.data(), y.data(), header_size);
}
void scribe::prepare_next_read() {
read_buf_.clear();
collected_ = 0;
// This cast does nothing, but prevents a weird compiler error on GCC <= 4.9.
// TODO: remove cast when dropping support for GCC 4.9.
switch (static_cast<net::receive_policy_flag>(rd_flag_)) {
case net::receive_policy_flag::exactly:
if (read_buf_.size() != max_)
read_buf_.resize(max_);
read_threshold_ = max_;
break;
case net::receive_policy_flag::at_most:
if (read_buf_.size() != max_)
read_buf_.resize(max_);
read_threshold_ = 1;
break;
case net::receive_policy_flag::at_least: {
// read up to 10% more, but at least allow 100 bytes more
auto max_size = max_ + std::max<size_t>(100, max_ / 10);
if (read_buf_.size() != max_size)
read_buf_.resize(max_size);
read_threshold_ = max_;
break;
}
}
header header::from_bytes(span<const byte> bytes) {
CAF_ASSERT(bytes.size() >= header_size);
header result;
auto ptr = bytes.data();
result.type = *reinterpret_cast<const message_type*>(ptr);
auto payload_len = *reinterpret_cast<const uint32_t*>(ptr + 1);
result.payload_len = detail::from_network_order(payload_len);
auto operation_data = *reinterpret_cast<const uint64_t*>(ptr + 5);
result.operation_data = detail::from_network_order(operation_data);
return result;
}
void scribe::configure_read(net::receive_policy::config cfg) {
rd_flag_ = cfg.first;
max_ = cfg.second;
prepare_next_read();
std::array<byte, header_size> to_bytes(header x) {
std::array<byte, header_size> result{};
to_bytes_impl(x, result.data());
return result;
}
void scribe::write_packet(span<const byte> buf) {
write_buf_.insert(write_buf_.end(), buf.begin(), buf.end());
void to_bytes(header x, std::vector<byte>& buf) {
buf.resize(header_size);
to_bytes_impl(x, buf.data());
}
} // namespace policy
} // namespace caf
} // namespace caf::net::basp
......@@ -25,8 +25,7 @@
#include "caf/net/socket.hpp"
#include "caf/none.hpp"
namespace caf {
namespace net {
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -53,5 +52,4 @@ void this_host::cleanup() {
#endif // CAF_WINDOWS
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -5,7 +5,7 @@
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
......@@ -16,6 +16,8 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/ip.hpp"
#include <cstddef>
#include <string>
#include <utility>
......@@ -25,23 +27,24 @@
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/error.hpp"
#include "caf/ip_address.hpp"
#include "caf/ip_subnet.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/logger.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/string_view.hpp"
// clang-format off
#ifdef CAF_WINDOWS
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0600
# endif
# include <iphlpapi.h>
# include <winsock.h>
#else
# include <ifaddrs.h>
# include <net/if.h>
# include <netdb.h>
# include <ifaddrs.h>
# include <sys/ioctl.h>
# include <sys/types.h>
#endif
// clang-format on
#ifndef HOST_NAME_MAX
# define HOST_NAME_MAX 255
......@@ -51,31 +54,111 @@ using std::pair;
using std::string;
using std::vector;
namespace caf {
namespace net {
namespace ip {
namespace caf::net::ip {
namespace {
// Dummy port to resolve empty string with getaddrinfo.
constexpr string_view dummy_port = "42";
constexpr string_view localhost = "localhost";
void* fetch_in_addr(int family, sockaddr* addr) {
if (family == AF_INET)
return &reinterpret_cast<sockaddr_in*>(addr)->sin_addr;
return &reinterpret_cast<sockaddr_in6*>(addr)->sin6_addr;
}
// TODO: Use getnameinfo instead?
int fetch_addr_str(bool get_ipv4, bool get_ipv6, char (&buf)[INET6_ADDRSTRLEN],
sockaddr* addr) {
int fetch_addr_str(char (&buf)[INET6_ADDRSTRLEN], sockaddr* addr) {
if (addr == nullptr)
return AF_UNSPEC;
auto family = addr->sa_family;
auto in_addr = fetch_in_addr(family, addr);
return ((family == AF_INET && get_ipv4) || (family == AF_INET6 && get_ipv6))
return (family == AF_INET || family == AF_INET6)
&& inet_ntop(family, in_addr, buf, INET6_ADDRSTRLEN) == buf
? family
: AF_UNSPEC;
}
#ifdef CAF_WINDOWS
template <class F>
void for_each_adapter(F f, bool is_link_local = false) {
using adapters_ptr = std::unique_ptr<IP_ADAPTER_ADDRESSES, void (*)(void*)>;
ULONG len = 0;
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr,
&len)
!= ERROR_BUFFER_OVERFLOW) {
CAF_LOG_ERROR("failed to get adapter addresses buffer length");
return;
}
auto adapters = adapters_ptr{reinterpret_cast<IP_ADAPTER_ADDRESSES*>(
::malloc(len)),
free};
if (!adapters) {
CAF_LOG_ERROR("malloc failed");
return;
}
// TODO: The Microsoft WIN32 API example propopses to try three times, other
// examples online just perform the call once. If we notice the call to be
// unreliable, we might adapt that behavior.
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr,
adapters.get(), &len)
!= ERROR_SUCCESS) {
CAF_LOG_ERROR("failed to get adapter addresses");
return;
}
char ip_buf[INET6_ADDRSTRLEN];
char name_buf[HOST_NAME_MAX];
std::vector<std::pair<std::string, ip_address>> interfaces;
for (auto* it = adapters.get(); it != nullptr; it = it->Next) {
memset(name_buf, 0, HOST_NAME_MAX);
WideCharToMultiByte(CP_ACP, 0, it->FriendlyName, wcslen(it->FriendlyName),
name_buf, HOST_NAME_MAX, nullptr, nullptr);
std::string name{name_buf};
for (auto* addr = it->FirstUnicastAddress; addr != nullptr;
addr = addr->Next) {
memset(ip_buf, 0, INET6_ADDRSTRLEN);
getnameinfo(addr->Address.lpSockaddr, addr->Address.iSockaddrLength,
ip_buf, sizeof(ip_buf), nullptr, 0, NI_NUMERICHOST);
ip_address ip;
if (!is_link_local && starts_with(ip_buf, "fe80:")) {
CAF_LOG_DEBUG("skipping link-local address: " << ip_buf);
continue;
} else if (auto err = parse(ip_buf, ip))
continue;
f(name_buf, ip);
}
}
}
#else // CAF_WINDOWS
template <class F>
void for_each_adapter(F f, bool is_link_local = false) {
ifaddrs* tmp = nullptr;
if (getifaddrs(&tmp) != 0)
return;
std::unique_ptr<ifaddrs, decltype(freeifaddrs)*> addrs{tmp, freeifaddrs};
char buffer[INET6_ADDRSTRLEN];
std::vector<std::pair<std::string, ip_address>> interfaces;
for (auto i = addrs.get(); i != nullptr; i = i->ifa_next) {
auto family = fetch_addr_str(buffer, i->ifa_addr);
if (family != AF_UNSPEC) {
ip_address ip;
if (!is_link_local && starts_with(buffer, "fe80:")) {
CAF_LOG_DEBUG("skipping link-local address: " << buffer);
continue;
} else if (auto err = parse(buffer, ip)) {
CAF_LOG_ERROR("could not parse into ip address " << buffer);
continue;
}
f({i->ifa_name, strlen(i->ifa_name)}, ip);
}
}
}
#endif // CAF_WINDOWS
} // namespace
std::vector<ip_address> resolve(string_view host) {
......@@ -87,13 +170,15 @@ std::vector<ip_address> resolve(string_view host) {
hint.ai_flags = AI_PASSIVE;
addrinfo* tmp = nullptr;
std::string host_str{host.begin(), host.end()};
if (getaddrinfo(host_str.c_str(), nullptr, &hint, &tmp) != 0)
if (getaddrinfo(host.empty() ? nullptr : host_str.c_str(),
host.empty() ? dummy_port.data() : nullptr, &hint, &tmp)
!= 0)
return {};
std::unique_ptr<addrinfo, decltype(freeaddrinfo)*> addrs{tmp, freeaddrinfo};
char buffer[INET6_ADDRSTRLEN];
std::vector<ip_address> results;
for (auto i = addrs.get(); i != nullptr; i = i->ai_next) {
auto family = fetch_addr_str(true, true, buffer, i->ai_addr);
auto family = fetch_addr_str(buffer, i->ai_addr);
if (family != AF_UNSPEC) {
ip_address ip;
if (auto err = parse(buffer, ip))
......@@ -110,27 +195,58 @@ std::vector<ip_address> resolve(string_view host) {
return results;
}
#ifdef CAF_WINDOWS
std::vector<ip_address> resolve(ip_address host) {
return resolve(to_string(host));
}
std::string hostname() {
TCHAR buf[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = MAX_COMPUTERNAME_LENGTH;
GetComputerName(buf, &size);
return buf;
std::vector<ip_address> local_addresses(string_view host) {
ip_address host_ip;
std::vector<ip_address> results;
if (host.empty()) {
for_each_adapter(
[&](string_view, ip_address ip) { results.push_back(ip); });
} else if (host == localhost) {
auto v6_local = ip_address{{0}, {0x1}};
auto v4_local = ip_address{make_ipv4_address(127, 0, 0, 1)};
for_each_adapter([&](string_view, ip_address ip) {
if (ip == v4_local || ip == v6_local)
results.push_back(ip);
});
} else if (auto err = parse(host, host_ip)) {
for_each_adapter([&](string_view iface, ip_address ip) {
if (iface == host)
results.push_back(ip);
});
} else {
return local_addresses(host_ip);
}
return results;
}
#else // CAF_WINDOWS
std::vector<ip_address> local_addresses(ip_address host) {
static auto v6_any = ip_address{{0}, {0}};
static auto v4_any = ip_address{make_ipv4_address(0, 0, 0, 0)};
if (host == v4_any || host == v6_any)
return resolve("");
auto link_local = ip_address({0xfe, 0x8, 0x0, 0x0}, {0x0, 0x0, 0x0, 0x0});
auto ll_prefix = ip_subnet(link_local, 10);
// Unless explicitly specified we are going to skip link-local addresses.
auto is_link_local = ll_prefix.contains(host);
std::vector<ip_address> results;
for_each_adapter(
[&](string_view, ip_address ip) {
if (host == ip)
results.push_back(ip);
},
is_link_local);
return results;
}
std::string hostname() {
char buf[HOST_NAME_MAX + 1];
buf[HOST_NAME_MAX] = '\0';
gethostname(buf, HOST_NAME_MAX);
gethostbyname(buf);
return buf;
}
#endif // CAF_WINDOWS
} // namespace ip
} // namespace net
} // namespace caf
} // namespace caf::net::ip
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/basp/message_queue.hpp"
#include <iterator>
namespace caf::net::basp {
message_queue::message_queue() : next_id(0), next_undelivered(0) {
// nop
}
void message_queue::push(execution_unit* ctx, uint64_t id,
strong_actor_ptr receiver,
mailbox_element_ptr content) {
std::unique_lock<std::mutex> guard{lock};
CAF_ASSERT(id >= next_undelivered);
CAF_ASSERT(id < next_id);
auto first = pending.begin();
auto last = pending.end();
if (id == next_undelivered) {
// Dispatch current head.
if (receiver != nullptr)
receiver->enqueue(std::move(content), ctx);
auto next = id + 1;
// Check whether we can deliver more.
if (first == last || first->id != next) {
next_undelivered = next;
CAF_ASSERT(next_undelivered <= next_id);
return;
}
// Deliver everything until reaching a non-consecutive ID or the end.
auto i = first;
for (; i != last && i->id == next; ++i, ++next)
if (i->receiver != nullptr)
i->receiver->enqueue(std::move(i->content), ctx);
next_undelivered = next;
pending.erase(first, i);
CAF_ASSERT(next_undelivered <= next_id);
return;
}
// Get the insertion point.
auto pred = [&](const actor_msg& x) { return x.id >= id; };
pending.emplace(std::find_if(first, last, pred),
actor_msg{id, std::move(receiver), std::move(content)});
}
void message_queue::drop(execution_unit* ctx, uint64_t id) {
push(ctx, id, nullptr, nullptr);
}
uint64_t message_queue::new_id() {
std::unique_lock<std::mutex> guard{lock};
return next_id++;
}
} // namespace caf::net::basp
......@@ -38,8 +38,7 @@
# include "caf/detail/socket_sys_includes.hpp"
#endif // CAF_WINDOWS
namespace caf {
namespace net {
namespace caf::net {
#ifndef POLLRDHUP
# define POLLRDHUP POLLHUP
......@@ -90,7 +89,6 @@ error multiplexer::init() {
auto pipe_handles = make_pipe();
if (!pipe_handles)
return std::move(pipe_handles.error());
tid_ = std::this_thread::get_id();
add(make_counted<pollset_updater>(pipe_handles->first, shared_from_this()));
write_handle_ = pipe_handles->second;
return none;
......@@ -107,24 +105,35 @@ ptrdiff_t multiplexer::index_of(const socket_manager_ptr& mgr) {
return i == last ? -1 : std::distance(first, i);
}
void multiplexer::update(const socket_manager_ptr& mgr) {
void multiplexer::register_reading(const socket_manager_ptr& mgr) {
if (std::this_thread::get_id() == tid_) {
auto i = std::find(dirty_managers_.begin(), dirty_managers_.end(), mgr);
if (i == dirty_managers_.end())
dirty_managers_.emplace_back(mgr);
if (mgr->mask() != operation::none) {
CAF_ASSERT(index_of(mgr) != -1);
if (mgr->mask_add(operation::read)) {
auto& fd = pollset_[index_of(mgr)];
fd.events |= input_mask;
}
} else if (mgr->mask_add(operation::read)) {
add(mgr);
}
} else {
mgr->ref();
auto value = reinterpret_cast<intptr_t>(mgr.get());
variant<size_t, sec> res;
{ // Lifetime scope of guard.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket)
res = write(write_handle_, as_bytes(make_span(&value, 1)));
else
res = sec::socket_invalid;
write_to_pipe(0, mgr);
}
}
void multiplexer::register_writing(const socket_manager_ptr& mgr) {
if (std::this_thread::get_id() == tid_) {
if (mgr->mask() != operation::none) {
CAF_ASSERT(index_of(mgr) != -1);
if (mgr->mask_add(operation::write)) {
auto& fd = pollset_[index_of(mgr)];
fd.events |= output_mask;
}
} else if (mgr->mask_add(operation::write)) {
add(mgr);
}
if (holds_alternative<sec>(res))
mgr->deref();
} else {
write_to_pipe(1, mgr);
}
}
......@@ -182,72 +191,95 @@ bool multiplexer::poll_once(bool blocking) {
return false;
// Scan pollset for events.
CAF_LOG_DEBUG("scan pollset for socket events");
for (size_t i = 0; i < pollset_.size() && presult > 0; ++i) {
auto& x = pollset_[i];
if (x.revents != 0) {
handle(managers_[i], x.revents);
for (size_t i = 0; i < pollset_.size() && presult > 0;) {
auto revents = pollset_[i].revents;
if (revents != 0) {
auto events = pollset_[i].events;
auto mgr = managers_[i];
auto new_events = handle(mgr, events, revents);
--presult;
if (new_events == 0) {
pollset_.erase(pollset_.begin() + i);
managers_.erase(managers_.begin() + i);
continue;
} else if (new_events != events) {
pollset_[i].events = new_events;
}
}
++i;
}
handle_updates();
return true;
}
}
void multiplexer::set_thread_id() {
tid_ = std::this_thread::get_id();
}
void multiplexer::run() {
CAF_LOG_TRACE("");
while (!pollset_.empty())
poll_once(true);
}
void multiplexer::handle_updates() {
for (auto mgr : dirty_managers_) {
auto index = index_of(mgr.get());
if (index == -1) {
add(std::move(mgr));
} else {
// Update or remove an existing manager in the pollset.
if (mgr->mask() == operation::none) {
pollset_.erase(pollset_.begin() + index);
managers_.erase(managers_.begin() + index);
} else {
pollset_[index].events = to_bitmask(mgr->mask());
}
}
}
dirty_managers_.clear();
}
void multiplexer::handle(const socket_manager_ptr& mgr, int mask) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle()) << CAF_ARG(mask));
short multiplexer::handle(const socket_manager_ptr& mgr, short events,
short revents) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle()));
CAF_ASSERT(mgr != nullptr);
bool checkerror = true;
if ((mask & input_mask) != 0) {
if ((revents & input_mask) != 0) {
checkerror = false;
if (!mgr->handle_read_event())
if (!mgr->handle_read_event()) {
mgr->mask_del(operation::read);
events &= ~input_mask;
}
}
if ((mask & output_mask) != 0) {
if ((revents & output_mask) != 0) {
checkerror = false;
if (!mgr->handle_write_event())
if (!mgr->handle_write_event()) {
mgr->mask_del(operation::write);
events &= ~output_mask;
}
}
if (checkerror && ((mask & error_mask) != 0)) {
if (mask & POLLNVAL)
if (checkerror && ((revents & error_mask) != 0)) {
if (revents & POLLNVAL)
mgr->handle_error(sec::socket_invalid);
else if (mask & POLLHUP)
else if (revents & POLLHUP)
mgr->handle_error(sec::socket_disconnected);
else
mgr->handle_error(sec::socket_operation_failed);
mgr->mask_del(operation::read_write);
events = 0;
}
return events;
}
void multiplexer::add(socket_manager_ptr mgr) {
CAF_ASSERT(index_of(mgr) == -1);
pollfd new_entry{socket_cast<socket_id>(mgr->handle()),
to_bitmask(mgr->mask()), 0};
pollset_.emplace_back(new_entry);
managers_.emplace_back(std::move(mgr));
}
} // namespace net
} // namespace caf
void multiplexer::write_to_pipe(uint8_t opcode, const socket_manager_ptr& mgr) {
CAF_ASSERT(opcode == 0 || opcode == 1);
CAF_ASSERT(mgr != nullptr);
pollset_updater::msg_buf buf;
mgr->ref();
buf[0] = static_cast<byte>(opcode);
auto value = reinterpret_cast<intptr_t>(mgr.get());
memcpy(buf.data() + 1, &value, sizeof(intptr_t));
variant<size_t, sec> res;
{ // Lifetime scope of guard.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket)
res = write(write_handle_, make_span(buf));
else
res = sec::socket_invalid;
}
if (holds_alternative<sec>(res))
mgr->deref();
}
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/backend/test.hpp"
#include "caf/expected.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/basp/application.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/raise_error.hpp"
#include "caf/send.hpp"
namespace caf::net::backend {
test::test(middleman& mm)
: middleman_backend("test"), mm_(mm), proxies_(mm.system(), *this) {
// nop
}
test::~test() {
// nop
}
error test::init() {
return none;
}
endpoint_manager_ptr test::peer(const node_id& id) {
return get_peer(id).second;
}
void test::resolve(const uri& locator, const actor& listener) {
auto id = locator.authority_only();
if (id)
peer(make_node_id(*id))->resolve(locator, listener);
else
anon_send(listener, error(basp::ec::invalid_locator));
}
strong_actor_ptr test::make_proxy(node_id nid, actor_id aid) {
using impl_type = actor_proxy_impl;
using hdl_type = strong_actor_ptr;
actor_config cfg;
return make_actor<impl_type, hdl_type>(aid, nid, &mm_.system(), cfg,
peer(nid));
}
void test::set_last_hop(node_id*) {
// nop
}
test::peer_entry& test::emplace(const node_id& peer_id, stream_socket first,
stream_socket second) {
using transport_type = stream_transport<basp::application>;
nonblocking(second, true);
auto mpx = mm_.mpx();
basp::application app{proxies_};
auto mgr = make_endpoint_manager(mpx, mm_.system(),
transport_type{second, std::move(app)});
if (auto err = mgr->init()) {
CAF_LOG_ERROR("mgr->init() failed: " << mm_.system().render(err));
CAF_RAISE_ERROR("mgr->init() failed");
}
mpx->register_reading(mgr);
auto& result = peers_[peer_id];
result = std::make_pair(first, std::move(mgr));
return result;
}
test::peer_entry& test::get_peer(const node_id& id) {
auto i = peers_.find(id);
if (i != peers_.end())
return i->second;
auto sockets = make_stream_socket_pair();
if (!sockets) {
CAF_LOG_ERROR("make_stream_socket_pair failed: "
<< mm_.system().render(sockets.error()));
CAF_RAISE_ERROR("make_stream_socket_pair failed");
}
return emplace(id, sockets->first, sockets->second);
}
} // namespace caf::net::backend
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/endpoint_manager_queue.hpp"
namespace caf::net {
endpoint_manager_queue::element::~element() {
// nop
}
endpoint_manager_queue::event::event(uri locator, actor listener)
: element(element_type::event),
value(resolve_request{std::move(locator), std::move(listener)}) {
// nop
}
endpoint_manager_queue::event::event(node_id peer, actor_id proxy_id)
: element(element_type::event), value(new_proxy{peer, proxy_id}) {
// nop
}
endpoint_manager_queue::event::event(node_id observing_peer,
actor_id local_actor_id, error reason)
: element(element_type::event),
value(local_actor_down{observing_peer, local_actor_id, std::move(reason)}) {
// nop
}
endpoint_manager_queue::event::event(atom_value type, uint64_t id)
: element(element_type::event), value(timeout{type, id}) {
// nop
}
endpoint_manager_queue::event::~event() {
// nop
}
size_t endpoint_manager_queue::event::task_size() const noexcept {
return 1;
}
endpoint_manager_queue::message::message(mailbox_element_ptr msg,
strong_actor_ptr receiver,
std::vector<byte> payload)
: element(element_type::message),
msg(std::move(msg)),
receiver(std::move(receiver)),
payload(std::move(payload)) {
// nop
}
size_t endpoint_manager_queue::message::task_size() const noexcept {
return message_policy::task_size(*this);
}
endpoint_manager_queue::message::~message() {
// nop
}
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/middleman.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/detail/set_thread_name.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/raise_error.hpp"
#include "caf/send.hpp"
#include "caf/uri.hpp"
namespace caf::net {
middleman::middleman(actor_system& sys) : sys_(sys) {
mpx_ = std::make_shared<multiplexer>();
}
middleman::~middleman() {
// nop
}
void middleman::start() {
if (!get_or(config(), "middleman.manual-multiplexing", false)) {
auto mpx = mpx_;
auto sys_ptr = &system();
mpx_thread_ = std::thread{[mpx, sys_ptr] {
CAF_SET_LOGGER_SYS(sys_ptr);
detail::set_thread_name("caf.multiplexer");
sys_ptr->thread_started();
mpx->set_thread_id();
mpx->run();
sys_ptr->thread_terminates();
}};
}
}
void middleman::stop() {
mpx_->close_pipe();
if (mpx_thread_.joinable())
mpx_thread_.join();
}
void middleman::init(actor_system_config& cfg) {
if (auto err = mpx_->init()) {
CAF_LOG_ERROR("mgr->init() failed: " << system().render(err));
CAF_RAISE_ERROR("mpx->init failed");
}
if (auto node_uri = get_if<uri>(&cfg, "middleman.this-node")) {
auto this_node = make_node_id(std::move(*node_uri));
sys_.node_.swap(this_node);
} else {
CAF_RAISE_ERROR("no valid entry for middleman.this-node found");
}
for (auto& backend : backends_)
if (auto err = backend->init()) {
CAF_LOG_ERROR("failed to initialize backend: " << system().render(err));
CAF_RAISE_ERROR("failed to initialize backend");
}
}
middleman::module::id_t middleman::id() const {
return module::network_manager;
}
void* middleman::subtype_ptr() {
return this;
}
void middleman::resolve(const uri& locator, const actor& listener) {
auto ptr = backend(locator.scheme());
if (ptr != nullptr)
ptr->resolve(locator, listener);
else
anon_send(listener, error{basp::ec::invalid_scheme});
}
middleman_backend* middleman::backend(string_view scheme) const noexcept {
auto predicate = [&](const middleman_backend_ptr& ptr) {
return ptr->id() == scheme;
};
auto i = std::find_if(backends_.begin(), backends_.end(), predicate);
if (i != backends_.end())
return i->get();
return nullptr;
}
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/middleman_backend.hpp"
namespace caf::net {
middleman_backend::middleman_backend(std::string id) : id_(std::move(id)) {
// nop
}
middleman_backend::~middleman_backend() {
// nop
}
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/packet_writer.hpp"
namespace caf::net {
packet_writer::~packet_writer() {
// nop
}
} // namespace caf::net
......@@ -54,8 +54,7 @@ uint16_t port_of(sockaddr& what) {
} // namespace
namespace caf {
namespace net {
namespace caf::net {
#if defined(CAF_MACOS) || defined(CAF_IOS) || defined(CAF_BSD)
# define CAF_HAS_NOSIGPIPE_SOCKET_FLAG
......@@ -80,7 +79,6 @@ error allow_udp_connreset(network_socket x, bool new_value) {
}
#else // CAF_WINDOWS
error allow_sigpipe(network_socket x, bool new_value) {
# ifdef CAF_HAS_NOSIGPIPE_SOCKET_FLAG
int value = new_value ? 0 : 1;
......@@ -192,5 +190,4 @@ void shutdown(network_socket x) {
::shutdown(x.id, 2);
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -34,8 +34,7 @@
#include "caf/span.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -102,5 +101,4 @@ check_pipe_socket_io_res(std::make_signed<size_t>::type res) {
return check_stream_socket_io_res(res);
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -23,12 +23,11 @@
#include "caf/span.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
namespace caf::net {
pollset_updater::pollset_updater(pipe_socket read_handle,
multiplexer_ptr parent)
: super(read_handle, std::move(parent)), buf_size_(0) {
const multiplexer_ptr& parent)
: super(read_handle, parent), buf_size_(0) {
mask_ = operation::read;
nonblocking(read_handle, true);
}
......@@ -45,10 +44,18 @@ bool pollset_updater::handle_read_event() {
buf_size_ += *num_bytes;
if (buf_.size() == buf_size_) {
buf_size_ = 0;
auto value = *reinterpret_cast<intptr_t*>(buf_.data());
auto opcode = static_cast<uint8_t>(buf_[0]);
intptr_t value;
memcpy(&value, buf_.data() + 1, sizeof(intptr_t));
socket_manager_ptr mgr{reinterpret_cast<socket_manager*>(value), false};
if (auto ptr = parent_.lock())
ptr->update(mgr);
if (auto ptr = parent_.lock()) {
if (opcode == 0) {
ptr->register_reading(mgr);
} else {
CAF_ASSERT(opcode == 1);
ptr->register_writing(mgr);
}
}
}
} else {
return get<sec>(res) == sec::unavailable_or_would_block;
......@@ -64,5 +71,4 @@ void pollset_updater::handle_error(sec) {
// nop
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -27,8 +27,7 @@
#include "caf/sec.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -180,5 +179,4 @@ error nonblocking(socket x, bool new_value) {
#endif // CAF_WINDOWS
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -21,8 +21,7 @@
#include "caf/config.hpp"
#include "caf/net/multiplexer.hpp"
namespace caf {
namespace net {
namespace caf::net {
socket_manager::socket_manager(socket handle, const multiplexer_ptr& parent)
: handle_(handle), mask_(operation::none), parent_(parent) {
......@@ -34,33 +33,38 @@ socket_manager::~socket_manager() {
close(handle_);
}
operation socket_manager::mask() const noexcept {
return mask_.load();
}
bool socket_manager::mask_add(operation flag) noexcept {
CAF_ASSERT(flag != operation::none);
auto x = mask();
while ((x & flag) != flag)
if (mask_.compare_exchange_strong(x, x | flag)) {
if (auto ptr = parent_.lock())
ptr->update(this);
return true;
}
return false;
if ((x & flag) == flag)
return false;
mask_ = x | flag;
return true;
}
bool socket_manager::mask_del(operation flag) noexcept {
CAF_ASSERT(flag != operation::none);
auto x = mask();
while ((x & flag) != operation::none)
if (mask_.compare_exchange_strong(x, x & ~flag)) {
if (auto ptr = parent_.lock())
ptr->update(this);
return true;
}
return false;
if ((x & flag) == operation::none)
return false;
mask_ = x & ~flag;
return true;
}
void socket_manager::register_reading() {
if ((mask() & operation::read) == operation::read)
return;
auto ptr = parent_.lock();
if (ptr != nullptr)
ptr->register_reading(this);
}
void socket_manager::register_writing() {
if ((mask() & operation::write) == operation::write)
return;
auto ptr = parent_.lock();
if (ptr != nullptr)
ptr->register_writing(this);
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -29,8 +29,11 @@
#include "caf/span.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
#ifdef CAF_POSIX
# include <sys/uio.h>
#endif
namespace caf::net {
#ifdef CAF_WINDOWS
......@@ -117,7 +120,7 @@ expected<std::pair<stream_socket, stream_socket>> make_stream_socket_pair() {
accept(listener, nullptr, nullptr));
close(socket{listener});
guard.disable();
return std::make_pair(read_fd, write_fd);
return std::make_pair(stream_socket{read_fd}, stream_socket{write_fd});
}
error keepalive(stream_socket x, bool new_value) {
......@@ -177,6 +180,47 @@ variant<size_t, sec> write(stream_socket x, span<const byte> buf) {
return check_stream_socket_io_res(res);
}
#ifdef CAF_WINDOWS
variant<size_t, sec> write(stream_socket x,
std::initializer_list<span<const byte>> bufs) {
CAF_ASSERT(bufs.size() < 10);
WSABUF buf_array[10];
auto convert = [](span<const byte> buf) {
auto data = const_cast<byte*>(buf.data());
return WSABUF{static_cast<ULONG>(buf.size()),
reinterpret_cast<CHAR*>(data)};
};
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
DWORD bytes_sent = 0;
auto res = WSASend(x.id, buf_array, static_cast<DWORD>(bufs.size()),
&bytes_sent, 0, nullptr, nullptr);
if (res != 0) {
auto code = last_socket_error();
if (code == std::errc::operation_would_block
|| code == std::errc::resource_unavailable_try_again)
return sec::unavailable_or_would_block;
return sec::socket_operation_failed;
}
return static_cast<size_t>(bytes_sent);
}
#else // CAF_WINDOWS
variant<size_t, sec> write(stream_socket x,
std::initializer_list<span<const byte>> bufs) {
CAF_ASSERT(bufs.size() < 10);
iovec buf_array[10];
auto convert = [](span<const byte> buf) {
return iovec{const_cast<byte*>(buf.data()), buf.size()};
};
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
auto res = writev(x.id, buf_array, static_cast<int>(bufs.size()));
return check_stream_socket_io_res(res);
}
#endif // CAF_WINDOWS
variant<size_t, sec>
check_stream_socket_io_res(std::make_signed<size_t>::type res) {
if (res == 0)
......@@ -191,5 +235,4 @@ check_stream_socket_io_res(std::make_signed<size_t>::type res) {
return static_cast<size_t>(res);
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -31,8 +31,7 @@
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/sec.hpp"
namespace caf {
namespace net {
namespace caf::net {
namespace {
......@@ -63,9 +62,10 @@ expected<tcp_accept_socket> new_tcp_acceptor_impl(uint16_t port,
socktype |= SOCK_CLOEXEC;
#endif
CAF_NET_SYSCALL("socket", fd, ==, -1, ::socket(Family, socktype, 0));
child_process_inherit(fd, false);
tcp_accept_socket sock{fd};
// sguard closes the socket in case of exception
auto sguard = make_socket_guard(tcp_accept_socket{fd});
child_process_inherit(sock, false);
if (reuse_addr) {
int on = 1;
CAF_NET_SYSCALL("setsockopt", tmp1, !=, 0,
......@@ -79,7 +79,7 @@ expected<tcp_accept_socket> new_tcp_acceptor_impl(uint16_t port,
memset(&sa, 0, sizeof(sockaddr_type));
detail::family_of(sa) = Family;
if (any)
if (auto err = set_inaddr_any(fd, sa))
if (auto err = set_inaddr_any(sock, sa))
return err;
CAF_NET_SYSCALL("inet_pton", tmp, !=, 1,
inet_pton(Family, addr, &detail::addr_of(sa)));
......@@ -102,7 +102,7 @@ expected<tcp_accept_socket> make_tcp_accept_socket(ip_endpoint node,
auto p = make_acceptor(node.port(), addr.c_str(), reuse_addr,
node.address().zero());
if (!p) {
CAF_LOG_WARNING("could not open tcp socket on: " << to_string(node));
CAF_LOG_WARNING("could not create tcp socket for: " << to_string(node));
return make_error(sec::cannot_open_port, "tcp socket creation failed",
to_string(node));
}
......@@ -120,7 +120,7 @@ make_tcp_accept_socket(const uri::authority_type& node, bool reuse_addr) {
auto host = get<std::string>(node.host);
auto addrs = ip::resolve(host);
if (addrs.empty())
return make_error(sec::cannot_open_port, "No local interface available",
return make_error(sec::cannot_open_port, "no local interface available",
to_string(node));
for (auto& addr : addrs) {
if (auto sock = make_tcp_accept_socket(ip_endpoint{addr, node.port},
......@@ -132,17 +132,16 @@ make_tcp_accept_socket(const uri::authority_type& node, bool reuse_addr) {
}
expected<tcp_stream_socket> accept(tcp_accept_socket x) {
auto sck = ::accept(x.id, nullptr, nullptr);
if (sck == net::invalid_socket_id) {
auto sock = ::accept(x.id, nullptr, nullptr);
if (sock == net::invalid_socket_id) {
auto err = net::last_socket_error();
if (err != std::errc::operation_would_block
&& err != std::errc::resource_unavailable_try_again) {
return caf::make_error(sec::unavailable_or_would_block);
}
return caf::make_error(sec::socket_operation_failed);
return caf::make_error(sec::socket_operation_failed, "tcp accept failed");
}
return tcp_stream_socket{sck};
return tcp_stream_socket{sock};
}
} // namespace net
} // namespace caf
} // namespace caf::net
......@@ -29,8 +29,7 @@
#include "caf/sec.hpp"
#include "caf/variant.hpp"
namespace caf {
namespace net {
namespace caf::net {
namespace {
......@@ -53,22 +52,24 @@ bool ip_connect(stream_socket fd, std::string host, uint16_t port) {
} // namespace
expected<tcp_stream_socket> make_connected_tcp_stream_socket(ip_endpoint node) {
CAF_LOG_DEBUG("try to connect to: " << to_string(node));
CAF_LOG_DEBUG("tcp connect to: " << to_string(node));
auto proto = node.address().embeds_v4() ? AF_INET : AF_INET6;
int socktype = SOCK_STREAM;
#ifdef SOCK_CLOEXEC
socktype |= SOCK_CLOEXEC;
#endif
CAF_NET_SYSCALL("socket", fd, ==, -1, ::socket(proto, socktype, 0));
child_process_inherit(fd, false);
auto sguard = make_socket_guard(tcp_stream_socket{fd});
tcp_stream_socket sock{fd};
child_process_inherit(sock, false);
auto sguard = make_socket_guard(sock);
if (proto == AF_INET6) {
if (ip_connect<AF_INET6>(fd, to_string(node.address()), node.port())) {
if (ip_connect<AF_INET6>(sock, to_string(node.address()), node.port())) {
CAF_LOG_INFO("successfully connected to (IPv6):" << to_string(node));
return sguard.release();
}
} else if (ip_connect<AF_INET>(fd, to_string(node.address().embedded_v4()),
} else if (ip_connect<AF_INET>(sock, to_string(node.address().embedded_v4()),
node.port())) {
CAF_LOG_INFO("successfully connected to (IPv4):" << to_string(node));
return sguard.release();
}
CAF_LOG_WARNING("could not connect to: " << to_string(node));
......@@ -86,7 +87,7 @@ make_connected_tcp_stream_socket(const uri::authority_type& node) {
else if (auto addr = get_if<ip_address>(&node.host))
addrs.push_back(*addr);
if (addrs.empty())
return make_error(sec::cannot_connect_to_node, "empty nodeority");
return make_error(sec::cannot_connect_to_node, "empty authority");
for (auto& addr : addrs) {
if (auto sock = make_connected_tcp_stream_socket(ip_endpoint{addr, port}))
return *sock;
......@@ -94,5 +95,4 @@ make_connected_tcp_stream_socket(const uri::authority_type& node) {
return make_error(sec::cannot_connect_to_node, to_string(node));
}
} // namespace net
} // namespace caf
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/byte.hpp"
#include "caf/detail/convert_ip_endpoint.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/logger.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/span.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
error allow_connreset(udp_datagram_socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
DWORD bytes_returned = 0;
CAF_NET_SYSCALL("WSAIoctl", res, !=, 0,
WSAIoctl(x.id, _WSAIOW(IOC_VENDOR, 12), &new_value,
sizeof(new_value), NULL, 0, &bytes_returned, NULL,
NULL));
return none;
}
#else // CAF_WINDOWS
error allow_connreset(udp_datagram_socket x, bool) {
if (socket_cast<net::socket>(x) == invalid_socket)
return sec::socket_invalid;
// nop; SIO_UDP_CONNRESET only exists on Windows
return none;
}
#endif // CAF_WINDOWS
expected<std::pair<udp_datagram_socket, uint16_t>>
make_udp_datagram_socket(ip_endpoint ep, bool reuse_addr) {
CAF_LOG_TRACE(CAF_ARG(ep));
sockaddr_storage addr = {};
detail::convert(ep, addr);
CAF_NET_SYSCALL("socket", fd, ==, invalid_socket_id,
::socket(addr.ss_family, SOCK_DGRAM, 0));
udp_datagram_socket sock{fd};
auto sguard = make_socket_guard(sock);
socklen_t len = (addr.ss_family == AF_INET) ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
if (reuse_addr) {
int on = 1;
CAF_NET_SYSCALL("setsockopt", tmp1, !=, 0,
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<setsockopt_ptr>(&on),
static_cast<socket_size_type>(sizeof(on))));
}
CAF_NET_SYSCALL("bind", err, !=, 0,
::bind(sock.id, reinterpret_cast<sockaddr*>(&addr), len));
CAF_NET_SYSCALL("getsockname", erro, !=, 0,
getsockname(sock.id, reinterpret_cast<sockaddr*>(&addr),
&len));
CAF_LOG_DEBUG(CAF_ARG(sock.id));
auto port = addr.ss_family == AF_INET
? reinterpret_cast<sockaddr_in*>(&addr)->sin_port
: reinterpret_cast<sockaddr_in6*>(&addr)->sin6_port;
return std::make_pair(sguard.release(), port);
}
variant<std::pair<size_t, ip_endpoint>, sec> read(udp_datagram_socket x,
span<byte> buf) {
sockaddr_storage addr = {};
socklen_t len = sizeof(sockaddr_storage);
auto res = ::recvfrom(x.id, reinterpret_cast<socket_recv_ptr>(buf.data()),
buf.size(), 0, reinterpret_cast<sockaddr*>(&addr),
&len);
auto ret = check_udp_datagram_socket_io_res(res);
if (auto num_bytes = get_if<size_t>(&ret)) {
CAF_LOG_INFO_IF(*num_bytes == 0, "Received empty datagram");
CAF_LOG_WARNING_IF(*num_bytes > buf.size(),
"recvfrom cut of message, only received "
<< CAF_ARG(buf.size()) << " of " << CAF_ARG(num_bytes)
<< " bytes");
ip_endpoint ep;
if (auto err = detail::convert(addr, ep)) {
CAF_ASSERT(err.category() == atom("system"));
return static_cast<sec>(err.code());
}
return std::pair<size_t, ip_endpoint>(*num_bytes, ep);
} else {
return get<sec>(ret);
}
}
variant<size_t, sec> write(udp_datagram_socket x, span<const byte> buf,
ip_endpoint ep) {
sockaddr_storage addr = {};
detail::convert(ep, addr);
auto len = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
auto res = ::sendto(x.id, reinterpret_cast<socket_send_ptr>(buf.data()),
buf.size(), 0, reinterpret_cast<sockaddr*>(&addr), len);
auto ret = check_udp_datagram_socket_io_res(res);
if (auto num_bytes = get_if<size_t>(&ret))
return *num_bytes;
else
return get<sec>(ret);
}
#ifdef CAF_WINDOWS
variant<size_t, sec> write(udp_datagram_socket x, span<std::vector<byte>*> bufs,
ip_endpoint ep) {
CAF_ASSERT(bufs.size() < 10);
WSABUF buf_array[10];
auto convert = [](std::vector<byte>* buf) {
return WSABUF{static_cast<ULONG>(buf->size()),
reinterpret_cast<CHAR*>(buf->data())};
};
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
sockaddr_storage addr = {};
detail::convert(ep, addr);
auto len = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
DWORD bytes_sent = 0;
auto res = WSASendTo(x.id, buf_array, static_cast<DWORD>(bufs.size()),
&bytes_sent, 0, reinterpret_cast<sockaddr*>(&addr), len,
nullptr, nullptr);
if (res != 0) {
auto code = last_socket_error();
if (code == std::errc::operation_would_block
|| code == std::errc::resource_unavailable_try_again)
return sec::unavailable_or_would_block;
return sec::socket_operation_failed;
}
return static_cast<size_t>(bytes_sent);
}
#else // CAF_WINDOWS
variant<size_t, sec> write(udp_datagram_socket x, span<std::vector<byte>*> bufs,
ip_endpoint ep) {
CAF_ASSERT(bufs.size() < 10);
auto convert = [](std::vector<byte>* buf) {
return iovec{buf->data(), buf->size()};
};
sockaddr_storage addr = {};
detail::convert(ep, addr);
iovec buf_array[10];
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
msghdr message = {};
memset(&message, 0, sizeof(msghdr));
message.msg_name = &addr;
message.msg_namelen = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
message.msg_iov = buf_array;
message.msg_iovlen = bufs.size();
auto res = sendmsg(x.id, &message, 0);
return check_udp_datagram_socket_io_res(res);
}
#endif // CAF_WINDOWS
variant<size_t, sec>
check_udp_datagram_socket_io_res(std::make_signed<size_t>::type res) {
if (res < 0) {
auto code = last_socket_error();
if (code == std::errc::operation_would_block
|| code == std::errc::resource_unavailable_try_again)
return sec::unavailable_or_would_block;
return sec::socket_operation_failed;
}
return static_cast<size_t>(res);
}
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/basp/worker.hpp"
#include "caf/actor_system.hpp"
#include "caf/byte.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/scheduler/abstract_coordinator.hpp"
namespace caf::net::basp {
// -- constructors, destructors, and assignment operators ----------------------
worker::worker(hub_type& hub, message_queue& queue, proxy_registry& proxies)
: hub_(&hub), queue_(&queue), proxies_(&proxies), system_(&proxies.system()) {
CAF_IGNORE_UNUSED(pad_);
}
worker::~worker() {
// nop
}
// -- management ---------------------------------------------------------------
void worker::launch(const node_id& last_hop, const basp::header& hdr,
span<const byte> payload) {
msg_id_ = queue_->new_id();
last_hop_ = last_hop;
memcpy(&hdr_, &hdr, sizeof(basp::header));
payload_.assign(payload.begin(), payload.end());
ref();
system_->scheduler().enqueue(this);
}
// -- implementation of resumable ----------------------------------------------
resumable::resume_result worker::resume(execution_unit* ctx, size_t) {
ctx->proxy_registry_ptr(proxies_);
handle_remote_message(ctx);
hub_->push(this);
return resumable::awaiting_message;
}
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE basp.application
#include "caf/net/basp/application.hpp"
#include "caf/test/dsl.hpp"
#include <vector>
#include "caf/byte.hpp"
#include "caf/forwarding_actor_proxy.hpp"
#include "caf/net/basp/connection_state.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/none.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
#define REQUIRE_OK(statement) \
if (auto err = statement) \
CAF_FAIL("failed to serialize data: " << sys.render(err));
namespace {
struct fixture : test_coordinator_fixture<>,
proxy_registry::backend,
basp::application::test_tag,
public packet_writer {
using buffer_type = std::vector<byte>;
fixture() : proxies(sys, *this), app(proxies) {
REQUIRE_OK(app.init(*this));
uri mars_uri;
REQUIRE_OK(parse("tcp://mars", mars_uri));
mars = make_node_id(mars_uri);
}
template <class... Ts>
buffer_type to_buf(const Ts&... xs) {
buffer_type buf;
serializer_impl<buffer_type> sink{system(), buf};
REQUIRE_OK(sink(xs...));
return buf;
}
template <class... Ts>
void set_input(const Ts&... xs) {
input = to_buf(xs...);
}
void handle_handshake() {
CAF_CHECK_EQUAL(app.state(),
basp::connection_state::await_handshake_header);
auto payload = to_buf(mars, defaults::middleman::app_identifiers);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()),
basp::version});
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(),
basp::connection_state::await_handshake_payload);
REQUIRE_OK(app.handle_data(*this, payload));
}
void consume_handshake() {
if (output.size() < basp::header_size)
CAF_FAIL("BASP application did not write a handshake header");
auto hdr = basp::header::from_bytes(output);
if (hdr.type != basp::message_type::handshake || hdr.payload_len == 0
|| hdr.operation_data != basp::version)
CAF_FAIL("invalid handshake header");
node_id nid;
std::vector<std::string> app_ids;
binary_deserializer source{sys, output};
source.skip(basp::header_size);
if (auto err = source(nid, app_ids))
CAF_FAIL("unable to deserialize payload: " << sys.render(err));
if (source.remaining() > 0)
CAF_FAIL("trailing bytes after reading payload");
output.clear();
}
actor_system& system() {
return sys;
}
fixture& transport() {
return *this;
}
endpoint_manager& manager() {
CAF_FAIL("unexpected function call");
}
buffer_type next_payload_buffer() override {
return {};
}
buffer_type next_header_buffer() override {
return {};
}
template <class... Ts>
void configure_read(Ts...) {
// nop
}
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override {
using impl_type = forwarding_actor_proxy;
using hdl_type = strong_actor_ptr;
actor_config cfg;
return make_actor<impl_type, hdl_type>(aid, nid, &sys, cfg, self);
}
void set_last_hop(node_id*) override {
// nop
}
protected:
void write_impl(span<buffer_type*> buffers) override {
for (auto buf : buffers)
output.insert(output.end(), buf->begin(), buf->end());
}
buffer_type input;
buffer_type output;
node_id mars;
proxy_registry proxies;
basp::application app;
};
} // namespace
#define MOCK(kind, op, ...) \
do { \
auto payload = to_buf(__VA_ARGS__); \
set_input(basp::header{kind, static_cast<uint32_t>(payload.size()), op}); \
if (auto err = app.handle_data(*this, input)) \
CAF_FAIL("application-under-test failed to process header: " \
<< sys.render(err)); \
if (auto err = app.handle_data(*this, payload)) \
CAF_FAIL("application-under-test failed to process payload: " \
<< sys.render(err)); \
} while (false)
#define RECEIVE(msg_type, op_data, ...) \
do { \
binary_deserializer source{sys, output}; \
basp::header hdr; \
if (auto err = source(hdr, __VA_ARGS__)) \
CAF_FAIL("failed to receive data: " << sys.render(err)); \
if (source.remaining() != 0) \
CAF_FAIL("unable to read entire message, " << source.remaining() \
<< " bytes left in buffer"); \
CAF_CHECK_EQUAL(hdr.type, msg_type); \
CAF_CHECK_EQUAL(hdr.operation_data, op_data); \
output.clear(); \
} while (false)
CAF_TEST_FIXTURE_SCOPE(application_tests, fixture)
CAF_TEST(missing handshake) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
set_input(basp::header{basp::message_type::heartbeat, 0, 0});
CAF_CHECK_EQUAL(app.handle_data(*this, input), basp::ec::missing_handshake);
}
CAF_TEST(version mismatch) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
set_input(basp::header{basp::message_type::handshake, 0, 0});
CAF_CHECK_EQUAL(app.handle_data(*this, input), basp::ec::version_mismatch);
}
CAF_TEST(missing payload in handshake) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
set_input(basp::header{basp::message_type::handshake, 0, basp::version});
CAF_CHECK_EQUAL(app.handle_data(*this, input), basp::ec::missing_payload);
}
CAF_TEST(invalid handshake) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
node_id no_nid;
std::vector<std::string> no_ids;
auto payload = to_buf(no_nid, no_ids);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_payload);
CAF_CHECK_EQUAL(app.handle_data(*this, payload), basp::ec::invalid_handshake);
}
CAF_TEST(app identifier mismatch) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
std::vector<std::string> wrong_ids{"YOLO!!!"};
auto payload = to_buf(mars, wrong_ids);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_payload);
CAF_CHECK_EQUAL(app.handle_data(*this, payload),
basp::ec::app_identifiers_mismatch);
}
CAF_TEST(repeated handshake) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
node_id no_nid;
std::vector<std::string> no_ids;
auto payload = to_buf(no_nid, no_ids);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
CAF_CHECK_EQUAL(app.handle_data(*this, input), none);
CAF_CHECK_EQUAL(app.handle_data(*this, payload),
basp::ec::unexpected_handshake);
}
CAF_TEST(actor message) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
CAF_REQUIRE_EQUAL(self->mailbox().size(), 0u);
MOCK(basp::message_type::actor_message, make_message_id().integer_value(),
mars, actor_id{42}, self->id(), std::vector<strong_actor_ptr>{},
make_message("hello world!"));
allow((atom_value, strong_actor_ptr),
from(_).to(self).with(atom("monitor"), _));
expect((std::string), from(_).to(self).with("hello world!"));
}
CAF_TEST(resolve request without result) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, std::string{"foo/bar"});
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, 0u);
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve request on id with result) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
auto path = "id/" + std::to_string(self->id());
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, path);
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, self->id());
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve request on name with result) {
handle_handshake();
consume_handshake();
sys.registry().put(atom("foo"), self);
std::string path = "name/foo";
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, path);
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, self->id());
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve response with invalid actor handle) {
handle_handshake();
consume_handshake();
app.resolve(*this, "foo/bar", self);
std::string path;
RECEIVE(basp::message_type::resolve_request, 1u, path);
CAF_CHECK_EQUAL(path, "foo/bar");
actor_id aid = 0;
std::set<std::string> ifs;
MOCK(basp::message_type::resolve_response, 1u, aid, ifs);
self->receive([&](strong_actor_ptr& hdl, std::set<std::string>& hdl_ifs) {
CAF_CHECK_EQUAL(hdl, nullptr);
CAF_CHECK_EQUAL(ifs, hdl_ifs);
});
}
CAF_TEST(resolve response with valid actor handle) {
handle_handshake();
consume_handshake();
app.resolve(*this, "foo/bar", self);
std::string path;
RECEIVE(basp::message_type::resolve_request, 1u, path);
CAF_CHECK_EQUAL(path, "foo/bar");
actor_id aid = 42;
std::set<std::string> ifs;
MOCK(basp::message_type::resolve_response, 1u, aid, ifs);
self->receive([&](strong_actor_ptr& hdl, std::set<std::string>& hdl_ifs) {
CAF_REQUIRE(hdl != nullptr);
CAF_CHECK_EQUAL(ifs, hdl_ifs);
CAF_CHECK_EQUAL(hdl->id(), aid);
});
}
CAF_TEST(heartbeat message) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
auto bytes = to_bytes(basp::header{basp::message_type::heartbeat, 0, 0});
set_input(bytes);
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE convert_ip_endpoint
#include "caf/detail/convert_ip_endpoint.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include <cstring>
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv6_endpoint.hpp"
using namespace caf;
using namespace caf::detail;
namespace {
struct fixture : host_fixture {
fixture() : host_fixture() {
memset(&sockaddr4_src, 0, sizeof(sockaddr_storage));
memset(&sockaddr6_src, 0, sizeof(sockaddr_storage));
auto sockaddr6_ptr = reinterpret_cast<sockaddr_in6*>(&sockaddr6_src);
sockaddr6_ptr->sin6_family = AF_INET6;
sockaddr6_ptr->sin6_port = htons(23);
sockaddr6_ptr->sin6_addr = in6addr_loopback;
auto sockaddr4_ptr = reinterpret_cast<sockaddr_in*>(&sockaddr4_src);
sockaddr4_ptr->sin_family = AF_INET;
sockaddr4_ptr->sin_port = htons(23);
sockaddr4_ptr->sin_addr.s_addr = INADDR_LOOPBACK;
}
sockaddr_storage sockaddr6_src;
sockaddr_storage sockaddr4_src;
sockaddr_storage dst;
ip_endpoint ep_src;
ip_endpoint ep_dst;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(convert_ip_endpoint_tests, fixture)
CAF_TEST(sockaddr_in6 roundtrip) {
ip_endpoint tmp;
CAF_MESSAGE("converting sockaddr_in6 to ip_endpoint");
CAF_CHECK_EQUAL(convert(sockaddr6_src, tmp), none);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in6");
convert(tmp, dst);
CAF_CHECK_EQUAL(memcmp(&sockaddr6_src, &dst, sizeof(sockaddr_storage)), 0);
}
CAF_TEST(ipv6_endpoint roundtrip) {
sockaddr_storage tmp = {};
if (auto err = detail::parse("[::1]:55555", ep_src))
CAF_FAIL("unable to parse input: " << err);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in6");
convert(ep_src, tmp);
CAF_MESSAGE("converting sockaddr_in6 to ip_endpoint");
CAF_CHECK_EQUAL(convert(tmp, ep_dst), none);
CAF_CHECK_EQUAL(ep_src, ep_dst);
}
CAF_TEST(sockaddr_in4 roundtrip) {
ip_endpoint tmp;
CAF_MESSAGE("converting sockaddr_in to ip_endpoint");
CAF_CHECK_EQUAL(convert(sockaddr4_src, tmp), none);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in");
convert(tmp, dst);
CAF_CHECK_EQUAL(memcmp(&sockaddr4_src, &dst, sizeof(sockaddr_storage)), 0);
}
CAF_TEST(ipv4_endpoint roundtrip) {
sockaddr_storage tmp = {};
if (auto err = detail::parse("127.0.0.1:55555", ep_src))
CAF_FAIL("unable to parse input: " << err);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in");
convert(ep_src, tmp);
CAF_MESSAGE("converting sockaddr_in to ip_endpoint");
CAF_CHECK_EQUAL(convert(tmp, ep_dst), none);
CAF_CHECK_EQUAL(ep_src, ep_dst);
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE datagram_transport
#include "caf/net/datagram_transport.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
using namespace caf::net::ip;
namespace {
constexpr string_view hello_manager = "hello manager!";
class dummy_application_factory;
struct fixture : test_coordinator_fixture<>, host_fixture {
using buffer_type = std::vector<byte>;
using buffer_ptr = std::shared_ptr<buffer_type>;
fixture() : shared_buf(std::make_shared<buffer_type>(1024)) {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << sys.render(err));
mpx->set_thread_id();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto addresses = local_addresses("localhost");
CAF_CHECK(!addresses.empty());
ep = ip_endpoint(*addresses.begin(), 0);
auto send_pair = unbox(make_udp_datagram_socket(ep));
send_socket = send_pair.first;
auto receive_pair = unbox(make_udp_datagram_socket(ep));
recv_socket = receive_pair.first;
ep.port(htons(receive_pair.second));
CAF_MESSAGE("sending message to " << CAF_ARG(ep));
if (auto err = nonblocking(recv_socket, true))
CAF_FAIL("nonblocking() returned an error: " << err);
}
~fixture() {
close(send_socket);
close(recv_socket);
}
bool handle_io_event() override {
return mpx->poll_once(false);
}
error read_from_socket(udp_datagram_socket sock, buffer_type& buf) {
uint8_t receive_attempts = 0;
variant<std::pair<size_t, ip_endpoint>, sec> read_ret;
do {
read_ret = read(sock, make_span(buf));
if (auto read_res = get_if<std::pair<size_t, ip_endpoint>>(&read_ret)) {
buf.resize(read_res->first);
} else if (get<sec>(read_ret) != sec::unavailable_or_would_block) {
return make_error(get<sec>(read_ret), "read failed");
}
if (++receive_attempts > 100)
return make_error(sec::runtime_error,
"too many unavailable_or_would_blocks");
} while (read_ret.index() != 0);
return none;
}
multiplexer_ptr mpx;
buffer_ptr shared_buf;
ip_endpoint ep;
udp_datagram_socket send_socket;
udp_datagram_socket recv_socket;
};
class dummy_application {
using buffer_type = std::vector<byte>;
using buffer_ptr = std::shared_ptr<buffer_type>;
public:
explicit dummy_application(buffer_ptr rec_buf)
: rec_buf_(std::move(rec_buf)){
// nop
};
template <class Parent>
error init(Parent&) {
return none;
}
template <class Transport>
void write_message(Transport& transport,
std::unique_ptr<endpoint_manager_queue::message> msg) {
transport.write_packet(msg->payload);
}
template <class Parent>
error handle_data(Parent&, span<const byte> data) {
rec_buf_->clear();
rec_buf_->insert(rec_buf_->begin(), data.begin(), data.end());
return none;
}
template <class Parent>
void resolve(Parent& parent, string_view path, const actor& listener) {
actor_id aid = 42;
auto uri = unbox(make_uri("test:/id/42"));
auto nid = make_node_id(uri);
actor_config cfg;
endpoint_manager_ptr ptr{&parent.manager()};
auto p = make_actor<actor_proxy_impl, strong_actor_ptr>(aid, nid,
&parent.system(),
cfg,
std::move(ptr));
anon_send(listener, resolve_atom::value,
std::string{path.begin(), path.end()}, p);
}
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
template <class Parent>
void timeout(Parent&, atom_value, uint64_t) {
// nop
}
void handle_error(sec sec) {
CAF_FAIL("handle_error called: " << to_string(sec));
}
static expected<buffer_type> serialize(actor_system& sys,
const type_erased_tuple& x) {
buffer_type result;
serializer_impl<buffer_type> sink{sys, result};
if (auto err = message::save(sink, x))
return err;
return result;
}
private:
buffer_ptr rec_buf_;
};
class dummy_application_factory {
using buffer_ptr = std::shared_ptr<std::vector<byte>>;
public:
using application_type = dummy_application;
explicit dummy_application_factory(buffer_ptr buf) : buf_(std::move(buf)) {
// nop
}
dummy_application make() {
return dummy_application{buf_};
}
private:
buffer_ptr buf_;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(datagram_transport_tests, fixture)
CAF_TEST(receive) {
using transport_type = datagram_transport<dummy_application_factory>;
if (auto err = nonblocking(recv_socket, true))
CAF_FAIL("nonblocking() returned an error: " << sys.render(err));
auto mgr = make_endpoint_manager(mpx, sys,
transport_type{recv_socket,
dummy_application_factory{
shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
auto mgr_impl = mgr.downcast<endpoint_manager_impl<transport_type>>();
CAF_CHECK(mgr_impl != nullptr);
auto& transport = mgr_impl->transport();
transport.configure_read(net::receive_policy::exactly(hello_manager.size()));
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
CAF_CHECK_EQUAL(write(send_socket, as_bytes(make_span(hello_manager)), ep),
hello_manager.size());
CAF_MESSAGE("wrote " << hello_manager.size() << " bytes.");
run();
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(shared_buf->data()),
shared_buf->size()),
hello_manager);
}
CAF_TEST(resolve and proxy communication) {
using transport_type = datagram_transport<dummy_application_factory>;
buffer_type recv_buf(1024);
auto uri = unbox(make_uri("test:/id/42"));
auto mgr = make_endpoint_manager(mpx, sys,
transport_type{send_socket,
dummy_application_factory{
shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
auto mgr_impl = mgr.downcast<endpoint_manager_impl<transport_type>>();
CAF_CHECK(mgr_impl != nullptr);
auto& transport = mgr_impl->transport();
transport.add_new_worker(make_node_id(uri), ep);
run();
mgr->resolve(uri, self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
CAF_MESSAGE("got a proxy, send a message to it");
self->send(actor_cast<actor>(p), "hello proxy!");
},
after(std::chrono::seconds(0)) >>
[&] { CAF_FAIL("manager did not respond with a proxy."); });
run();
CAF_CHECK_EQUAL(read_from_socket(recv_socket, recv_buf), none);
CAF_MESSAGE("receive buffer contains " << recv_buf.size() << " bytes");
message msg;
binary_deserializer source{sys, recv_buf};
CAF_CHECK_EQUAL(source(msg), none);
if (msg.match_elements<std::string>())
CAF_CHECK_EQUAL(msg.get_as<std::string>(0), "hello proxy!");
else
CAF_ERROR("expected a string, got: " << to_string(msg));
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -20,10 +20,9 @@
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
#include "caf/byte.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
......@@ -46,12 +45,14 @@ string_view hello_test{"hello test!"};
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
mpx = std::make_shared<multiplexer>();
mpx->set_thread_id();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << sys.render(err));
if (mpx->num_socket_managers() != 1)
CAF_FAIL("mpx->num_socket_managers() != 1");
}
bool handle_io_event() override {
mpx->handle_updates();
return mpx->poll_once(false);
}
......@@ -72,6 +73,8 @@ public:
class dummy_transport {
public:
using application_type = dummy_application;
dummy_transport(stream_socket handle, std::shared_ptr<std::vector<byte>> data)
: handle_(handle), data_(data), read_buf_(1024) {
// nop
......@@ -84,8 +87,8 @@ public:
template <class Manager>
error init(Manager& manager) {
auto test_bytes = as_bytes(make_span(hello_test));
write_buf_.insert(write_buf_.end(), test_bytes.begin(), test_bytes.end());
CAF_CHECK(manager.mask_add(operation::read_write));
buf_.insert(buf_.end(), test_bytes.begin(), test_bytes.end());
manager.register_writing();
return none;
}
......@@ -104,23 +107,22 @@ public:
bool handle_write_event(Manager& mgr) {
for (auto x = mgr.next_message(); x != nullptr; x = mgr.next_message()) {
auto& payload = x->payload;
write_buf_.insert(write_buf_.end(), payload.begin(), payload.end());
buf_.insert(buf_.end(), payload.begin(), payload.end());
}
auto res = write(handle_, make_span(write_buf_));
auto res = write(handle_, make_span(buf_));
if (auto num_bytes = get_if<size_t>(&res)) {
write_buf_.erase(write_buf_.begin(), write_buf_.begin() + *num_bytes);
return write_buf_.size() > 0;
buf_.erase(buf_.begin(), buf_.begin() + *num_bytes);
return buf_.size() > 0;
}
return get<sec>(res) == sec::unavailable_or_would_block;
}
template <class Manager>
void handle_error(Manager&, sec) {
void handle_error(sec) {
// nop
}
template <class Manager>
void resolve(Manager& mgr, std::string path, actor listener) {
void resolve(Manager& mgr, const uri& locator, const actor& listener) {
actor_id aid = 42;
auto hid = "0011223344556677889900112233445566778899";
auto nid = unbox(make_node_id(42, hid));
......@@ -128,6 +130,7 @@ public:
auto p = make_actor<actor_proxy_impl, strong_actor_ptr>(aid, nid,
&mgr.system(), cfg,
&mgr);
std::string path{locator.path().begin(), locator.path().end()};
anon_send(listener, resolve_atom::value, std::move(path), p);
}
......@@ -136,6 +139,16 @@ public:
// nop
}
template <class Parent>
void new_proxy(Parent&, const node_id&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, const node_id&, actor_id, error) {
// nop
}
private:
stream_socket handle_;
......@@ -143,7 +156,7 @@ private:
std::vector<byte> read_buf_;
std::vector<byte> write_buf_;
std::vector<byte> buf_;
};
} // namespace
......@@ -152,7 +165,6 @@ CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(send and receive) {
std::vector<byte> read_buf(1024);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto buf = std::make_shared<std::vector<byte>>();
auto sockets = unbox(make_stream_socket_pair());
nonblocking(sockets.second, true);
......@@ -160,10 +172,10 @@ CAF_TEST(send and receive) {
sec::unavailable_or_would_block);
auto guard = detail::make_scope_guard([&] { close(sockets.second); });
auto mgr = make_endpoint_manager(mpx, sys,
dummy_transport{sockets.first, buf},
dummy_application{});
dummy_transport{sockets.first, buf});
CAF_CHECK_EQUAL(mgr->mask(), operation::none);
CAF_CHECK_EQUAL(mgr->init(), none);
mpx->handle_updates();
CAF_CHECK_EQUAL(mgr->mask(), operation::read_write);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
CAF_CHECK_EQUAL(write(sockets.second, as_bytes(make_span(hello_manager))),
hello_manager.size());
......@@ -184,13 +196,12 @@ CAF_TEST(resolve and proxy communication) {
nonblocking(sockets.second, true);
auto guard = detail::make_scope_guard([&] { close(sockets.second); });
auto mgr = make_endpoint_manager(mpx, sys,
dummy_transport{sockets.first, buf},
dummy_application{});
dummy_transport{sockets.first, buf});
CAF_CHECK_EQUAL(mgr->init(), none);
mpx->handle_updates();
CAF_CHECK_EQUAL(mgr->mask(), operation::read_write);
run();
CAF_CHECK_EQUAL(read(sockets.second, make_span(read_buf)), hello_test.size());
mgr->resolve("/id/42", self);
mgr->resolve(unbox(make_uri("test:id/42")), self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE basp.header
#include "caf/net/basp/header.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/serializer_impl.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST(serialization) {
basp::header x{basp::message_type::handshake, 42, 4};
std::vector<byte> buf;
{
serializer_impl<std::vector<byte>> sink{nullptr, buf};
CAF_CHECK_EQUAL(sink(x), none);
}
CAF_CHECK_EQUAL(buf.size(), basp::header_size);
auto buf2 = to_bytes(x);
CAF_REQUIRE_EQUAL(buf.size(), buf2.size());
CAF_CHECK(std::equal(buf.begin(), buf.end(), buf2.begin()));
basp::header y;
{
binary_deserializer source{nullptr, buf};
CAF_CHECK_EQUAL(source(y), none);
}
CAF_CHECK_EQUAL(x, y);
auto z = basp::header::from_bytes(buf);
CAF_CHECK_EQUAL(x, z);
CAF_CHECK_EQUAL(y, z);
}
CAF_TEST(to_string) {
basp::header x{basp::message_type::handshake, 42, 4};
CAF_CHECK_EQUAL(to_string(x), "basp::header(handshake, 42, 4)");
}
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE ip
#include "caf/net/ip.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/ip_address.hpp"
#include "caf/ipv4_address.hpp"
using namespace caf;
using namespace caf::net;
namespace {
struct fixture : host_fixture {
fixture() : v6_local{{0}, {0x1}} {
v4_local = ip_address{make_ipv4_address(127, 0, 0, 1)};
v4_any_addr = ip_address{make_ipv4_address(0, 0, 0, 0)};
}
bool contains(ip_address x) {
return std::count(addrs.begin(), addrs.end(), x) > 0;
}
ip_address v4_any_addr;
ip_address v6_any_addr;
ip_address v4_local;
ip_address v6_local;
std::vector<ip_address> addrs;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(ip_tests, fixture)
CAF_TEST(resolve localhost) {
addrs = ip::resolve("localhost");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_local) || contains(v6_local));
}
CAF_TEST(resolve any) {
addrs = ip::resolve("");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_any_addr) || contains(v6_any_addr));
}
CAF_TEST(local addresses localhost) {
addrs = ip::local_addresses("localhost");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_local) || contains(v6_local));
}
CAF_TEST(local addresses any) {
addrs = ip::local_addresses("0.0.0.0");
auto tmp = ip::local_addresses("::");
addrs.insert(addrs.end(), tmp.begin(), tmp.end());
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_any_addr) || contains(v6_any_addr));
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -20,10 +20,9 @@
#include "caf/net/multiplexer.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
#include <new>
#include <tuple>
#include <vector>
......@@ -123,7 +122,7 @@ using dummy_manager_ptr = intrusive_ptr<dummy_manager>;
struct fixture : host_fixture {
fixture() : manager_count(0), mpx(std::make_shared<multiplexer>()) {
// nop
mpx->set_thread_id();
}
~fixture() {
......@@ -165,13 +164,11 @@ CAF_TEST(send and receive) {
auto sockets = unbox(make_stream_socket_pair());
auto alice = make_counted<dummy_manager>(manager_count, sockets.first, mpx);
auto bob = make_counted<dummy_manager>(manager_count, sockets.second, mpx);
alice->mask_add(operation::read);
bob->mask_add(operation::read);
mpx->handle_updates();
alice->register_reading();
bob->register_reading();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 3u);
alice->send("hello bob");
alice->mask_add(operation::write);
mpx->handle_updates();
alice->register_writing();
exhaust();
CAF_CHECK_EQUAL(bob->receive(), "hello bob");
}
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE net.basp.message_queue
#include "caf/net/basp/message_queue.hpp"
#include "caf/test/dsl.hpp"
#include "caf/actor_cast.hpp"
#include "caf/actor_system.hpp"
#include "caf/behavior.hpp"
using namespace caf;
namespace {
behavior testee_impl() {
return {
[](ok_atom, int) {
// nop
},
};
}
struct fixture : test_coordinator_fixture<> {
net::basp::message_queue queue;
strong_actor_ptr testee;
fixture() {
auto hdl = sys.spawn<lazy_init>(testee_impl);
testee = actor_cast<strong_actor_ptr>(hdl);
}
void acquire_ids(size_t num) {
for (size_t i = 0; i < num; ++i)
queue.new_id();
}
void push(int msg_id) {
queue.push(nullptr, static_cast<uint64_t>(msg_id), testee,
make_mailbox_element(self->ctrl(), make_message_id(), {},
ok_atom::value, msg_id));
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(message_queue_tests, fixture)
CAF_TEST(default construction) {
CAF_CHECK_EQUAL(queue.next_id, 0u);
CAF_CHECK_EQUAL(queue.next_undelivered, 0u);
CAF_CHECK_EQUAL(queue.pending.size(), 0u);
}
CAF_TEST(ascending IDs) {
CAF_CHECK_EQUAL(queue.new_id(), 0u);
CAF_CHECK_EQUAL(queue.new_id(), 1u);
CAF_CHECK_EQUAL(queue.new_id(), 2u);
CAF_CHECK_EQUAL(queue.next_undelivered, 0u);
}
CAF_TEST(push order 0 - 1 - 2) {
acquire_ids(3);
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
push(1);
expect((ok_atom, int), from(self).to(testee).with(_, 1));
push(2);
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 0 - 2 - 1) {
acquire_ids(3);
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(1);
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 1 - 0 - 2) {
acquire_ids(3);
push(1);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 1));
push(2);
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 1 - 2 - 0) {
acquire_ids(3);
push(1);
disallow((ok_atom, int), from(self).to(testee));
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 2 - 0 - 1) {
acquire_ids(3);
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
push(1);
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 2 - 1 - 0) {
acquire_ids(3);
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(1);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(dropping) {
acquire_ids(3);
push(2);
disallow((ok_atom, int), from(self).to(testee));
queue.drop(nullptr, 1);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE net.basp.ping_pong
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/backend/test.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/multiplexer.hpp"
using namespace caf;
using namespace caf::net;
namespace {
using ping_atom = caf::atom_constant<caf::atom("ping")>;
using pong_atom = caf::atom_constant<caf::atom("pong")>;
struct earth_node {
uri operator()() {
return unbox(make_uri("test://earth"));
}
};
struct mars_node {
uri operator()() {
return unbox(make_uri("test://mars"));
}
};
template <class Node>
struct config : actor_system_config {
config() {
Node this_node;
put(content, "middleman.this-node", this_node());
load<middleman, backend::test>();
}
};
class planet_driver {
public:
virtual ~planet_driver() {
// nop
}
virtual bool consume_message() = 0;
virtual bool handle_io_event() = 0;
virtual bool trigger_timeout() = 0;
};
template <class Node>
class planet : public test_coordinator_fixture<config<Node>> {
public:
planet(planet_driver& driver)
: mpx(*this->sys.network_manager().mpx()), driver_(driver) {
mpx.set_thread_id();
}
net::backend::test& backend() {
auto& mm = this->sys.network_manager();
return *dynamic_cast<net::backend::test*>(mm.backend("test"));
}
node_id id() const {
return this->sys.node();
}
bool consume_message() override {
return driver_.consume_message();
}
bool handle_io_event() override {
return driver_.handle_io_event();
}
bool trigger_timeout() override {
return driver_.trigger_timeout();
}
actor resolve(string_view locator) {
auto hdl = actor_cast<actor>(this->self);
this->sys.network_manager().resolve(unbox(make_uri(locator)), hdl);
this->run();
actor result;
this->self->receive(
[&](strong_actor_ptr& ptr, const std::set<std::string>&) {
CAF_MESSAGE("resolved " << locator);
result = actor_cast<actor>(std::move(ptr));
});
return result;
}
multiplexer& mpx;
private:
planet_driver& driver_;
};
behavior ping_actor(event_based_actor* self, actor pong, size_t num_pings,
std::shared_ptr<size_t> count) {
CAF_MESSAGE("num_pings: " << num_pings);
self->send(pong, ping_atom::value, 1);
return {
[=](pong_atom, int value) -> std::tuple<atom_value, int> {
CAF_MESSAGE("received `pong_atom`");
if (++*count >= num_pings) {
CAF_MESSAGE("received " << num_pings << " pings, call self->quit");
self->quit();
}
return std::make_tuple(ping_atom::value, value + 1);
},
};
}
behavior pong_actor(event_based_actor* self) {
CAF_MESSAGE("pong actor started");
self->set_down_handler([=](down_msg& dm) {
CAF_MESSAGE("received down_msg{" << to_string(dm.reason) << "}");
self->quit(dm.reason);
});
return {
[=](ping_atom, int value) -> std::tuple<atom_value, int> {
CAF_MESSAGE("received `ping_atom` from " << self->current_sender());
if (self->current_sender() == self->ctrl())
abort();
self->monitor(self->current_sender());
// set next behavior
self->become([](ping_atom, int val) {
return std::make_tuple(pong_atom::value, val);
});
// reply to 'ping'
return std::make_tuple(pong_atom::value, value);
},
};
}
struct fixture : host_fixture, planet_driver {
fixture() : earth(*this), mars(*this) {
auto sockets = unbox(make_stream_socket_pair());
earth.backend().emplace(mars.id(), sockets.first, sockets.second);
mars.backend().emplace(earth.id(), sockets.second, sockets.first);
run();
}
bool consume_message() override {
return earth.sched.try_run_once() || mars.sched.try_run_once();
}
bool handle_io_event() override {
return earth.mpx.poll_once(false) || mars.mpx.poll_once(false);
}
bool trigger_timeout() override {
return earth.sched.trigger_timeout() || mars.sched.trigger_timeout();
}
void run() {
earth.run();
}
planet<earth_node> earth;
planet<mars_node> mars;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(ping_pong_tests, fixture)
CAF_TEST(full setup) {
auto pong = earth.sys.spawn(pong_actor);
run();
earth.sys.registry().put(atom("pong"), pong);
auto remote_pong = mars.resolve("test://earth/name/pong");
auto count = std::make_shared<size_t>(0);
auto ping = mars.sys.spawn(ping_actor, remote_pong, 10, count);
run();
anon_send_exit(pong, exit_reason::kill);
anon_send_exit(ping, exit_reason::kill);
CAF_CHECK_EQUAL(*count, 10u);
run();
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE net.basp.worker
#include "caf/net/basp/worker.hpp"
#include "caf/test/dsl.hpp"
#include "caf/actor_cast.hpp"
#include "caf/actor_control_block.hpp"
#include "caf/actor_system.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/serializer_impl.hpp"
using namespace caf;
namespace {
behavior testee_impl() {
return {
[](ok_atom) {
// nop
},
};
}
class mock_actor_proxy : public actor_proxy {
public:
explicit mock_actor_proxy(actor_config& cfg) : actor_proxy(cfg) {
// nop
}
void enqueue(mailbox_element_ptr, execution_unit*) override {
CAF_FAIL("mock_actor_proxy::enqueue called");
}
void kill_proxy(execution_unit*, error) override {
// nop
}
};
class mock_proxy_registry_backend : public proxy_registry::backend {
public:
mock_proxy_registry_backend(actor_system& sys) : sys_(sys) {
// nop
}
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override {
actor_config cfg;
return make_actor<mock_actor_proxy, strong_actor_ptr>(aid, nid, &sys_, cfg);
}
void set_last_hop(node_id*) override {
// nop
}
private:
actor_system& sys_;
};
struct fixture : test_coordinator_fixture<> {
detail::worker_hub<net::basp::worker> hub;
net::basp::message_queue queue;
mock_proxy_registry_backend proxies_backend;
proxy_registry proxies;
node_id last_hop;
actor testee;
fixture() : proxies_backend(sys), proxies(sys, proxies_backend) {
auto tmp = make_node_id(123, "0011223344556677889900112233445566778899");
last_hop = unbox(std::move(tmp));
testee = sys.spawn<lazy_init>(testee_impl);
sys.registry().put(testee.id(), testee);
}
~fixture() {
sys.registry().erase(testee.id());
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(worker_tests, fixture)
CAF_TEST(deliver serialized message) {
CAF_MESSAGE("create the BASP worker");
CAF_REQUIRE_EQUAL(hub.peek(), nullptr);
hub.add_new_worker(queue, proxies);
CAF_REQUIRE_NOT_EQUAL(hub.peek(), nullptr);
auto w = hub.pop();
CAF_MESSAGE("create a fake message + BASP header");
std::vector<byte> payload;
std::vector<strong_actor_ptr> stages;
serializer_impl<std::vector<byte>> sink{sys, payload};
if (auto err = sink(node_id{}, self->id(), testee.id(), stages,
make_message(ok_atom::value)))
CAF_FAIL("unable to serialize message: " << sys.render(err));
net::basp::header hdr{net::basp::message_type::actor_message,
static_cast<uint32_t>(payload.size()),
make_message_id().integer_value()};
CAF_MESSAGE("launch worker");
w->launch(last_hop, hdr, payload);
sched.run_once();
expect((ok_atom), from(_).to(testee));
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -20,10 +20,9 @@
#include "caf/net/network_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
using namespace caf;
using namespace caf::net;
......
......@@ -20,10 +20,9 @@
#include "caf/net/pipe_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
#include <vector>
#include "caf/byte.hpp"
......
......@@ -20,10 +20,9 @@
#include "caf/net/socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
using namespace caf;
using namespace caf::net;
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE socket_guard
#include "caf/net/socket_guard.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/socket_id.hpp"
using namespace caf;
using namespace caf::net;
namespace {
constexpr socket_id dummy_id = 13;
struct dummy_socket {
dummy_socket(socket_id id, bool& closed) : id(id), closed(closed) {
// nop
}
dummy_socket(const dummy_socket&) = default;
dummy_socket& operator=(const dummy_socket& other) {
id = other.id;
closed = other.closed;
return *this;
}
socket_id id;
bool& closed;
};
void close(dummy_socket x) {
x.id = invalid_socket_id;
x.closed = true;
}
struct fixture {
fixture() : closed{false}, sock{dummy_id, closed} {
// nop
}
bool closed;
dummy_socket sock;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(socket_guard_tests, fixture)
CAF_TEST(cleanup) {
{
auto guard = make_socket_guard(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
}
CAF_CHECK(sock.closed);
}
CAF_TEST(reset) {
{
auto guard = make_socket_guard(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
guard.release();
CAF_CHECK_EQUAL(guard.socket().id, invalid_socket_id);
guard.reset(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
}
CAF_CHECK_EQUAL(sock.closed, true);
}
CAF_TEST(release) {
{
auto guard = make_socket_guard(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
guard.release();
CAF_CHECK_EQUAL(guard.socket().id, invalid_socket_id);
}
CAF_CHECK_EQUAL(sock.closed, false);
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE basp.stream_application
#include "caf/net/basp/application.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include <vector>
#include "caf/byte.hpp"
#include "caf/net/backend/test.hpp"
#include "caf/net/basp/connection_state.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
#define REQUIRE_OK(statement) \
if (auto err = statement) \
CAF_FAIL(sys.render(err));
namespace {
using buffer_type = std::vector<byte>;
using transport_type = stream_transport<basp::application>;
size_t fetch_size(variant<size_t, sec> x) {
if (holds_alternative<sec>(x))
CAF_FAIL("read/write failed: " << to_string(get<sec>(x)));
return get<size_t>(x);
}
struct config : actor_system_config {
config() {
put(content, "middleman.this-node", unbox(make_uri("test:earth")));
load<middleman, backend::test>();
}
};
struct fixture : host_fixture, test_coordinator_fixture<config> {
fixture() : mars(make_node_id(unbox(make_uri("test:mars")))) {
auto& mm = sys.network_manager();
mm.mpx()->set_thread_id();
auto backend = dynamic_cast<backend::test*>(mm.backend("test"));
auto mgr = backend->peer(mars);
auto& dref = dynamic_cast<endpoint_manager_impl<transport_type>&>(*mgr);
app = &dref.transport().application();
sock = backend->socket(mars);
}
bool handle_io_event() override {
auto mpx = sys.network_manager().mpx();
return mpx->poll_once(false);
}
template <class... Ts>
buffer_type to_buf(const Ts&... xs) {
buffer_type buf;
serializer_impl<buffer_type> sink{system(), buf};
REQUIRE_OK(sink(xs...));
return buf;
}
template <class... Ts>
void mock(const Ts&... xs) {
auto buf = to_buf(xs...);
if (fetch_size(write(sock, make_span(buf))) != buf.size())
CAF_FAIL("unable to write " << buf.size() << " bytes");
run();
}
void handle_handshake() {
CAF_CHECK_EQUAL(app->state(),
basp::connection_state::await_handshake_header);
auto payload = to_buf(mars, defaults::middleman::app_identifiers);
mock(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
CAF_CHECK_EQUAL(app->state(),
basp::connection_state::await_handshake_payload);
write(sock, make_span(payload));
run();
}
void consume_handshake() {
buffer_type buf(basp::header_size);
if (fetch_size(read(sock, make_span(buf))) != basp::header_size)
CAF_FAIL("unable to read " << basp::header_size << " bytes");
auto hdr = basp::header::from_bytes(buf);
if (hdr.type != basp::message_type::handshake || hdr.payload_len == 0
|| hdr.operation_data != basp::version)
CAF_FAIL("invalid handshake header");
buf.resize(hdr.payload_len);
if (fetch_size(read(sock, make_span(buf))) != hdr.payload_len)
CAF_FAIL("unable to read " << hdr.payload_len << " bytes");
node_id nid;
std::vector<std::string> app_ids;
binary_deserializer source{sys, buf};
if (auto err = source(nid, app_ids))
CAF_FAIL("unable to deserialize payload: " << sys.render(err));
if (source.remaining() > 0)
CAF_FAIL("trailing bytes after reading payload");
}
actor_system& system() {
return sys;
}
node_id mars;
stream_socket sock;
basp::application* app;
unit_t no_payload;
};
} // namespace
#define MOCK(kind, op, ...) \
do { \
CAF_MESSAGE("mock " << kind); \
if (!std::is_same<decltype(std::make_tuple(__VA_ARGS__)), \
std::tuple<unit_t>>::value) { \
auto payload = to_buf(__VA_ARGS__); \
mock(basp::header{kind, static_cast<uint32_t>(payload.size()), op}); \
write(sock, make_span(payload)); \
} else { \
mock(basp::header{kind, 0, op}); \
} \
run(); \
} while (false)
#define RECEIVE(msg_type, op_data, ...) \
do { \
CAF_MESSAGE("receive " << msg_type); \
buffer_type buf(basp::header_size); \
if (fetch_size(read(sock, make_span(buf))) != basp::header_size) \
CAF_FAIL("unable to read " << basp::header_size << " bytes"); \
auto hdr = basp::header::from_bytes(buf); \
CAF_CHECK_EQUAL(hdr.type, msg_type); \
CAF_CHECK_EQUAL(hdr.operation_data, op_data); \
if (!std::is_same<decltype(std::make_tuple(__VA_ARGS__)), \
std::tuple<unit_t>>::value) { \
buf.resize(hdr.payload_len); \
if (fetch_size(read(sock, make_span(buf))) != size_t{hdr.payload_len}) \
CAF_FAIL("unable to read " << hdr.payload_len << " bytes"); \
binary_deserializer source{sys, buf}; \
if (auto err = source(__VA_ARGS__)) \
CAF_FAIL("failed to receive data: " << sys.render(err)); \
} else { \
if (hdr.payload_len != 0) \
CAF_FAIL("unexpected payload"); \
} \
} while (false)
CAF_TEST_FIXTURE_SCOPE(application_tests, fixture)
CAF_TEST(actor message and down message) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
CAF_REQUIRE_EQUAL(self->mailbox().size(), 0u);
MOCK(basp::message_type::actor_message, make_message_id().integer_value(),
mars, actor_id{42}, self->id(), std::vector<strong_actor_ptr>{},
make_message("hello world!"));
MOCK(basp::message_type::monitor_message, 42u, no_payload);
strong_actor_ptr proxy;
self->receive([&](const std::string& str) {
CAF_CHECK_EQUAL(str, "hello world!");
proxy = self->current_sender();
CAF_REQUIRE_NOT_EQUAL(proxy, nullptr);
self->monitor(proxy);
});
MOCK(basp::message_type::down_message, 42u,
error{exit_reason::user_shutdown});
expect((down_msg),
from(_).to(self).with(down_msg{actor_cast<actor_addr>(proxy),
exit_reason::user_shutdown}));
}
CAF_TEST(resolve request without result) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, std::string{"foo/bar"});
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, 0u);
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve request on id with result) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
auto path = "id/" + std::to_string(self->id());
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, path);
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, self->id());
CAF_CHECK(ifs.empty());
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -20,17 +20,24 @@
#include "caf/net/stream_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
#include "caf/byte.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST_FIXTURE_SCOPE(network_socket_tests, host_fixture)
namespace {
byte operator"" _b(unsigned long long x) {
return static_cast<byte>(x);
}
} // namespace
CAF_TEST_FIXTURE_SCOPE(network_socket_simple_tests, host_fixture)
CAF_TEST(invalid socket) {
stream_socket x;
......@@ -39,36 +46,72 @@ CAF_TEST(invalid socket) {
CAF_CHECK_EQUAL(allow_sigpipe(x, true), sec::network_syscall_failed);
}
CAF_TEST(connected socket pair) {
std::vector<byte> wr_buf{byte(1), byte(2), byte(4), byte(8),
byte(16), byte(32), byte(64)};
std::vector<byte> rd_buf(124);
CAF_MESSAGE("create sockets and configure nonblocking I/O");
auto x = unbox(make_stream_socket_pair());
CAF_CHECK_EQUAL(nonblocking(x.first, true), caf::none);
CAF_CHECK_EQUAL(nonblocking(x.second, true), caf::none);
CAF_CHECK_NOT_EQUAL(unbox(send_buffer_size(x.first)), 0u);
CAF_CHECK_NOT_EQUAL(unbox(send_buffer_size(x.second)), 0u);
CAF_MESSAGE("verify nonblocking communication");
CAF_CHECK_EQUAL(read(x.first, make_span(rd_buf)),
CAF_TEST_FIXTURE_SCOPE_END()
namespace {
struct fixture : host_fixture {
fixture() : rd_buf(124) {
std::tie(first, second) = unbox(make_stream_socket_pair());
CAF_REQUIRE_EQUAL(nonblocking(first, true), caf::none);
CAF_REQUIRE_EQUAL(nonblocking(second, true), caf::none);
CAF_REQUIRE_NOT_EQUAL(unbox(send_buffer_size(first)), 0u);
CAF_REQUIRE_NOT_EQUAL(unbox(send_buffer_size(second)), 0u);
}
~fixture() {
close(first);
close(second);
}
stream_socket first;
stream_socket second;
std::vector<byte> rd_buf;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(network_socket_tests, fixture)
CAF_TEST(read on empty sockets) {
CAF_CHECK_EQUAL(read(first, make_span(rd_buf)),
sec::unavailable_or_would_block);
CAF_CHECK_EQUAL(read(x.second, make_span(rd_buf)),
CAF_CHECK_EQUAL(read(second, make_span(rd_buf)),
sec::unavailable_or_would_block);
}
CAF_TEST(transfer data from first to second socket) {
std::vector<byte> wr_buf{1_b, 2_b, 4_b, 8_b, 16_b, 32_b, 64_b};
CAF_MESSAGE("transfer data from first to second socket");
CAF_CHECK_EQUAL(write(x.first, make_span(wr_buf)), wr_buf.size());
CAF_CHECK_EQUAL(read(x.second, make_span(rd_buf)), wr_buf.size());
CAF_CHECK_EQUAL(write(first, make_span(wr_buf)), wr_buf.size());
CAF_CHECK_EQUAL(read(second, make_span(rd_buf)), wr_buf.size());
CAF_CHECK(std::equal(wr_buf.begin(), wr_buf.end(), rd_buf.begin()));
rd_buf.assign(rd_buf.size(), byte(0));
CAF_MESSAGE("transfer data from second to first socket");
CAF_CHECK_EQUAL(write(x.second, make_span(wr_buf)), wr_buf.size());
CAF_CHECK_EQUAL(read(x.first, make_span(rd_buf)), wr_buf.size());
}
CAF_TEST(transfer data from second to first socket) {
std::vector<byte> wr_buf{1_b, 2_b, 4_b, 8_b, 16_b, 32_b, 64_b};
CAF_CHECK_EQUAL(write(second, make_span(wr_buf)), wr_buf.size());
CAF_CHECK_EQUAL(read(first, make_span(rd_buf)), wr_buf.size());
CAF_CHECK(std::equal(wr_buf.begin(), wr_buf.end(), rd_buf.begin()));
rd_buf.assign(rd_buf.size(), byte(0));
CAF_MESSAGE("shut down first socket and observe shutdown on the second one");
close(x.first);
CAF_CHECK_EQUAL(read(x.second, make_span(rd_buf)), sec::socket_disconnected);
CAF_MESSAGE("done (cleanup)");
close(x.second);
}
CAF_TEST(shut down first socket and observe shutdown on the second one) {
close(first);
CAF_CHECK_EQUAL(read(second, make_span(rd_buf)), sec::socket_disconnected);
first.id = invalid_socket_id;
}
CAF_TEST(transfer data using multiple buffers) {
std::vector<byte> wr_buf_1{1_b, 2_b, 4_b};
std::vector<byte> wr_buf_2{8_b, 16_b, 32_b, 64_b};
std::vector<byte> full_buf;
full_buf.insert(full_buf.end(), wr_buf_1.begin(), wr_buf_1.end());
full_buf.insert(full_buf.end(), wr_buf_2.begin(), wr_buf_2.end());
CAF_CHECK_EQUAL(write(second, {make_span(wr_buf_1), make_span(wr_buf_2)}),
full_buf.size());
CAF_CHECK_EQUAL(read(first, make_span(rd_buf)), full_buf.size());
CAF_CHECK(std::equal(full_buf.begin(), full_buf.end(), rd_buf.begin()));
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -16,22 +16,23 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE scribe_policy
#define CAF_SUITE stream_transport
#include "caf/policy/scribe.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/byte.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/span.hpp"
......@@ -40,27 +41,44 @@ using namespace caf;
using namespace caf::net;
namespace {
constexpr string_view hello_manager = "hello manager!";
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
using buffer_type = std::vector<byte>;
using buffer_ptr = std::shared_ptr<buffer_type>;
fixture() : recv_buf(1024), shared_buf{std::make_shared<buffer_type>()} {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << sys.render(err));
mpx->set_thread_id();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto sockets = unbox(make_stream_socket_pair());
send_socket_guard.reset(sockets.first);
recv_socket_guard.reset(sockets.second);
if (auto err = nonblocking(recv_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
}
bool handle_io_event() override {
mpx->handle_updates();
return mpx->poll_once(false);
}
multiplexer_ptr mpx;
buffer_type recv_buf;
socket_guard<stream_socket> send_socket_guard;
socket_guard<stream_socket> recv_socket_guard;
buffer_ptr shared_buf;
};
class dummy_application {
using buffer_type = std::vector<byte>;
using buffer_ptr = std::shared_ptr<buffer_type>;
public:
dummy_application(std::shared_ptr<std::vector<byte>> rec_buf)
dummy_application(buffer_ptr rec_buf)
: rec_buf_(std::move(rec_buf)){
// nop
};
......@@ -72,32 +90,46 @@ public:
return none;
}
template <class Transport>
void write_message(Transport& transport,
std::unique_ptr<endpoint_manager::message> msg) {
transport.write_packet(msg->payload);
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> ptr) {
parent.write_packet(ptr->payload);
}
template <class Parent>
void handle_data(Parent&, span<const byte> data) {
error handle_data(Parent&, span<const byte> data) {
rec_buf_->clear();
rec_buf_->insert(rec_buf_->begin(), data.begin(), data.end());
return none;
}
template <class Manager>
void resolve(Manager& manager, const std::string& path, actor listener) {
template <class Parent>
void resolve(Parent& parent, string_view path, const actor& listener) {
actor_id aid = 42;
auto hid = "0011223344556677889900112233445566778899";
auto nid = unbox(make_node_id(42, hid));
actor_config cfg;
endpoint_manager_ptr ptr{&parent.manager()};
auto p = make_actor<actor_proxy_impl, strong_actor_ptr>(aid, nid,
&manager.system(),
cfg, &manager);
anon_send(listener, resolve_atom::value, std::move(path), p);
&parent.system(),
cfg,
std::move(ptr));
anon_send(listener, resolve_atom::value,
std::string{path.begin(), path.end()}, p);
}
template <class Transport>
void timeout(Transport&, atom_value, uint64_t) {
template <class Parent>
void timeout(Parent&, atom_value, uint64_t) {
// nop
}
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
......@@ -105,17 +137,17 @@ public:
// nop
}
static expected<std::vector<byte>> serialize(actor_system& sys,
const type_erased_tuple& x) {
std::vector<byte> result;
serializer_impl<std::vector<byte>> sink{sys, result};
static expected<buffer_type> serialize(actor_system& sys,
const type_erased_tuple& x) {
buffer_type result;
serializer_impl<buffer_type> sink{sys, result};
if (auto err = message::save(sink, x))
return err;
return result;
}
private:
std::shared_ptr<std::vector<byte>> rec_buf_;
buffer_ptr rec_buf_;
};
} // namespace
......@@ -123,42 +155,36 @@ private:
CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(receive) {
std::vector<byte> read_buf(1024);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto buf = std::make_shared<std::vector<byte>>();
auto sockets = unbox(make_stream_socket_pair());
nonblocking(sockets.second, true);
CAF_CHECK_EQUAL(read(sockets.second, make_span(read_buf)),
sec::unavailable_or_would_block);
auto guard = detail::make_scope_guard([&] { close(sockets.second); });
CAF_MESSAGE("configure scribe_policy");
policy::scribe scribe{sockets.first};
scribe.configure_read(net::receive_policy::exactly(hello_manager.size()));
auto mgr = make_endpoint_manager(mpx, sys, scribe, dummy_application{buf});
using transport_type = stream_transport<dummy_application>;
auto mgr = make_endpoint_manager(mpx, sys,
transport_type{recv_socket_guard.release(),
dummy_application{
shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
mpx->handle_updates();
auto mgr_impl = mgr.downcast<endpoint_manager_impl<transport_type>>();
CAF_CHECK(mgr_impl != nullptr);
auto& transport = mgr_impl->transport();
transport.configure_read(receive_policy::exactly(hello_manager.size()));
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
CAF_MESSAGE("sending data to scribe_policy");
CAF_CHECK_EQUAL(write(sockets.second, as_bytes(make_span(hello_manager))),
CAF_CHECK_EQUAL(write(send_socket_guard.socket(),
as_bytes(make_span(hello_manager))),
hello_manager.size());
CAF_MESSAGE("wrote " << hello_manager.size() << " bytes.");
run();
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(buf->data()),
buf->size()),
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(shared_buf->data()),
shared_buf->size()),
hello_manager);
}
CAF_TEST(resolve and proxy communication) {
std::vector<byte> read_buf(1024);
auto buf = std::make_shared<std::vector<byte>>();
auto sockets = unbox(make_stream_socket_pair());
nonblocking(sockets.second, true);
auto guard = detail::make_scope_guard([&] { close(sockets.second); });
auto mgr = make_endpoint_manager(mpx, sys, policy::scribe{sockets.first},
dummy_application{buf});
using transport_type = stream_transport<dummy_application>;
auto mgr = make_endpoint_manager(mpx, sys,
transport_type{send_socket_guard.release(),
dummy_application{
shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
mpx->handle_updates();
run();
mgr->resolve("/id/42", self);
mgr->resolve(unbox(make_uri("test:/id/42")), self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
......@@ -168,13 +194,13 @@ CAF_TEST(resolve and proxy communication) {
after(std::chrono::seconds(0)) >>
[&] { CAF_FAIL("manager did not respond with a proxy."); });
run();
auto read_res = read(sockets.second, make_span(read_buf));
auto read_res = read(recv_socket_guard.socket(), make_span(recv_buf));
if (!holds_alternative<size_t>(read_res))
CAF_FAIL("read() returned an error: " << sys.render(get<sec>(read_res)));
read_buf.resize(get<size_t>(read_res));
CAF_MESSAGE("receive buffer contains " << read_buf.size() << " bytes");
recv_buf.resize(get<size_t>(read_res));
CAF_MESSAGE("receive buffer contains " << recv_buf.size() << " bytes");
message msg;
binary_deserializer source{sys, read_buf};
binary_deserializer source{sys, recv_buf};
CAF_CHECK_EQUAL(source(msg), none);
if (msg.match_elements<std::string>())
CAF_CHECK_EQUAL(msg.get_as<std::string>(0), "hello proxy!");
......
......@@ -18,12 +18,11 @@
#define CAF_SUITE string_application
#include "caf/policy/scribe.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "host_fixture.hpp"
#include <vector>
#include "caf/binary_deserializer.hpp"
......@@ -44,15 +43,17 @@ using namespace caf::policy;
namespace {
using buffer_type = std::vector<byte>;
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << sys.render(err));
mpx->set_thread_id();
}
bool handle_io_event() override {
mpx->handle_updates();
return mpx->poll_once(false);
}
......@@ -99,13 +100,15 @@ public:
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<net::endpoint_manager::message> msg) {
std::vector<byte> buf;
header_type header{static_cast<uint32_t>(msg->payload.size())};
buf.resize(sizeof(header_type));
memcpy(buf.data(), &header, buf.size());
buf.insert(buf.end(), msg->payload.begin(), msg->payload.end());
parent.write_packet(buf);
std::unique_ptr<endpoint_manager_queue::message> ptr) {
// Ignore proxy announcement messages.
if (ptr->msg == nullptr)
return;
auto header_buf = parent.next_header_buffer();
serializer_impl<buffer_type> sink{sys_, header_buf};
header_type header{static_cast<uint32_t>(ptr->payload.size())};
sink(header);
parent.write_packet(header_buf, ptr->payload);
}
static expected<std::vector<byte>> serialize(actor_system& sys,
......@@ -141,44 +144,58 @@ public:
}
template <class Parent>
void handle_data(Parent& parent, span<const byte> data) {
error handle_data(Parent& parent, span<const byte> data) {
if (await_payload_) {
Base::handle_packet(parent, header_, data);
await_payload_ = false;
} else {
if (data.size() != sizeof(header_type))
CAF_FAIL("");
memcpy(&header_, data.data(), sizeof(header_type));
binary_deserializer source{nullptr, data};
source(header_);
if (header_.payload == 0)
Base::handle_packet(parent, header_, span<const byte>{});
else
parent.configure_read(net::receive_policy::exactly(header_.payload));
parent.transport().configure_read(
net::receive_policy::exactly(header_.payload));
await_payload_ = true;
}
return none;
}
template <class Manager>
void resolve(Manager& manager, const std::string& path, actor listener) {
template <class Parent>
void resolve(Parent& parent, string_view path, actor listener) {
actor_id aid = 42;
auto hid = "0011223344556677889900112233445566778899";
auto nid = unbox(make_node_id(aid, hid));
actor_config cfg;
auto p = make_actor<net::actor_proxy_impl, strong_actor_ptr>(aid, nid,
&manager
.system(),
cfg, &manager);
anon_send(listener, resolve_atom::value, std::move(path), p);
auto sys = &parent.system();
auto mgr = &parent.manager();
auto p = make_actor<net::actor_proxy_impl, strong_actor_ptr>(aid, nid, sys,
cfg, mgr);
anon_send(listener, resolve_atom::value,
std::string{path.begin(), path.end()}, p);
}
template <class Parent>
void timeout(Parent&, atom_value, uint64_t) {
// nop
}
template <class Transport>
void timeout(Transport&, atom_value, uint64_t) {
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
void handle_error(sec) {
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
void handle_error(sec sec) {
CAF_FAIL("handle_error called: " << CAF_ARG(sec));
}
private:
header_type header_;
bool await_payload_;
......@@ -191,6 +208,7 @@ CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(receive) {
using application_type = extend<string_application>::with<
stream_string_application>;
using transport_type = stream_transport<application_type>;
std::vector<byte> read_buf(1024);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto buf = std::make_shared<std::vector<byte>>();
......@@ -199,18 +217,18 @@ CAF_TEST(receive) {
CAF_CHECK_EQUAL(read(sockets.second, make_span(read_buf)),
sec::unavailable_or_would_block);
CAF_MESSAGE("adding both endpoint managers");
auto mgr1 = make_endpoint_manager(mpx, sys, policy::scribe{sockets.first},
application_type{sys, buf});
auto mgr1 = make_endpoint_manager(mpx, sys,
transport_type{sockets.first,
application_type{sys, buf}});
CAF_CHECK_EQUAL(mgr1->init(), none);
mpx->handle_updates();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
auto mgr2 = make_endpoint_manager(mpx, sys, policy::scribe{sockets.second},
application_type{sys, buf});
auto mgr2 = make_endpoint_manager(mpx, sys,
transport_type{sockets.second,
application_type{sys, buf}});
CAF_CHECK_EQUAL(mgr2->init(), none);
mpx->handle_updates();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 3u);
CAF_MESSAGE("resolve actor-proxy");
mgr1->resolve("/id/42", self);
mgr1->resolve(unbox(make_uri("test:/id/42")), self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE tcp_sockets
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/socket_guard.hpp"
using namespace caf;
using namespace caf::net;
namespace {
// TODO: switch to std::operator""s when switching to C++14
std::string operator"" _s(const char* str, size_t size) {
return std::string(str, size);
}
struct fixture : host_fixture {
fixture() {
auth.port = 0;
auth.host = "0.0.0.0"_s;
}
uri::authority_type auth;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(tcp_sockets_tests, fixture)
CAF_TEST(open tcp port) {
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto port = unbox(local_port(acceptor));
CAF_CHECK_NOT_EQUAL(port, 0);
auto acceptor_guard = make_socket_guard(acceptor);
CAF_MESSAGE("opened acceptor on port " << port);
}
CAF_TEST(tcp connect) {
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto port = unbox(local_port(acceptor));
CAF_CHECK_NOT_EQUAL(port, 0);
auto acceptor_guard = make_socket_guard(acceptor);
CAF_MESSAGE("opened acceptor on port " << port);
uri::authority_type dst;
dst.port = port;
dst.host = "localhost"_s;
CAF_MESSAGE("connecting to localhost");
auto conn = unbox(make_connected_tcp_stream_socket(dst));
auto conn_guard = make_socket_guard(conn);
CAF_CHECK_NOT_EQUAL(conn, invalid_socket);
auto accepted = unbox(accept(acceptor));
auto accepted_guard = make_socket_guard(conn);
CAF_CHECK_NOT_EQUAL(accepted, invalid_socket);
CAF_MESSAGE("connected");
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE transport_worker
#include "caf/net/transport_worker.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/serializer_impl.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
using buffer_type = std::vector<byte>;
constexpr string_view hello_test = "hello test!";
struct application_result {
bool initialized;
std::vector<byte> data_buffer;
std::string resolve_path;
actor resolve_listener;
atom_value timeout_value;
uint64_t timeout_id;
sec err;
};
struct transport_result {
std::vector<byte> packet_buffer;
ip_endpoint ep;
};
class dummy_application {
public:
dummy_application(std::shared_ptr<application_result> res)
: res_(std::move(res)){
// nop
};
~dummy_application() = default;
template <class Parent>
error init(Parent&) {
res_->initialized = true;
return none;
}
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto header_buffer = parent.next_header_buffer();
parent.write_packet(header_buffer, msg->payload);
}
template <class Parent>
error handle_data(Parent&, span<const byte> data) {
auto& buf = res_->data_buffer;
buf.clear();
buf.insert(buf.begin(), data.begin(), data.end());
return none;
}
template <class Parent>
void resolve(Parent&, string_view path, const actor& listener) {
res_->resolve_path.assign(path.begin(), path.end());
res_->resolve_listener = listener;
}
template <class Parent>
void timeout(Parent&, atom_value value, uint64_t id) {
res_->timeout_value = value;
res_->timeout_id = id;
}
void handle_error(sec err) {
res_->err = err;
}
static expected<std::vector<byte>> serialize(actor_system& sys,
const type_erased_tuple& x) {
std::vector<byte> result;
serializer_impl<std::vector<byte>> sink{sys, result};
if (auto err = message::save(sink, x))
return err;
return result;
}
private:
std::shared_ptr<application_result> res_;
};
class dummy_transport {
public:
using transport_type = dummy_transport;
using application_type = dummy_application;
dummy_transport(std::shared_ptr<transport_result> res)
: res_(std::move(res)) {
// nop
}
void write_packet(ip_endpoint ep, span<buffer_type*> buffers) {
res_->ep = ep;
auto& packet_buf = res_->packet_buffer;
packet_buf.clear();
for (auto buf : buffers)
packet_buf.insert(packet_buf.end(), buf->begin(), buf->end());
}
transport_type& transport() {
return *this;
}
std::vector<byte> next_header_buffer() {
return {};
}
std::vector<byte> next_payload_buffer() {
return {};
}
private:
std::shared_ptr<transport_result> res_;
};
struct fixture : test_coordinator_fixture<>, host_fixture {
using worker_type = transport_worker<dummy_application, ip_endpoint>;
fixture()
: transport_results{std::make_shared<transport_result>()},
application_results{std::make_shared<application_result>()},
transport(transport_results),
worker{dummy_application{application_results}} {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << sys.render(err));
if (auto err = parse("[::1]:12345", ep))
CAF_FAIL("parse returned an error: " << err);
worker = worker_type{dummy_application{application_results}, ep};
}
bool handle_io_event() override {
return mpx->poll_once(false);
}
multiplexer_ptr mpx;
std::shared_ptr<transport_result> transport_results;
std::shared_ptr<application_result> application_results;
dummy_transport transport;
worker_type worker;
ip_endpoint ep;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(construction and initialization) {
CAF_CHECK_EQUAL(worker.init(transport), none);
CAF_CHECK_EQUAL(application_results->initialized, true);
}
CAF_TEST(handle_data) {
auto test_span = as_bytes(make_span(hello_test));
worker.handle_data(transport, test_span);
auto& buf = application_results->data_buffer;
string_view result{reinterpret_cast<char*>(buf.data()), buf.size()};
CAF_CHECK_EQUAL(result, hello_test);
}
CAF_TEST(write_message) {
actor act;
auto strong_actor = actor_cast<strong_actor_ptr>(act);
mailbox_element::forwarding_stack stack;
auto msg = make_message();
auto elem = make_mailbox_element(strong_actor, make_message_id(12345), stack,
msg);
auto test_span = make_span(reinterpret_cast<byte*>(
const_cast<char*>(hello_test.data())),
hello_test.size());
std::vector<byte> payload(test_span.begin(), test_span.end());
using message_type = endpoint_manager_queue::message;
auto message = detail::make_unique<message_type>(std::move(elem), nullptr,
payload);
worker.write_message(transport, std::move(message));
auto& buf = transport_results->packet_buffer;
string_view result{reinterpret_cast<char*>(buf.data()), buf.size()};
CAF_CHECK_EQUAL(result, hello_test);
CAF_CHECK_EQUAL(transport_results->ep, ep);
}
CAF_TEST(resolve) {
worker.resolve(transport, "foo", self);
CAF_CHECK_EQUAL(application_results->resolve_path, "foo");
CAF_CHECK_EQUAL(application_results->resolve_listener, self);
}
CAF_TEST(timeout) {
worker.timeout(transport, atom("bar"), 42u);
CAF_CHECK_EQUAL(application_results->timeout_value, atom("bar"));
CAF_CHECK_EQUAL(application_results->timeout_id, 42u);
}
CAF_TEST(handle_error) {
worker.handle_error(sec::feature_disabled);
CAF_CHECK_EQUAL(application_results->err, sec::feature_disabled);
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE transport_worker_dispatcher
#include "caf/net/transport_worker_dispatcher.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/make_actor.hpp"
#include "caf/monitorable_actor.hpp"
#include "caf/node_id.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
namespace {
using buffer_type = std::vector<byte>;
constexpr string_view hello_test = "hello_test";
struct dummy_actor : public monitorable_actor {
dummy_actor(actor_config& cfg) : monitorable_actor(cfg) {
// nop
}
void enqueue(mailbox_element_ptr, execution_unit*) override {
// nop
}
};
class dummy_application {
public:
dummy_application(std::shared_ptr<buffer_type> rec_buf, uint8_t id)
: rec_buf_(std::move(rec_buf)),
id_(id){
// nop
};
~dummy_application() = default;
template <class Parent>
error init(Parent&) {
rec_buf_->push_back(static_cast<byte>(id_));
return none;
}
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
rec_buf_->push_back(static_cast<byte>(id_));
auto header_buf = parent.next_header_buffer();
parent.write_packet(header_buf, msg->payload);
}
template <class Parent>
error handle_data(Parent&, span<const byte>) {
rec_buf_->push_back(static_cast<byte>(id_));
return none;
}
template <class Manager>
void resolve(Manager&, const std::string&, actor) {
rec_buf_->push_back(static_cast<byte>(id_));
}
template <class Transport>
void timeout(Transport&, atom_value, uint64_t) {
rec_buf_->push_back(static_cast<byte>(id_));
}
void handle_error(sec) {
rec_buf_->push_back(static_cast<byte>(id_));
}
static expected<buffer_type> serialize(actor_system&,
const type_erased_tuple&) {
return buffer_type{};
}
private:
std::shared_ptr<buffer_type> rec_buf_;
uint8_t id_;
};
struct dummy_application_factory {
public:
using application_type = dummy_application;
dummy_application_factory(std::shared_ptr<buffer_type> buf)
: buf_(std::move(buf)), application_cnt_(0) {
// nop
}
dummy_application make() {
return dummy_application{buf_, application_cnt_++};
}
private:
std::shared_ptr<buffer_type> buf_;
uint8_t application_cnt_;
};
struct dummy_transport {
using transport_type = dummy_transport;
using factory_type = dummy_application_factory;
using application_type = dummy_application;
dummy_transport(std::shared_ptr<buffer_type> buf) : buf_(std::move(buf)) {
// nop
}
template <class IdType>
void write_packet(IdType, span<buffer_type*> buffers) {
for (auto buf : buffers)
buf_->insert(buf_->end(), buf->begin(), buf->end());
}
transport_type& transport() {
return *this;
}
buffer_type next_header_buffer() {
return {};
}
buffer_type next_payload_buffer() {
return {};
}
private:
std::shared_ptr<buffer_type> buf_;
};
struct testdata {
testdata(uint8_t worker_id, node_id id, ip_endpoint ep)
: worker_id(worker_id), nid(std::move(id)), ep(ep) {
// nop
}
uint8_t worker_id;
node_id nid;
ip_endpoint ep;
};
// TODO: switch to std::operator""s when switching to C++14
ip_endpoint operator"" _ep(const char* cstr, size_t cstr_len) {
ip_endpoint ep;
string_view str(cstr, cstr_len);
if (auto err = parse(str, ep))
CAF_FAIL("parse returned error: " << err);
return ep;
}
uri operator"" _u(const char* cstr, size_t cstr_len) {
uri result;
string_view str{cstr, cstr_len};
auto err = parse(str, result);
if (err)
CAF_FAIL("error while parsing " << str << ": " << to_string(err));
return result;
}
struct fixture : host_fixture {
using dispatcher_type = transport_worker_dispatcher<dummy_application_factory,
ip_endpoint>;
fixture()
: buf{std::make_shared<buffer_type>()},
dispatcher{dummy_application_factory{buf}},
dummy{buf} {
add_new_workers();
}
std::unique_ptr<net::endpoint_manager_queue::message>
make_dummy_message(node_id nid) {
actor_id aid = 42;
actor_config cfg;
auto p = make_actor<dummy_actor, strong_actor_ptr>(aid, nid, &sys, cfg);
auto test_span = as_bytes(make_span(hello_test));
buffer_type payload(test_span.begin(), test_span.end());
auto receiver = actor_cast<strong_actor_ptr>(p);
if (!receiver)
CAF_FAIL("failed to cast receiver to a strong_actor_ptr");
mailbox_element::forwarding_stack stack;
auto elem = make_mailbox_element(nullptr, make_message_id(12345),
std::move(stack), make_message());
return detail::make_unique<endpoint_manager_queue::message>(std::move(elem),
receiver,
payload);
}
bool contains(byte x) {
return std::count(buf->begin(), buf->end(), x) > 0;
}
void add_new_workers() {
for (auto& data : test_data) {
auto worker = dispatcher.add_new_worker(dummy, data.nid, data.ep);
if (!worker)
CAF_FAIL("add_new_worker returned an error: " << worker.error());
}
buf->clear();
}
void test_write_message(testdata& testcase) {
auto msg = make_dummy_message(testcase.nid);
if (!msg->receiver)
CAF_FAIL("receiver is null");
dispatcher.write_message(dummy, std::move(msg));
}
actor_system_config cfg{};
actor_system sys{cfg};
std::shared_ptr<buffer_type> buf;
dispatcher_type dispatcher;
dummy_transport dummy;
std::vector<testdata> test_data{
{0, make_node_id("http:file"_u), "[::1]:1"_ep},
{1, make_node_id("http:file?a=1&b=2"_u), "[fe80::2:34]:12345"_ep},
{2, make_node_id("http:file#42"_u), "[1234::17]:4444"_ep},
{3, make_node_id("http:file?a=1&b=2#42"_u), "[2332::1]:12"_ep},
};
};
#define CHECK_HANDLE_DATA(testcase) \
dispatcher.handle_data(dummy, span<byte>{}, testcase.ep); \
CAF_CHECK_EQUAL(buf->size(), 1u); \
CAF_CHECK_EQUAL(static_cast<byte>(testcase.worker_id), buf->at(0)); \
buf->clear();
#define CHECK_WRITE_MESSAGE(testcase) \
test_write_message(testcase); \
CAF_CHECK_EQUAL(buf->size(), hello_test.size() + 1u); \
CAF_CHECK_EQUAL(static_cast<byte>(testcase.worker_id), buf->at(0)); \
CAF_CHECK_EQUAL(memcmp(buf->data() + 1, hello_test.data(), \
hello_test.size()), \
0); \
buf->clear();
#define CHECK_TIMEOUT(testcase) \
dispatcher.set_timeout(1u, testcase.ep); \
dispatcher.timeout(dummy, atom("dummy"), 1u); \
CAF_CHECK_EQUAL(buf->size(), 1u); \
CAF_CHECK_EQUAL(static_cast<byte>(testcase.worker_id), buf->at(0)); \
buf->clear();
} // namespace
CAF_TEST_FIXTURE_SCOPE(transport_worker_dispatcher_test, fixture)
CAF_TEST(init) {
dispatcher_type dispatcher{dummy_application_factory{buf}};
CAF_CHECK_EQUAL(dispatcher.init(dummy), none);
}
CAF_TEST(handle_data) {
CHECK_HANDLE_DATA(test_data.at(0));
CHECK_HANDLE_DATA(test_data.at(1));
CHECK_HANDLE_DATA(test_data.at(2));
CHECK_HANDLE_DATA(test_data.at(3));
}
CAF_TEST(write_message write_packet) {
CHECK_WRITE_MESSAGE(test_data.at(0));
CHECK_WRITE_MESSAGE(test_data.at(1));
CHECK_WRITE_MESSAGE(test_data.at(2));
CHECK_WRITE_MESSAGE(test_data.at(3));
}
CAF_TEST(resolve) {
// TODO think of a test for this
}
CAF_TEST(timeout) {
CHECK_TIMEOUT(test_data.at(0));
CHECK_TIMEOUT(test_data.at(1));
CHECK_TIMEOUT(test_data.at(2));
CHECK_TIMEOUT(test_data.at(3));
}
CAF_TEST(handle_error) {
dispatcher.handle_error(sec::unavailable_or_would_block);
CAF_CHECK_EQUAL(buf->size(), 4u);
CAF_CHECK(contains(byte(0)));
CAF_CHECK(contains(byte(1)));
CAF_CHECK(contains(byte(2)));
CAF_CHECK(contains(byte(3)));
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE udp_datagram_socket
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/ip_address.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/net/ip.hpp"
using namespace caf;
using namespace caf::net;
using namespace caf::net::ip;
namespace {
constexpr string_view hello_test = "Hello test!";
struct fixture : host_fixture {
fixture() : host_fixture(), buf(1024) {
addresses = local_addresses("localhost");
CAF_CHECK(!addresses.empty());
ep = ip_endpoint(*addresses.begin(), 0);
auto send_pair = unbox(make_udp_datagram_socket(ep));
send_socket = send_pair.first;
auto receive_pair = unbox(make_udp_datagram_socket(ep));
receive_socket = receive_pair.first;
ep.port(ntohs(receive_pair.second));
}
~fixture() {
close(send_socket);
close(receive_socket);
}
std::vector<ip_address> addresses;
actor_system_config cfg;
actor_system sys{cfg};
ip_endpoint ep;
udp_datagram_socket send_socket;
udp_datagram_socket receive_socket;
std::vector<byte> buf;
};
error read_from_socket(udp_datagram_socket sock, std::vector<byte>& buf) {
uint8_t receive_attempts = 0;
variant<std::pair<size_t, ip_endpoint>, sec> read_ret;
do {
read_ret = read(sock, make_span(buf));
if (auto read_res = get_if<std::pair<size_t, ip_endpoint>>(&read_ret)) {
buf.resize(read_res->first);
} else if (get<sec>(read_ret) != sec::unavailable_or_would_block) {
return make_error(get<sec>(read_ret), "read failed");
}
if (++receive_attempts > 100)
return make_error(sec::runtime_error,
"too many unavailable_or_would_blocks");
} while (read_ret.index() != 0);
return none;
}
struct header {
header(size_t payload_size) : payload_size(payload_size) {
// nop
}
header() : header(0) {
// nop
}
template <class Inspector>
friend typename Inspector::result_type inspect(Inspector& f, header& x) {
return f(meta::type_name("header"), x.payload_size);
}
size_t payload_size;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(udp_datagram_socket_test, fixture)
CAF_TEST(read / write using span<byte>) {
if (auto err = nonblocking(socket_cast<net::socket>(receive_socket), true))
CAF_FAIL("setting socket to nonblocking failed: " << err);
CAF_CHECK_EQUAL(read(receive_socket, make_span(buf)),
sec::unavailable_or_would_block);
CAF_MESSAGE("sending data to " << to_string(ep));
CAF_CHECK_EQUAL(write(send_socket, as_bytes(make_span(hello_test)), ep),
hello_test.size());
CAF_CHECK_EQUAL(read_from_socket(receive_socket, buf), none);
string_view received{reinterpret_cast<const char*>(buf.data()), buf.size()};
CAF_CHECK_EQUAL(received, hello_test);
}
CAF_TEST(read / write using span<std::vector<byte>*>) {
// generate header and payload in separate buffers
header hdr{hello_test.size()};
std::vector<byte> hdr_buf;
serializer_impl<std::vector<byte>> sink(sys, hdr_buf);
if (auto err = sink(hdr))
CAF_FAIL("serializing payload failed" << sys.render(err));
auto bytes = as_bytes(make_span(hello_test));
std::vector<byte> payload_buf(bytes.begin(), bytes.end());
auto packet_size = hdr_buf.size() + payload_buf.size();
std::vector<std::vector<byte>*> bufs{&hdr_buf, &payload_buf};
CAF_CHECK_EQUAL(write(send_socket, make_span(bufs), ep), packet_size);
// receive both as one single packet.
buf.resize(packet_size);
CAF_CHECK_EQUAL(read_from_socket(receive_socket, buf), none);
CAF_CHECK_EQUAL(buf.size(), packet_size);
binary_deserializer source(nullptr, buf);
header recv_hdr;
source(recv_hdr);
CAF_CHECK_EQUAL(hdr.payload_size, recv_hdr.payload_size);
string_view received{reinterpret_cast<const char*>(buf.data())
+ sizeof(header),
buf.size() - sizeof(header)};
CAF_CHECK_EQUAL(received, hello_test);
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment