Commit 161e554b authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/neverlord/caf-net-redesign'

parents 4aa07140 7d55cca0
......@@ -139,5 +139,9 @@ else()
endfunction()
endif()
add_net_example(web_socket quote-server)
add_net_example(http time-server)
add_net_example(web_socket echo)
add_net_example(web_socket hello-client)
add_net_example(web_socket quote-server)
add_net_example(web_socket stock-ticker)
// Simple WebSocket server that sends everything it receives back to the sender.
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/caf_main.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/net/http/serve.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include <iostream>
#include <utility>
static constexpr uint16_t default_port = 8080;
struct config : caf::actor_system_config {
config() {
opt_group{custom_options_, "global"} //
.add<uint16_t>("port,p", "port to listen for incoming connections");
}
};
int caf_main(caf::actor_system& sys, const config& cfg) {
using namespace caf;
// Open up a TCP port for incoming connections.
auto port = caf::get_or(cfg, "port", default_port);
net::tcp_accept_socket fd;
if (auto maybe_fd = net::make_tcp_accept_socket({ipv4_address{}, port},
true)) {
std::cout << "*** started listening for incoming connections on port "
<< port << '\n';
fd = std::move(*maybe_fd);
} else {
std::cerr << "*** unable to open port " << port << ": "
<< to_string(maybe_fd.error()) << '\n';
return EXIT_FAILURE;
}
// Create buffers to signal events from the WebSocket server to the worker.
auto [worker_pull, server_push] = net::http::make_request_resource();
// Spin up the HTTP server.
net::http::serve(sys, fd, std::move(server_push));
// Spin up a worker to handle the HTTP requests.
auto worker = sys.spawn([wres = worker_pull](event_based_actor* self) {
// For each incoming request ...
wres
.observe_on(self) //
.for_each([](const net::http::request& req) {
// ... we simply return the current time as string.
// Note: we cannot respond more than once to a request.
auto str = caf::deep_to_string(make_timestamp());
req.respond(net::http::status::ok, "text/plain", str);
});
});
sys.await_all_actors_done();
return EXIT_SUCCESS;
}
CAF_MAIN(caf::net::middleman)
......@@ -50,8 +50,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
.for_each([self](const event_t& event) {
// ... that simply pushes data back to the sender.
auto [pull, push] = event.data();
self->make_observable()
.from_resource(pull)
pull.observe_on(self)
.do_on_next([](const ws::frame& x) {
if (x.is_binary()) {
std::cout << "*** received a binary WebSocket frame of size "
......
// Simple WebSocket server that sends everything it receives back to the sender.
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/caf_main.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/web_socket/connect.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include <iostream>
#include <utility>
static constexpr uint16_t default_port = 8080;
struct config : caf::actor_system_config {
config() {
opt_group{custom_options_, "global"} //
.add<std::string>("host", "TCP endpoint to connect to")
.add<uint16_t>("port,p", "port for connecting to the host")
.add<std::string>("endpoint", "sets the Request-URI field")
.add<std::string>("protocols", "sets the Sec-WebSocket-Protocol field")
.add<size_t>("max,m", "maximum number of message to receive");
}
};
int caf_main(caf::actor_system& sys, const config& cfg) {
namespace cn = caf::net;
namespace ws = cn::web_socket;
// Sanity checking.
auto host = caf::get_or(cfg, "host", "");
if (host.empty()) {
std::cerr << "*** missing mandatory argument: host\n";
return EXIT_FAILURE;
}
// Ask user for the hello message.
std::string hello;
std::cout << "Please enter a hello message for the server: " << std::flush;
std::getline(std::cin, hello);
// Open up the TCP connection.
auto port = caf::get_or(cfg, "port", default_port);
cn::tcp_stream_socket fd;
if (auto maybe_fd = cn::make_connected_tcp_stream_socket(host, port)) {
std::cout << "*** connected to " << host << ':' << port << '\n';
fd = std::move(*maybe_fd);
} else {
std::cerr << "*** unable to connect to " << host << ':' << port << ": "
<< to_string(maybe_fd.error()) << '\n';
return EXIT_FAILURE;
}
// Spin up the WebSocket.
ws::handshake hs;
hs.host(host);
hs.endpoint(caf::get_or(cfg, "endpoint", "/"));
if (auto str = caf::get_as<std::string>(cfg, "protocols");
str && !str->empty())
hs.protocols(std::move(*str));
ws::connect(sys, fd, std::move(hs), [&](const ws::connect_event_t& conn) {
sys.spawn([conn, hello](caf::event_based_actor* self) {
auto [pull, push] = conn.data();
// Print everything from the server to stdout.
pull.observe_on(self)
.do_on_error([](const caf::error& what) {
std::cerr << "*** error while reading from the WebSocket: "
<< to_string(what) << '\n';
})
.compose([self](auto in) {
if (auto limit = caf::get_as<size_t>(self->config(), "max")) {
return std::move(in).take(*limit).as_observable();
} else {
return std::move(in).as_observable();
}
})
.for_each([](const ws::frame& msg) {
if (msg.is_text()) {
std::cout << "Server: " << msg.as_text() << '\n';
} else if (msg.is_binary()) {
std::cout << "Server: [binary message of size "
<< msg.as_binary().size() << "]\n";
}
});
// Send our hello message and wait until the server closes the socket.
self->make_observable()
.just(ws::frame{hello})
.concat(self->make_observable().never<ws::frame>())
.subscribe(push);
});
});
return EXIT_SUCCESS;
}
CAF_MAIN(caf::net::middleman)
......@@ -102,7 +102,8 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
// Open up a TCP port for incoming connections.
auto port = caf::get_or(cfg, "port", default_port);
cn::tcp_accept_socket fd;
if (auto maybe_fd = cn::make_tcp_accept_socket({caf::ipv4_address{}, port})) {
if (auto maybe_fd = cn::make_tcp_accept_socket({caf::ipv4_address{}, port},
true)) {
std::cout << "*** started listening for incoming connections on port "
<< port << '\n';
fd = std::move(*maybe_fd);
......@@ -127,7 +128,8 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
auto quote = quotes.empty() ? "Try /epictetus, /seneca or /plato."
: f(quotes);
self->make_observable().just(ws::frame{quote}).subscribe(push);
// Note: we simply drop `pull` here, which will close the buffer.
// We ignore whatever the client may send to us.
pull.observe_on(self).subscribe(std::ignore);
});
});
// Callback for incoming WebSocket requests.
......
// Pseudo "Stock Ticker" that publishes random updates once per second via
// WebSocket feed.
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/caf_main.hpp"
#include "caf/cow_string.hpp"
#include "caf/cow_tuple.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/json_writer.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/web_socket/accept.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include "caf/span.hpp"
#include <cassert>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <utility>
using namespace std::literals;
static constexpr uint16_t default_port = 8080;
namespace stock {
struct info {
std::string symbol;
std::string currency;
double current;
double open;
double high;
double low;
};
template <class Inspector>
bool inspect(Inspector& f, info& x) {
return f.object(x).fields(f.field("symbol", x.symbol),
f.field("currency", x.currency),
f.field("open", x.open), f.field("high", x.high),
f.field("low", x.low));
}
} // namespace stock
struct random_feed_state {
using frame = caf::net::web_socket::frame;
using accept_event = caf::net::web_socket::accept_event_t<>;
random_feed_state(caf::event_based_actor* selfptr,
caf::async::consumer_resource<accept_event> events,
caf::timespan update_interval)
: self(selfptr), val_dist(0, 100000), index_dist(0, 19) {
// Init random number generator.
std::random_device rd;
rng.seed(rd());
// Fill vector with some stuff.
for (size_t i = 0; i < 20; ++i) {
std::uniform_int_distribution<int> char_dist{'A', 'Z'};
std::string symbol;
for (size_t j = 0; j < 5; ++j)
symbol += static_cast<char>(char_dist(rng));
auto val = next_value();
infos.emplace_back(
stock::info{std::move(symbol), "USD", val, val, val, val});
}
// Create the feed to push updates once per second.
writer.skip_object_type_annotation(true);
feed = self->make_observable()
.interval(update_interval)
.map([this](int64_t) {
writer.reset();
auto& x = update();
if (!writer.apply(x)) {
std::cerr << "*** failed to generate JSON: "
<< to_string(writer.get_error()) << '\n';
return frame{};
}
return frame{writer.str()};
})
.filter([](const frame& x) {
// Just in case: drop frames that failed to generate JSON.
return x.is_text();
})
.share();
// Subscribe once to start the feed immediately and to keep it running.
feed.for_each([n = 1](const frame&) mutable {
std::cout << "*** tick " << n++ << "\n";
});
// Add each incoming WebSocket listener to the feed.
auto n = std::make_shared<int>(0);
events
.observe_on(self) //
.for_each([this, n](const accept_event& ev) {
std::cout << "*** added listener (n = " << ++*n << ")\n";
auto [pull, push] = ev.data();
pull.observe_on(self)
.do_finally([n] { //
std::cout << "*** removed listener (n = " << --*n << ")\n";
})
.subscribe(std::ignore);
feed.subscribe(push);
});
}
// Picks a random stock, assigns a new value to it, and returns it.
stock::info& update() {
auto& x = infos[index_dist(rng)];
auto val = next_value();
x.current = val;
x.high = std::max(x.high, val);
x.low = std::min(x.high, val);
return x;
}
double next_value() {
return val_dist(rng) / 100.0;
}
caf::event_based_actor* self;
caf::flow::observable<frame> feed;
caf::json_writer writer;
std::vector<stock::info> infos;
std::minstd_rand rng;
std::uniform_int_distribution<int> val_dist;
std::uniform_int_distribution<size_t> index_dist;
};
using random_feed_impl = caf::stateful_actor<random_feed_state>;
struct config : caf::actor_system_config {
config() {
opt_group{custom_options_, "global"} //
.add<uint16_t>("port,p", "port to listen for incoming connections")
.add<caf::timespan>("interval,i", "update interval");
}
};
int caf_main(caf::actor_system& sys, const config& cfg) {
namespace cn = caf::net;
namespace ws = cn::web_socket;
// Open up a TCP port for incoming connections.
auto port = caf::get_or(cfg, "port", default_port);
cn::tcp_accept_socket fd;
if (auto maybe_fd = cn::make_tcp_accept_socket({caf::ipv4_address{}, port},
true)) {
std::cout << "*** started listening for incoming connections on port "
<< port << '\n';
fd = std::move(*maybe_fd);
} else {
std::cerr << "*** unable to open port " << port << ": "
<< to_string(maybe_fd.error()) << '\n';
return EXIT_FAILURE;
}
// Create buffers to signal events from the WebSocket server to the worker.
auto [wres, sres] = ws::make_accept_event_resources();
// Spin up a worker to handle the events.
auto interval = caf::get_or(cfg, "interval", caf::timespan{1s});
auto worker = sys.spawn<random_feed_impl>(std::move(wres), interval);
// Callback for incoming WebSocket requests.
auto on_request = [](const caf::settings&, auto& req) { req.accept(); };
// Set everything in motion.
ws::accept(sys, fd, std::move(sres), on_request);
return EXIT_SUCCESS;
}
CAF_MAIN(caf::net::middleman)
......@@ -224,6 +224,10 @@ caf_add_component(
actor_system_config
actor_termination
aout
async.blocking_consumer
async.blocking_producer
async.consumer_adapter
async.producer_adapter
async.promise
async.spsc_buffer
behavior
......
......@@ -33,20 +33,24 @@ public:
template <class ErrorPolicy, class TimePoint>
read_result pull(ErrorPolicy policy, T& item, TimePoint timeout) {
if (!buf_) {
return abort_reason_ ? read_result::abort : read_result::stop;
}
val_ = &item;
std::unique_lock guard{buf_->mtx()};
if constexpr (std::is_same_v<TimePoint, none_t>) {
buf_->await_consumer_ready(guard, cv_);
} else {
if (!buf_->await_consumer_ready(guard, cv_, timeout))
return read_result::timeout;
return read_result::try_again_later;
}
auto [again, n] = buf_->pull_unsafe(guard, policy, 1u, *this);
CAF_IGNORE_UNUSED(again);
CAF_ASSERT(!again || n == 1);
if (!again) {
buf_ = nullptr;
}
if (n == 1) {
return read_result::ok;
} else if (buf_->abort_reason_unsafe()) {
} else if (abort_reason_) {
return read_result::abort;
} else {
return read_result::stop;
......@@ -58,19 +62,18 @@ public:
return pull(policy, item, none);
}
void on_next(span<const T> items) {
CAF_ASSERT(items.size() == 1);
*val_ = items[0];
void on_next(const T& item) {
*val_ = item;
}
void on_complete() {
}
void on_error(const caf::error&) {
// nop
void on_error(const caf::error& abort_reason) {
abort_reason_ = abort_reason;
}
void dispose() {
void cancel() {
if (buf_) {
buf_->cancel();
buf_ = nullptr;
......@@ -94,8 +97,8 @@ public:
deref();
}
error abort_reason() const {
buf_->abort_reason()();
const error& abort_reason() const {
return abort_reason_;
}
CAF_INTRUSIVE_PTR_FRIENDS(impl)
......@@ -104,6 +107,7 @@ public:
spsc_buffer_ptr<T> buf_;
std::condition_variable cv_;
T* val_ = nullptr;
error abort_reason_;
};
using impl_ptr = intrusive_ptr<impl>;
......@@ -122,6 +126,10 @@ public:
// nop
}
explicit blocking_consumer(spsc_buffer_ptr<T> buf) {
impl_.emplace(std::move(buf));
}
~blocking_consumer() {
if (impl_)
impl_->cancel();
......
......@@ -4,14 +4,15 @@
#pragma once
#include <condition_variable>
#include <type_traits>
#include "caf/async/producer.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include <condition_variable>
#include <optional>
#include <type_traits>
namespace caf::async {
......@@ -19,7 +20,7 @@ namespace caf::async {
template <class T>
class blocking_producer {
public:
class impl : public ref_counted, public producer {
class impl : public detail::atomic_ref_counted, public producer {
public:
impl() = delete;
impl(const impl&) = delete;
......@@ -99,8 +100,6 @@ public:
deref();
}
CAF_INTRUSIVE_PTR_FRIENDS(impl)
private:
spsc_buffer_ptr<T> buf_;
mutable std::mutex mtx_;
......@@ -169,18 +168,30 @@ public:
return impl_->canceled();
}
explicit operator bool() const noexcept {
return static_cast<bool>(impl_);
}
private:
intrusive_ptr<impl> impl_;
};
/// @pre `buf != nullptr`
/// @relates blocking_producer
template <class T>
blocking_producer<T> make_blocking_producer(spsc_buffer_ptr<T> buf) {
using impl_t = typename blocking_producer<T>::impl;
return blocking_producer<T>{make_counted<impl_t>(std::move(buf))};
}
/// @relates blocking_producer
template <class T>
expected<blocking_producer<T>>
std::optional<blocking_producer<T>>
make_blocking_producer(producer_resource<T> res) {
if (auto buf = res.try_open()) {
using impl_t = typename blocking_producer<T>::impl;
return {blocking_producer<T>{make_counted<impl_t>(std::move(buf))}};
return {make_blocking_producer(std::move(buf))};
} else {
return {make_error(sec::cannot_open_resource)};
return {};
}
}
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/consumer.hpp"
#include "caf/async/execution_context.hpp"
#include "caf/async/read_result.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp"
#include <optional>
namespace caf::async {
/// Integrates an SPSC buffer consumer into an asynchronous event loop.
template <class T>
class consumer_adapter {
public:
class impl : public detail::atomic_ref_counted, public consumer {
public:
impl() = delete;
impl(const impl&) = delete;
impl& operator=(const impl&) = delete;
impl(spsc_buffer_ptr<T> buf, execution_context_ptr ctx, action do_wakeup)
: buf_(std::move(buf)),
ctx_(std::move(ctx)),
do_wakeup_(std::move(do_wakeup)) {
buf_->set_consumer(this);
}
~impl() {
if (buf_) {
buf_->cancel();
do_wakeup_.dispose();
}
}
void cancel() {
if (buf_) {
buf_->cancel();
buf_ = nullptr;
do_wakeup_.dispose();
do_wakeup_ = nullptr;
}
}
template <class ErrorPolicy>
read_result pull(ErrorPolicy policy, T& item) {
if (buf_) {
val_ = &item;
auto [again, n] = buf_->pull(policy, 1u, *this);
if (!again) {
buf_ = nullptr;
}
if (n == 1) {
return read_result::ok;
} else if (again) {
CAF_ASSERT(n == 0);
return read_result::try_again_later;
} else {
CAF_ASSERT(n == 0);
return abort_reason_ ? read_result::abort : read_result::stop;
}
} else {
return abort_reason_ ? read_result::abort : read_result::stop;
}
}
const error& abort_reason() const noexcept {
return abort_reason_;
}
bool has_data() const noexcept {
return buf_ ? buf_->has_data() : false;
}
bool has_consumer_event() const noexcept {
return buf_ ? buf_->has_consumer_event() : false;
}
void on_next(const T& item) {
*val_ = item;
}
void on_complete() {
// nop
}
void on_error(const caf::error& what) {
abort_reason_ = what;
}
void on_producer_ready() override {
// nop
}
void on_producer_wakeup() override {
ctx_->schedule(do_wakeup_);
}
void ref_consumer() const noexcept override {
ref();
}
void deref_consumer() const noexcept override {
deref();
}
private:
spsc_buffer_ptr<T> buf_;
execution_context_ptr ctx_;
action do_wakeup_;
error abort_reason_;
T* val_ = nullptr;
};
using impl_ptr = intrusive_ptr<impl>;
consumer_adapter() = default;
consumer_adapter(const consumer_adapter&) = delete;
consumer_adapter& operator=(const consumer_adapter&) = delete;
consumer_adapter(consumer_adapter&&) = default;
consumer_adapter& operator=(consumer_adapter&&) = default;
explicit consumer_adapter(impl_ptr ptr) : impl_(std::move(ptr)) {
// nop
}
~consumer_adapter() {
if (impl_)
impl_->cancel();
}
template <class Policy>
read_result pull(Policy policy, T& result) {
if (impl_)
return impl_->pull(policy, result);
else
return read_result::abort;
}
void cancel() {
if (impl_) {
impl_->cancel();
impl_ = nullptr;
}
}
error abort_reason() const noexcept {
if (impl_)
return impl_->abort_reason();
else
return make_error(sec::disposed);
}
explicit operator bool() const noexcept {
return static_cast<bool>(impl_);
}
bool has_data() const noexcept {
return impl_ ? impl_->has_data() : false;
}
bool has_consumer_event() const noexcept {
return impl_ ? impl_->has_consumer_event() : false;
}
static consumer_adapter make(spsc_buffer_ptr<T> buf,
execution_context_ptr ctx, action do_wakeup) {
if (buf) {
using impl_t = typename consumer_adapter<T>::impl;
auto impl = make_counted<impl_t>(std::move(buf), std::move(ctx),
std::move(do_wakeup));
return consumer_adapter<T>{std::move(impl)};
} else {
return {};
}
}
static std::optional<consumer_adapter>
make(consumer_resource<T> res, execution_context_ptr ctx, action do_wakeup) {
if (auto buf = res.try_open()) {
return {make(std::move(buf), std::move(ctx), std::move(do_wakeup))};
} else {
return {};
}
}
private:
intrusive_ptr<impl> impl_;
};
/// @pre `buf != nullptr`
/// @relates consumer_adapter
template <class T>
consumer_adapter<T> make_consumer_adapter(spsc_buffer_ptr<T> buf,
execution_context_ptr ctx,
action do_wakeup) {
return consumer_adapter<T>::make(std::move(buf), std::move(ctx),
std::move(do_wakeup));
}
/// @relates consumer_adapter
template <class T>
std::optional<consumer_adapter<T>>
make_consumer_adapter(consumer_resource<T> res, execution_context_ptr ctx,
action do_wakeup) {
if (auto buf = res.try_open()) {
return {consumer_adapter<T>::make(std::move(buf), std::move(ctx),
std::move(do_wakeup))};
} else {
return {};
}
}
} // namespace caf::async
......@@ -109,12 +109,24 @@ public:
return {ctx, std::move(cell_)};
}
/// Binds this future to an @ref execution_context to run callbacks.
/// @pre `valid()`
bound_future<T> bind_to(execution_context& ctx) && {
return {&ctx, std::move(cell_)};
}
/// Binds this future to an @ref execution_context to run callbacks.
/// @pre `valid()`
bound_future<T> bind_to(execution_context* ctx) const& {
return {ctx, cell_};
}
/// Binds this future to an @ref execution_context to run callbacks.
/// @pre `valid()`
bound_future<T> bind_to(execution_context& ctx) const& {
return {&ctx, cell_};
}
/// Queries whether the result of the asynchronous computation is still
/// pending, i.e., neither `set_value` nor `set_error` has been called on the
/// @ref promise.
......
......@@ -4,7 +4,7 @@
#pragma once
#include <caf/fwd.hpp>
#include "caf/fwd.hpp"
namespace caf::async {
......@@ -12,6 +12,7 @@ namespace caf::async {
class batch;
class consumer;
class execution_context;
class producer;
// -- template classes ---------------------------------------------------------
......@@ -31,6 +32,10 @@ class promise;
template <class T>
class future;
// -- smart pointer aliases ----------------------------------------------------
using execution_context_ptr = intrusive_ptr<execution_context>;
// -- free function templates --------------------------------------------------
template <class T>
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/execution_context.hpp"
#include "caf/async/producer.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp"
#include <optional>
namespace caf::async {
/// Integrates an SPSC buffer producer into an asynchronous event loop.
template <class T>
class producer_adapter {
public:
class impl : public detail::atomic_ref_counted, public producer {
public:
impl() = delete;
impl(const impl&) = delete;
impl& operator=(const impl&) = delete;
impl(spsc_buffer_ptr<T> buf, execution_context_ptr ctx, action do_resume,
action do_cancel)
: buf_(std::move(buf)),
ctx_(std::move(ctx)),
do_resume_(std::move(do_resume)),
do_cancel_(std::move(do_cancel)) {
buf_->set_producer(this);
}
size_t push(span<const T> items) {
return buf_->push(items);
}
bool push(const T& item) {
return buf_->push(item);
}
void close() {
if (buf_) {
buf_->close();
buf_ = nullptr;
do_resume_.dispose();
do_resume_ = nullptr;
do_cancel_.dispose();
do_cancel_ = nullptr;
}
}
void abort(error reason) {
if (buf_) {
buf_->abort(std::move(reason));
buf_ = nullptr;
do_resume_.dispose();
do_resume_ = nullptr;
do_cancel_.dispose();
do_cancel_ = nullptr;
}
}
void on_consumer_ready() override {
// nop
}
void on_consumer_cancel() override {
ctx_->schedule(do_cancel_);
}
void on_consumer_demand(size_t) override {
ctx_->schedule(do_resume_);
}
void ref_producer() const noexcept override {
ref();
}
void deref_producer() const noexcept override {
deref();
}
private:
spsc_buffer_ptr<T> buf_;
execution_context_ptr ctx_;
action do_resume_;
action do_cancel_;
};
using impl_ptr = intrusive_ptr<impl>;
producer_adapter() = default;
producer_adapter(const producer_adapter&) = delete;
producer_adapter& operator=(const producer_adapter&) = delete;
producer_adapter(producer_adapter&&) = default;
producer_adapter& operator=(producer_adapter&&) = default;
explicit producer_adapter(impl_ptr ptr) : impl_(std::move(ptr)) {
// nop
}
~producer_adapter() {
if (impl_)
impl_->close();
}
/// Makes `item` available to the consumer.
/// @returns the remaining demand.
size_t push(const T& item) {
if (!impl_)
CAF_RAISE_ERROR("cannot push to a closed producer adapter");
return impl_->push(item);
}
/// Makes `items` available to the consumer.
/// @returns the remaining demand.
size_t push(span<const T> items) {
if (!impl_)
CAF_RAISE_ERROR("cannot push to a closed producer adapter");
return impl_->push(items);
}
void close() {
if (impl_) {
impl_->close();
impl_ = nullptr;
}
}
void abort(error reason) {
if (impl_) {
impl_->abort(std::move(reason));
impl_ = nullptr;
}
}
explicit operator bool() const noexcept {
return static_cast<bool>(impl_);
}
/// @pre `buf != nullptr`
static producer_adapter make(spsc_buffer_ptr<T> buf,
execution_context_ptr ctx, action do_resume,
action do_cancel) {
if (buf) {
using impl_t = typename producer_adapter<T>::impl;
auto impl = make_counted<impl_t>(std::move(buf), std::move(ctx),
std::move(do_resume),
std::move(do_cancel));
return producer_adapter<T>{std::move(impl)};
} else {
return {};
}
}
static std::optional<producer_adapter>
make(producer_resource<T> res, execution_context_ptr ctx, action do_resume,
action do_cancel) {
if (auto buf = res.try_open()) {
return {make(std::move(buf), std::move(ctx), std::move(do_resume),
std::move(do_cancel))};
} else {
return {};
}
}
private:
intrusive_ptr<impl> impl_;
};
/// @pre `buf != nullptr`
/// @relates producer_adapter
template <class T>
producer_adapter<T> make_producer_adapter(spsc_buffer_ptr<T> buf,
execution_context_ptr ctx,
action do_resume, action do_cancel) {
return producer_adapter<T>::make(std::move(buf), std::move(ctx),
std::move(do_resume), std::move(do_cancel));
}
/// @relates producer_adapter
template <class T>
std::optional<producer_adapter<T>>
make_producer_adapter(producer_resource<T> res, execution_context_ptr ctx,
action do_resume, action do_cancel) {
if (auto buf = res.try_open()) {
return {producer_adapter<T>::make(std::move(buf), std::move(ctx),
std::move(do_resume),
std::move(do_cancel))};
} else {
return {};
}
}
} // namespace caf::async
......@@ -20,8 +20,9 @@ enum class read_result {
stop,
/// Signals that the source failed with an error.
abort,
/// Signals that the read operation timed out.
timeout,
/// Signals that the read operation cannot produce a result at the moment,
/// e.g., because of a timeout.
try_again_later,
};
/// @relates read_result
......
......@@ -62,7 +62,7 @@ public:
buf_.insert(buf_.end(), items.begin(), items.end());
if (buf_.size() == items.size() && consumer_)
consumer_->on_producer_wakeup();
if (capacity_ >= buf_.size())
if (capacity_ > buf_.size())
return capacity_ - buf_.size();
else
return 0;
......@@ -235,13 +235,8 @@ public:
consumer_buf_.assign(make_move_iterator(buf_.begin()),
make_move_iterator(buf_.begin() + n));
buf_.erase(buf_.begin(), buf_.begin() + n);
if (overflow == 0) {
signal_demand(n);
} else if (n <= overflow) {
overflow -= n;
} else {
if (n > overflow) {
signal_demand(n - overflow);
overflow = 0;
}
guard.unlock();
auto items = span<const T>{consumer_buf_.data(), n};
......@@ -251,6 +246,7 @@ public:
consumed += n;
consumer_buf_.clear();
guard.lock();
overflow = buf_.size() <= capacity_ ? 0u : buf_.size() - capacity_;
}
if (!buf_.empty() || !closed_) {
return {true, consumed};
......@@ -268,9 +264,13 @@ private:
void ready() {
producer_->on_consumer_ready();
consumer_->on_producer_ready();
if (!buf_.empty())
if (!buf_.empty()) {
consumer_->on_producer_wakeup();
signal_demand(capacity_);
if (capacity_ > buf_.size())
signal_demand(capacity_ - buf_.size());
} else {
signal_demand(capacity_);
}
}
void signal_demand(uint32_t new_demand) {
......@@ -329,7 +329,7 @@ struct resource_ctrl : ref_counted {
~resource_ctrl() {
if (buf) {
if constexpr (IsProducer) {
auto err = make_error(sec::invalid_upstream,
auto err = make_error(sec::disposed,
"producer_resource destroyed without opening it");
buf->abort(err);
} else {
......@@ -397,11 +397,19 @@ public:
}
}
/// Convenience function for calling
/// `ctx->make_observable().from_resource(*this)`.
template <class Coordinator>
auto observe_on(Coordinator* ctx) const {
return ctx->make_observable().from_resource(*this);
}
/// Calls `try_open` and on success immediately calls `cancel` on the buffer.
void cancel() {
if (auto buf = try_open())
buf->cancel();
}
explicit operator bool() const noexcept {
return ctrl_ != nullptr;
}
......@@ -454,6 +462,12 @@ public:
}
}
/// Calls `try_open` and on success immediately calls `close` on the buffer.
void close() {
if (auto buf = try_open())
buf->close();
}
explicit operator bool() const noexcept {
return ctrl_ != nullptr;
}
......
......@@ -17,6 +17,23 @@
// -- hard-coded default values for various CAF options ------------------------
namespace caf::defaults {
/// Stores the name of a parameter along with the fallback value.
template <class T>
struct parameter {
std::string_view name;
T fallback;
};
/// @relates parameter
template <class T>
constexpr parameter<T> make_parameter(std::string_view name, T fallback) {
return {name, fallback};
}
} // namespace caf::defaults
namespace caf::defaults::stream {
constexpr auto max_batch_delay = timespan{1'000'000};
......@@ -108,12 +125,12 @@ constexpr auto format = std::string_view{"[%c:%p] %d %m"};
namespace caf::defaults::middleman {
constexpr auto app_identifier = std::string_view{"generic-caf-app"};
constexpr auto network_backend = std::string_view{"default"};
constexpr auto max_consecutive_reads = size_t{50};
constexpr auto heartbeat_interval = timespan{10'000'000'000};
constexpr auto connection_timeout = timespan{30'000'000'000};
constexpr auto cached_udp_buffers = size_t{10};
constexpr auto connection_timeout = timespan{30'000'000'000};
constexpr auto heartbeat_interval = timespan{10'000'000'000};
constexpr auto max_consecutive_reads = size_t{50};
constexpr auto max_pending_msgs = size_t{10};
constexpr auto network_backend = std::string_view{"default"};
} // namespace caf::defaults::middleman
......@@ -124,3 +141,12 @@ constexpr auto batch_size = size_t{32};
constexpr auto buffer_size = size_t{128};
} // namespace caf::defaults::flow
namespace caf::defaults::net {
/// Configures how many concurrent connections an acceptor allows. When reaching
/// this limit, the connector stops accepting additional connections until a
/// previous connection has been closed.
constexpr auto max_connections = make_parameter("max-connections", size_t{64});
} // namespace caf::defaults::net
......@@ -131,6 +131,12 @@ public:
return pimpl_.compare(other.pimpl_);
}
// -- utility ----------------------------------------------------------------
/// Erases each `x` from `xs` where `x.disposed()`.
/// @returns The number of erased elements.
static size_t erase_disposed(std::vector<disposable>& xs);
private:
intrusive_ptr<impl> pimpl_;
};
......
......@@ -470,6 +470,11 @@ disposable observable<T>::subscribe(async::producer_resource<T> resource) {
}
}
template <class T>
disposable observable<T>::subscribe(ignore_t) {
return subscribe(observer<T>::ignore());
}
template <class T>
template <class OnNext>
disposable observable<T>::for_each(OnNext on_next) {
......
......@@ -15,6 +15,7 @@
#include "caf/intrusive_ptr.hpp"
#include <cstddef>
#include <tuple>
#include <utility>
#include <vector>
......@@ -32,6 +33,9 @@ public:
/// The pointer-to-implementation type.
using pimpl_type = intrusive_ptr<op::base<T>>;
/// Type for drop-all subscribers.
using ignore_t = decltype(std::ignore);
// -- constructors, destructors, and assignment operators --------------------
explicit observable(pimpl_type pimpl) noexcept : pimpl_(std::move(pimpl)) {
......@@ -57,6 +61,9 @@ public:
/// Creates a new observer that pushes all observed items to the resource.
disposable subscribe(async::producer_resource<T> resource);
/// Subscribes a new observer to the items emitted by this observable.
disposable subscribe(ignore_t);
/// Calls `on_next` for each item emitted by this observable.
template <class OnNext>
disposable for_each(OnNext on_next);
......
......@@ -47,7 +47,7 @@ public:
}
void on_error(const error& what) {
CAF_ASSERT(sub->subscribed());
CAF_ASSERT(sub->in_.valid());
sub->in_.dispose();
sub->in_ = nullptr;
sub->err_ = what;
......@@ -215,7 +215,9 @@ private:
--demand_;
out_.on_next(item);
}
if (!in_ && buf_.empty()) {
if (in_) {
pull();
} else if (buf_.empty()) {
if (err_)
out_.on_error(err_);
else
......
......@@ -55,6 +55,8 @@ public:
CAF_ASSERT(buf.empty());
--demand;
out.on_next(item);
if (when_consumed_some)
ctx->delay(when_consumed_some);
} else {
buf.push_back(item);
}
......@@ -120,6 +122,8 @@ public:
out.on_complete();
out = nullptr;
do_dispose();
} else if (got_some && when_consumed_some) {
ctx->delay(when_consumed_some);
}
}
}
......
......@@ -117,18 +117,23 @@ public:
void fwd_on_next(input_key key, const T& item) {
CAF_LOG_TRACE(CAF_ARG(key) << CAF_ARG(item));
if (auto ptr = get(key)) {
ptr->buf.push_back(item);
if (ptr->buf.size() == 1 && !flags_.running) {
flags_.running = true;
do_run();
if (!flags_.running && demand_ > 0) {
CAF_ASSERT(out_.valid());
--demand_;
out_.on_next(item);
ptr->sub.request(1);
} else {
ptr->buf.push_back(item);
}
}
}
void fwd_on_next(input_key key, const observable<T>& item) {
CAF_LOG_TRACE(CAF_ARG(key) << CAF_ARG(item));
if (auto ptr = get(key))
if (auto ptr = get(key)) {
subscribe_to(item);
ptr->sub.request(1);
}
}
// -- implementation of subscription_impl ------------------------------------
......@@ -150,7 +155,8 @@ public:
void request(size_t n) override {
CAF_ASSERT(out_.valid());
demand_ += n;
run_later();
if (demand_ == n)
run_later();
}
size_t buffered() const noexcept {
......@@ -181,9 +187,10 @@ private:
if (has_items_at(start))
return inputs_.begin() + start;
while (pos_ != start) {
if (has_items_at(pos_))
return inputs_.begin() + pos_;
auto p = pos_;
pos_ = (pos_ + 1) % inputs_.size();
if (has_items_at(p))
return inputs_.begin() + p;
}
return inputs_.end();
}
......@@ -195,14 +202,14 @@ private:
auto& buf = i->second->buf;
auto tmp = std::move(buf.front());
buf.pop_front();
if (auto& sub = i->second->sub)
if (auto& sub = i->second->sub) {
sub.request(1);
else if (buf.empty())
} else if (buf.empty()) {
inputs_.erase(i);
}
out_.on_next(tmp);
} else {
flags_.running = false;
return;
break;
}
}
if (out_ && inputs_.empty())
......
......@@ -24,8 +24,8 @@ public:
}
template <class Next, class... Steps>
void on_error(Next& next, Steps&... steps) {
next.on_error(steps...);
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
......
......@@ -203,14 +203,13 @@ public:
bool value(span<std::byte> x) override;
/// @private
std::string current_field_name();
private:
[[nodiscard]] position pos() const noexcept;
void append_current_field_name(std::string& str);
std::string current_field_name();
std::string mandatory_field_missing_str(std::string_view name);
template <bool PopOrAdvanceOnSuccess, class F>
......
......@@ -7,6 +7,7 @@
#include <string_view>
#include "caf/config_value.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/move_if_not_ptr.hpp"
#include "caf/dictionary.hpp"
......@@ -68,6 +69,12 @@ auto get_or(const settings& xs, std::string_view name, Fallback&& fallback) {
}
}
/// Convenience overload for calling `get_or(xs, param.name, param.fallback)`.
template <class T>
auto get_or(const settings& xs, defaults::parameter<T> param) {
return get_or(xs, param.name, param.fallback);
}
/// Tries to retrieve the value associated to `name` from `xs` as an instance of
/// type `T`.
/// @relates actor_system_config
......
......@@ -31,6 +31,7 @@ private:
telemetry::int_gauge* rss_ = nullptr;
telemetry::int_gauge* vms_ = nullptr;
telemetry::dbl_gauge* cpu_ = nullptr;
telemetry::int_gauge* fds_ = nullptr;
};
} // namespace caf::telemetry::importer
......@@ -238,100 +238,27 @@ std::ostream& operator<<(std::ostream& out, indentation indent) {
return out;
}
// Fakes a buffer interface but really prints to std::cout.
struct out_buf {
int end() {
return 0;
}
void push_back(char c) {
std::cout << c;
}
template <class Iterator, class Sentinel>
void insert(int, Iterator i, Sentinel e) {
while (i != e)
std::cout << *i++;
}
};
void print(const config_value::dictionary& xs, indentation indent);
void print_val(const config_value& val, indentation indent) {
out_buf out;
using std::cout;
switch (val.get_data().index()) {
default: // none
// omit
break;
case 1: // integer
detail::print(out, get<config_value::integer>(val));
break;
case 2: // boolean
detail::print(out, get<config_value::boolean>(val));
break;
case 3: // real
detail::print(out, get<config_value::real>(val));
break;
case 4: // timespan
detail::print(out, get<timespan>(val));
break;
case 5: // uri
cout << '<' << get<uri>(val).str() << '>';
break;
case 6: // string
detail::print_escaped(out, get<std::string>(val));
break;
case 7: { // list
auto& xs = get<config_value::list>(val);
if (xs.empty()) {
cout << "[]";
} else {
auto list_indent = indent + 2;
cout << "[\n";
for (auto& x : xs) {
cout << list_indent;
print_val(x, list_indent);
cout << ",\n";
}
cout << indent << ']';
}
break;
}
case 8: { // dictionary
print(get<config_value::dictionary>(val), indent + 2);
}
}
}
bool needs_quotes(const std::string& key) {
auto is_alnum_or_dash = [](char x) {
return isalnum(x) || x == '-' || x == '_';
};
return key.empty() || !std::all_of(key.begin(), key.end(), is_alnum_or_dash);
}
void print(const config_value::dictionary& xs, indentation indent) {
out_buf out;
using std::cout;
bool top_level = indent.size == 0;
for (const auto& [key, val] : xs) {
if (!top_level || (key != "dump-config" && key != "config-file")) {
cout << indent;
if (!needs_quotes(key)) {
cout << key;
} else {
detail::print_escaped(out, key);
}
if (!holds_alternative<config_value::dictionary>(val)) {
cout << " = ";
print_val(val, indent);
cout << '\n';
} else if (auto xs = get<config_value::dictionary>(val); xs.empty()) {
cout << "{}\n";
for (const auto& kvp : xs) {
if (kvp.first == "dump-config")
continue;
if (auto submap = get_if<config_value::dictionary>(&kvp.second)) {
cout << indent << kvp.first << " {\n";
print(*submap, indent + 2);
cout << indent << "}\n";
} else if (auto lst = get_if<config_value::list>(&kvp.second)) {
if (lst->empty()) {
cout << indent << kvp.first << " = []\n";
} else {
cout << " {\n";
print(get<config_value::dictionary>(val), indent + 2);
cout << indent << "}\n";
cout << indent << kvp.first << " = [\n";
auto list_indent = indent + 2;
for (auto& x : *lst)
cout << list_indent << to_string(x) << ",\n";
cout << indent << "]\n";
}
} else {
cout << indent << kvp.first << " = " << to_string(kvp.second) << '\n';
}
}
}
......
......@@ -10,6 +10,18 @@
namespace caf {
// -- member types -------------------------------------------------------------
disposable::impl::~impl() {
// nop
}
disposable disposable::impl::as_disposable() noexcept {
return disposable{intrusive_ptr<impl>{this}};
}
// -- factories ----------------------------------------------------------------
namespace {
class composite_impl : public ref_counted, public disposable::impl {
......@@ -52,14 +64,6 @@ private:
} // namespace
disposable::impl::~impl() {
// nop
}
disposable disposable::impl::as_disposable() noexcept {
return disposable{intrusive_ptr<impl>{this}};
}
disposable disposable::make_composite(std::vector<disposable> entries) {
if (entries.empty())
return {};
......@@ -67,4 +71,18 @@ disposable disposable::make_composite(std::vector<disposable> entries) {
return disposable{make_counted<composite_impl>(std::move(entries))};
}
// -- utility ------------------------------------------------------------------
size_t disposable::erase_disposed(std::vector<disposable>& xs) {
auto is_disposed = [](auto& hdl) { return hdl.disposed(); };
auto xs_end = xs.end();
if (auto e = std::remove_if(xs.begin(), xs_end, is_disposed); e != xs_end) {
auto res = std::distance(e, xs_end);
xs.erase(e, xs_end);
return static_cast<size_t>(res);
} else {
return 0;
}
}
} // namespace caf
......@@ -634,15 +634,10 @@ json_reader::position json_reader::pos() const noexcept {
}
void json_reader::append_current_field_name(std::string& result) {
if (field_.empty()) {
result += "null";
} else {
auto i = field_.begin();
result += *i++;
while (i != field_.end()) {
result += '.';
result += *i++;
}
result += "ROOT";
for (auto& key : field_) {
result += '.';
result.insert(result.end(), key.begin(), key.end());
}
}
......
......@@ -752,12 +752,9 @@ void scheduled_actor::run_actions() {
void scheduled_actor::update_watched_disposables() {
CAF_LOG_TRACE("");
auto disposed = [](auto& hdl) { return hdl.disposed(); };
auto& xs = watched_disposables_;
if (auto e = std::remove_if(xs.begin(), xs.end(), disposed); e != xs.end()) {
xs.erase(e, xs.end());
CAF_LOG_DEBUG("now watching" << xs.size() << "disposables");
}
[[maybe_unused]] auto n = disposable::erase_disposed(watched_disposables_);
CAF_LOG_DEBUG_IF(n > 0, "now watching" << watched_disposables_.size()
<< "disposables");
}
} // namespace caf
......@@ -21,18 +21,19 @@
namespace {
struct sys_stats {
int64_t rss;
int64_t vms;
double cpu_time;
int64_t rss = 0;
int64_t vms = 0;
double cpu_time = 0.0;
int64_t fds = 0;
};
sys_stats read_sys_stats();
static constexpr bool platform_supported_v = true;
constexpr bool platform_supported_v = true;
template <class CpuPtr, class RssPtr, class VmsPtr>
template <class CpuPtr, class RssPtr, class VmsPtr, class FdsPtr>
void sys_stats_init(caf::telemetry::metric_registry& reg, RssPtr& rss,
VmsPtr& vms, CpuPtr& cpu) {
VmsPtr& vms, CpuPtr& cpu, FdsPtr& fds) {
rss = reg.gauge_singleton("process", "resident_memory",
"Resident memory size.", "bytes");
vms = reg.gauge_singleton("process", "virtual_memory", "Virtual memory size.",
......@@ -40,15 +41,19 @@ void sys_stats_init(caf::telemetry::metric_registry& reg, RssPtr& rss,
cpu = reg.gauge_singleton<double>("process", "cpu",
"Total user and system CPU time spent.",
"seconds", true);
fds = reg.gauge_singleton("process", "open_fds",
"Number of open file descriptors.");
}
void update_impl(caf::telemetry::int_gauge* rss_gauge,
caf::telemetry::int_gauge* vms_gauge,
caf::telemetry::dbl_gauge* cpu_gauge) {
auto [rss, vmsize, cpu] = read_sys_stats();
caf::telemetry::dbl_gauge* cpu_gauge,
caf::telemetry::int_gauge* fds_gauge) {
auto [rss, vmsize, cpu, fds] = read_sys_stats();
rss_gauge->value(rss);
vms_gauge->value(vmsize);
cpu_gauge->value(cpu);
fds_gauge->value(fds);
}
} // namespace
......@@ -57,7 +62,7 @@ void update_impl(caf::telemetry::int_gauge* rss_gauge,
namespace {
static constexpr bool platform_supported_v = false;
constexpr bool platform_supported_v = false;
template <class... Ts>
void sys_stats_init(Ts&&...) {
......@@ -77,14 +82,16 @@ void update_impl(Ts&&...) {
#ifdef CAF_MACOS
# include <libproc.h>
# include <mach/mach.h>
# include <mach/task.h>
# include <sys/resource.h>
# include <unistd.h>
namespace {
sys_stats read_sys_stats() {
sys_stats result{0, 0, 0};
sys_stats result;
// Fetch memory usage.
{
mach_task_basic_info info;
......@@ -110,6 +117,20 @@ sys_stats read_sys_stats() {
result.cpu_time += ceil(info.system_time.microseconds / 1000.0) / 1000.0;
}
}
// Fetch open file handles.
{
// proc_pidinfo is undocumented, but this is what lsof also uses.
auto suggested_buf_size = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0,
nullptr, 0);
if (suggested_buf_size > 0) {
auto buf_size = suggested_buf_size;
auto buf = malloc(buf_size); // TODO: could be thread-local
auto res = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, buf, buf_size);
free(buf);
if (res > 0)
result.fds = res / sizeof(proc_fdinfo);
}
}
return result;
}
......@@ -121,8 +142,22 @@ sys_stats read_sys_stats() {
#if defined(CAF_LINUX) || defined(CAF_NET_BSD)
# include <dirent.h>
# include <unistd.h>
int64_t count_entries_in_directory(const char* path) {
int64_t result = 0;
if (auto dptr = opendir(path); dptr != nullptr) {
for (auto entry = readdir(dptr); entry != nullptr; entry = readdir(dptr)) {
auto fname = entry->d_name;
if (strcmp(".", fname) != 0 && strcmp("..", fname) != 0)
++result;
}
closedir(dptr);
}
return result;
}
/// Caches the result from a `sysconf` call in a cache variable to avoid
/// frequent syscalls. Sets `cache_var` to -1 in case of an error. Initially,
/// `cache_var` must be 0 and we assume a successful syscall would always return
......@@ -160,6 +195,7 @@ bool load_system_setting(std::atomic<long>& cache_var, long& var, int name,
#ifdef CAF_LINUX
# include <cstdio>
# include <dirent.h>
namespace {
......@@ -168,7 +204,7 @@ std::atomic<long> global_ticks_per_second;
std::atomic<long> global_page_size;
sys_stats read_sys_stats() {
sys_stats result{0, 0, 0};
sys_stats result;
long ticks_per_second = 0;
long page_size = 0;
if (!TRY_LOAD(ticks_per_second, _SC_CLK_TCK)
......@@ -218,6 +254,7 @@ sys_stats read_sys_stats() {
result.cpu_time += stime_ticks;
result.cpu_time /= ticks_per_second;
}
result.fds = count_entries_in_directory("/proc/self/fd");
return result;
}
......@@ -234,7 +271,7 @@ namespace {
std::atomic<long> global_page_size;
sys_stats read_sys_stats() {
auto result = sys_stats{0, 0, 0};
auto result = sys_stats;
auto kip2 = kinfo_proc2{};
auto kip2_size = sizeof(kip2);
int mib[6] = {
......@@ -263,7 +300,7 @@ sys_stats read_sys_stats() {
namespace caf::telemetry::importer {
process::process(metric_registry& reg) {
sys_stats_init(reg, rss_, vms_, cpu_);
sys_stats_init(reg, rss_, vms_, cpu_, fds_);
}
bool process::platform_supported() noexcept {
......@@ -271,7 +308,7 @@ bool process::platform_supported() noexcept {
}
void process::update() {
update_impl(rss_, vms_, cpu_);
update_impl(rss_, vms_, cpu_, fds_);
}
} // namespace caf::telemetry::importer
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE async.blocking_consumer
#include "caf/async/blocking_consumer.hpp"
#include "core-test.hpp"
#include "caf/async/blocking_producer.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include <mutex>
using namespace caf;
namespace {
struct fixture {
actor_system_config cfg;
actor_system sys;
fixture()
: sys(cfg.set("caf.scheduler.max-threads", 2)
.set("caf.scheduler.policy", "sharing")) {
// nop
}
};
void produce(async::producer_resource<int> push) {
async::blocking_producer out{push.try_open()};
for (int i = 0; i < 5000; ++i)
out.push(i);
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("blocking consumers allow threads to receive data") {
GIVEN("a producers running in a separate thread") {
WHEN("consuming the generated value with a blocking consumer") {
THEN("the consumer receives all values in order") {
auto [pull, push] = async::make_spsc_buffer_resource<int>();
std::thread producer{produce, push};
async::blocking_consumer<int> in{pull.try_open()};
std::vector<int> got;
bool done = false;
while (!done) {
int tmp = 0;
switch (in.pull(async::delay_errors, tmp)) {
case async::read_result::ok:
got.push_back(tmp);
break;
case async::read_result::stop:
done = true;
break;
case async::read_result::abort:
CAF_FAIL("did not expect async::read_result::abort");
done = true;
break;
default:
CAF_FAIL("unexpected pull result");
done = true;
}
}
auto want = std::vector<int>(5000);
std::iota(want.begin(), want.end(), 0);
CHECK_EQ(got.size(), 5000u);
CHECK_EQ(got, want);
producer.join();
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE async.blocking_producer
#include "caf/async/blocking_producer.hpp"
#include "core-test.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include <mutex>
using namespace caf;
namespace {
struct fixture {
actor_system_config cfg;
actor_system sys;
fixture() : sys(cfg.set("caf.scheduler.max-threads", 2)) {
// nop
}
};
struct sync_t {
std::mutex mtx;
std::condition_variable cv;
int available = 0;
void release() {
std::unique_lock guard{mtx};
if (++available == 1)
cv.notify_all();
}
void acquire() {
std::unique_lock guard{mtx};
while (available == 0)
cv.wait(guard);
--available;
}
};
using push_val_t = async::producer_resource<int>;
using pull_val_t = async::consumer_resource<int>;
using push_resource_t = async::producer_resource<pull_val_t>;
using pull_resource_t = async::consumer_resource<pull_val_t>;
void do_push(sync_t* sync, push_val_t push, int begin, int end) {
auto buf = push.try_open();
if (!buf) {
CAF_RAISE_ERROR("push.try_open failed");
}
async::blocking_producer out{std::move(buf)};
for (auto i = begin; i != end; ++i)
out.push(i);
sync->release();
}
std::pair<std::thread, pull_val_t> start_worker(sync_t* sync, int begin,
int end) {
auto [pull, push] = async::make_spsc_buffer_resource<int>();
return {std::thread{do_push, sync, std::move(push), begin, end},
std::move(pull)};
}
void run(push_resource_t push) {
auto buf = push.try_open();
if (!buf) {
CAF_RAISE_ERROR("push.try_open failed");
}
async::blocking_producer out{std::move(buf)};
sync_t sync;
std::vector<std::thread> threads;
auto add_worker = [&](int begin, int end) {
auto [hdl, pull] = start_worker(&sync, begin, end);
threads.push_back(std::move(hdl));
out.push(std::move(pull));
};
std::vector<std::pair<int, int>> ranges{{0, 1337}, {1337, 1338},
{1338, 1338}, {1338, 2777},
{2777, 3000}, {3000, 3003},
{3003, 3500}, {3500, 4000}};
add_worker(4000, 4007);
add_worker(4007, 4333);
add_worker(4333, 4500);
add_worker(4500, 5000);
for (auto [begin, end] : ranges) {
sync.acquire();
add_worker(begin, end);
}
for (auto& hdl : threads)
hdl.join();
}
void receiver_impl(event_based_actor* self, pull_resource_t inputs,
actor parent) {
inputs.observe_on(self)
.flat_map([self](const pull_val_t& in) { return in.observe_on(self); })
.to_vector()
.for_each([self, parent](const cow_vector<int>& values) { //
self->send(parent, values);
});
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("blocking producers allow threads to generate data") {
GIVEN("a dynamic set of blocking producers") {
WHEN("consuming the generated values from an actor via flat_map") {
THEN("the actor merges all values from all buffers into one") {
auto [pull, push] = async::make_spsc_buffer_resource<pull_val_t>();
scoped_actor self{sys};
auto receiver = self->spawn(receiver_impl, std::move(pull),
actor{self});
std::thread runner{run, std::move(push)};
self->receive([](const cow_vector<int>& values) {
auto want = std::vector<int>(5000);
std::iota(want.begin(), want.end(), 0);
auto got = values.std();
std::sort(got.begin(), got.end());
CHECK_EQ(got.size(), 5000u);
CHECK_EQ(got, want);
});
runner.join();
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE async.consumer_adapter
#include "caf/async/consumer_adapter.hpp"
#include "core-test.hpp"
#include "caf/async/blocking_producer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include <mutex>
using namespace caf;
namespace {
struct fixture {
actor_system_config cfg;
actor_system sys;
fixture()
: sys(cfg.set("caf.scheduler.max-threads", 2)
.set("caf.scheduler.policy", "sharing")) {
// nop
}
};
void produce(async::producer_resource<int> push) {
async::blocking_producer out{push.try_open()};
for (int i = 0; i < 5000; ++i)
out.push(i);
}
class runner_t {
public:
runner_t(async::spsc_buffer_ptr<int> buf, async::execution_context_ptr ctx) {
do_wakeup_ = make_action([this] { resume(); });
in_ = make_consumer_adapter(buf, ctx, do_wakeup_);
}
disposable as_disposable() {
return do_wakeup_.as_disposable();
}
void resume() {
int tmp = 0;
for (;;) {
auto res = in_.pull(async::delay_errors, tmp);
switch (res) {
case async::read_result::ok:
values_.push_back(tmp);
break;
case async::read_result::stop:
do_wakeup_.dispose();
return;
case async::read_result::try_again_later:
return;
default:
CAF_FAIL("unexpected pull result: " << res);
do_wakeup_.dispose();
return;
}
}
}
void start() {
resume();
}
const std::vector<int>& values() {
return values_;
}
private:
async::consumer_adapter<int> in_;
action do_wakeup_;
std::vector<int> values_;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("consumer adapters allow integrating consumers into event loops") {
GIVEN("a producers running in a separate thread") {
WHEN("consuming the generated value with a blocking consumer") {
THEN("the consumer receives all values in order") {
auto [pull, push] = async::make_spsc_buffer_resource<int>();
std::thread producer{produce, push};
auto loop = flow::make_scoped_coordinator();
runner_t runner{pull.try_open(), loop};
loop->watch(runner.as_disposable());
runner.start();
loop->run();
auto& got = runner.values();
auto want = std::vector<int>(5000);
std::iota(want.begin(), want.end(), 0);
CHECK_EQ(got.size(), 5000u);
CHECK_EQ(got, want);
producer.join();
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE async.producer_adapter
#include "caf/async/producer_adapter.hpp"
#include "core-test.hpp"
#include "caf/async/blocking_producer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include <mutex>
using namespace caf;
namespace {
struct fixture {
actor_system_config cfg;
actor_system sys;
fixture() : sys(cfg.set("caf.scheduler.max-threads", 2)) {
// nop
}
};
struct sync_t {
std::mutex mtx;
std::condition_variable cv;
int available = 0;
void release() {
std::unique_lock guard{mtx};
if (++available == 1)
cv.notify_all();
}
void acquire() {
std::unique_lock guard{mtx};
while (available == 0)
cv.wait(guard);
--available;
}
};
using push_val_t = async::producer_resource<int>;
using pull_val_t = async::consumer_resource<int>;
using push_resource_t = async::producer_resource<pull_val_t>;
using pull_resource_t = async::consumer_resource<pull_val_t>;
class runner_t {
public:
runner_t(async::spsc_buffer_ptr<int> buf, async::execution_context_ptr ctx,
int first, int last)
: n(first), end(last) {
do_resume_ = make_action([this] { resume(); });
do_cancel_ = make_action([this] { do_resume_.dispose(); });
out = make_producer_adapter(buf, ctx, do_resume_, do_cancel_);
}
disposable as_disposable() {
return do_resume_.as_disposable();
}
void resume() {
while (n < end)
if (out.push(n++) == 0)
return;
do_resume_.dispose();
}
private:
int n;
int end;
async::producer_adapter<int> out;
action do_resume_;
action do_cancel_;
};
void do_push(sync_t* sync, push_val_t push, int begin, int end) {
auto buf = push.try_open();
if (!buf) {
CAF_RAISE_ERROR("push.try_open failed");
}
auto loop = flow::make_scoped_coordinator();
runner_t runner{buf, loop, begin, end};
loop->watch(runner.as_disposable());
loop->run();
sync->release();
}
std::pair<std::thread, pull_val_t> start_worker(sync_t* sync, int begin,
int end) {
auto [pull, push] = async::make_spsc_buffer_resource<int>();
return {std::thread{do_push, sync, std::move(push), begin, end},
std::move(pull)};
}
void run(push_resource_t push) {
auto buf = push.try_open();
if (!buf) {
CAF_RAISE_ERROR("push.try_open failed");
}
// Note: using the adapter here as well would be tricky since we would need to
// wait for available threads.
async::blocking_producer out{std::move(buf)};
sync_t sync;
std::vector<std::thread> threads;
auto add_worker = [&](int begin, int end) {
auto [hdl, pull] = start_worker(&sync, begin, end);
threads.push_back(std::move(hdl));
out.push(std::move(pull));
};
std::vector<std::pair<int, int>> ranges{{0, 1337}, {1337, 1338},
{1338, 1338}, {1338, 2777},
{2777, 3000}, {3000, 3003},
{3003, 3500}, {3500, 4000}};
add_worker(4000, 4007);
add_worker(4007, 4333);
add_worker(4333, 4500);
add_worker(4500, 5000);
for (auto [begin, end] : ranges) {
sync.acquire();
add_worker(begin, end);
}
for (auto& hdl : threads)
hdl.join();
}
void receiver_impl(event_based_actor* self, pull_resource_t inputs,
actor parent) {
inputs.observe_on(self)
.flat_map([self](const pull_val_t& in) { return in.observe_on(self); })
.to_vector()
.for_each([self, parent](const cow_vector<int>& values) { //
self->send(parent, values);
});
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("producers adapters allow integrating producers into event loops") {
GIVEN("a dynamic set of blocking producers") {
WHEN("consuming the generated values from an actor via flat_map") {
THEN("the actor merges all values from all buffers into one") {
auto [pull, push] = async::make_spsc_buffer_resource<pull_val_t>();
auto loop = flow::make_scoped_coordinator();
scoped_actor self{sys};
auto receiver = self->spawn(receiver_impl, std::move(pull),
actor{self});
std::thread runner{run, std::move(push)};
self->receive([](const cow_vector<int>& values) {
auto want = std::vector<int>(5000);
std::iota(want.begin(), want.end(), 0);
auto got = values.std();
std::sort(got.begin(), got.end());
CHECK_EQ(got.size(), 5000u);
CHECK_EQ(got, want);
});
runner.join();
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE async.publishing_queue
#include "caf/async/publishing_queue.hpp"
#include "core-test.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
struct fixture {
actor_system_config cfg;
actor_system sys;
fixture() : sys(cfg.set("caf.scheduler.max-threads", 2)) {
// nop
}
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("publishing queues connect asynchronous producers to observers") {
GIVEN("a producer and a consumer, living in separate threads") {
WHEN("connecting producer and consumer via a publishing queue") {
THEN("the consumer receives all produced values in order") {
auto [queue, src] = async::make_publishing_queue<int>(sys, 100);
auto producer_thread = std::thread{[q{std::move(queue)}] {
for (int i = 0; i < 5000; ++i)
q->push(i);
}};
std::vector<int> values;
auto consumer_thread = std::thread{[&values, src{src}] {
src.blocking_for_each([&](int x) { values.emplace_back(x); });
}};
producer_thread.join();
consumer_thread.join();
std::vector<int> expected_values;
expected_values.resize(5000);
std::iota(expected_values.begin(), expected_values.end(), 0);
CHECK_EQ(values, expected_values);
}
}
}
}
END_FIXTURE_SCOPE()
#include "caf/cow_vector.hpp"
#include "caf/fwd.hpp"
#include "caf/result.hpp"
#include "caf/test/bdd_dsl.hpp"
......@@ -418,6 +419,7 @@ bool inspect(Inspector& f, phone_book& x) {
CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((caf::cow_vector<int>) )
ADD_TYPE_ID((circle))
ADD_TYPE_ID((dummy_enum))
ADD_TYPE_ID((dummy_enum_class))
......
......@@ -124,64 +124,4 @@ CAF_TEST(json baselines) {
}
}
constexpr std::string_view foo_json = R"_({
"@first-type": "int32_t",
"first": 42,
"top": {
"mid": {
"bottom": null
}
}
})_";
SCENARIO("the json reader keeps track of the current field") {
GIVEN("a sequence of member functions calls for type inspection") {
WHEN("encountering new fields") {
THEN("the json reader updates the current field name") {
auto dummy = int32_t{0};
auto first_present = false;
auto types_vec = std::vector{type_id_v<int32_t>, type_id_v<double>};
auto types = make_span(types_vec);
auto first_index = size_t{0};
auto top_present = false;
auto bottom_present = false;
auto bottom_index = size_t{0};
json_reader uut;
CHECK(uut.load(foo_json));
CHECK_EQ(uut.current_field_name(), "null");
// clang-format off
uut.begin_object(invalid_type_id, "foo");
// begin_field overload #4
uut.begin_field("first", first_present, types, first_index);
CHECK_EQ(first_present, true);
CHECK_EQ(first_index, 0u);
CHECK_EQ(uut.current_field_name(), "first");
uut.value(dummy);
CHECK_EQ(dummy, 42);
uut.end_field();
// begin_field overload #2
uut.begin_field("top", top_present);
CHECK_EQ(top_present, true);
CHECK_EQ(uut.current_field_name(), "top");
uut.begin_object(invalid_type_id, "top");
// begin_field overload #1
uut.begin_field("mid");
CHECK_EQ(uut.current_field_name(), "top.mid");
uut.begin_object(invalid_type_id, "bottom");
// begin_field overload #3
uut.begin_field("bottom", bottom_present, types, bottom_index);
CHECK_EQ(bottom_present, false);
CHECK_EQ(uut.current_field_name(), "top.mid.bottom");
uut.end_field();
uut.end_object();
uut.end_field();
uut.end_object();
uut.end_field();
uut.end_object();
// clang-format on
}
}
}
}
END_FIXTURE_SCOPE()
......@@ -421,14 +421,17 @@ strong_actor_ptr middleman::remote_lookup(std::string name,
void middleman::start() {
CAF_LOG_TRACE("");
// Launch background tasks.
if (auto prom = get_if<config_value::dictionary>(
&system().config(), "caf.middleman.prometheus-http")) {
auto ptr = std::make_unique<prometheus_scraping>(system());
if (auto port = ptr->start(*prom)) {
CAF_ASSERT(*port != 0);
prometheus_scraping_port_ = *port;
background_tasks_.emplace_back(std::move(ptr));
// Launch background tasks unless caf-net is also available. In that case, the
// net::middleman takes care of these.
if (!system().has_network_manager()) {
if (auto prom = get_if<config_value::dictionary>(
&system().config(), "caf.middleman.prometheus-http")) {
auto ptr = std::make_unique<prometheus_scraping>(system());
if (auto port = ptr->start(*prom)) {
CAF_ASSERT(*port != 0);
prometheus_scraping_port_ = *port;
background_tasks_.emplace_back(std::move(ptr));
}
}
}
// Launch backend.
......
......@@ -33,24 +33,31 @@ caf_add_component(
src/detail/rfc6455.cpp
src/net/abstract_actor_shell.cpp
src/net/actor_shell.cpp
src/net/binary/default_trait.cpp
src/net/binary/frame.cpp
src/net/binary/lower_layer.cpp
src/net/binary/upper_layer.cpp
src/net/datagram_socket.cpp
src/net/generic_lower_layer.cpp
src/net/generic_upper_layer.cpp
src/net/http/header.cpp
src/net/http/lower_layer.cpp
src/net/http/method.cpp
src/net/http/request.cpp
src/net/http/response.cpp
src/net/http/serve.cpp
src/net/http/server.cpp
src/net/http/status.cpp
src/net/http/upper_layer.cpp
src/net/http/v1.cpp
src/net/ip.cpp
src/net/length_prefix_framing.cpp
src/net/message_oriented.cpp
src/net/middleman.cpp
src/net/multiplexer.cpp
src/net/network_socket.cpp
src/net/pipe_socket.cpp
src/net/pollset_updater.cpp
src/net/prometheus/server.cpp
src/net/socket.cpp
src/net/socket_event_layer.cpp
src/net/socket_manager.cpp
......@@ -84,7 +91,6 @@ caf_add_component(
detail.rfc6455
net.accept_socket
net.actor_shell
net.consumer_adapter
net.datagram_socket
net.http.server
net.ip
......@@ -93,7 +99,7 @@ caf_add_component(
net.network_socket
net.operation
net.pipe_socket
net.producer_adapter
net.prometheus.server
net.socket
net.socket_guard
net.ssl.transport
......
......@@ -5,6 +5,7 @@
#pragma once
#include "caf/actor_traits.hpp"
#include "caf/async/execution_context.hpp"
#include "caf/callback.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/extend.hpp"
......@@ -46,7 +47,7 @@ public:
// -- constructors, destructors, and assignment operators --------------------
abstract_actor_shell(actor_config& cfg, socket_manager* owner);
abstract_actor_shell(actor_config& cfg, async::execution_context_ptr loop);
~abstract_actor_shell() override;
......@@ -56,7 +57,7 @@ public:
// -- state modifiers --------------------------------------------------------
/// Detaches the shell from its owner and closes the mailbox.
/// Detaches the shell from its loop and closes the mailbox.
void quit(error reason);
/// Overrides the default handler for unexpected messages.
......@@ -94,11 +95,6 @@ public:
// -- message processing -----------------------------------------------------
/// Dequeues and processes the next message from the mailbox.
/// @returns `true` if a message was dequeued and processed, `false` if the
/// mailbox was empty.
bool consume_message();
/// Adds a callback for a multiplexed response.
void add_multiplexed_response_handler(message_id response_id, behavior bhvr);
......@@ -117,23 +113,35 @@ public:
bool cleanup(error&& fail_state, execution_unit* host) override;
protected:
// Stores incoming actor messages.
void set_behavior_impl(behavior bhvr) {
bhvr_ = std::move(bhvr);
}
private:
/// Stores incoming actor messages.
mailbox_type mailbox_;
// Guards access to owner_.
std::mutex owner_mtx_;
/// Guards access to loop_.
std::mutex loop_mtx_;
// Points to the owning manager (nullptr after quit was called).
socket_manager* owner_;
/// Points to the loop in which this "actor" runs (nullptr after calling
/// quit).
async::execution_context_ptr loop_;
// Handler for consuming messages from the mailbox.
/// Handler for consuming messages from the mailbox.
behavior bhvr_;
// Handler for unexpected messages.
/// Handler for unexpected messages.
fallback_handler fallback_;
// Stores callbacks for multiplexed responses.
/// Stores callbacks for multiplexed responses.
unordered_flat_map<message_id, behavior> multiplexed_responses_;
/// Callback for processing the next message on the event loop.
action resume_;
/// Dequeues and processes the next message from the mailbox.
bool consume_message();
};
} // namespace caf::net
......@@ -38,7 +38,7 @@ public:
// -- constructors, destructors, and assignment operators --------------------
actor_shell(actor_config& cfg, socket_manager* owner);
actor_shell(actor_config& cfg, async::execution_context_ptr loop);
~actor_shell() override;
......@@ -47,7 +47,7 @@ public:
/// Overrides the callbacks for incoming messages.
template <class... Fs>
void set_behavior(Fs... fs) {
bhvr_ = behavior{std::move(fs)...};
set_behavior_impl(behavior{std::move(fs)...});
}
// -- overridden functions of local_actor ------------------------------------
......@@ -61,7 +61,9 @@ class CAF_NET_EXPORT actor_shell_ptr {
public:
// -- friends ----------------------------------------------------------------
friend class socket_manager;
template <class Handle>
friend actor_shell_ptr_t<Handle>
make_actor_shell(actor_system&, async::execution_context_ptr);
// -- member types -----------------------------------------------------------
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/byte_span.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/binary/fwd.hpp"
#include <string_view>
#include <vector>
namespace caf::net::binary {
class CAF_NET_EXPORT default_trait {
public:
/// The input type of the application, i.e., what that flows from the
/// WebSocket to the application layer.
using input_type = frame;
/// The output type of the application, i.e., what flows from the application
/// layer to the WebSocket.
using output_type = frame;
bool convert(const output_type& x, byte_buffer& bytes);
bool convert(const_byte_span bytes, input_type& x);
error last_error();
};
} // namespace caf::net::binary
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/consumer_adapter.hpp"
#include "caf/async/producer_adapter.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/fwd.hpp"
#include "caf/net/binary/lower_layer.hpp"
#include "caf/net/binary/upper_layer.hpp"
#include "caf/net/flow_connector.hpp"
#include "caf/sec.hpp"
#include <utility>
namespace caf::net::binary {
/// Translates between a message-oriented transport and data flows.
template <class Trait>
class flow_bridge : public upper_layer {
public:
using input_type = typename Trait::input_type;
using output_type = typename Trait::output_type;
/// Type for the consumer adapter. We consume the output of the application.
using consumer_type = async::consumer_adapter<output_type>;
/// Type for the producer adapter. We produce the input of the application.
using producer_type = async::producer_adapter<input_type>;
using connector_pointer = flow_connector_ptr<Trait>;
explicit flow_bridge(async::execution_context_ptr loop,
connector_pointer conn)
: loop_(std::move(loop)), conn_(std::move(conn)) {
// nop
}
static std::unique_ptr<flow_bridge> make(async::execution_context_ptr loop,
connector_pointer conn) {
return std::make_unique<flow_bridge>(std::move(loop), std::move(conn));
}
bool write(const output_type& item) {
down_->begin_message();
auto& bytes = down_->message_buffer();
return trait_.convert(item, bytes) && down_->end_message();
}
bool running() const noexcept {
return in_ || out_;
}
void self_ref(disposable ref) {
self_ref_ = std::move(ref);
}
// -- implementation of binary::lower_layer ----------------------------------
error start(binary::lower_layer* down, const settings& cfg) override {
CAF_ASSERT(down != nullptr);
down_ = down;
auto [err, pull, push] = conn_->on_request(cfg);
if (!err) {
auto do_wakeup = make_action([this] {
prepare_send();
if (!running())
down_->shutdown();
});
auto do_resume = make_action([this] { down_->request_messages(); });
auto do_cancel = make_action([this] {
if (!running()) {
down_->shutdown();
}
});
in_ = consumer_type::make(pull.try_open(), loop_, std::move(do_wakeup));
out_ = producer_type::make(push.try_open(), loop_, std::move(do_resume),
std::move(do_cancel));
conn_ = nullptr;
if (in_ && out_) {
return none;
} else {
return make_error(sec::runtime_error,
"cannot init flow bridge: no buffers");
}
} else {
conn_ = nullptr;
return err;
}
}
void prepare_send() override {
input_type tmp;
while (down_->can_send_more()) {
switch (in_.pull(async::delay_errors, tmp)) {
case async::read_result::ok:
if (!write(tmp)) {
down_->shutdown(trait_.last_error());
return;
}
break;
case async::read_result::stop:
down_->shutdown();
break;
case async::read_result::abort:
down_->shutdown(in_.abort_reason());
break;
default: // try later
return;
}
}
}
bool done_sending() override {
return !in_.has_consumer_event();
}
void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) {
if (reason == sec::connection_closed || reason == sec::socket_disconnected
|| reason == sec::disposed)
out_.close();
else
out_.abort(reason);
}
in_.cancel();
self_ref_ = nullptr;
}
ptrdiff_t consume(byte_span buf) override {
if (!out_)
return -1;
input_type val;
if (!trait_.convert(buf, val))
return -1;
if (out_.push(std::move(val)) == 0)
down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
private:
lower_layer* down_;
/// The output of the application. Serialized to the socket.
consumer_type in_;
/// The input to the application. Deserialized from the socket.
producer_type out_;
/// Converts between raw bytes and native C++ objects.
Trait trait_;
/// Our event loop.
async::execution_context_ptr loop_;
/// Initializes the bridge. Disposed (set to null) after initializing.
connector_pointer conn_;
/// Type-erased handle to the @ref socket_manager. This reference is important
/// to keep the bridge alive while the manager is not registered for writing
/// or reading.
disposable self_ref_;
};
} // namespace caf::net::binary
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/fwd.hpp"
#include "caf/byte_span.hpp"
#include "caf/config.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/net/web_socket/frame.hpp"
#include "caf/raise_error.hpp"
#include "caf/type_id.hpp"
namespace caf::net::binary {
/// An implicitly shared type for binary data frames.
class CAF_NET_EXPORT frame {
public:
frame() = default;
frame(frame&&) = default;
frame(const frame&) = default;
frame& operator=(frame&&) = default;
frame& operator=(const frame&) = default;
explicit frame(const_byte_span data);
explicit operator bool() const noexcept {
return static_cast<bool>(data_);
}
size_t size() const noexcept {
return data_ ? data_->size() : 0;
}
bool empty() const noexcept {
return data_ ? data_->size() == 0 : true;
}
void swap(frame& other) {
data_.swap(other.data_);
}
const_byte_span bytes() const noexcept {
return {data_->storage(), data_->size()};
}
private:
intrusive_ptr<web_socket::frame::data> data_;
};
} // namespace caf::net::binary
......@@ -4,9 +4,10 @@
#pragma once
namespace caf::net::http {
namespace caf::net::binary {
/// Stores context information for a request. For HTTP/2, this is the stream ID.
class context {};
class frame;
class lower_layer;
class upper_layer;
} // namespace caf::net::http
} // namespace caf::net::binary
......@@ -7,39 +7,13 @@
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/binary/fwd.hpp"
#include "caf/net/generic_lower_layer.hpp"
#include "caf/net/generic_upper_layer.hpp"
namespace caf::net::message_oriented {
namespace caf::net::binary {
class lower_layer;
/// Consumes binary messages from the lower layer.
class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param owner A pointer to the socket manager that owns the entire
/// protocol stack. Remains valid for the lifetime of the upper
/// layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
virtual error
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
/// Consumes bytes from the lower layer.
/// @param payload Payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
[[nodiscard]] virtual ptrdiff_t consume(byte_span payload) = 0;
};
/// Provides access to a resource that operates on the granularity of messages,
/// e.g., a UDP socket.
/// Provides access to a resource that operates on the granularity of binary
/// messages.
class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~lower_layer();
......@@ -47,6 +21,9 @@ public:
/// Pulls messages from the transport until calling `suspend_reading`.
virtual void request_messages() = 0;
/// Stops reading messages until calling `request_messages`.
virtual void suspend_reading() = 0;
/// Prepares the layer for an outgoing message, e.g., by allocating an
/// output buffer as necessary.
virtual void begin_message() = 0;
......@@ -62,17 +39,6 @@ public:
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
virtual bool end_message() = 0;
/// Inform the remote endpoint that no more messages will arrive.
/// @note Not every protocol has a dedicated close message. Some
/// implementation may simply do nothing.
virtual void send_close_message() = 0;
/// Inform the remote endpoint that no more messages will arrive because of
/// an error.
/// @note Not every protocol has a dedicated close message. Some
/// implementation may simply do nothing.
virtual void send_close_message(const error& reason) = 0;
};
} // namespace caf::net::message_oriented
} // namespace caf::net::binary
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/binary/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_upper_layer.hpp"
namespace caf::net::binary {
/// Consumes binary messages from the lower layer.
class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
/// @param config Protocol-dependent configuration parameters.
virtual error start(lower_layer* down, const settings& config) = 0;
/// Consumes bytes from the lower layer.
/// @param payload Payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
[[nodiscard]] virtual ptrdiff_t consume(byte_span payload) = 0;
};
} // namespace caf::net::binary
......@@ -4,6 +4,7 @@
#pragma once
#include "caf/net/connection_factory.hpp"
#include "caf/net/socket_event_layer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/settings.hpp"
......@@ -12,101 +13,129 @@ namespace caf::net {
/// A connection_acceptor accepts connections from an accept socket and creates
/// socket managers to handle them via its factory.
template <class Socket, class Factory>
template <class Socket>
class connection_acceptor : public socket_event_layer {
public:
// -- member types -----------------------------------------------------------
using socket_type = Socket;
using factory_type = Factory;
using connected_socket_type = typename socket_type::connected_socket_type;
using read_result = typename socket_event_layer::read_result;
using write_result = typename socket_event_layer::write_result;
using factory_type = connection_factory<connected_socket_type>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit connection_acceptor(Socket fd, size_t limit, Ts&&... xs)
: fd_(fd), limit_(limit), factory_(std::forward<Ts>(xs)...) {
// nop
template <class FactoryPtr, class... Ts>
connection_acceptor(Socket fd, FactoryPtr fptr, size_t max_connections)
: fd_(fd),
factory_(factory_type::decorate(std::move(fptr))),
max_connections_(max_connections) {
CAF_ASSERT(max_connections_ > 0);
}
~connection_acceptor() {
on_conn_close_.dispose();
if (fd_.id != invalid_socket_id)
close(fd_);
}
// -- factories --------------------------------------------------------------
template <class... Ts>
template <class FactoryPtr, class... Ts>
static std::unique_ptr<connection_acceptor>
make(Socket fd, size_t limit, Ts&&... xs) {
return std::make_unique<connection_acceptor>(fd, limit,
std::forward<Ts>(xs)...);
make(Socket fd, FactoryPtr fptr, size_t max_connections) {
return std::make_unique<connection_acceptor>(fd, std::move(fptr),
max_connections);
}
// -- implementation of socket_event_layer -----------------------------------
error init(socket_manager* owner, const settings& cfg) override {
error start(socket_manager* owner, const settings& cfg) override {
CAF_LOG_TRACE("");
owner_ = owner;
cfg_ = cfg;
if (auto err = factory_.init(owner, cfg))
if (auto err = factory_->start(owner, cfg)) {
CAF_LOG_DEBUG("factory_->start failed:" << err);
return err;
}
on_conn_close_ = make_action([this] { connection_closed(); });
owner->register_reading();
return none;
}
read_result handle_read_event() override {
socket handle() const override {
return fd_;
}
void handle_read_event() override {
CAF_LOG_TRACE("");
if (auto x = accept(fd_)) {
socket_manager_ptr child = factory_.make(owner_->mpx_ptr(), *x);
CAF_ASSERT(owner_ != nullptr);
if (open_connections_ == max_connections_) {
owner_->deregister_reading();
} else if (auto x = accept(fd_)) {
socket_manager_ptr child = factory_->make(owner_->mpx_ptr(), *x);
if (!child) {
CAF_LOG_ERROR("factory failed to create a new child");
return read_result::abort;
}
if (auto err = child->init(cfg_)) {
CAF_LOG_ERROR("failed to initialize new child:" << err);
return read_result::abort;
}
if (limit_ == 0) {
return read_result::again;
} else {
return ++accepted_ < limit_ ? read_result::again : read_result::stop;
on_conn_close_.dispose();
owner_->shutdown();
return;
}
if (++open_connections_ == max_connections_)
owner_->deregister_reading();
child->add_cleanup_listener(on_conn_close_);
std::ignore = child->start(cfg_);
} else if (x.error() == sec::unavailable_or_would_block) {
// Encountered a "soft" error: simply try again later.
CAF_LOG_DEBUG("accept failed:" << x.error());
} else {
CAF_LOG_ERROR("accept failed:" << x.error());
return read_result::stop;
// Encountered a "hard" error: stop.
abort(x.error());
owner_->deregister_reading();
}
}
read_result handle_buffered_data() override {
return read_result::again;
void handle_write_event() override {
CAF_LOG_ERROR("connection_acceptor received write event");
owner_->deregister_writing();
}
read_result handle_continue_reading() override {
return read_result::again;
void abort(const error& reason) override {
CAF_LOG_ERROR("connection_acceptor aborts due to an error:" << reason);
factory_->abort(reason);
on_conn_close_.dispose();
self_ref_ = nullptr;
}
write_result handle_write_event() override {
CAF_LOG_ERROR("connection_acceptor received write event");
return write_result::stop;
void self_ref(disposable ref) {
self_ref_ = std::move(ref);
}
void abort(const error& reason) override {
CAF_LOG_ERROR("connection_acceptor aborts due to an error: " << reason);
factory_.abort(reason);
private:
void connection_closed() {
if (open_connections_ == max_connections_)
owner_->register_reading();
--open_connections_;
}
private:
Socket fd_;
size_t limit_;
connection_factory_ptr<connected_socket_type> factory_;
factory_type factory_;
size_t max_connections_;
size_t open_connections_ = 0;
socket_manager* owner_ = nullptr;
size_t accepted_ = 0;
action on_conn_close_;
settings cfg_;
/// Type-erased handle to the @ref socket_manager. This reference is important
/// to keep the acceptor alive while the manager is not registered for writing
/// or reading.
disposable self_ref_;
};
} // namespace caf::net
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include <memory>
namespace caf::net {
/// Creates new socket managers for a @ref socket_acceptor.
template <class Socket>
class connection_factory {
public:
using socket_type = Socket;
virtual ~connection_factory() {
// nop
}
virtual error start(net::socket_manager*, const settings&) {
return none;
}
virtual void abort(const error&) {
// nop
}
virtual socket_manager_ptr make(multiplexer*, Socket) = 0;
template <class Factory>
static std::unique_ptr<connection_factory>
decorate(std::unique_ptr<Factory> ptr);
};
template <class Socket>
using connection_factory_ptr = std::unique_ptr<connection_factory<Socket>>;
template <class Socket, class DecoratedSocket>
class connection_factory_decorator : public connection_factory<Socket> {
public:
using decorated_ptr = connection_factory_ptr<DecoratedSocket>;
explicit connection_factory_decorator(decorated_ptr decorated)
: decorated_(std::move(decorated)) {
// nop
}
error start(net::socket_manager* mgr, const settings& cfg) override {
return decorated_->start(mgr, cfg);
}
void abort(const error& reason) override {
decorated_->abort(reason);
}
socket_manager_ptr make(multiplexer* mpx, Socket fd) override {
return decorated_->make(mpx, fd);
}
private:
decorated_ptr decorated_;
};
/// Lifts a factory from a subtype of `Socket` to a factory for `Socket`.
template <class Socket>
template <class Factory>
std::unique_ptr<connection_factory<Socket>>
connection_factory<Socket>::decorate(std::unique_ptr<Factory> ptr) {
using socket_sub_type = typename Factory::socket_type;
if constexpr (std::is_same_v<Socket, socket_sub_type>) {
return ptr;
} else {
static_assert(std::is_assignable_v<socket_sub_type, Socket>);
using decorator_t = connection_factory_decorator<Socket, socket_sub_type>;
return std::make_unique<decorator_t>(std::move(ptr));
}
}
} // namespace caf::net
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/consumer.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/ref_counted.hpp"
namespace caf::net {
/// Connects a socket manager to an asynchronous consumer resource. Whenever new
/// data becomes ready, the adapter registers the socket manager for writing.
template <class Buffer>
class consumer_adapter final : public ref_counted, public async::consumer {
public:
using buf_ptr = intrusive_ptr<Buffer>;
void on_producer_ready() override {
// nop
}
void on_producer_wakeup() override {
mgr_->mpx().schedule_fn([adapter = strong_this()] { //
adapter->on_wakeup();
});
}
void ref_consumer() const noexcept override {
this->ref();
}
void deref_consumer() const noexcept override {
this->deref();
}
template <class Policy, class Observer>
std::pair<bool, size_t> pull(Policy policy, size_t demand, Observer& dst) {
return buf_->pull(policy, demand, dst);
}
void cancel() {
buf_->cancel();
buf_ = nullptr;
}
bool has_data() const noexcept {
return buf_->has_data();
}
/// Tries to open the resource for reading.
/// @returns a connected adapter that reads from the resource on success,
/// `nullptr` otherwise.
template <class Resource>
static intrusive_ptr<consumer_adapter>
try_open(socket_manager* owner, Resource src) {
CAF_ASSERT(owner != nullptr);
if (auto buf = src.try_open()) {
using ptr_type = intrusive_ptr<consumer_adapter>;
auto adapter = ptr_type{new consumer_adapter(owner, buf), false};
buf->set_consumer(adapter);
return adapter;
} else {
return nullptr;
}
}
friend void intrusive_ptr_add_ref(const consumer_adapter* ptr) noexcept {
ptr->ref();
}
friend void intrusive_ptr_release(const consumer_adapter* ptr) noexcept {
ptr->deref();
}
private:
consumer_adapter(socket_manager* owner, buf_ptr buf)
: mgr_(owner), buf_(std::move(buf)) {
// nop
}
auto strong_this() {
return intrusive_ptr{this};
}
void on_wakeup() {
if (buf_ && buf_->has_consumer_event()) {
mgr_->mpx().register_writing(mgr_);
}
}
intrusive_ptr<socket_manager> mgr_;
intrusive_ptr<Buffer> buf_;
};
template <class T>
using consumer_adapter_ptr = intrusive_ptr<consumer_adapter<T>>;
} // namespace caf::net
......@@ -56,9 +56,9 @@ public:
// -- public member functions ------------------------------------------------
error init(endpoint_manager& manager) override {
error start(endpoint_manager& manager) override {
CAF_LOG_TRACE("");
if (auto err = super::init(manager))
if (auto err = super::start(manager))
return err;
prepare_next_read();
return none;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/blocking_producer.hpp"
#include "caf/error.hpp"
#include "caf/net/web_socket/request.hpp"
#include "caf/sec.hpp"
#include <tuple>
namespace caf::net {
/// Connects a flow bridge to input and output buffers.
template <class Trait>
class flow_connector {
public:
using input_type = typename Trait::input_type;
using output_type = typename Trait::output_type;
using pull_t = async::consumer_resource<input_type>;
using push_t = async::producer_resource<output_type>;
using result_type = std::tuple<error, pull_t, push_t>;
virtual ~flow_connector() {
// nop
}
virtual result_type on_request(const settings& cfg) = 0;
/// Returns a trivial implementation that simply returns @p pull and @p push
/// from @p on_request.
static flow_connector_ptr<Trait> make_trivial(pull_t pull, push_t push);
};
/// Trivial flow connector that passes its constructor arguments to the
/// @ref flow_bridge.
template <class Trait>
class flow_connector_trivial_impl : public flow_connector<Trait> {
public:
using super = flow_connector<Trait>;
using input_type = typename Trait::input_type;
using output_type = typename Trait::output_type;
using pull_t = async::consumer_resource<input_type>;
using push_t = async::producer_resource<output_type>;
using result_type = typename super::result_type;
flow_connector_trivial_impl(pull_t pull, push_t push)
: pull_(std::move(pull)), push_(std::move(push)) {
// nop
}
result_type on_request(const settings&) override {
return {{}, std::move(pull_), std::move(push_)};
}
private:
pull_t pull_;
push_t push_;
};
template <class Trait>
flow_connector_ptr<Trait>
flow_connector<Trait>::make_trivial(pull_t pull, push_t push) {
using impl_t = flow_connector_trivial_impl<Trait>;
return std::make_shared<impl_t>(std::move(pull), std::move(push));
}
} // namespace caf::net
......@@ -4,6 +4,8 @@
#pragma once
#include "caf/async/fwd.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/type_id.hpp"
......@@ -25,6 +27,9 @@ class typed_actor_shell;
template <class... Sigs>
class typed_actor_shell_ptr;
template <class Trait>
class flow_connector;
// -- classes ------------------------------------------------------------------
class actor_shell;
......@@ -48,12 +53,36 @@ struct udp_datagram_socket;
// -- smart pointer aliases ----------------------------------------------------
using multiplexer_ptr = std::shared_ptr<multiplexer>;
using multiplexer_ptr = intrusive_ptr<multiplexer>;
using socket_manager_ptr = intrusive_ptr<socket_manager>;
using weak_multiplexer_ptr = std::weak_ptr<multiplexer>;
template <class Trait>
using flow_connector_ptr = std::shared_ptr<flow_connector<Trait>>;
// -- miscellaneous aliases ----------------------------------------------------
using text_buffer = std::vector<char>;
// -- factory functions --------------------------------------------------------
template <class>
struct actor_shell_ptr_oracle;
template <>
struct actor_shell_ptr_oracle<actor> {
using type = actor_shell_ptr;
};
template <class... Sigs>
struct actor_shell_ptr_oracle<typed_actor<Sigs...>> {
using type = typed_actor_shell_ptr<Sigs...>;
};
template <class Handle>
using actor_shell_ptr_t = typename actor_shell_ptr_oracle<Handle>::type;
template <class Handle = caf::actor>
actor_shell_ptr_t<Handle>
make_actor_shell(actor_system&, async::execution_context_ptr);
} // namespace caf::net
......@@ -5,6 +5,7 @@
#pragma once
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
namespace caf::net {
......@@ -17,12 +18,22 @@ public:
/// Queries whether the output device can accept more data straight away.
[[nodiscard]] virtual bool can_send_more() const noexcept = 0;
/// Halt receiving of additional bytes or messages.
virtual void suspend_reading() = 0;
/// Queries whether the lower layer is currently reading from its input
/// device.
[[nodiscard]] virtual bool is_reading() const noexcept = 0;
/// Queries whether the lower layer is currently configured to halt receiving
/// of additional bytes or messages.
[[nodiscard]] virtual bool stopped_reading() const noexcept = 0;
/// Triggers a write callback after the write device signals downstream
/// capacity. Does nothing if this layer is already writing.
virtual void write_later() = 0;
/// Shuts down any connection or session gracefully. Any pending data gets
/// flushed before closing the socket.
virtual void shutdown() = 0;
/// Shuts down any connection or session du to an error. Any pending data gets
/// flushed before closing the socket. Protocols with a dedicated closing
/// handshake such as WebSocket may send the close reason to the peer.
virtual void shutdown(const error& reason);
};
} // namespace caf::net
......@@ -15,21 +15,16 @@ class CAF_NET_EXPORT generic_upper_layer {
public:
virtual ~generic_upper_layer();
/// Prepares any pending data for sending.
/// @returns `false` in case of an error to cause the lower layer to stop,
/// `true` otherwise.
[[nodiscard]] virtual bool prepare_send() = 0;
/// Gives the upper layer an opportunity to add additional data to the output
/// buffer.
virtual void prepare_send() = 0;
/// Queries whether all pending data has been sent. The lower calls this
/// function to decide whether it has to wait for write events on the socket.
[[nodiscard]] virtual bool done_sending() = 0;
/// Called by the lower layer after some event triggered re-registering the
/// socket manager for read operations after it has been stopped previously
/// by the read policy. May restart consumption of bytes or messages.
virtual void continue_reading() = 0;
/// Called by the lower layer for cleaning up any state in case of an error.
/// Called by the lower layer for cleaning up any state in case of an error or
/// when disposed.
virtual void abort(const error& reason) = 0;
};
......
......@@ -10,7 +10,6 @@
namespace caf::net::http {
class context;
class header;
class lower_layer;
class server;
......
......@@ -11,6 +11,8 @@
#include "caf/net/generic_lower_layer.hpp"
#include "caf/net/http/fwd.hpp"
#include <string_view>
namespace caf::net::http {
/// Parses HTTP requests and passes them to the upper layer.
......@@ -18,28 +20,40 @@ class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~lower_layer();
/// Sends the next header to the client.
virtual bool
send_header(context ctx, status code, const header_fields_map& fields)
= 0;
/// Start or re-start reading data from the client.
virtual void request_messages() = 0;
/// Stops reading messages until calling `request_messages`.
virtual void suspend_reading() = 0;
/// Starts writing an HTTP header.
virtual void begin_header(status code) = 0;
/// Adds a header field. Users may only call this function between
/// `begin_header` and `end_header`.
virtual void add_header_field(std::string_view key, std::string_view val) = 0;
/// Seals the header and transports it to the client.
virtual bool end_header() = 0;
/// Sends the payload after the header.
virtual bool send_payload(context, const_byte_span bytes) = 0;
virtual bool send_payload(const_byte_span bytes) = 0;
/// Sends a chunk of data if the full payload is unknown when starting to
/// send.
virtual bool send_chunk(context, const_byte_span bytes) = 0;
virtual bool send_chunk(const_byte_span bytes) = 0;
/// Sends the last chunk, completing a chunked payload.
virtual bool send_end_of_chunks() = 0;
/// Terminates an HTTP context.
virtual void fin(context) = 0;
/// Convenience function for sending header and payload. Automatically sets
/// the header fields `Content-Type` and `Content-Length`.
bool send_response(context ctx, status codek, std::string_view content_type,
bool send_response(status code, std::string_view content_type,
const_byte_span content);
/// @copydoc send_response
bool send_response(status code, std::string_view content_type,
std::string_view content);
};
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/execution_context.hpp"
#include "caf/async/promise.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/fwd.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/response.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
namespace caf::net::http {
/// Handle type (implicitly shared) that represents an HTTP client request.
class CAF_NET_EXPORT request {
public:
struct impl {
header hdr;
std::vector<std::byte> body;
async::promise<response> prom;
};
request() = default;
request(request&&) = default;
request(const request&) = default;
request& operator=(request&&) = default;
request& operator=(const request&) = default;
/// @private
explicit request(std::shared_ptr<impl> pimpl) : pimpl_(std::move(pimpl)) {
// nop
}
/// Returns the HTTP header.
/// @pre `valid()`
const header& hdr() const {
return pimpl_->hdr;
}
/// Returns the HTTP body (payload).
/// @pre `valid()`
const_byte_span body() const {
return make_span(pimpl_->body);
}
/// Sends an HTTP response message to the client. Automatically sets the
/// `Content-Type` and `Content-Length` header fields.
/// @pre `valid()`
void respond(status code, std::string_view content_type,
const_byte_span content) const;
/// Sends an HTTP response message to the client. Automatically sets the
/// `Content-Type` and `Content-Length` header fields.
/// @pre `valid()`
void respond(status code, std::string_view content_type,
std::string_view content) const {
return respond(code, content_type, as_bytes(make_span(content)));
}
private:
std::shared_ptr<impl> pimpl_;
};
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/promise.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/fwd.hpp"
#include "caf/span.hpp"
#include "caf/unordered_flat_map.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
namespace caf::net::http {
/// Handle type (implicitly shared) that represents an HTTP server response.
class CAF_NET_EXPORT response {
public:
using fields_map = unordered_flat_map<std::string, std::string>;
struct impl {
status code;
fields_map fields;
byte_buffer body;
};
response(status code, fields_map fields, byte_buffer body);
/// Returns the HTTP status code.
status code() const {
return pimpl_->code;
}
/// Returns the HTTP header fields.
span<const std::pair<std::string, std::string>> header_fields() const {
return pimpl_->fields.container();
}
/// Returns the HTTP body (payload).
const_byte_span body() const {
return make_span(pimpl_->body);
}
private:
std::shared_ptr<impl> pimpl_;
};
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/actor_system.hpp"
#include "caf/async/blocking_producer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/request.hpp"
#include "caf/net/http/server.hpp"
#include "caf/net/middleman.hpp"
#include <memory>
#include <type_traits>
namespace caf::detail {
class CAF_NET_EXPORT http_request_producer : public atomic_ref_counted,
public async::producer {
public:
using buffer_ptr = async::spsc_buffer_ptr<net::http::request>;
http_request_producer(buffer_ptr buf) : buf_(std::move(buf)) {
// nop
}
static auto make(buffer_ptr buf) {
auto ptr = make_counted<http_request_producer>(buf);
buf->set_producer(ptr);
return ptr;
}
void on_consumer_ready() override;
void on_consumer_cancel() override;
void on_consumer_demand(size_t) override;
void ref_producer() const noexcept override;
void deref_producer() const noexcept override;
bool push(const net::http::request& item);
private:
buffer_ptr buf_;
};
using http_request_producer_ptr = intrusive_ptr<http_request_producer>;
class CAF_NET_EXPORT http_flow_adapter : public net::http::upper_layer {
public:
explicit http_flow_adapter(async::execution_context_ptr loop,
http_request_producer_ptr ptr)
: loop_(std::move(loop)), producer_(std::move(ptr)) {
// nop
}
void prepare_send() override;
bool done_sending() override;
void abort(const error& reason) override;
error start(net::http::lower_layer* down, const settings& config) override;
ptrdiff_t consume(const net::http::header& hdr,
const_byte_span payload) override;
static auto make(async::execution_context_ptr loop,
http_request_producer_ptr ptr) {
return std::make_unique<http_flow_adapter>(loop, ptr);
}
private:
async::execution_context_ptr loop_;
net::http::lower_layer* down_ = nullptr;
std::vector<disposable> pending_;
http_request_producer_ptr producer_;
};
template <class Transport>
class http_acceptor_factory
: public net::connection_factory<typename Transport::socket_type> {
public:
using socket_type = typename Transport::socket_type;
explicit http_acceptor_factory(http_request_producer_ptr producer)
: producer_(std::move(producer)) {
// nop
}
net::socket_manager_ptr make(net::multiplexer* mpx, socket_type fd) override {
auto app = http_flow_adapter::make(mpx, producer_);
auto serv = net::http::server::make(std::move(app));
auto transport = Transport::make(fd, std::move(serv));
auto res = net::socket_manager::make(mpx, std::move(transport));
mpx->watch(res->as_disposable());
return res;
}
private:
http_request_producer_ptr producer_;
};
} // namespace caf::detail
namespace caf::net::http {
/// Convenience function for creating async resources for connecting the HTTP
/// server to a worker.
inline auto make_request_resource() {
return async::make_spsc_buffer_resource<request>();
}
/// Listens for incoming HTTP requests on @p fd.
/// @param sys The host system.
/// @param fd An accept socket in listening mode. For a TCP socket, this socket
/// must already listen to a port.
/// @param out A buffer resource that connects the server to a listener that
/// processes the requests.
/// @param cfg Configuration parameters for the acceptor.
template <class Transport = stream_transport, class Socket>
disposable serve(actor_system& sys, Socket fd,
async::producer_resource<request> out,
const settings& cfg = {}) {
using factory_t = detail::http_acceptor_factory<Transport>;
using impl_t = connection_acceptor<Socket>;
auto max_connections = get_or(cfg, defaults::net::max_connections);
if (auto buf = out.try_open()) {
auto& mpx = sys.network_manager().mpx();
auto producer = detail::http_request_producer::make(std::move(buf));
auto factory = std::make_unique<factory_t>(std::move(producer));
auto impl = impl_t::make(fd, std::move(factory), max_connections);
auto ptr = socket_manager::make(&mpx, std::move(impl));
mpx.start(ptr);
return ptr->as_disposable();
} else {
return disposable{};
}
}
} // namespace caf::net::http
......@@ -6,11 +6,11 @@
#include "caf/byte_span.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/connection_acceptor.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/http/context.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/lower_layer.hpp"
......@@ -31,7 +31,8 @@
namespace caf::net::http {
/// Implements the server part for the HTTP Protocol as defined in RFC 7231.
class server : public stream_oriented::upper_layer, public http::lower_layer {
class CAF_NET_EXPORT server : public stream_oriented::upper_layer,
public http::lower_layer {
public:
// -- member types -----------------------------------------------------------
......@@ -39,8 +40,6 @@ public:
using status_code_type = status;
using context_type = context;
using header_type = header;
enum class mode {
......@@ -80,34 +79,39 @@ public:
bool can_send_more() const noexcept override;
bool is_reading() const noexcept override;
void write_later() override;
void shutdown() override;
void request_messages() override;
void suspend_reading() override;
bool stopped_reading() const noexcept override;
void begin_header(status code) override;
bool send_header(context, status code,
const header_fields_map& fields) override;
void add_header_field(std::string_view key, std::string_view val) override;
bool send_payload(context, const_byte_span bytes) override;
bool end_header() override;
bool send_chunk(context, const_byte_span bytes) override;
bool send_payload(const_byte_span bytes) override;
bool send_end_of_chunks() override;
bool send_chunk(const_byte_span bytes) override;
void fin(context) override;
bool send_end_of_chunks() override;
// -- stream_oriented::upper_layer implementation ----------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
error start(stream_oriented::lower_layer* down,
const settings& config) override;
void abort(const error& reason) override;
bool prepare_send() override;
void prepare_send() override;
bool done_sending() override;
void continue_reading() override;
ptrdiff_t consume(byte_span input, byte_span) override;
private:
......
......@@ -18,28 +18,17 @@ public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param owner A pointer to the socket manager that owns the entire
/// protocol stack. Remains valid for the lifetime of the upper
/// layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
virtual error
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
virtual error start(lower_layer* down, const settings& config) = 0;
/// Consumes an HTTP message.
/// @param ctx Identifies this request. The response message must pass this
/// back to the lower layer. Allows clients to send multiple
/// requests in "parallel" (i.e., send multiple requests before
/// receiving the response on the first one).
/// @param hdr The header fields for the received message.
/// @param payload The payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
virtual ptrdiff_t
consume(context ctx, const header& hdr, const_byte_span payload)
= 0;
virtual ptrdiff_t consume(const header& hdr, const_byte_span payload) = 0;
};
} // namespace caf::net::http
......@@ -20,18 +20,27 @@ namespace caf::net::http::v1 {
CAF_NET_EXPORT std::pair<std::string_view, byte_span>
split_header(byte_span bytes);
/// Writes an HTTP header to the buffer.
/// Writes an HTTP header to @p buf.
CAF_NET_EXPORT void write_header(status code, const header_fields_map& fields,
byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets
/// Content-Type and Content-Length header fields.
/// Write the status code for an HTTP header to @p buf.
CAF_NET_EXPORT void begin_header(status code, byte_buffer& buf);
/// Write a header field to @p buf.
CAF_NET_EXPORT void add_header_field(std::string_view key, std::string_view val,
byte_buffer& buf);
/// Write the status code for an HTTP header to @buf.
CAF_NET_EXPORT bool end_header(byte_buffer& buf);
/// Writes a complete HTTP response to @p buf. Automatically sets Content-Type
/// and Content-Length header fields.
CAF_NET_EXPORT void write_response(status code, std::string_view content_type,
std::string_view content, byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets
/// Content-Type and Content-Length header fields followed by the user-defined
/// @p fields.
/// Writes a complete HTTP response to @p buf. Automatically sets Content-Type
/// and Content-Length header fields followed by the user-defined @p fields.
CAF_NET_EXPORT void write_response(status code, std::string_view content_type,
std::string_view content,
const header_fields_map& fields,
......
......@@ -4,9 +4,17 @@
#pragma once
#include "caf/actor_system.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/cow_tuple.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/disposable.hpp"
#include "caf/net/binary/default_trait.hpp"
#include "caf/net/binary/flow_bridge.hpp"
#include "caf/net/binary/lower_layer.hpp"
#include "caf/net/binary/upper_layer.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/stream_oriented.hpp"
#include <cstdint>
......@@ -23,11 +31,11 @@ namespace caf::net {
/// on 32-bit platforms.
class CAF_NET_EXPORT length_prefix_framing
: public stream_oriented::upper_layer,
public message_oriented::lower_layer {
public binary::lower_layer {
public:
// -- member types -----------------------------------------------------------
using upper_layer_ptr = std::unique_ptr<message_oriented::upper_layer>;
using upper_layer_ptr = std::unique_ptr<binary::upper_layer>;
// -- constants --------------------------------------------------------------
......@@ -45,49 +53,70 @@ public:
// -- high-level factory functions -------------------------------------------
// /// Runs a WebSocket server on the connected socket `fd`.
// /// @param mpx The multiplexer that takes ownership of the socket.
// /// @param fd A connected stream socket.
// /// @param cfg Additional configuration parameters for the protocol
// stack.
// /// @param in Inputs for writing to the socket.
// /// @param out Outputs from the socket.
// /// @param trait Converts between the native and the wire format.
// /// @relates length_prefix_framing
// template <template <class> class Transport = stream_transport, class
// Socket,
// class T, class Trait, class... TransportArgs>
// error run(actor_system&sys,Socket fd,
// const settings& cfg,
// async::consumer_resource<T> in,
// async::producer_resource<T> out,
// Trait trait, TransportArgs&&...
// args) {
// using app_t
// = Transport<length_prefix_framing<message_flow_bridge<T, Trait>>>;
// auto mgr = make_socket_manager<app_t>(fd, &mpx,
// std::forward<TransportArgs>(args)...,
// std::move(in), std::move(out),
// std::move(trait));
// return mgr->init(cfg);
// }
/// Describes the one-time connection event.
using connect_event_t
= cow_tuple<async::consumer_resource<binary::frame>, // Socket to App.
async::producer_resource<binary::frame>>; // App to Socket.
/// Runs length-prefix framing on given connection.
/// @param sys The host system.
/// @param conn A connected stream socket or SSL connection, depending on the
/// `Transport`.
/// @param init Function object for setting up the created flows.
template <class Transport = stream_transport, class Connection, class Init>
static disposable run(actor_system& sys, Connection conn, Init init) {
using trait_t = binary::default_trait;
using frame_t = binary::frame;
static_assert(std::is_invocable_v<Init, connect_event_t&&>,
"invalid signature found for init");
auto [fd_pull, app_push] = async::make_spsc_buffer_resource<frame_t>();
auto [app_pull, fd_push] = async::make_spsc_buffer_resource<frame_t>();
auto mpx = sys.network_manager().mpx_ptr();
auto fc = flow_connector<trait_t>::make_trivial(std::move(fd_pull),
std::move(fd_push));
auto bridge = binary::flow_bridge<trait_t>::make(mpx, std::move(fc));
auto bridge_ptr = bridge.get();
auto impl = length_prefix_framing::make(std::move(bridge));
auto transport = Transport::make(std::move(conn), std::move(impl));
auto ptr = socket_manager::make(mpx, std::move(transport));
bridge_ptr->self_ref(ptr->as_disposable());
mpx->start(ptr);
init(connect_event_t{app_pull, app_push});
return disposable{std::move(ptr)};
}
/// The default number of concurrently open connections when using `accept`.
static constexpr size_t default_max_connections = 128;
/// A producer resource for the acceptor. Any accepted connection is
/// represented by two buffers: one for input and one for output.
using acceptor_resource_t = async::producer_resource<connect_event_t>;
/// Listens for incoming connection on @p fd.
/// @param sys The host system.
/// @param fd An accept socket in listening mode. For a TCP socket, this
/// socket must already listen to a port.
/// @param cfg Configures the acceptor. Currently, the only supported
/// configuration parameter is `max-connections`.
template <class Transport = stream_transport, class Socket, class OnConnect>
static disposable accept(actor_system& sys, Socket fd,
acceptor_resource_t out, const settings& cfg = {}) {
}
// -- implementation of stream_oriented::upper_layer -------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
error start(stream_oriented::lower_layer* down,
const settings& config) override;
void abort(const error& reason) override;
ptrdiff_t consume(byte_span buffer, byte_span delta) override;
void continue_reading() override;
bool prepare_send() override;
void prepare_send() override;
bool done_sending() override;
// -- implementation of message_oriented::lower_layer ------------------------
// -- implementation of binary::lower_layer ----------------------------------
bool can_send_more() const noexcept override;
......@@ -95,7 +124,9 @@ public:
void suspend_reading() override;
bool stopped_reading() const noexcept override;
bool is_reading() const noexcept override;
void write_later() override;
void begin_message() override;
......@@ -103,9 +134,7 @@ public:
bool end_message() override;
void send_close_message() override;
void send_close_message(const error& reason) override;
void shutdown() override;
// -- utility functions ------------------------------------------------------
......
#include "caf/actor_system.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/typed_actor_shell.hpp"
namespace caf::net {
template <class Handle>
actor_shell_ptr_t<Handle>
make_actor_shell(actor_system& sys, async::execution_context_ptr loop) {
auto f = [](abstract_actor_shell* self, message& msg) -> result<message> {
self->quit(make_error(sec::unexpected_message, std::move(msg)));
return make_error(sec::unexpected_message);
};
using ptr_type = actor_shell_ptr_t<Handle>;
using impl_type = typename ptr_type::element_type;
auto hdl = sys.spawn<impl_type>(loop);
auto ptr = ptr_type{actor_cast<strong_actor_ptr>(std::move(hdl))};
ptr->set_fallback(std::move(f));
return ptr;
}
} // namespace caf::net
......@@ -5,11 +5,11 @@
#pragma once
#include "caf/actor_system.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/net/consumer_adapter.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/async/consumer_adapter.hpp"
#include "caf/async/producer_adapter.hpp"
#include "caf/net/binary/lower_layer.hpp"
#include "caf/net/binary/upper_layer.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/producer_adapter.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/sec.hpp"
#include "caf/settings.hpp"
......@@ -32,7 +32,7 @@ namespace caf::net {
/// };
/// ~~~
template <class Trait>
class message_flow_bridge : public message_oriented::upper_layer {
class message_flow_bridge : public binary::upper_layer {
public:
/// The input type for the application.
using input_type = typename Trait::input_type;
......@@ -64,15 +64,17 @@ public:
// nop
}
error init(net::socket_manager* mgr, message_oriented::lower_layer* down,
const settings&) {
error start(net::socket_manager* mgr, binary::lower_layer* down,
const settings&) {
down_ = down;
if (in_res_) {
in_ = consumer_adapter<pull_buffer_t>::try_open(mgr, in_res_);
if (auto in = make_consumer_adapter(in_res_, mgr->mpx_ptr(),
do_wakeup_cb())) {
in_ = std::move(*in);
in_res_ = nullptr;
}
if (out_res_) {
out_ = producer_adapter<push_buffer_t>::try_open(mgr, out_res_);
if (auto out = make_producer_adapter(out_res_, mgr->mpx_ptr(),
do_resume_cb(), do_cancel_cb())) {
out_ = std::move(*out);
out_res_ = nullptr;
}
if (!in_ && !out_)
......@@ -87,63 +89,39 @@ public:
return trait_.convert(item, buf) && down_->end_message();
}
struct write_helper {
message_flow_bridge* bridge;
bool aborted = false;
error err;
write_helper(message_flow_bridge* bridge) : bridge(bridge) {
// nop
}
void on_next(const input_type& item) {
if (!bridge->write(item))
aborted = true;
}
void on_complete() {
// nop
}
void on_error(const error& x) {
err = x;
}
};
bool prepare_send() override {
write_helper helper{this};
while (down_->can_send_more() && in_) {
auto [again, consumed] = in_->pull(async::delay_errors, 1, helper);
if (!again) {
if (helper.err) {
down_->send_close_message(helper.err);
} else {
down_->send_close_message();
}
in_ = nullptr;
} else if (helper.aborted) {
in_->cancel();
in_ = nullptr;
return false;
} else if (consumed == 0) {
return true;
void prepare_send() override {
input_type tmp;
while (down_->can_send_more()) {
switch (in_.pull(async::delay_errors, tmp)) {
case async::read_result::ok:
if (!write(tmp)) {
down_->shutdown(trait_.last_error());
return;
}
break;
case async::read_result::stop:
down_->shutdown();
break;
case async::read_result::abort:
down_->shutdown(in_.abort_reason());
break;
default: // try later
return;
}
}
return true;
}
bool done_sending() override {
return !in_ || !in_->has_data();
return !in_->has_consumer_event();
}
void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed)
out_->close();
out_.close();
else
out_->abort(reason);
out_ = nullptr;
out_ > abort(reason);
}
if (in_) {
in_->cancel();
......@@ -163,14 +141,31 @@ public:
}
private:
action do_wakeup_cb() {
return make_action([this] { down_->write_later(); });
}
action do_resume_cb() {
return make_action([this] { down_->request_messages(); });
}
action do_cancel_cb() {
return make_action([this] {
if (out_) {
out_ = nullptr;
down_->shutdown();
}
});
}
/// Points to the next layer down the protocol stack.
message_oriented::lower_layer* down_ = nullptr;
binary::lower_layer* down_ = nullptr;
/// Incoming messages, serialized to the socket.
consumer_adapter_ptr<pull_buffer_t> in_;
async::consumer_adapter<input_type> in_;
/// Outgoing messages, deserialized from the socket.
producer_adapter_ptr<push_buffer_t> out_;
async::producer_adapter<output_type> out_;
/// Converts between raw bytes and items.
Trait trait_;
......
......@@ -69,19 +69,15 @@ public:
}
multiplexer& mpx() noexcept {
return mpx_;
return *mpx_;
}
const multiplexer& mpx() const noexcept {
return mpx_;
return *mpx_;
}
multiplexer* mpx_ptr() noexcept {
return &mpx_;
}
const multiplexer* mpx_ptr() const noexcept {
return &mpx_;
multiplexer* mpx_ptr() const noexcept {
return mpx_.get();
}
private:
......@@ -91,7 +87,7 @@ private:
actor_system& sys_;
/// Stores the global socket I/O multiplexer.
multiplexer mpx_;
multiplexer_ptr mpx_;
/// Runs the multiplexer's event loop
std::thread mpx_thread_;
......
......@@ -9,6 +9,8 @@
#include <thread>
#include "caf/action.hpp"
#include "caf/async/execution_context.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/operation.hpp"
......@@ -28,7 +30,8 @@ namespace caf::net {
class pollset_updater;
/// Multiplexes any number of ::socket_manager objects with a ::socket.
class CAF_NET_EXPORT multiplexer {
class CAF_NET_EXPORT multiplexer : public detail::atomic_ref_counted,
public async::execution_context {
public:
// -- member types -----------------------------------------------------------
......@@ -55,13 +58,18 @@ public:
// -- constructors, destructors, and assignment operators --------------------
~multiplexer();
// -- factories --------------------------------------------------------------
/// @param parent Points to the owning middleman instance. May be `nullptr`
/// only for the purpose of unit testing if no @ref
/// socket_manager requires access to the @ref middleman or the
/// @ref actor_system.
explicit multiplexer(middleman* parent);
~multiplexer();
static multiplexer_ptr make(middleman* parent) {
auto ptr = new multiplexer(parent);
return multiplexer_ptr{ptr, false};
}
// -- initialization ---------------------------------------------------------
......@@ -73,10 +81,10 @@ public:
size_t num_socket_managers() const noexcept;
/// Returns the index of `mgr` in the pollset or `-1`.
ptrdiff_t index_of(const socket_manager_ptr& mgr);
ptrdiff_t index_of(const socket_manager_ptr& mgr) const noexcept;
/// Returns the index of `fd` in the pollset or `-1`.
ptrdiff_t index_of(socket fd);
ptrdiff_t index_of(socket fd) const noexcept;
/// Returns the owning @ref middleman instance.
middleman& owner();
......@@ -87,54 +95,48 @@ public:
/// Computes the current mask for the manager. Mostly useful for testing.
operation mask_of(const socket_manager_ptr& mgr);
// -- thread-safe signaling --------------------------------------------------
// -- implementation of execution_context ------------------------------------
/// Registers `mgr` for read events.
/// @thread-safe
void register_reading(const socket_manager_ptr& mgr);
void ref_execution_context() const noexcept override;
/// Registers `mgr` for write events.
/// @thread-safe
void register_writing(const socket_manager_ptr& mgr);
void deref_execution_context() const noexcept override;
/// Triggers a continue reading event for `mgr`.
/// @thread-safe
void continue_reading(const socket_manager_ptr& mgr);
void schedule(action what) override;
/// Triggers a continue writing event for `mgr`.
/// @thread-safe
void continue_writing(const socket_manager_ptr& mgr);
void watch(disposable what) override;
/// Schedules a call to `mgr->handle_error(sec::discarded)`.
/// @thread-safe
void discard(const socket_manager_ptr& mgr);
// -- thread-safe signaling --------------------------------------------------
/// Stops further reading by `mgr`.
/// Registers `mgr` for initialization in the multiplexer's thread.
/// @thread-safe
void shutdown_reading(const socket_manager_ptr& mgr);
void start(socket_manager_ptr mgr);
/// Stops further writing by `mgr`.
/// Signals the multiplexer to initiate shutdown.
/// @thread-safe
void shutdown_writing(const socket_manager_ptr& mgr);
void shutdown();
/// Schedules an action for execution on this multiplexer.
/// @thread-safe
void schedule(const action& what);
// -- callbacks for socket managers ------------------------------------------
/// Schedules an action for execution on this multiplexer.
/// @thread-safe
template <class F>
void schedule_fn(F f) {
schedule(make_action(std::move(f)));
}
/// Registers `mgr` for read events.
void register_reading(socket_manager* mgr);
/// Registers `mgr` for initialization in the multiplexer's thread.
/// @thread-safe
void init(const socket_manager_ptr& mgr);
/// Registers `mgr` for write events.
void register_writing(socket_manager* mgr);
/// Signals the multiplexer to initiate shutdown.
/// @thread-safe
void shutdown();
/// Deregisters `mgr` from read events.
void deregister_reading(socket_manager* mgr);
/// Deregisters `mgr` from write events.
void deregister_writing(socket_manager* mgr);
/// Deregisters @p mgr from read and write events.
void deregister(socket_manager* mgr);
/// Queries whether `mgr` is currently registered for reading.
bool is_reading(const socket_manager* mgr) const noexcept;
/// Queries whether `mgr` is currently registered for writing.
bool is_writing(const socket_manager* mgr) const noexcept;
// -- control flow -----------------------------------------------------------
......@@ -160,18 +162,16 @@ protected:
/// Handles an I/O event on given manager.
void handle(const socket_manager_ptr& mgr, short events, short revents);
/// Transfers socket ownership from one manager to another.
void do_handover(const socket_manager_ptr& mgr);
/// Returns a change entry for the socket at given index. Lazily creates a new
/// entry before returning if necessary.
poll_update& update_for(ptrdiff_t index);
/// Returns a change entry for the socket of the manager.
poll_update& update_for(const socket_manager_ptr& mgr);
poll_update& update_for(socket_manager* mgr);
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater.
/// @warning assumes ownership of @p ptr.
template <class T>
void write_to_pipe(uint8_t opcode, T* ptr);
......@@ -182,13 +182,7 @@ protected:
}
/// Queries the currently active event bitmask for `mgr`.
short active_mask_of(const socket_manager_ptr& mgr);
/// Queries whether `mgr` is currently registered for reading.
bool is_reading(const socket_manager_ptr& mgr);
/// Queries whether `mgr` is currently registered for writing.
bool is_writing(const socket_manager_ptr& mgr);
short active_mask_of(const socket_manager* mgr) const noexcept;
// -- member variables -------------------------------------------------------
......@@ -219,26 +213,21 @@ protected:
/// Signals whether shutdown has been requested.
bool shutting_down_ = false;
/// Keeps track of watched disposables.
std::vector<disposable> watched_;
private:
// -- constructors, destructors, and assignment operators --------------------
explicit multiplexer(middleman* parent);
// -- internal callbacks the pollset updater ---------------------------------
void do_shutdown();
void do_register_reading(const socket_manager_ptr& mgr);
void do_register_writing(const socket_manager_ptr& mgr);
void do_continue_reading(const socket_manager_ptr& mgr);
void do_continue_writing(const socket_manager_ptr& mgr);
void do_discard(const socket_manager_ptr& mgr);
void do_shutdown_reading(const socket_manager_ptr& mgr);
void do_shutdown_writing(const socket_manager_ptr& mgr);
void do_init(const socket_manager_ptr& mgr);
void do_start(const socket_manager_ptr& mgr);
};
} // namespace caf::net
......@@ -25,12 +25,7 @@ public:
using msg_buf = std::array<std::byte, sizeof(intptr_t) + 1>;
enum class code : uint8_t {
register_reading,
continue_reading,
register_writing,
continue_writing,
init_manager,
discard_manager,
start_manager,
shutdown_reading,
shutdown_writing,
run_action,
......@@ -47,20 +42,19 @@ public:
// -- implementation of socket_event_layer -----------------------------------
error init(socket_manager* owner, const settings& cfg) override;
error start(socket_manager* owner, const settings& cfg) override;
read_result handle_read_event() override;
socket handle() const override;
read_result handle_buffered_data() override;
void handle_read_event() override;
read_result handle_continue_reading() override;
write_result handle_write_event() override;
void handle_write_event() override;
void abort(const error& reason) override;
private:
pipe_socket fd_;
socket_manager* owner_ = nullptr;
multiplexer* mpx_ = nullptr;
msg_buf buf_;
size_t buf_size_ = 0;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <memory>
#include <new>
#include "caf/async/producer.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/ref_counted.hpp"
namespace caf::net {
/// Connects a socket manager to an asynchronous producer resource.
template <class Buffer>
class producer_adapter final : public ref_counted, public async::producer {
public:
using atomic_count = std::atomic<size_t>;
using buf_ptr = intrusive_ptr<Buffer>;
using value_type = typename Buffer::value_type;
void on_consumer_ready() override {
// nop
}
void on_consumer_cancel() override {
mgr_->mpx().schedule_fn([adapter = strong_this()] { //
adapter->on_cancel();
});
}
void on_consumer_demand(size_t) override {
mgr_->continue_reading();
}
void ref_producer() const noexcept override {
this->ref();
}
void deref_producer() const noexcept override {
this->deref();
}
/// Tries to open the resource for writing.
/// @returns a connected adapter that writes to the resource on success,
/// `nullptr` otherwise.
template <class Resource>
static intrusive_ptr<producer_adapter>
try_open(socket_manager* owner, Resource src) {
CAF_ASSERT(owner != nullptr);
if (auto buf = src.try_open()) {
using ptr_type = intrusive_ptr<producer_adapter>;
auto adapter = ptr_type{new producer_adapter(owner, buf), false};
buf->set_producer(adapter);
return adapter;
} else {
return nullptr;
}
}
/// Makes `item` available to the consumer.
/// @returns the remaining demand.
size_t push(const value_type& item) {
if (buf_) {
return buf_->push(item);
} else {
return 0;
}
}
/// Makes `items` available to the consumer.
/// @returns the remaining demand.
size_t push(span<const value_type> items) {
if (buf_) {
return buf_->push(items);
} else {
return 0;
}
}
void close() {
if (buf_) {
buf_->close();
buf_ = nullptr;
mgr_ = nullptr;
}
}
void abort(error reason) {
if (buf_) {
buf_->abort(std::move(reason));
buf_ = nullptr;
mgr_ = nullptr;
}
}
friend void intrusive_ptr_add_ref(const producer_adapter* ptr) noexcept {
ptr->ref();
}
friend void intrusive_ptr_release(const producer_adapter* ptr) noexcept {
ptr->deref();
}
private:
producer_adapter(socket_manager* owner, buf_ptr buf)
: mgr_(owner), buf_(std::move(buf)) {
// nop
}
void on_cancel() {
if (buf_) {
mgr_->mpx().shutdown_reading(mgr_);
buf_ = nullptr;
mgr_ = nullptr;
}
}
auto strong_this() {
return intrusive_ptr{this};
}
intrusive_ptr<socket_manager> mgr_;
intrusive_ptr<Buffer> buf_;
};
template <class T>
using producer_adapter_ptr = intrusive_ptr<producer_adapter<T>>;
} // namespace caf::net
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/actor_system.hpp"
#include "caf/fwd.hpp"
#include "caf/net/http/server.hpp"
#include "caf/net/prometheus/server.hpp"
#include "caf/net/stream_transport.hpp"
namespace caf::detail {
template <class Transport>
class prometheus_acceptor_factory
: public net::connection_factory<typename Transport::socket_type> {
public:
using state_ptr = net::prometheus::server::scrape_state_ptr;
using socket_type = typename Transport::socket_type;
explicit prometheus_acceptor_factory(state_ptr ptr) : ptr_(std::move(ptr)) {
// nop
}
net::socket_manager_ptr make(net::multiplexer* mpx, socket_type fd) override {
auto prom_serv = net::prometheus::server::make(ptr_);
auto http_serv = net::http::server::make(std::move(prom_serv));
auto transport = Transport::make(fd, std::move(http_serv));
return net::socket_manager::make(mpx, std::move(transport));
}
private:
state_ptr ptr_;
};
} // namespace caf::detail
namespace caf::net::prometheus {
/// Listens for incoming WebSocket connection on @p fd.
/// @param sys The host system.
/// @param fd An accept socket in listening mode. For a TCP socket, this socket
/// must already listen to a port.
template <class Transport = stream_transport, class Socket>
disposable serve(actor_system& sys, Socket fd, const settings& cfg = {}) {
using factory_t = detail::prometheus_acceptor_factory<Transport>;
using impl_t = connection_acceptor<Socket>;
auto mpx = &sys.network_manager().mpx();
auto registry = &sys.metrics();
auto state = prometheus::server::scrape_state::make(registry);
auto factory = std::make_unique<factory_t>(std::move(state));
auto max_connections = get_or(cfg, defaults::net::max_connections);
auto impl = impl_t::make(fd, std::move(factory), max_connections);
auto mgr = socket_manager::make(mpx, std::move(impl));
mpx->start(mgr);
return mgr->as_disposable();
}
} // namespace caf::net::prometheus
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/fwd.hpp"
#include "caf/net/http/fwd.hpp"
#include "caf/net/http/upper_layer.hpp"
#include "caf/telemetry/collector/prometheus.hpp"
#include "caf/telemetry/importer/process.hpp"
#include <chrono>
#include <memory>
#include <string_view>
namespace caf::net::prometheus {
/// Makes metrics available to clients via the Prometheus exposition format.
class server : public http::upper_layer {
public:
// -- member types -----------------------------------------------------------
/// State for scraping Metrics data. Shared between all server instances.
class scrape_state {
public:
using clock_type = std::chrono::steady_clock;
using time_point = clock_type::time_point;
using duration = clock_type::duration;
std::string_view scrape();
explicit scrape_state(telemetry::metric_registry* ptr)
: registry(ptr), last_scrape(duration{0}), proc_importer(*ptr) {
// nop
}
static std::shared_ptr<scrape_state> make(telemetry::metric_registry* ptr) {
return std::make_shared<scrape_state>(ptr);
}
telemetry::metric_registry* registry;
std::chrono::steady_clock::time_point last_scrape;
telemetry::importer::process proc_importer;
telemetry::collector::prometheus collector;
};
using scrape_state_ptr = std::shared_ptr<scrape_state>;
// -- factories --------------------------------------------------------------
static std::unique_ptr<server> make(scrape_state_ptr state) {
return std::unique_ptr<server>{new server(std::move(state))};
}
// -- implementation of http::upper_layer ------------------------------------
void prepare_send() override;
bool done_sending() override;
void abort(const error& reason) override;
error start(http::lower_layer* down, const settings& config) override;
ptrdiff_t consume(const http::header& hdr, const_byte_span payload) override;
private:
explicit server(scrape_state_ptr state) : state_(std::move(state)) {
// nop
}
scrape_state_ptr state_;
http::lower_layer* down_ = nullptr;
};
} // namespace caf::net::prometheus
......@@ -37,6 +37,14 @@ struct CAF_NET_EXPORT socket : detail::comparable<socket> {
return static_cast<signed_socket_id>(id)
- static_cast<signed_socket_id>(other.id);
}
constexpr explicit operator bool() const noexcept {
return id != invalid_socket_id;
}
constexpr bool operator!() const noexcept {
return id == invalid_socket_id;
}
};
/// @relates socket
......
......@@ -16,64 +16,27 @@ class CAF_NET_EXPORT socket_event_layer {
public:
virtual ~socket_event_layer();
/// Encodes how to proceed after a read operation.
enum class read_result {
/// Indicates that a manager wants to read again later.
again,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop,
/// Indicates that a manager wants to write to the socket instead of reading
/// from the socket.
want_write,
/// Indicates that the manager no longer reads from the socket.
close,
/// Indicates that the manager encountered a fatal error and stops both
/// reading and writing.
abort,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover,
};
/// Starts processing on this layer.
virtual error start(socket_manager* owner, const settings& cfg) = 0;
/// Encodes how to proceed after a write operation.
enum class write_result {
/// Indicates that a manager wants to read again later.
again,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop,
/// Indicates that a manager wants to read from the socket instead of
/// writing to the socket.
want_read,
/// Indicates that the manager no longer writes to the socket.
close,
/// Indicates that the manager encountered a fatal error and stops both
/// reading and writing.
abort,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover,
};
/// Initializes the layer.
virtual error init(socket_manager* owner, const settings& cfg) = 0;
/// Returns the handle for the managed socket.
virtual socket handle() const = 0;
/// Handles a read event on the managed socket.
virtual read_result handle_read_event() = 0;
/// Handles internally buffered data.
virtual read_result handle_buffered_data() = 0;
/// Handles a request to continue reading on the socket.
virtual read_result handle_continue_reading() = 0;
virtual void handle_read_event() = 0;
/// Handles a write event on the managed socket.
virtual write_result handle_write_event() = 0;
virtual void handle_write_event() = 0;
/// Called after returning `handover` from a read or write handler.
virtual bool do_handover(std::unique_ptr<socket_event_layer>& next);
/// Called on hard errors on the managed socket.
/// Called on socket errors or when the manager gets disposed.
virtual void abort(const error& reason) = 0;
/// Queries whether the object can be safely discarded after calling
/// @ref abort on it, e.g., that pending data has been written.
virtual bool finalized() const noexcept;
};
} // namespace caf::net
......@@ -44,6 +44,13 @@ public:
fd_ = x;
}
void reset() noexcept {
if (fd_.id != invalid_socket_id) {
close(fd_);
fd_.id = invalid_socket_id;
}
}
Socket release() noexcept {
auto sock = fd_;
fd_.id = invalid_socket_id;
......
......@@ -4,11 +4,14 @@
#pragma once
#include "caf/action.hpp"
#include "caf/actor.hpp"
#include "caf/actor_system.hpp"
#include "caf/callback.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/detail/infer_actor_shell_ptr_type.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/actor_shell.hpp"
......@@ -16,7 +19,6 @@
#include "caf/net/socket.hpp"
#include "caf/net/socket_event_layer.hpp"
#include "caf/net/typed_actor_shell.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include <type_traits>
......@@ -24,27 +26,18 @@
namespace caf::net {
/// Manages the lifetime of a single socket and handles any I/O events on it.
class CAF_NET_EXPORT socket_manager : public ref_counted {
class CAF_NET_EXPORT socket_manager : public detail::atomic_ref_counted,
public disposable_impl {
public:
// -- member types -----------------------------------------------------------
using read_result = socket_event_layer::read_result;
using write_result = socket_event_layer::write_result;
using event_handler_ptr = std::unique_ptr<socket_event_layer>;
/// Stores manager-related flags in a single block.
struct flags_t {
bool read_closed : 1;
bool write_closed : 1;
};
// -- constructors, destructors, and assignment operators --------------------
/// @pre `handle != invalid_socket`
/// @pre `mpx!= nullptr`
socket_manager(multiplexer* mpx, socket handle, event_handler_ptr handler);
socket_manager(multiplexer* mpx, event_handler_ptr handler);
~socket_manager() override;
......@@ -54,28 +47,7 @@ public:
// -- factories --------------------------------------------------------------
static socket_manager_ptr make(multiplexer* mpx, socket handle,
event_handler_ptr handler);
template <class Handle = actor, class FallbackHandler>
detail::infer_actor_shell_ptr_type<Handle>
make_actor_shell(FallbackHandler f) {
using ptr_type = detail::infer_actor_shell_ptr_type<Handle>;
using impl_type = typename ptr_type::element_type;
auto hdl = system().spawn<impl_type>(this);
auto ptr = ptr_type{actor_cast<strong_actor_ptr>(std::move(hdl))};
ptr->set_fallback(std::move(f));
return ptr;
}
template <class Handle = actor>
auto make_actor_shell() {
auto f = [](abstract_actor_shell* self, message& msg) -> result<message> {
self->quit(make_error(sec::unexpected_message, std::move(msg)));
return make_error(sec::unexpected_message);
};
return make_actor_shell<Handle>(std::move(f));
}
static socket_manager_ptr make(multiplexer* mpx, event_handler_ptr handler);
// -- properties -------------------------------------------------------------
......@@ -112,92 +84,112 @@ public:
return mpx_;
}
/// Returns whether the manager closed read operations on the socket.
[[nodiscard]] bool read_closed() const noexcept {
return flags_.read_closed;
}
/// Queries whether the manager is registered for reading.
bool is_reading() const noexcept;
/// Returns whether the manager closed write operations on the socket.
[[nodiscard]] bool write_closed() const noexcept {
return flags_.write_closed;
}
/// Queries whether the manager is registered for writing.
bool is_writing() const noexcept;
// -- event loop management --------------------------------------------------
/// Registers the manager for read operations on the @ref multiplexer.
/// @thread-safe
/// Registers the manager for read operations.
void register_reading();
/// Registers the manager for write operations on the @ref multiplexer.
/// @thread-safe
/// Registers the manager for write operations.
void register_writing();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer.
/// This mechanism allows users to signal changes in the environment to the
/// manager that allow it to make progress, e.g., new demand in asynchronous
/// buffer that allow the manager to push available data downstream. The event
/// is a no-op if the manager is already registered for reading.
/// @thread-safe
void continue_reading();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer.
/// This mechanism allows users to signal changes in the environment to the
/// manager that allow it to make progress, e.g., new data for writing in an
/// asynchronous buffer. The event is a no-op if the manager is already
/// registered for writing.
/// @thread-safe
void continue_writing();
/// Deregisters the manager from read operations.
void deregister_reading();
// -- callbacks for the multiplexer ------------------------------------------
/// Deregisters the manager from write operations.
void deregister_writing();
/// Closes the read channel of the socket.
void close_read() noexcept;
/// Deregisters the manager from both read and write operations.
void deregister();
/// Closes the write channel of the socket.
void close_write() noexcept;
/// Schedules a call to `fn` on the multiplexer when this socket manager
/// cleans up its state.
/// @note Must be called before `start`.
void add_cleanup_listener(action fn) {
cleanup_listeners_.push_back(std::move(fn));
}
/// Initializes the manager and its all of its sub-components.
error init(const settings& cfg);
// -- callbacks for the handler ----------------------------------------------
/// Called whenever the socket received new data.
read_result handle_read_event();
/// Schedules a call to `do_handover` on the handler.
void schedule_handover();
/// Called after handovers to allow the manager to process any data that is
/// already buffered at the transport policy and thus would not trigger a read
/// event on the socket.
read_result handle_buffered_data();
/// Schedules @p what to run later.
void schedule(action what);
/// Restarts a socket manager that suspended reads. Calling this member
/// function on active managers is a no-op. This function also should read any
/// data buffered outside of the socket.
read_result handle_continue_reading();
/// Schedules @p what to run later.
template <class F>
void schedule_fn(F&& what) {
schedule(make_action(std::forward<F>(what)));
}
/// Shuts down this socket manager.
void shutdown();
// -- callbacks for the multiplexer ------------------------------------------
/// Starts the manager and its all of its processing layers.
error start(const settings& cfg);
/// Called whenever the socket received new data.
void handle_read_event();
/// Called whenever the socket is allowed to send data.
write_result handle_write_event();
void handle_write_event();
/// Called when the remote side becomes unreachable due to an error.
/// @param code The error code as reported by the operating system.
/// Called when the remote side becomes unreachable due to an error or after
/// calling @ref dispose.
/// @param code The error code as reported by the operating system or
/// @ref sec::disposed.
void handle_error(sec code);
/// Performs a handover to another transport after `handle_read_event` or
/// `handle_read_event` returned `handover`.
[[nodiscard]] bool do_handover();
// -- implementation of disposable_impl --------------------------------------
void dispose() override;
bool disposed() const noexcept override;
void ref_disposable() const noexcept override;
void deref_disposable() const noexcept override;
private:
// -- utility functions ------------------------------------------------------
void cleanup();
socket_manager_ptr strong_this();
// -- member variables -------------------------------------------------------
/// Stores the socket file descriptor. The socket manager automatically closes
/// the socket in its destructor.
socket fd_;
/// Points to the multiplexer that owns this manager.
/// Points to the multiplexer that executes this manager. Note: we do not need
/// to increase the reference count for the multiplexer, because the
/// multiplexer owns all managers in the sense that calling any member
/// function on a socket manager may not occur if the actor system has shut
/// down (and the multiplexer is part of the actor system).
multiplexer* mpx_;
/// Stores flags for the socket file descriptor.
flags_t flags_;
/// Stores the event handler that operators on the socket file descriptor.
event_handler_ptr handler_;
/// Stores whether `shutdown` has been called.
bool shutting_down_ = false;
/// Stores whether the manager has been either explicitly disposed or shut
/// down by demand of the application.
std::atomic<bool> disposed_;
/// Callbacks to run when calling `cleanup`.
std::vector<action> cleanup_listeners_;
};
/// @relates socket_manager
......
......@@ -13,6 +13,10 @@
namespace caf::net::ssl {
struct close_on_shutdown_t {};
constexpr close_on_shutdown_t close_on_shutdown = close_on_shutdown_t{};
/// SSL state, shared by multiple connections.
class CAF_NET_EXPORT context {
public:
......@@ -71,8 +75,15 @@ public:
// -- connections ------------------------------------------------------------
/// Creates a new SSL connection on `fd`. The connection does not take
/// ownership of the socket, i.e., does not close the socket when the SSL
/// session ends.
expected<connection> new_connection(stream_socket fd);
/// Creates a new SSL connection on `fd`. The connection takes ownership of
/// the socket, i.e., closes the socket when the SSL session ends.
expected<connection> new_connection(stream_socket fd, close_on_shutdown_t);
// -- certificates and keys --------------------------------------------------
/// Configure the context to use the default locations for loading CA
......
......@@ -21,14 +21,9 @@ public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param owner A pointer to the socket manager that owns the entire
/// protocol stack. Remains valid for the lifetime of the upper
/// layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
virtual error
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
virtual error start(lower_layer* down, const settings& config) = 0;
/// Consumes bytes from the lower layer.
/// @param buffer Available bytes to read.
......@@ -45,6 +40,10 @@ class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~lower_layer();
/// Queries whether the transport is currently configured to read from its
/// socket.
virtual bool is_reading() const noexcept = 0;
/// Configures threshold for the next receive operations. Policies remain
/// active until calling this function again.
/// @warning Calling this function in `consume` invalidates both byte spans.
......
......@@ -85,6 +85,9 @@ public:
/// Stores whether we left a write handler due to want_read.
bool wanted_write_from_read_event : 1;
/// Stores whether the application has asked to shut down.
bool shutting_down : 1;
};
// -- constants --------------------------------------------------------------
......@@ -118,9 +121,11 @@ public:
bool end_output() override;
void suspend_reading() override;
bool is_reading() const noexcept override;
void write_later() override;
bool stopped_reading() const noexcept override;
void shutdown() override;
// -- properties -------------------------------------------------------------
......@@ -150,19 +155,29 @@ public:
// -- implementation of socket_event_layer -----------------------------------
error init(socket_manager* owner, const settings& config) override;
error start(socket_manager* owner, const settings& config) override;
read_result handle_read_event() override;
socket handle() const override;
read_result handle_buffered_data() override;
void handle_read_event() override;
write_result handle_write_event() override;
read_result handle_continue_reading() override;
void handle_write_event() override;
void abort(const error& reason) override;
bool finalized() const noexcept override;
protected:
// -- utility functions ------------------------------------------------------
/// Consumes as much data from the buffer as possible.
void handle_buffered_data();
/// Calls abort on the upper layer and deregisters the transport from events.
void fail(const error& reason);
// -- member variables -------------------------------------------------------
/// The socket file descriptor.
stream_socket fd_;
......
......@@ -24,8 +24,8 @@ struct CAF_NET_EXPORT tcp_accept_socket : network_socket {
/// 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.
/// 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>
CAF_NET_EXPORT make_tcp_accept_socket(ip_endpoint node,
......@@ -33,14 +33,24 @@ expected<tcp_accept_socket>
/// 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.
/// 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>
CAF_NET_EXPORT make_tcp_accept_socket(const uri::authority_type& node,
bool reuse_addr = false);
/// Creates a new TCP socket to accept connections on a given port.
/// @param port The port for listening to incoming connection. Passing 0 lets
/// the OS choose a port.
/// @param addr The filter for incoming addresses. Passing the address `0.0.0.0`
/// will accept incoming connection from any host.
/// @param reuse_addr Optionally sets the SO_REUSEADDR option on the socket.
/// @relates tcp_accept_socket
expected<tcp_accept_socket> CAF_NET_EXPORT make_tcp_accept_socket(
uint16_t port, std::string addr = "0.0.0.0", bool reuse_addr = false);
/// Accepts a connection on `x`.
/// @param x Listening endpoint.
/// @returns The socket that handles the accepted connection on success, an
......
......@@ -55,7 +55,7 @@ public:
template <class... Fs>
void set_behavior(Fs... fs) {
auto new_bhvr = behavior_type{std::move(fs)...};
this->bhvr_ = std::move(new_bhvr.unbox());
this->set_behavior_impl(std::move(new_bhvr.unbox()));
}
// -- overridden functions of local_actor ------------------------------------
......@@ -72,7 +72,9 @@ class typed_actor_shell_ptr {
public:
// -- friends ----------------------------------------------------------------
friend class socket_manager;
template <class Handle>
friend actor_shell_ptr_t<Handle>
make_actor_shell(actor_system&, async::execution_context_ptr);
// -- member types -----------------------------------------------------------
......@@ -154,23 +156,3 @@ private:
};
} // namespace caf::net
namespace caf::detail {
template <class T>
struct typed_actor_shell_ptr_oracle;
template <class... Sigs>
struct typed_actor_shell_ptr_oracle<typed_actor<Sigs...>> {
using type = net::typed_actor_shell_ptr<Sigs...>;
};
} // namespace caf::detail
namespace caf::net {
template <class Handle>
using typed_actor_shell_ptr_t =
typename detail::typed_actor_shell_ptr_oracle<Handle>::type;
} // namespace caf::net
......@@ -6,9 +6,10 @@
#include "caf/actor_system.hpp"
#include "caf/cow_tuple.hpp"
#include "caf/net/flow_connector.hpp"
#include "caf/net/web_socket/default_trait.hpp"
#include "caf/net/web_socket/flow_bridge.hpp"
#include "caf/net/web_socket/flow_connector.hpp"
#include "caf/net/web_socket/flow_connector_request_impl.hpp"
#include "caf/net/web_socket/frame.hpp"
#include "caf/net/web_socket/request.hpp"
#include "caf/net/web_socket/server.hpp"
......@@ -18,29 +19,26 @@
namespace caf::detail {
template <class Transport, class Trait>
class ws_acceptor_factory {
class ws_acceptor_factory
: public net::connection_factory<typename Transport::socket_type> {
public:
using connector_pointer = net::web_socket::flow_connector_ptr<Trait>;
using socket_type = typename Transport::socket_type;
using connector_pointer = net::flow_connector_ptr<Trait>;
explicit ws_acceptor_factory(connector_pointer connector)
: connector_(std::move(connector)) {
// nop
}
error init(net::socket_manager*, const settings&) {
return none;
}
template <class Socket>
net::socket_manager_ptr make(net::multiplexer* mpx, Socket fd) {
auto app = net::web_socket::flow_bridge<Trait>::make(connector_);
net::socket_manager_ptr make(net::multiplexer* mpx, socket_type fd) override {
auto app = net::web_socket::flow_bridge<Trait>::make(mpx, connector_);
auto app_ptr = app.get();
auto ws = net::web_socket::server::make(std::move(app));
auto transport = Transport::make(fd, std::move(ws));
return net::socket_manager::make(mpx, fd, std::move(transport));
}
void abort(const error&) {
// nop
auto mgr = net::socket_manager::make(mpx, std::move(transport));
app_ptr->self_ref(mgr->as_disposable());
return mgr;
}
private:
......@@ -79,28 +77,32 @@ auto make_accept_event_resources() {
/// @param out A buffer resource that connects the server to a listener that
/// processes the buffer pairs for each incoming connection.
/// @param on_request Function object for accepting incoming requests.
/// @param limit The maximum amount of connections before closing @p fd. Passing
/// 0 means "no limit".
/// @param cfg Configuration parameters for the acceptor.
template <class Transport = stream_transport, class Socket, class... Ts,
class OnRequest>
void accept(actor_system& sys, Socket fd, acceptor_resource_t<Ts...> out,
OnRequest on_request, size_t limit = 0) {
disposable accept(actor_system& sys, Socket fd, acceptor_resource_t<Ts...> out,
OnRequest on_request, const settings& cfg = {}) {
using trait_t = default_trait;
using request_t = request<default_trait, Ts...>;
static_assert(std::is_invocable_v<OnRequest, const settings&, request_t&>,
"invalid signature found for on_request");
using factory_t = detail::ws_acceptor_factory<Transport, trait_t>;
using impl_t = connection_acceptor<Socket, factory_t>;
using conn_t = flow_connector_impl<OnRequest, trait_t, Ts...>;
using impl_t = connection_acceptor<Socket>;
using conn_t = flow_connector_request_impl<OnRequest, trait_t, Ts...>;
auto max_connections = get_or(cfg, defaults::net::max_connections);
if (auto buf = out.try_open()) {
auto& mpx = sys.network_manager().mpx();
auto conn = std::make_shared<conn_t>(std::move(on_request), std::move(buf));
auto factory = factory_t{std::move(conn)};
auto impl = impl_t::make(fd, limit, std::move(factory));
auto ptr = socket_manager::make(&mpx, fd, std::move(impl));
mpx.init(ptr);
auto factory = std::make_unique<factory_t>(std::move(conn));
auto impl = impl_t::make(fd, std::move(factory), max_connections);
auto impl_ptr = impl.get();
auto ptr = socket_manager::make(&mpx, std::move(impl));
impl_ptr->self_ref(ptr->as_disposable());
mpx.start(ptr);
return disposable{std::move(ptr)};
} else {
return {};
}
// TODO: accept() should return a disposable to stop the WebSocket server.
}
} // namespace caf::net::web_socket
......@@ -27,7 +27,7 @@ namespace caf::net::web_socket {
/// 6455. Initially, the layer performs the WebSocket handshake. Once completed,
/// this layer decodes RFC 6455 frames and forwards binary and text messages to
/// `UpperLayer`.
class client : public stream_oriented::upper_layer {
class CAF_NET_EXPORT client : public stream_oriented::upper_layer {
public:
// -- member types -----------------------------------------------------------
......@@ -43,6 +43,10 @@ public:
static std::unique_ptr<client> make(handshake_ptr hs, upper_layer_ptr up);
static std::unique_ptr<client> make(handshake&& hs, upper_layer_ptr up) {
return make(std::make_unique<handshake>(std::move(hs)), std::move(up));
}
// -- properties -------------------------------------------------------------
web_socket::upper_layer& upper_layer() noexcept {
......@@ -67,16 +71,14 @@ public:
// -- implementation of stream_oriented::upper_layer -------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
error start(stream_oriented::lower_layer* down,
const settings& config) override;
void abort(const error& reason) override;
ptrdiff_t consume(byte_span buffer, byte_span delta) override;
void continue_reading() override;
bool prepare_send() override;
void prepare_send() override;
bool done_sending() override;
......@@ -93,9 +95,6 @@ private:
/// Stores the upper layer.
framing framing_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
settings cfg_;
};
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/net/stream_transport.hpp"
#include "caf/net/web_socket/client.hpp"
#include "caf/net/web_socket/default_trait.hpp"
#include "caf/net/web_socket/flow_bridge.hpp"
#include "caf/net/web_socket/frame.hpp"
#include "caf/net/web_socket/handshake.hpp"
namespace caf::net::web_socket {
/// Describes the one-time connection event.
using connect_event_t
= cow_tuple<async::consumer_resource<frame>, // Socket to App.
async::producer_resource<frame>>; // App to Socket.
/// Tries to establish a WebSocket connection on @p fd.
/// @param sys The host system.
/// @param fd An connected socket.
/// @param init Function object for setting up the created flows.
template <class Transport = stream_transport, class Socket, class Init>
void connect(actor_system& sys, Socket fd, handshake hs, Init init) {
// TODO: connect() should return a disposable to stop the WebSocket.
using trait_t = default_trait;
static_assert(std::is_invocable_v<Init, connect_event_t&&>,
"invalid signature found for init");
auto [ws_pull, app_push] = async::make_spsc_buffer_resource<frame>();
auto [app_pull, ws_push] = async::make_spsc_buffer_resource<frame>();
using conn_t = flow_connector_trivial_impl<trait_t>;
auto mpx = sys.network_manager().mpx_ptr();
auto conn = std::make_shared<conn_t>(std::move(ws_pull), std::move(ws_push));
auto bridge = flow_bridge<trait_t>::make(mpx, std::move(conn));
auto impl = client::make(std::move(hs), std::move(bridge));
auto transport = Transport::make(fd, std::move(impl));
auto ptr = socket_manager::make(mpx, std::move(transport));
mpx->start(ptr);
init(connect_event_t{app_pull, app_push});
}
} // namespace caf::net::web_socket
......@@ -32,6 +32,8 @@ public:
bool convert(const_byte_span bytes, input_type& x);
bool convert(std::string_view text, input_type& x);
error last_error();
};
} // namespace caf::net::web_socket
......@@ -4,11 +4,11 @@
#pragma once
#include "caf/async/consumer_adapter.hpp"
#include "caf/async/producer_adapter.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/fwd.hpp"
#include "caf/net/consumer_adapter.hpp"
#include "caf/net/producer_adapter.hpp"
#include "caf/net/web_socket/flow_connector.hpp"
#include "caf/net/flow_connector.hpp"
#include "caf/net/web_socket/lower_layer.hpp"
#include "caf/net/web_socket/request.hpp"
#include "caf/net/web_socket/upper_layer.hpp"
......@@ -27,21 +27,24 @@ public:
using output_type = typename Trait::output_type;
/// Type for the consumer adapter. We consume the output of the application.
using consumer_type = consumer_adapter<async::spsc_buffer<output_type>>;
using consumer_type = async::consumer_adapter<output_type>;
/// Type for the producer adapter. We produce the input of the application.
using producer_type = producer_adapter<async::spsc_buffer<input_type>>;
using producer_type = async::producer_adapter<input_type>;
using request_type = request<Trait>;
using connector_pointer = flow_connector_ptr<Trait>;
explicit flow_bridge(connector_pointer conn) : conn_(std::move(conn)) {
explicit flow_bridge(async::execution_context_ptr loop,
connector_pointer conn)
: loop_(std::move(loop)), conn_(std::move(conn)) {
// nop
}
static std::unique_ptr<flow_bridge> make(connector_pointer conn) {
return std::make_unique<flow_bridge>(std::move(conn));
static std::unique_ptr<flow_bridge> make(async::execution_context_ptr loop,
connector_pointer conn) {
return std::make_unique<flow_bridge>(std::move(loop), std::move(conn));
}
bool write(const output_type& item) {
......@@ -56,92 +59,83 @@ public:
}
}
struct write_helper {
flow_bridge* thisptr;
bool aborted = false;
error err;
explicit write_helper(flow_bridge* thisptr) : thisptr(thisptr) {
// nop
}
void on_next(const output_type& item) {
if (!thisptr->write(item))
aborted = true;
}
void on_complete() {
// nop
}
bool running() const noexcept {
return in_ || out_;
}
void on_error(const error& x) {
err = x;
}
};
void self_ref(disposable ref) {
self_ref_ = std::move(ref);
}
// -- implementation of web_socket::lower_layer ------------------------------
error init(net::socket_manager* mgr, web_socket::lower_layer* down,
const settings& cfg) override {
error start(web_socket::lower_layer* down, const settings& cfg) override {
down_ = down;
auto [err, pull, push] = conn_->on_request(cfg);
if (!err) {
in_ = consumer_type::try_open(mgr, pull);
out_ = producer_type::try_open(mgr, push);
CAF_ASSERT(in_ != nullptr);
CAF_ASSERT(out_ != nullptr);
auto do_wakeup = make_action([this] {
prepare_send();
if (!running())
down_->shutdown();
});
auto do_resume = make_action([this] { down_->request_messages(); });
auto do_cancel = make_action([this] {
if (!running())
down_->shutdown();
});
in_ = consumer_type::make(pull.try_open(), loop_, std::move(do_wakeup));
out_ = producer_type::make(push.try_open(), loop_, std::move(do_resume),
std::move(do_cancel));
conn_ = nullptr;
return none;
if (running())
return none;
else
return make_error(sec::runtime_error,
"cannot init flow bridge: no buffers");
} else {
conn_ = nullptr;
return err;
}
}
void continue_reading() override {
// nop
}
bool prepare_send() override {
write_helper helper{this};
while (down_->can_send_more() && in_) {
auto [again, consumed] = in_->pull(async::delay_errors, 1, helper);
if (!again) {
if (helper.err) {
down_->send_close_message(helper.err);
} else {
down_->send_close_message();
}
in_ = nullptr;
return false;
} else if (helper.aborted) {
in_->cancel();
in_ = nullptr;
return false;
} else if (consumed == 0) {
return true;
void prepare_send() override {
input_type tmp;
while (down_->can_send_more()) {
switch (in_.pull(async::delay_errors, tmp)) {
case async::read_result::ok:
if (!write(tmp)) {
down_->shutdown(trait_.last_error());
return;
}
break;
case async::read_result::stop:
down_->shutdown();
break;
case async::read_result::abort:
down_->shutdown(in_.abort_reason());
break;
default: // try later
return;
}
}
return true;
}
bool done_sending() override {
return !in_ || !in_->has_data();
return !in_.has_consumer_event();
}
void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed)
out_->close();
else
out_->abort(reason);
out_ = nullptr;
}
if (in_) {
in_->cancel();
in_ = nullptr;
if (reason == sec::connection_closed || reason == sec::socket_disconnected
|| reason == sec::disposed) {
out_.close();
} else {
out_.abort(reason);
}
}
in_.cancel();
self_ref_ = nullptr;
}
ptrdiff_t consume_binary(byte_span buf) override {
......@@ -150,7 +144,7 @@ public:
input_type val;
if (!trait_.convert(buf, val))
return -1;
if (out_->push(std::move(val)) == 0)
if (out_.push(std::move(val)) == 0)
down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
......@@ -161,7 +155,7 @@ public:
input_type val;
if (!trait_.convert(buf, val))
return -1;
if (out_->push(std::move(val)) == 0)
if (out_.push(std::move(val)) == 0)
down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
......@@ -170,16 +164,24 @@ private:
web_socket::lower_layer* down_;
/// The output of the application. Serialized to the socket.
intrusive_ptr<consumer_type> in_;
consumer_type in_;
/// The input to the application. Deserialized from the socket.
intrusive_ptr<producer_type> out_;
producer_type out_;
/// Converts between raw bytes and native C++ objects.
Trait trait_;
/// Runs callbacks in the I/O event loop.
async::execution_context_ptr loop_;
/// Initializes the bridge. Disposed (set to null) after initializing.
connector_pointer conn_;
/// Type-erased handle to the @ref socket_manager. This reference is important
/// to keep the bridge alive while the manager is not registered for writing
/// or reading.
disposable self_ref_;
};
} // namespace caf::net::web_socket
......@@ -4,37 +4,12 @@
#pragma once
#include "caf/async/blocking_producer.hpp"
#include "caf/error.hpp"
#include "caf/net/web_socket/request.hpp"
#include "caf/sec.hpp"
#include <tuple>
namespace caf::net::web_socket {
template <class Trait>
class flow_connector {
public:
using input_type = typename Trait::input_type;
using output_type = typename Trait::output_type;
using result_type = std::tuple<error, async::consumer_resource<input_type>,
async::producer_resource<output_type>>;
virtual ~flow_connector() {
// nop
}
virtual result_type on_request(const settings& cfg) = 0;
};
template <class Trait>
using flow_connector_ptr = std::shared_ptr<flow_connector<Trait>>;
/// Calls an `OnRequest` handler with a @ref request object and passes the
/// generated buffers to the @ref flow_bridge.
template <class OnRequest, class Trait, class... Ts>
class flow_connector_impl : public flow_connector<Trait> {
class flow_connector_request_impl : public flow_connector<Trait> {
public:
using super = flow_connector<Trait>;
......@@ -47,7 +22,7 @@ public:
using producer_type = async::blocking_producer<app_res_type>;
template <class T>
flow_connector_impl(OnRequest&& on_request, T&& out)
flow_connector_request_impl(OnRequest&& on_request, T&& out)
: on_request_(std::move(on_request)), out_(std::forward<T>(out)) {
// nop
}
......
......@@ -72,7 +72,6 @@ public:
std::string_view as_text() const noexcept;
private:
class data {
public:
data() = delete;
......@@ -133,6 +132,7 @@ private:
std::byte storage_[];
};
private:
explicit frame(intrusive_ptr<data> ptr) : data_(std::move(ptr)) {
// nop
}
......
......@@ -25,7 +25,7 @@
namespace caf::net::web_socket {
/// Implements the WebSocket framing protocol as defined in RFC-6455.
class framing : public web_socket::lower_layer {
class CAF_NET_EXPORT framing : public web_socket::lower_layer {
public:
// -- member types -----------------------------------------------------------
......@@ -49,7 +49,7 @@ public:
// -- initialization ---------------------------------------------------------
void init(socket_manager* owner, stream_oriented::lower_layer* down);
void start(stream_oriented::lower_layer* down);
// -- properties -------------------------------------------------------------
......@@ -77,11 +77,17 @@ public:
// -- web_socket::lower_layer implementation ---------------------------------
using web_socket::lower_layer::shutdown;
bool can_send_more() const noexcept override;
void suspend_reading() override;
bool stopped_reading() const noexcept override;
bool is_reading() const noexcept override;
void write_later() override;
void shutdown(status code, std::string_view desc) override;
void request_messages() override;
......@@ -97,17 +103,8 @@ public:
bool end_text_message() override;
void send_close_message() override;
void send_close_message(status code, std::string_view desc) override;
// -- interface for the lower layer ------------------------------------------
template <class LowerLayerPtr>
static void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::up_to(2048));
}
ptrdiff_t consume(byte_span input, byte_span);
private:
......@@ -117,10 +114,6 @@ private:
void ship_pong(byte_span payload);
void ship_close(uint16_t code, std::string_view msg);
void ship_close();
template <class T>
void ship_frame(std::vector<T>& buf);
......
......@@ -6,11 +6,8 @@
namespace caf::net::web_socket {
template <class Trait>
class flow_connector;
template <class OnRequest, class Trait, class... Ts>
class flow_connector_impl;
class flow_connector_request_impl;
template <class Trait>
class flow_bridge;
......
......@@ -22,6 +22,9 @@ public:
/// Pulls messages from the transport until calling `suspend_reading`.
virtual void request_messages() = 0;
/// Stops reading messages until calling `request_messages`.
virtual void suspend_reading() = 0;
/// Begins transmission of a binary message.
virtual void begin_binary_message() = 0;
......@@ -42,14 +45,12 @@ public:
/// Seals the current text message buffer and ships a new WebSocket frame.
virtual bool end_text_message() = 0;
/// Sends the close message with @ref status `normal_close`.
virtual void send_close_message() = 0;
void shutdown() override;
/// Sends the close message with an custom @ref status.
virtual void send_close_message(status code, std::string_view desc) = 0;
void shutdown(const error& reason) override;
/// Sends the close message with @ref status `unexpected_condition`.
void send_close_message(const error& reason);
/// Sends a shutdown message with custom @ref status @p code and @p msg text.
virtual void shutdown(status code, std::string_view msg) = 0;
};
} // namespace caf::net::web_socket
......@@ -19,7 +19,7 @@ template <class Trait, class... Ts>
class request {
public:
template <class, class, class...>
friend class flow_connector_impl;
friend class flow_connector_request_impl;
using input_type = typename Trait::input_type;
......
......@@ -74,16 +74,14 @@ public:
// -- stream_oriented::upper_layer implementation ----------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
error start(stream_oriented::lower_layer* down,
const settings& config) override;
void abort(const error& reason) override;
ptrdiff_t consume(byte_span input, byte_span) override;
void continue_reading() override;
bool prepare_send() override;
void prepare_send() override;
bool done_sending() override;
......@@ -100,9 +98,6 @@ private:
/// Stores the upper layer.
framing framing_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
/// Holds a copy of the settings in order to delay initialization of the upper
/// layer until the handshake completed. We also fill this dictionary with the
/// contents of the HTTP GET header.
......
......@@ -16,9 +16,7 @@ class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
virtual ~upper_layer();
virtual error
init(net::socket_manager* mgr, lower_layer* down, const settings& cfg)
= 0;
virtual error start(lower_layer* down, const settings& cfg) = 0;
virtual ptrdiff_t consume_binary(byte_span buf) = 0;
virtual ptrdiff_t consume_text(std::string_view buf) = 0;
};
......
......@@ -4,6 +4,7 @@
#include "caf/net/abstract_actor_shell.hpp"
#include "caf/action.hpp"
#include "caf/callback.hpp"
#include "caf/config.hpp"
#include "caf/detail/default_invoke_result_visitor.hpp"
......@@ -18,9 +19,15 @@ namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
abstract_actor_shell::abstract_actor_shell(actor_config& cfg,
socket_manager* owner)
: super(cfg), mailbox_(policy::normal_messages{}), owner_(owner) {
async::execution_context_ptr loop)
: super(cfg), mailbox_(policy::normal_messages{}), loop_(loop) {
mailbox_.try_block();
resume_ = make_action([this] {
for (;;) {
if (!consume_message() && try_block_mailbox())
return;
}
});
}
abstract_actor_shell::~abstract_actor_shell() {
......@@ -117,13 +124,13 @@ bool abstract_actor_shell::enqueue(mailbox_element_ptr ptr, execution_unit*) {
switch (mailbox().push_back(std::move(ptr))) {
case intrusive::inbox_result::unblocked_reader: {
CAF_LOG_ACCEPT_EVENT(true);
std::unique_lock<std::mutex> guard{owner_mtx_};
// The owner can only be null if this enqueue succeeds, then we close the
// mailbox and reset owner_ in cleanup() before acquiring the mutex here.
std::unique_lock<std::mutex> guard{loop_mtx_};
// The loop can only be null if this enqueue succeeds, then we close the
// mailbox and reset loop_ in cleanup() before acquiring the mutex here.
// Hence, the mailbox element has already been disposed and we can simply
// skip any further processing.
if (owner_)
owner_->mpx().register_writing(owner_);
if (loop_)
loop_->schedule(resume_);
return true;
}
case intrusive::inbox_result::success:
......@@ -175,8 +182,9 @@ bool abstract_actor_shell::cleanup(error&& fail_state, execution_unit* host) {
}
// Detach from owner.
{
std::unique_lock<std::mutex> guard{owner_mtx_};
owner_ = nullptr;
std::unique_lock<std::mutex> guard{loop_mtx_};
loop_ = nullptr;
resume_.dispose();
}
// Dispatch to parent's `cleanup` function.
return super::cleanup(std::move(fail_state), host);
......
......@@ -8,8 +8,8 @@ namespace caf::net {
// -- actor_shell --------------------------------------------------------------
actor_shell::actor_shell(actor_config& cfg, socket_manager* owner)
: super(cfg, owner) {
actor_shell::actor_shell(actor_config& cfg, async::execution_context_ptr loop)
: super(cfg, loop) {
// nop
}
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/binary/default_trait.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/binary/frame.hpp"
#include "caf/sec.hpp"
namespace caf::net::binary {
bool default_trait::convert(const output_type& x, byte_buffer& bytes) {
auto src = x.bytes();
bytes.insert(bytes.end(), src.begin(), src.end());
return true;
}
bool default_trait::convert(const_byte_span bytes, input_type& x) {
x = frame{bytes};
return true;
}
error default_trait::last_error() {
CAF_LOG_ERROR("default_trait::last_error called");
return {sec::logic_error};
}
} // namespace caf::net::binary
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/binary/frame.hpp"
#include <cstring>
#include <new>
namespace caf::net::binary {
frame::frame(const_byte_span data) {
auto total_size = sizeof(web_socket::frame::data) + data.size();
auto vptr = malloc(total_size);
data_.reset(new (vptr) web_socket::frame::data(true, data.size()), false);
memcpy(data_->storage(), data.data(), data.size());
}
} // namespace caf::net::binary
......@@ -2,16 +2,12 @@
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/message_oriented.hpp"
#include "caf/net/binary/lower_layer.hpp"
namespace caf::net::message_oriented {
upper_layer::~upper_layer() {
// nop
}
namespace caf::net::binary {
lower_layer::~lower_layer() {
// nop
}
} // namespace caf::net::message_oriented
} // namespace caf::net::binary
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/binary/upper_layer.hpp"
namespace caf::net::binary {
upper_layer::~upper_layer() {
// nop
}
} // namespace caf::net::binary
......@@ -10,4 +10,8 @@ generic_lower_layer::~generic_lower_layer() {
// nop
}
void generic_lower_layer::shutdown(const error&) {
shutdown();
}
} // namespace caf::net
......@@ -4,7 +4,6 @@
#include "caf/net/http/lower_layer.hpp"
#include "caf/net/http/context.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/status.hpp"
......@@ -19,14 +18,18 @@ lower_layer::~lower_layer() {
// nop
}
bool lower_layer::send_response(context ctx, status code,
std::string_view content_type,
bool lower_layer::send_response(status code, std::string_view content_type,
const_byte_span content) {
auto content_size = std::to_string(content.size());
header_fields_map fields;
fields.emplace("Content-Type"sv, content_type);
fields.emplace("Content-Length"sv, content_size);
return send_header(ctx, code, fields) && send_payload(ctx, content);
begin_header(code);
add_header_field("Content-Type"sv, content_type);
add_header_field("Content-Length"sv, content_size);
return end_header() && send_payload(content);
}
bool lower_layer::send_response(status code, std::string_view content_type,
std::string_view content) {
return send_response(code, content_type, as_bytes(make_span(content)));
}
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/http/request.hpp"
using namespace std::literals;
// TODO: reduce number of memory allocations for producing the response.
namespace caf::net::http {
void request::respond(status code, std::string_view content_type,
const_byte_span content) const {
auto content_size = std::to_string(content.size());
unordered_flat_map<std::string, std::string> fields;
fields.emplace("Content-Type"sv, content_type);
fields.emplace("Content-Length"sv, content_size);
auto body = std::vector<std::byte>{content.begin(), content.end()};
pimpl_->prom.set_value(response{code, std::move(fields), std::move(body)});
}
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/http/response.hpp"
namespace caf::net::http {
response::response(status code, fields_map fm, std::vector<std::byte> body) {
pimpl_ = std::make_shared<impl>(impl{code, std::move(fm), std::move(body)});
}
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/http/serve.hpp"
namespace caf::detail {
// TODO: there is currently no back-pressure from the worker to the server.
// -- http_request_producer ----------------------------------------------------
void http_request_producer::on_consumer_ready() {
// nop
}
void http_request_producer::on_consumer_cancel() {
}
void http_request_producer::on_consumer_demand(size_t) {
// nop
}
void http_request_producer::ref_producer() const noexcept {
ref();
}
void http_request_producer::deref_producer() const noexcept {
deref();
}
bool http_request_producer::push(const net::http::request& item) {
return buf_->push(item);
}
// -- http_flow_adapter --------------------------------------------------------
void http_flow_adapter::prepare_send() {
// nop
}
bool http_flow_adapter::done_sending() {
return true;
}
void http_flow_adapter::abort(const error&) {
for (auto& pending : pending_)
pending.dispose();
}
error http_flow_adapter::start(net::http::lower_layer* down, const settings&) {
down_ = down;
down_->request_messages();
return none;
}
ptrdiff_t http_flow_adapter::consume(const net::http::header& hdr,
const_byte_span payload) {
using namespace net::http;
if (!pending_.empty()) {
CAF_LOG_WARNING("received multiple requests from the same HTTP client: "
"not implemented yet (drop request)");
return static_cast<ptrdiff_t>(payload.size());
}
auto prom = async::promise<response>();
auto fut = prom.get_future();
auto buf = std::vector<std::byte>{payload.begin(), payload.end()};
auto impl = request::impl{hdr, std::move(buf), std::move(prom)};
producer_->push(request{std::make_shared<request::impl>(std::move(impl))});
auto hdl = fut.bind_to(*loop_).then(
[this](const response& res) {
down_->begin_header(res.code());
for (auto& [key, val] : res.header_fields())
down_->add_header_field(key, val);
std::ignore = down_->end_header();
down_->send_payload(res.body());
down_->shutdown();
},
[this](const error& err) {
auto description = to_string(err);
down_->send_response(status::internal_server_error, "text/plain",
description);
down_->shutdown();
});
pending_.emplace_back(std::move(hdl));
return static_cast<ptrdiff_t>(payload.size());
}
} // namespace caf::detail
......@@ -14,23 +14,41 @@ bool server::can_send_more() const noexcept {
return down_->can_send_more();
}
void server::suspend_reading() {
return down_->suspend_reading();
bool server::is_reading() const noexcept {
return down_->is_reading();
}
void server::write_later() {
down_->write_later();
}
void server::shutdown() {
down_->shutdown();
}
bool server::stopped_reading() const noexcept {
return down_->stopped_reading();
void server::request_messages() {
if (!down_->is_reading())
down_->configure_read(receive_policy::up_to(max_request_size_));
}
bool server::send_header(context, status code,
const header_fields_map& fields) {
void server::suspend_reading() {
down_->configure_read(receive_policy::stop());
}
void server::begin_header(status code) {
down_->begin_output();
v1::write_header(code, fields, down_->output_buffer());
down_->end_output();
return true;
v1::begin_header(code, down_->output_buffer());
}
bool server::send_payload(context, const_byte_span bytes) {
void server::add_header_field(std::string_view key, std::string_view val) {
v1::add_header_field(key, val, down_->output_buffer());
}
bool server::end_header() {
return v1::end_header(down_->output_buffer()) && down_->end_output();
}
bool server::send_payload(const_byte_span bytes) {
down_->begin_output();
auto& buf = down_->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
......@@ -38,7 +56,7 @@ bool server::send_payload(context, const_byte_span bytes) {
return true;
}
bool server::send_chunk(context, const_byte_span bytes) {
bool server::send_chunk(const_byte_span bytes) {
down_->begin_output();
auto& buf = down_->output_buffer();
auto size = bytes.size();
......@@ -60,39 +78,30 @@ bool server::send_end_of_chunks() {
return down_->end_output();
}
void server::fin(context) {
// nop
}
// -- stream_oriented::upper_layer implementation ------------------------------
error server::init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& cfg) {
error server::start(stream_oriented::lower_layer* down, const settings& cfg) {
down_ = down;
if (auto err = up_->init(owner, this, cfg))
return err;
if (auto max_size = get_as<uint32_t>(cfg, "http.max-request-size"))
max_request_size_ = *max_size;
down_->configure_read(receive_policy::up_to(max_request_size_));
return none;
if (auto err = up_->start(this, cfg))
return err;
else
return none;
}
void server::abort(const error& reason) {
up_->abort(reason);
}
bool server::prepare_send() {
return up_->prepare_send();
void server::prepare_send() {
up_->prepare_send();
}
bool server::done_sending() {
return up_->done_sending();
}
void server::continue_reading() {
down_->configure_read(receive_policy::up_to(max_request_size_));
}
ptrdiff_t server::consume(byte_span input, byte_span) {
using namespace literals;
CAF_LOG_TRACE(CAF_ARG2("bytes", input.size()));
......@@ -174,7 +183,7 @@ void server::write_response(status code, std::string_view content) {
}
bool server::invoke_upper_layer(const_byte_span payload) {
return up_->consume(context{}, hdr_, payload) >= 0;
return up_->consume(hdr_, payload) >= 0;
}
bool server::handle_header(std::string_view http) {
......
......@@ -61,6 +61,24 @@ void write_header(status code, const header_fields_map& fields,
out << "\r\n"sv;
}
void begin_header(status code, byte_buffer& buf) {
writer out{&buf};
out << "HTTP/1.1 "sv << std::to_string(static_cast<int>(code)) << ' '
<< phrase(code) << "\r\n"sv;
}
void add_header_field(std::string_view key, std::string_view val,
byte_buffer& buf) {
writer out{&buf};
out << key << ": "sv << val << "\r\n"sv;
}
bool end_header(byte_buffer& buf) {
writer out{&buf};
out << "\r\n"sv;
return true;
}
void write_response(status code, std::string_view content_type,
std::string_view content, byte_buffer& buf) {
header_fields_map fields;
......
......@@ -24,11 +24,10 @@ length_prefix_framing::make(upper_layer_ptr up) {
// -- implementation of stream_oriented::upper_layer ---------------------------
error length_prefix_framing::init(socket_manager* owner,
stream_oriented::lower_layer* down,
const settings& cfg) {
error length_prefix_framing::start(stream_oriented::lower_layer* down,
const settings& cfg) {
down_ = down;
return up_->init(owner, this, cfg);
return up_->start(this, cfg);
}
void length_prefix_framing::abort(const error& reason) {
......@@ -36,7 +35,7 @@ void length_prefix_framing::abort(const error& reason) {
}
ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) {
CAF_LOG_TRACE("got" << input.size() << "bytes");
CAF_LOG_TRACE("got" << input.size() << "bytes\n");
if (input.size() < sizeof(uint32_t)) {
CAF_LOG_ERROR("received too few bytes from underlying transport");
up_->abort(make_error(sec::logic_error,
......@@ -48,8 +47,10 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) {
auto msg_size = static_cast<size_t>(detail::from_network_order(u32_size));
if (msg_size == 0) {
// Ignore empty messages.
CAF_LOG_DEBUG("received empty message");
return static_cast<ptrdiff_t>(input.size());
CAF_LOG_ERROR("received empty message");
up_->abort(make_error(sec::logic_error,
"received empty buffer from stream layer"));
return -1;
} else if (msg_size > max_message_length) {
CAF_LOG_DEBUG("exceeded maximum message size");
up_->abort(
......@@ -65,7 +66,7 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) {
if (msg_size == msg.size() && msg_size + hdr_size == input.size()) {
CAF_LOG_DEBUG("got message of size" << msg_size);
if (up_->consume(msg) >= 0) {
if (!down_->stopped_reading())
if (down_->is_reading())
down_->configure_read(receive_policy::exactly(hdr_size));
return static_cast<ptrdiff_t>(input.size());
} else {
......@@ -79,34 +80,34 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) {
}
}
void length_prefix_framing::continue_reading() {
up_->continue_reading();
}
bool length_prefix_framing::prepare_send() {
return up_->prepare_send();
void length_prefix_framing::prepare_send() {
up_->prepare_send();
}
bool length_prefix_framing::done_sending() {
return up_->done_sending();
}
// -- implementation of message_oriented::lower_layer --------------------------
// -- implementation of binary::lower_layer ------------------------------------
bool length_prefix_framing::can_send_more() const noexcept {
return down_->can_send_more();
}
void length_prefix_framing::suspend_reading() {
down_->suspend_reading();
down_->configure_read(receive_policy::stop());
}
bool length_prefix_framing::stopped_reading() const noexcept {
return down_->stopped_reading();
bool length_prefix_framing::is_reading() const noexcept {
return down_->is_reading();
}
void length_prefix_framing::write_later() {
down_->write_later();
}
void length_prefix_framing::request_messages() {
if (down_->stopped_reading())
if (!down_->is_reading())
down_->configure_read(receive_policy::exactly(hdr_size));
}
......@@ -139,12 +140,8 @@ bool length_prefix_framing::end_message() {
}
}
void length_prefix_framing::send_close_message() {
// nop: this layer has no close message
}
void length_prefix_framing::send_close_message(const error&) {
// nop: this layer has no close message
void length_prefix_framing::shutdown() {
down_->shutdown();
}
// -- utility functions ------------------------------------------------------
......
......@@ -7,6 +7,9 @@
#include "caf/actor_system_config.hpp"
#include "caf/detail/set_thread_name.hpp"
#include "caf/expected.hpp"
#include "caf/net/prometheus/serve.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/raise_error.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
......@@ -14,11 +17,46 @@
namespace caf::net {
namespace {
struct prom_config {
uint16_t port;
std::string address = "0.0.0.0";
bool reuse_address = true;
};
template <class Inspector>
bool inspect(Inspector& f, prom_config& x) {
return f.object(x).fields(
f.field("port", x.port), f.field("address", x.address).fallback("0.0.0.0"),
f.field("reuse-address", x.reuse_address).fallback(true));
}
void launch_prom_server(actor_system& sys, const prom_config& cfg) {
if (auto fd = make_tcp_accept_socket(cfg.port, cfg.address,
cfg.reuse_address)) {
CAF_LOG_INFO("start Prometheus server at port" << local_port(*fd));
prometheus::serve(sys, std::move(*fd));
} else {
CAF_LOG_WARNING("failed to start Prometheus server: " << fd.error());
}
}
void launch_background_tasks(actor_system& sys) {
auto& cfg = sys.config();
if (auto pcfg = get_as<prom_config>(cfg, "caf.middleman.prometheus-http")) {
launch_prom_server(sys, *pcfg);
}
}
} // namespace
void middleman::init_global_meta_objects() {
// nop
}
middleman::middleman(actor_system& sys) : sys_(sys), mpx_(this) {
middleman::middleman(actor_system& sys)
: sys_(sys), mpx_(multiplexer::make(this)) {
// nop
}
......@@ -29,26 +67,27 @@ middleman::~middleman() {
void middleman::start() {
if (!get_or(config(), "caf.middleman.manual-multiplexing", false)) {
mpx_thread_ = sys_.launch_thread("caf.net.mpx", [this] {
mpx_.set_thread_id();
mpx_.run();
mpx_->set_thread_id();
launch_background_tasks(sys_);
mpx_->run();
});
} else {
mpx_.set_thread_id();
mpx_->set_thread_id();
}
}
void middleman::stop() {
mpx_.shutdown();
mpx_->shutdown();
if (mpx_thread_.joinable())
mpx_thread_.join();
else
mpx_.run();
mpx_->run();
}
void middleman::init(actor_system_config& cfg) {
if (auto err = mpx_.init()) {
CAF_LOG_ERROR("mpx_.init() failed: " << err);
CAF_RAISE_ERROR("mpx_.init() failed");
if (auto err = mpx_->init()) {
CAF_LOG_ERROR("mpx_->init() failed: " << err);
CAF_RAISE_ERROR("mpx_->init() failed");
}
if (auto node_uri = get_if<uri>(&cfg, "caf.middleman.this-node")) {
auto this_node = make_node_id(std::move(*node_uri));
......@@ -85,6 +124,9 @@ void middleman::add_module_options(actor_system_config& cfg) {
"max. time between messages before declaring a node dead "
"(disabled if 0, ignored if heartbeats are disabled)")
.add<std::string>("network-backend", "legacy option");
config_option_adder{cfg.custom_options(), "caf.middleman.prometheus-http"}
.add<uint16_t>("port", "listening port for incoming scrapes")
.add<std::string>("address", "bind address for the HTTP server socket");
}
} // namespace caf::net
......@@ -57,13 +57,8 @@ const short output_mask = POLLOUT;
// | (is_writing(mask) ? output_mask : 0));
// }
operation to_operation(const socket_manager_ptr& mgr,
std::optional<short> mask) {
operation to_operation(const socket_manager_ptr&, std::optional<short> mask) {
operation res = operation::none;
if (mgr->read_closed())
res = block_reads(res);
if (mgr->write_closed())
res = block_writes(res);
if (mask) {
if ((*mask & input_mask) != 0)
res = add_read_flag(res);
......@@ -115,9 +110,8 @@ error multiplexer::init() {
if (!pipe_handles)
return std::move(pipe_handles.error());
auto updater = pollset_updater::make(pipe_handles->first);
auto mgr = socket_manager::make(this, pipe_handles->first,
std::move(updater));
if (auto err = mgr->init(settings{}))
auto mgr = socket_manager::make(this, std::move(updater));
if (auto err = mgr->start(settings{}))
return err;
write_handle_ = pipe_handles->second;
pollset_.emplace_back(pollfd{pipe_handles->first.id, input_mask, 0});
......@@ -131,17 +125,18 @@ size_t multiplexer::num_socket_managers() const noexcept {
return managers_.size();
}
ptrdiff_t multiplexer::index_of(const socket_manager_ptr& mgr) {
ptrdiff_t multiplexer::index_of(const socket_manager_ptr& mgr) const noexcept {
auto first = managers_.begin();
auto last = managers_.end();
auto i = std::find(first, last, mgr);
return i == last ? -1 : std::distance(first, i);
}
ptrdiff_t multiplexer::index_of(socket fd) {
ptrdiff_t multiplexer::index_of(socket fd) const noexcept {
auto first = pollset_.begin();
auto last = pollset_.end();
auto i = std::find_if(first, last, [fd](pollfd& x) { return x.fd == fd.id; });
auto i = std::find_if(first, last,
[fd](const pollfd& x) { return x.fd == fd.id; });
return i == last ? -1 : std::distance(first, i);
}
......@@ -164,91 +159,80 @@ operation multiplexer::mask_of(const socket_manager_ptr& mgr) {
return to_operation(mgr, std::nullopt);
}
// -- thread-safe signaling ----------------------------------------------------
// -- implementation of execution_context --------------------------------------
void multiplexer::register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_register_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::register_reading, mgr.get());
}
void multiplexer::ref_execution_context() const noexcept {
ref();
}
void multiplexer::register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
if (std::this_thread::get_id() == tid_) {
do_register_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::register_writing, mgr.get());
}
void multiplexer::deref_execution_context() const noexcept {
deref();
}
void multiplexer::continue_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
write_to_pipe(pollset_updater::code::continue_reading, mgr.get());
void multiplexer::schedule(action what) {
CAF_LOG_TRACE("");
auto ptr = std::move(what).as_intrusive_ptr().release();
write_to_pipe(pollset_updater::code::run_action, ptr);
}
void multiplexer::watch(disposable what) {
watched_.emplace_back(what);
}
void multiplexer::continue_writing(const socket_manager_ptr& mgr) {
// -- thread-safe signaling ----------------------------------------------------
void multiplexer::start(socket_manager_ptr mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
if (std::this_thread::get_id() == tid_) {
do_continue_writing(mgr);
do_start(mgr);
} else {
write_to_pipe(pollset_updater::code::continue_writing, mgr.get());
write_to_pipe(pollset_updater::code::start_manager, mgr.release());
}
}
void multiplexer::discard(const socket_manager_ptr& mgr) {
void multiplexer::shutdown() {
CAF_LOG_TRACE("");
// Note: there is no 'shortcut' when calling the function in the multiplexer's
// thread, because do_shutdown calls apply_updates. This must only be called
// from the pollset_updater.
CAF_LOG_DEBUG("push shutdown event to pipe");
write_to_pipe(pollset_updater::code::shutdown,
static_cast<socket_manager*>(nullptr));
}
// -- callbacks for socket managers --------------------------------------------
void multiplexer::register_reading(socket_manager* mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_discard(mgr);
} else {
write_to_pipe(pollset_updater::code::discard_manager, mgr.get());
}
update_for(mgr).events |= input_mask;
}
void multiplexer::shutdown_reading(const socket_manager_ptr& mgr) {
void multiplexer::register_writing(socket_manager* mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_shutdown_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::shutdown_reading, mgr.get());
}
update_for(mgr).events |= output_mask;
}
void multiplexer::shutdown_writing(const socket_manager_ptr& mgr) {
void multiplexer::deregister_reading(socket_manager* mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_shutdown_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::shutdown_writing, mgr.get());
}
update_for(mgr).events &= ~input_mask;
}
void multiplexer::schedule(const action& what) {
CAF_LOG_TRACE("");
write_to_pipe(pollset_updater::code::run_action, what.ptr());
void multiplexer::deregister_writing(socket_manager* mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
update_for(mgr).events &= ~output_mask;
}
void multiplexer::init(const socket_manager_ptr& mgr) {
void multiplexer::deregister(socket_manager* mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_init(mgr);
} else {
write_to_pipe(pollset_updater::code::init_manager, mgr.get());
}
update_for(mgr).events = 0;
}
void multiplexer::shutdown() {
CAF_LOG_TRACE("");
// Note: there is no 'shortcut' when calling the function in the multiplexer's
// thread, because do_shutdown calls apply_updates. This must only be called
// from the pollset_updater.
CAF_LOG_DEBUG("push shutdown event to pipe");
write_to_pipe(pollset_updater::code::shutdown,
static_cast<socket_manager*>(nullptr));
bool multiplexer::is_reading(const socket_manager* mgr) const noexcept {
return (active_mask_of(mgr) & input_mask) != 0;
}
bool multiplexer::is_writing(const socket_manager* mgr) const noexcept {
return (active_mask_of(mgr) & output_mask) != 0;
}
// -- control flow -------------------------------------------------------------
......@@ -271,7 +255,6 @@ bool multiplexer::poll_once(bool blocking) {
CAF_LOG_DEBUG("poll() on" << pollset_.size() << "sockets reported"
<< presult << "event(s)");
// Scan pollset for events.
CAF_LOG_DEBUG("scan pollset for socket events");
if (auto revents = pollset_[0].revents; revents != 0) {
// Index 0 is always the pollset updater. This is the only handler that
// is allowed to modify pollset_ and managers_. Since this may very well
......@@ -280,6 +263,7 @@ bool multiplexer::poll_once(bool blocking) {
handle(mgr, pollset_[0].events, revents);
--presult;
}
apply_updates();
for (size_t i = 1; i < pollset_.size() && presult > 0; ++i) {
if (auto revents = pollset_[i].revents; revents != 0) {
handle(managers_[i], pollset_[i].events, revents);
......@@ -352,12 +336,16 @@ void multiplexer::set_thread_id() {
void multiplexer::run() {
CAF_LOG_TRACE("");
CAF_LOG_DEBUG("run multiplexer" << CAF_ARG(input_mask) << CAF_ARG(error_mask)
<< CAF_ARG(output_mask));
// On systems like Linux, we cannot disable sigpipe on the socket alone. We
// need to block the signal at thread level since some APIs (such as OpenSSL)
// are unsafe to call otherwise.
block_sigpipe();
while (!shutting_down_ || pollset_.size() > 1)
while (!shutting_down_ || pollset_.size() > 1 || !watched_.empty()) {
poll_once(true);
disposable::erase_disposed(watched_);
}
// Close the pipe to block any future event.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket) {
......@@ -374,45 +362,19 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
<< CAF_ARG(events) << CAF_ARG(revents));
CAF_ASSERT(mgr != nullptr);
bool checkerror = true;
CAF_LOG_DEBUG("handle event on socket" << mgr->handle().id << CAF_ARG(events)
<< CAF_ARG(revents));
// Note: we double-check whether the manager is actually reading because a
// previous action from the pipe may have called shutdown_reading.
if ((events & revents & input_mask) != 0) {
// previous action from the pipe may have disabled reading.
if ((revents & input_mask) != 0 && is_reading(mgr.get())) {
checkerror = false;
switch (mgr->handle_read_event()) {
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::abort:
case socket_manager::read_result::stop:
update_for(mgr).events &= ~input_mask;
break;
case socket_manager::read_result::want_write:
update_for(mgr).events = output_mask;
break;
case socket_manager::read_result::handover: {
do_handover(mgr);
return;
}
}
mgr->handle_read_event();
}
// Similar reasoning than before: double-check whether this event should still
// get dispatched.
if ((events & revents & output_mask) != 0) {
if ((revents & output_mask) != 0 && is_writing(mgr.get())) {
checkerror = false;
switch (mgr->handle_write_event()) {
default: // socket_manager::write_result::again
break;
case socket_manager::write_result::abort:
case socket_manager::write_result::stop:
update_for(mgr).events &= ~output_mask;
break;
case socket_manager::write_result::want_read:
update_for(mgr).events = input_mask;
break;
case socket_manager::write_result::handover:
do_handover(mgr);
return;
}
mgr->handle_write_event();
}
if (checkerror && ((revents & error_mask) != 0)) {
if (revents & POLLNVAL)
......@@ -421,38 +383,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
mgr->handle_error(sec::socket_disconnected);
else
mgr->handle_error(sec::socket_operation_failed);
update_for(mgr).events = 0;
}
}
void multiplexer::do_handover(const socket_manager_ptr& mgr) {
// Make sure to override the manager pointer in the update. Updates are
// associated to sockets, so the new manager is likely to modify this update
// again. Hence, it *must not* point to the old manager.
auto& update = update_for(mgr);
update.events = 0;
if (mgr->do_handover()) {
// If the new manager registered itself for reading, make sure it processes
// whatever data is available in buffers outside of the socket that may not
// trigger read events.
if ((update.events & input_mask)) {
switch (mgr->handle_buffered_data()) {
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::abort:
case socket_manager::read_result::stop:
update.events &= ~input_mask;
break;
case socket_manager::read_result::want_write:
update.events = output_mask;
break;
case socket_manager::read_result::handover: {
// Down the rabbit hole we go!
do_handover(mgr);
}
}
}
update_for(mgr.get()).events = 0;
}
}
......@@ -467,14 +398,13 @@ multiplexer::poll_update& multiplexer::update_for(ptrdiff_t index) {
}
}
multiplexer::poll_update&
multiplexer::update_for(const socket_manager_ptr& mgr) {
multiplexer::poll_update& multiplexer::update_for(socket_manager* mgr) {
auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second;
} else if (auto index = index_of(fd); index != -1) {
updates_.container().emplace_back(fd,
poll_update{pollset_[index].events, mgr});
updates_.container().emplace_back(fd, poll_update{pollset_[index].events,
socket_manager_ptr{mgr}});
return updates_.container().back().second;
} else {
updates_.container().emplace_back(fd, poll_update{0, mgr});
......@@ -485,9 +415,7 @@ multiplexer::update_for(const socket_manager_ptr& mgr) {
template <class T>
void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
pollset_updater::msg_buf buf;
if (ptr) {
intrusive_ptr_add_ref(ptr);
}
// Note: no intrusive_ptr_add_ref(ptr) since we take ownership of `ptr`.
buf[0] = static_cast<std::byte>(opcode);
auto value = reinterpret_cast<intptr_t>(ptr);
memcpy(buf.data() + 1, &value, sizeof(intptr_t));
......@@ -501,7 +429,7 @@ void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
intrusive_ptr_release(ptr);
}
short multiplexer::active_mask_of(const socket_manager_ptr& mgr) {
short multiplexer::active_mask_of(const socket_manager* mgr) const noexcept {
auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second.events;
......@@ -512,14 +440,6 @@ short multiplexer::active_mask_of(const socket_manager_ptr& mgr) {
}
}
bool multiplexer::is_reading(const socket_manager_ptr& mgr) {
return (active_mask_of(mgr) & input_mask) != 0;
}
bool multiplexer::is_writing(const socket_manager_ptr& mgr) {
return (active_mask_of(mgr) & output_mask) != 0;
}
// -- internal callbacks the pollset updater -----------------------------------
void multiplexer::do_shutdown() {
......@@ -529,96 +449,25 @@ void multiplexer::do_shutdown() {
shutting_down_ = true;
apply_updates();
// Skip the first manager (the pollset updater).
for (size_t i = 1; i < managers_.size(); ++i) {
auto& mgr = managers_[i];
mgr->close_read();
update_for(static_cast<ptrdiff_t>(i)).events &= ~input_mask;
}
for (size_t i = 1; i < managers_.size(); ++i)
managers_[i]->dispose();
apply_updates();
}
void multiplexer::do_register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, no new reads are allowed.
if (shutting_down_)
mgr->close_read();
else if (!mgr->read_closed())
update_for(mgr).events |= input_mask;
}
void multiplexer::do_register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, we do allow managers to write whatever is currently
// pending but we make sure that all read channels are closed.
if (shutting_down_)
mgr->close_read();
if (!mgr->write_closed())
update_for(mgr).events |= output_mask;
}
void multiplexer::do_continue_reading(const socket_manager_ptr& mgr) {
if (!is_reading(mgr)) {
switch (mgr->handle_continue_reading()) {
default: // socket_manager::read_result::(stop | abort)
update_for(mgr).events &= ~input_mask;
break;
case socket_manager::read_result::again:
update_for(mgr).events |= input_mask;
break;
case socket_manager::read_result::want_write:
update_for(mgr).events = output_mask;
break;
case socket_manager::read_result::handover: {
do_handover(mgr);
}
}
}
}
void multiplexer::do_continue_writing(const socket_manager_ptr& mgr) {
if (!is_writing(mgr)) {
update_for(mgr).events |= output_mask;
}
}
void multiplexer::do_discard(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
mgr->handle_error(sec::disposed);
update_for(mgr).events = 0;
}
void multiplexer::do_shutdown_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->read_closed()) {
mgr->close_read();
update_for(mgr).events &= ~input_mask;
}
}
void multiplexer::do_shutdown_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->write_closed()) {
mgr->close_write();
update_for(mgr).events &= ~output_mask;
}
}
void multiplexer::do_init(const socket_manager_ptr& mgr) {
void multiplexer::do_start(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_) {
error err;
if (owner_)
err = mgr->init(content(system().config()));
err = mgr->start(content(system().config()));
else
err = mgr->init(settings{});
err = mgr->start(settings{});
if (err) {
CAF_LOG_DEBUG("mgr->init failed: " << err);
// The socket manager should not register itself for any events if
// initialization fails. Purge any state just in case.
update_for(mgr).events = 0;
update_for(mgr.get()).events = 0;
}
// Else: no update since the manager is supposed to call continue_reading
// and continue_writing as necessary.
}
}
......
......@@ -29,13 +29,18 @@ std::unique_ptr<pollset_updater> pollset_updater::make(pipe_socket fd) {
// -- interface functions ------------------------------------------------------
error pollset_updater::init(socket_manager* owner, const settings&) {
error pollset_updater::start(socket_manager* owner, const settings&) {
CAF_LOG_TRACE("");
owner_ = owner;
mpx_ = owner->mpx_ptr();
return nonblocking(fd_, true);
}
pollset_updater::read_result pollset_updater::handle_read_event() {
socket pollset_updater::handle() const {
return fd_;
}
void pollset_updater::handle_read_event() {
CAF_LOG_TRACE("");
auto as_mgr = [](intptr_t ptr) {
return intrusive_ptr{reinterpret_cast<socket_manager*>(ptr), false};
......@@ -56,29 +61,8 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
intptr_t ptr;
memcpy(&ptr, buf_.data() + 1, sizeof(intptr_t));
switch (static_cast<code>(opcode)) {
case code::register_reading:
mpx_->do_register_reading(as_mgr(ptr));
break;
case code::continue_reading:
mpx_->do_continue_reading(as_mgr(ptr));
break;
case code::register_writing:
mpx_->do_register_writing(as_mgr(ptr));
break;
case code::continue_writing:
mpx_->do_continue_writing(as_mgr(ptr));
break;
case code::init_manager:
mpx_->do_init(as_mgr(ptr));
break;
case code::discard_manager:
mpx_->do_discard(as_mgr(ptr));
break;
case code::shutdown_reading:
mpx_->do_shutdown_reading(as_mgr(ptr));
break;
case code::shutdown_writing:
mpx_->do_shutdown_writing(as_mgr(ptr));
case code::start_manager:
mpx_->do_start(as_mgr(ptr));
break;
case code::run_action:
run_action(ptr);
......@@ -94,25 +78,20 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
}
} else if (num_bytes == 0) {
CAF_LOG_DEBUG("pipe closed, assume shutdown");
return read_result::stop;
owner_->deregister();
return;
} else if (last_socket_error_is_temporary()) {
return read_result::again;
return;
} else {
return read_result::stop;
CAF_LOG_ERROR("pollset updater failed to read from the pipe");
owner_->deregister();
return;
}
}
}
pollset_updater::read_result pollset_updater::handle_buffered_data() {
return read_result::again;
}
pollset_updater::read_result pollset_updater::handle_continue_reading() {
return read_result::again;
}
pollset_updater::write_result pollset_updater::handle_write_event() {
return write_result::stop;
void pollset_updater::handle_write_event() {
owner_->deregister_writing();
}
void pollset_updater::abort(const error&) {
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/prometheus/server.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/lower_layer.hpp"
using namespace std::literals;
namespace caf::net::prometheus {
// -- server::scrape_state -----------------------------------------------------
std::string_view server::scrape_state::scrape() {
// Scrape system metrics at most once per second. TODO: make configurable.
if (auto now = std::chrono::steady_clock::now(); last_scrape + 1s <= now) {
last_scrape = now;
proc_importer.update();
}
return collector.collect_from(*registry);
}
// -- implementation of http::upper_layer --------------------------------------
void server::prepare_send() {
// nop
}
bool server::done_sending() {
return true;
}
void server::abort(const error&) {
// nop
}
error server::start(http::lower_layer* down, const settings&) {
down_ = down;
down_->request_messages();
return caf::none;
}
ptrdiff_t server::consume(const http::header& hdr, const_byte_span payload) {
if (hdr.path() != "/metrics") {
down_->send_response(http::status::not_found, "text/plain", "Not found.");
down_->shutdown();
} else if (hdr.method() != http::method::get) {
down_->send_response(http::status::method_not_allowed, "text/plain",
"Method not allowed.");
down_->shutdown();
} else if (!hdr.query().empty() || !hdr.fragment().empty()) {
down_->send_response(http::status::bad_request, "text/plain",
"No fragment or query allowed.");
down_->shutdown();
} else {
auto str = state_->scrape();
down_->send_response(http::status::ok, "text/plain;version=0.0.4", str);
down_->shutdown();
}
return static_cast<ptrdiff_t>(payload.size());
}
} // namespace caf::net::prometheus
......@@ -14,4 +14,8 @@ bool socket_event_layer::do_handover(std::unique_ptr<socket_event_layer>&) {
return false;
}
bool socket_event_layer::finalized() const noexcept {
return true;
}
} // namespace caf::net
......@@ -13,24 +13,36 @@ namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
socket_manager::socket_manager(multiplexer* mpx, socket fd,
event_handler_ptr handler)
: fd_(fd), mpx_(mpx), handler_(std::move(handler)) {
socket_manager::socket_manager(multiplexer* mpx, event_handler_ptr handler)
: fd_(handler->handle()),
mpx_(mpx),
handler_(std::move(handler)),
disposed_(false) {
CAF_ASSERT(fd_ != invalid_socket);
CAF_ASSERT(mpx_ != nullptr);
CAF_ASSERT(handler_ != nullptr);
memset(&flags_, 0, sizeof(flags_t));
}
socket_manager::~socket_manager() {
close(fd_);
// Note: may not call cleanup since it calls the multiplexer via deregister().
handler_.reset();
if (fd_) {
close(fd_);
fd_ = invalid_socket;
}
if (!cleanup_listeners_.empty()) {
for (auto& f : cleanup_listeners_)
mpx_->schedule(std::move(f));
cleanup_listeners_.clear();
}
}
// -- factories ----------------------------------------------------------------
socket_manager_ptr socket_manager::make(multiplexer* mpx, socket handle,
socket_manager_ptr socket_manager::make(multiplexer* mpx,
event_handler_ptr handler) {
return make_counted<socket_manager>(mpx, handle, std::move(handler));
CAF_ASSERT(mpx != nullptr);
return make_counted<socket_manager>(std::move(mpx), std::move(handler));
}
// -- properties ---------------------------------------------------------------
......@@ -40,88 +52,160 @@ actor_system& socket_manager::system() noexcept {
return mpx_->system();
}
bool socket_manager::is_reading() const noexcept {
return mpx_->is_reading(this);
}
bool socket_manager::is_writing() const noexcept {
return mpx_->is_writing(this);
}
// -- event loop management ----------------------------------------------------
void socket_manager::register_reading() {
mpx_->register_reading(this);
}
void socket_manager::continue_reading() {
mpx_->continue_reading(this);
}
void socket_manager::register_writing() {
mpx_->register_writing(this);
}
void socket_manager::continue_writing() {
mpx_->continue_writing(this);
void socket_manager::deregister_reading() {
mpx_->deregister_reading(this);
}
// -- callbacks for the multiplexer --------------------------------------------
void socket_manager::deregister_writing() {
mpx_->deregister_writing(this);
}
void socket_manager::close_read() noexcept {
// TODO: extend transport API for closing read operations.
flags_.read_closed = true;
void socket_manager::deregister() {
mpx_->deregister(this);
}
void socket_manager::close_write() noexcept {
// TODO: extend transport API for closing write operations.
flags_.write_closed = true;
// -- callbacks for the handler ------------------------------------------------
void socket_manager::schedule_handover() {
deregister();
mpx_->schedule_fn([ptr = strong_this()] {
event_handler_ptr next;
if (ptr->handler_->do_handover(next)) {
ptr->handler_.swap(next);
}
});
}
bool socket_manager::do_handover() {
event_handler_ptr next;
if (handler_->do_handover(next)) {
handler_.swap(next);
return true;
void socket_manager::schedule(action what) {
// Wrap the action to make sure the socket manager is still alive when running
// the action later.
mpx_->schedule_fn([ptr = strong_this(), f = std::move(what)]() mutable {
CAF_IGNORE_UNUSED(ptr);
f.run();
});
}
void socket_manager::shutdown() {
if (!shutting_down_) {
shutting_down_ = true;
dispose();
} else {
return false;
// This usually only happens after disposing the manager if the handler
// still had data to send.
mpx_->schedule_fn([ptr = strong_this()] { //
ptr->cleanup();
});
}
}
error socket_manager::init(const settings& cfg) {
// -- callbacks for the multiplexer --------------------------------------------
error socket_manager::start(const settings& cfg) {
CAF_LOG_TRACE(CAF_ARG(cfg));
if (auto err = nonblocking(fd_, true)) {
CAF_LOG_ERROR("failed to set nonblocking flag in socket:" << err);
cleanup();
return err;
} else if (err = handler_->start(this, cfg); err) {
CAF_LOG_DEBUG("failed to initialize handler:" << err);
cleanup();
return err;
} else {
return none;
}
return handler_->init(this, cfg);
}
socket_manager::read_result socket_manager::handle_read_event() {
auto result = handler_->handle_read_event();
switch (result) {
default:
break;
case read_result::close:
flags_.read_closed = true;
break;
case read_result::abort:
flags_.read_closed = true;
flags_.write_closed = true;
break;
}
void socket_manager::handle_read_event() {
if (handler_)
handler_->handle_read_event();
else
deregister();
}
void socket_manager::handle_write_event() {
if (handler_)
handler_->handle_write_event();
else
deregister();
}
void socket_manager::handle_error(sec code) {
if (!disposed_)
disposed_ = true;
if (handler_) {
if (!shutting_down_) {
handler_->abort(make_error(code));
shutting_down_ = true;
}
if (code == sec::disposed && !handler_->finalized()) {
// When disposing the manger, the transport is still allowed to send any
// pending data and it will call shutdown() later to trigger cleanup().
deregister_reading();
} else {
cleanup();
}
}
return result;
}
socket_manager::read_result socket_manager::handle_buffered_data() {
return handler_->handle_buffered_data();
// -- implementation of disposable_impl ----------------------------------------
void socket_manager::dispose() {
bool expected = false;
if (disposed_.compare_exchange_strong(expected, true)) {
mpx_->schedule_fn([ptr = strong_this()] { //
ptr->handle_error(sec::disposed);
});
}
}
socket_manager::read_result socket_manager::handle_continue_reading() {
return handler_->handle_continue_reading();
bool socket_manager::disposed() const noexcept {
return disposed_.load();
}
socket_manager::write_result socket_manager::handle_write_event() {
return handler_->handle_write_event();
void socket_manager::ref_disposable() const noexcept {
ref();
}
void socket_manager::handle_error(sec code) {
if (handler_) {
handler_->abort(make_error(code));
handler_ = nullptr;
void socket_manager::deref_disposable() const noexcept {
deref();
}
// -- utility functions --------------------------------------------------------
void socket_manager::cleanup() {
deregister();
handler_.reset();
if (fd_) {
close(fd_);
fd_ = invalid_socket;
}
if (!cleanup_listeners_.empty()) {
for (auto& f : cleanup_listeners_)
mpx_->schedule(std::move(f));
cleanup_listeners_.clear();
}
}
socket_manager_ptr socket_manager::strong_this() {
return socket_manager_ptr{this};
}
} // namespace caf::net
......@@ -132,7 +132,7 @@ size_t connection::buffered() const noexcept {
}
stream_socket connection::fd() const noexcept {
if (auto id = SSL_get_fd(native(pimpl_)))
if (auto id = SSL_get_fd(native(pimpl_)); id != -1)
return stream_socket{static_cast<socket_id>(id)};
else
return stream_socket{invalid_socket_id};
......
......@@ -183,6 +183,21 @@ bool context::has_last_error() noexcept {
// -- connections --------------------------------------------------------------
expected<connection> context::new_connection(stream_socket fd) {
if (auto ptr = SSL_new(native(pimpl_))) {
auto conn = connection::from_native(ptr);
if (auto bio_ptr = BIO_new_socket(fd.id, BIO_NOCLOSE)) {
SSL_set_bio(ptr, bio_ptr, bio_ptr);
return {std::move(conn)};
} else {
return {make_error(sec::logic_error, "BIO_new_socket failed")};
}
} else {
return {make_error(sec::logic_error, "SSL_new returned null")};
}
}
expected<connection> context::new_connection(stream_socket fd,
close_on_shutdown_t) {
if (auto ptr = SSL_new(native(pimpl_))) {
auto conn = connection::from_native(ptr);
if (SSL_set_fd(ptr, fd.id) == 1)
......
......@@ -26,10 +26,6 @@ public:
using super = socket_event_layer;
using read_result = typename super::read_result;
using write_result = typename super::write_result;
using upper_layer_ptr = transport::upper_layer_ptr;
handshake_worker(connection conn, bool is_server, upper_layer_ptr up)
......@@ -39,69 +35,74 @@ public:
// -- interface functions ----------------------------------------------------
error init(socket_manager* owner, const settings& cfg) override {
error start(socket_manager* owner, const settings& cfg) override {
owner_ = owner;
cfg_ = cfg;
owner->register_writing();
return caf::none;
}
read_result handle_read_event() override {
socket handle() const override {
return policy_.conn.fd();
}
void handle_read_event() override {
if (auto res = advance_handshake(); res > 0) {
return read_result::handover;
owner_->deregister();
owner_->schedule_handover();
} else if (res == 0) {
up_->abort(make_error(sec::connection_closed));
return read_result::stop;
owner_->deregister();
} else {
auto err = policy_.last_error(policy_.conn.fd(), res);
switch (err) {
case stream_transport_error::want_read:
case stream_transport_error::temporary:
return read_result::again;
break;
case stream_transport_error::want_write:
return read_result::want_write;
default:
owner_->deregister_reading();
owner_->register_writing();
break;
default: {
auto err = make_error(sec::cannot_connect_to_node,
policy_.conn.last_error_string(res));
up_->abort(std::move(err));
return read_result::stop;
owner_->deregister();
}
}
}
}
read_result handle_buffered_data() override {
return read_result::again;
}
read_result handle_continue_reading() override {
return read_result::again;
}
write_result handle_write_event() override {
void handle_write_event() override {
if (auto res = advance_handshake(); res > 0) {
return write_result::handover;
owner_->deregister();
owner_->schedule_handover();
return;
} else if (res == 0) {
up_->abort(make_error(sec::connection_closed));
return write_result::stop;
owner_->deregister();
} else {
switch (policy_.last_error(policy_.conn.fd(), res)) {
case stream_transport_error::want_write:
case stream_transport_error::temporary:
return write_result::again;
break;
case stream_transport_error::want_read:
return write_result::want_read;
default:
owner_->deregister_writing();
owner_->register_reading();
break;
default: {
auto err = make_error(sec::cannot_connect_to_node,
policy_.conn.last_error_string(res));
up_->abort(std::move(err));
return write_result::stop;
owner_->deregister();
}
}
}
}
bool do_handover(std::unique_ptr<socket_event_layer>& next) override {
next = transport::make(std::move(policy_.conn), std::move(up_));
if (auto err = next->init(owner_, cfg_))
if (auto err = next->start(owner_, cfg_))
return false;
else
return true;
......
......@@ -49,14 +49,14 @@ size_t stream_transport::default_policy::buffered() const noexcept {
// -- constructors, destructors, and assignment operators ----------------------
stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up)
: fd_(fd), up_(std::move(up)) {
policy_ = &default_policy_;
: fd_(fd), up_(std::move(up)), policy_(&default_policy_) {
memset(&flags_, 0, sizeof(flags_t));
}
stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up,
policy* policy)
: fd_(fd), up_(std::move(up)), policy_(policy) {
// nop
memset(&flags_, 0, sizeof(flags_t));
}
// -- factories ----------------------------------------------------------------
......@@ -73,11 +73,26 @@ bool stream_transport::can_send_more() const noexcept {
}
void stream_transport::configure_read(receive_policy rd) {
if (rd.max_size > 0 && max_read_size_ == 0) {
parent_->register_reading();
}
auto restarting = rd.max_size > 0 && max_read_size_ == 0;
min_read_size_ = rd.min_size;
max_read_size_ = rd.max_size;
if (restarting && !parent_->is_reading()) {
if (buffered_ > 0 && buffered_ >= min_read_size_
&& delta_offset_ < buffered_) {
// We can already make progress with the data we have. Hence, we need
// schedule a call to read from our buffer before we can wait for
// additional data from the peer.
parent_->schedule_fn([this] {
parent_->register_reading();
handle_buffered_data();
});
} else {
// Simply ask for more data.
parent_->register_reading();
}
} else if (max_read_size_ == 0) {
parent_->deregister_reading();
}
}
void stream_transport::begin_output() {
......@@ -93,17 +108,27 @@ bool stream_transport::end_output() {
return true;
}
void stream_transport::suspend_reading() {
configure_read(receive_policy::stop());
bool stream_transport::is_reading() const noexcept {
return max_read_size_ > 0;
}
bool stream_transport::stopped_reading() const noexcept {
return max_read_size_ == 0;
void stream_transport::write_later() {
parent_->register_writing();
}
void stream_transport::shutdown() {
if (write_buf_.empty()) {
parent_->shutdown();
} else {
configure_read(receive_policy::stop());
parent_->deregister_reading();
flags_.shutting_down = true;
}
}
// -- implementation of transport ----------------------------------------------
error stream_transport::init(socket_manager* owner, const settings& config) {
error stream_transport::start(socket_manager* owner, const settings& config) {
parent_ = owner;
namespace mm = defaults::middleman;
auto default_max_reads = static_cast<uint32_t>(mm::max_consecutive_reads);
......@@ -121,35 +146,35 @@ error stream_transport::init(socket_manager* owner, const settings& config) {
CAF_LOG_ERROR("send_buffer_size: " << socket_buf_size.error());
return std::move(socket_buf_size.error());
}
return up_->init(owner, this, config);
return up_->start(this, config);
}
stream_transport::read_result stream_transport::handle_read_event() {
socket stream_transport::handle() const {
return fd_;
}
void stream_transport::handle_read_event() {
CAF_LOG_TRACE(CAF_ARG2("socket", fd_.id));
// Convenience lambda for failing the application.
auto fail = [this](error reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
up_->abort(reason);
return read_result::abort;
};
// Resume a write operation if the transport waited for the socket to be
// readable from the last call to handle_write_event.
if (flags_.wanted_read_from_write_event) {
flags_.wanted_read_from_write_event = false;
switch (handle_write_event()) {
case write_result::want_read:
CAF_ASSERT(flags_.wanted_read_from_write_event);
return read_result::again;
case write_result::handover:
return read_result::handover;
case write_result::again:
parent_->register_writing();
break;
default:
break;
// The subsequent call to handle_write_event expects a writing manager.
parent_->register_writing();
handle_write_event();
if (!parent_->is_reading()) {
// The call to handle_write_event deregisters the manager from reading in
// case of an error. So we need to double-check that flag here.
return;
}
// Check if we have actually some reading to do.
if (max_read_size_ == 0) {
if (!flags_.wanted_read_from_write_event)
parent_->deregister_reading();
return;
}
}
// Make sure the buffer is large enough.
// Make sure our read buffer is large enough.
if (read_buf_.size() < max_read_size_)
read_buf_.resize(max_read_size_);
// Fill up our buffer.
......@@ -160,19 +185,23 @@ stream_transport::read_result stream_transport::handle_read_event() {
switch (policy_->last_error(fd_, rd)) {
case stream_transport_error::temporary:
case stream_transport_error::want_read:
return read_result::again;
// Try again later.
return;
case stream_transport_error::want_write:
// Wait for writable socket and then call handle_read_event again.
flags_.wanted_write_from_read_event = true;
return read_result::want_write;
parent_->register_writing();
parent_->deregister_reading();
return;
default:
return fail(sec::socket_operation_failed);
return fail(make_error(sec::socket_operation_failed));
}
} else if (rd == 0) {
return fail(sec::socket_disconnected);
return fail(make_error(sec::socket_disconnected));
}
// Make sure we actually have all data currently available to us and the
// policy is not holding on to some bytes. This may happen when using
// OpenSSL or any other transport policy operating on blocks.
// OpenSSL or any other transport policy that operates on blocks.
buffered_ += static_cast<size_t>(rd);
if (auto policy_buffered = policy_->buffered(); policy_buffered > 0) {
if (auto n = buffered_ + policy_buffered; n > read_buf_.size())
......@@ -181,27 +210,20 @@ stream_transport::read_result stream_transport::handle_read_event() {
policy_buffered));
if (rd2 != static_cast<ptrdiff_t>(policy_buffered)) {
CAF_LOG_ERROR("failed to read buffered data from the policy");
return fail(sec::socket_operation_failed);
return fail(make_error(sec::socket_operation_failed));
}
buffered_ += static_cast<size_t>(rd2);
}
// Try to make progress on the application and return control to the
// multiplexer to allow other sockets to run.
return handle_buffered_data();
// Read buffered data and then allow other sockets to run.
handle_buffered_data();
}
stream_transport::read_result stream_transport::handle_buffered_data() {
void stream_transport::handle_buffered_data() {
CAF_LOG_TRACE(CAF_ARG(buffered_));
// Convenience lambda for failing the application.
auto fail = [this](error reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
up_->abort(reason);
return read_result::abort;
};
// Loop until we have drained the buffer as much as we can.
CAF_ASSERT(min_read_size_ <= max_read_size_);
while (max_read_size_ > 0 && buffered_ >= min_read_size_) {
auto old_max_read_size = max_read_size_;
while (parent_->is_reading() && max_read_size_ > 0
&& buffered_ >= min_read_size_) {
auto n = std::min(buffered_, size_t{max_read_size_});
auto bytes = make_span(read_buf_.data(), n);
auto delta = bytes.subspan(delta_offset_);
......@@ -210,34 +232,31 @@ stream_transport::read_result stream_transport::handle_buffered_data() {
// Negative values indicate that the application encountered an
// unrecoverable error.
up_->abort(make_error(caf::sec::runtime_error, "consumed < 0"));
return read_result::abort;
} else if (consumed == 0) {
// Returning 0 means that the application wants more data. Note:
// max_read_size_ may have changed if the application realized it
// requires more data to parse the input. It may of course only increase
// the max_read_size_ in this case, everything else makes no sense.
delta_offset_ = static_cast<ptrdiff_t>(n);
if (n == max_read_size_ || max_read_size_ < old_max_read_size) {
CAF_LOG_ERROR("application failed to make progress");
return fail(sec::runtime_error);
} else if (n == buffered_) {
// Either the application has increased max_read_size_ or we
// did not reach max_read_size_ the first time. In both cases, we
// cannot proceed without receiving more data.
return read_result::again;
}
parent_->deregister();
return;
} else if (static_cast<size_t>(consumed) > n) {
// Must not happen. An application cannot handle more data then we pass
// to it.
up_->abort(make_error(sec::logic_error, "consumed > buffer.size"));
return read_result::abort;
parent_->deregister();
return;
} else if (consumed == 0) {
// See whether the next iteration would change what we pass to the
// application (max_read_size_ may have changed). Otherwise, we'll try
// again later.
delta_offset_ = static_cast<ptrdiff_t>(n);
if (n == std::min(buffered_, size_t{max_read_size_})) {
return;
} else {
// "Fall through".
}
} else {
// Shove the unread bytes to the beginning of the buffer and continue
// to the next loop iteration.
auto del = static_cast<size_t>(consumed);
auto prev = buffered_;
buffered_ -= del;
delta_offset_ = 0;
delta_offset_ = static_cast<ptrdiff_t>(n - del);
if (buffered_ > 0) {
auto new_begin = read_buf_.begin() + del;
auto new_end = read_buf_.begin() + prev;
......@@ -245,80 +264,80 @@ stream_transport::read_result stream_transport::handle_buffered_data() {
}
}
}
return max_read_size_ > 0 ? read_result::again : read_result::stop;
}
stream_transport::write_result stream_transport::handle_write_event() {
void stream_transport::fail(const error& reason) {
CAF_LOG_TRACE(CAF_ARG(reason));
up_->abort(reason);
up_.reset();
parent_->deregister();
parent_->shutdown();
}
void stream_transport::handle_write_event() {
CAF_LOG_TRACE(CAF_ARG2("socket", fd_.id));
auto fail = [this](sec code) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(code));
up_->abort(make_error(code));
return write_result::abort;
};
// Resume a read operation if the transport waited for the socket to be
// writable from the last call to handle_read_event.
if (flags_.wanted_write_from_read_event) {
flags_.wanted_write_from_read_event = false;
switch (handle_read_event()) {
case read_result::want_write:
CAF_ASSERT(flags_.wanted_write_from_read_event);
return write_result::again;
case read_result::handover:
return write_result::handover;
case read_result::again:
parent_->register_reading();
break;
default:
break;
// The subsequent call to handle_read_event expects a writing manager.
parent_->register_writing();
handle_read_event();
if (!parent_->is_writing()) {
// The call to handle_read_event deregisters the manager from writing in
// case of an error. So we need to double-check that flag here.
return;
}
// Fall though and see if we also have something to write.
}
// Allow the upper layer to add extra data to the write buffer.
if (!up_->prepare_send()) {
up_->abort(make_error(caf::sec::runtime_error, "prepare_send failed"));
return write_result::abort;
// When shutting down, we flush our buffer and then shut down the manager.
if (flags_.shutting_down) {
if (write_buf_.empty()) {
parent_->shutdown();
return;
}
} else if (can_send_more()) {
// Allow the upper layer to add extra data to the write buffer.
up_->prepare_send();
}
if (write_buf_.empty())
return !up_->done_sending() ? write_result::again : write_result::stop;
auto write_res = policy_->write(fd_, write_buf_);
if (write_res > 0) {
write_buf_.erase(write_buf_.begin(), write_buf_.begin() + write_res);
return !write_buf_.empty() || !up_->done_sending() ? write_result::again
: write_result::stop;
if (write_buf_.empty() && up_->done_sending()) {
if (!flags_.shutting_down) {
parent_->deregister_writing();
} else {
parent_->shutdown();
}
}
return;
} else if (write_res < 0) {
// Try again later on temporary errors such as EWOULDBLOCK and
// stop writing to the socket on hard errors.
switch (policy_->last_error(fd_, write_res)) {
case stream_transport_error::temporary:
case stream_transport_error::want_write:
return write_result::again;
return;
case stream_transport_error::want_read:
flags_.wanted_read_from_write_event = true;
return write_result::want_read;
parent_->register_reading();
parent_->deregister_writing();
return;
default:
return fail(sec::socket_operation_failed);
return fail(make_error(sec::socket_operation_failed));
}
} else {
// write() returns 0 if the connection was closed.
return fail(sec::socket_disconnected);
}
}
stream_transport::read_result stream_transport::handle_continue_reading() {
if (max_read_size_ == 0) {
up_->continue_reading();
if (max_read_size_ != 0) {
return handle_buffered_data();
} else {
return read_result::stop;
}
} else {
return handle_buffered_data();
return fail(make_error(sec::socket_disconnected));
}
}
void stream_transport::abort(const error& reason) {
up_->abort(reason);
flags_.shutting_down = true;
}
bool stream_transport::finalized() const noexcept {
return write_buf_.empty();
}
// -- free functions -----------------------------------------------------------
......
......@@ -75,6 +75,7 @@ expected<tcp_accept_socket> new_tcp_acceptor_impl(uint16_t port,
CAF_NET_SYSCALL("bind", res, !=, 0,
bind(fd, reinterpret_cast<sockaddr*>(&sa),
static_cast<socket_size_type>(sizeof(sa))));
CAF_LOG_DEBUG("bound socket" << fd << "to listen on port" << port);
return sguard.release();
}
......@@ -121,7 +122,16 @@ make_tcp_accept_socket(const uri::authority_type& node, bool reuse_addr) {
to_string(node));
}
expected<tcp_accept_socket>
make_tcp_accept_socket(uint16_t port, std::string addr, bool reuse_addr) {
uri::authority_type auth;
auth.port = port;
auth.host = std::move(addr);
return make_tcp_accept_socket(auth, reuse_addr);
}
expected<tcp_stream_socket> accept(tcp_accept_socket x) {
CAF_LOG_TRACE(CAF_ARG(x));
auto sock = ::accept(x.id, nullptr, nullptr);
if (sock == net::invalid_socket_id) {
auto err = net::last_socket_error();
......@@ -131,6 +141,7 @@ expected<tcp_stream_socket> accept(tcp_accept_socket x) {
}
return caf::make_error(sec::socket_operation_failed, "tcp accept failed");
}
CAF_LOG_DEBUG("accepted TCP socket" << sock << "on accept socket" << x.id);
return tcp_stream_socket{sock};
}
......
......@@ -38,10 +38,9 @@ std::unique_ptr<client> client::make(handshake_ptr hs, upper_layer_ptr up) {
// -- implementation of stream_oriented::upper_layer ---------------------------
error client::init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& cfg) {
error client::start(stream_oriented::lower_layer* down, const settings& cfg) {
CAF_ASSERT(hs_ != nullptr);
framing_.init(owner, down);
framing_.start(down);
if (!hs_->has_mandatory_fields())
return make_error(sec::runtime_error,
"handshake data lacks mandatory fields");
......@@ -95,13 +94,9 @@ ptrdiff_t client::consume(byte_span buffer, byte_span delta) {
}
}
void client::continue_reading() {
void client::prepare_send() {
if (handshake_completed())
upper_layer().continue_reading();
}
bool client::prepare_send() {
return handshake_completed() ? upper_layer().prepare_send() : true;
upper_layer().prepare_send();
}
bool client::done_sending() {
......@@ -115,7 +110,7 @@ bool client::handle_header(std::string_view http) {
auto http_ok = hs_->is_valid_http_1_response(http);
hs_.reset();
if (http_ok) {
if (auto err = upper_layer().init(owner_, &framing_, cfg_)) {
if (auto err = upper_layer().start(&framing_, cfg_)) {
CAF_LOG_DEBUG("failed to initialize WebSocket framing layer");
return false;
} else {
......
......@@ -4,7 +4,10 @@
#include "caf/net/web_socket/default_trait.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/web_socket/frame.hpp"
#include "caf/sec.hpp"
namespace caf::net::web_socket {
......@@ -34,4 +37,9 @@ bool default_trait::convert(std::string_view text, input_type& x) {
return true;
}
error default_trait::last_error() {
CAF_LOG_ERROR("default_trait::last_error called");
return {sec::logic_error};
}
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/web_socket/frame.hpp"
#include <cstring>
......
......@@ -10,7 +10,7 @@ namespace caf::net::web_socket {
// -- initialization ---------------------------------------------------------
void framing::init(socket_manager*, stream_oriented::lower_layer* down) {
void framing::start(stream_oriented::lower_layer* down) {
std::random_device rd;
rng_.seed(rd());
down_ = down;
......@@ -23,15 +23,39 @@ bool framing::can_send_more() const noexcept {
}
void framing::suspend_reading() {
down_->suspend_reading();
down_->configure_read(receive_policy::stop());
}
bool framing::stopped_reading() const noexcept {
return down_->stopped_reading();
bool framing::is_reading() const noexcept {
return down_->is_reading();
}
void framing::write_later() {
down_->write_later();
}
void framing::shutdown(status code, std::string_view msg) {
auto code_val = static_cast<uint16_t>(code);
uint32_t mask_key = 0;
byte_buffer payload;
payload.reserve(msg.size() + 2);
payload.push_back(static_cast<std::byte>((code_val & 0xFF00) >> 8));
payload.push_back(static_cast<std::byte>(code_val & 0x00FF));
for (auto c : msg)
payload.push_back(static_cast<std::byte>(c));
if (mask_outgoing_frames) {
mask_key = static_cast<uint32_t>(rng_());
detail::rfc6455::mask_data(mask_key, payload);
}
down_->begin_output();
detail::rfc6455::assemble_frame(detail::rfc6455::connection_close, mask_key,
payload, down_->output_buffer());
down_->end_output();
down_->shutdown();
}
void framing::request_messages() {
if (down_->stopped_reading())
if (!down_->is_reading())
down_->configure_read(receive_policy::up_to(2048));
}
......@@ -61,121 +85,96 @@ bool framing::end_text_message() {
return true;
}
void framing::send_close_message() {
ship_close();
}
void framing::send_close_message(status code, std::string_view desc) {
ship_close(static_cast<uint16_t>(code), desc);
}
// -- interface for the lower layer --------------------------------------------
ptrdiff_t framing::consume(byte_span input, byte_span) {
auto buffer = input;
ptrdiff_t consumed = 0;
// Parse all frames in the current input.
for (;;) {
// Parse header.
detail::rfc6455::header hdr;
auto hdr_bytes = detail::rfc6455::decode_header(buffer, hdr);
if (hdr_bytes < 0) {
CAF_LOG_DEBUG("decoded malformed data: hdr_bytes < 0");
ptrdiff_t framing::consume(byte_span buffer, byte_span) {
// Make sure we're overriding any 'exactly' setting.
down_->configure_read(receive_policy::up_to(2048));
// Parse header.
detail::rfc6455::header hdr;
auto hdr_bytes = detail::rfc6455::decode_header(buffer, hdr);
if (hdr_bytes < 0) {
CAF_LOG_DEBUG("decoded malformed data: hdr_bytes < 0");
up_->abort(make_error(sec::protocol_error,
"negative header size on WebSocket connection"));
return -1;
}
if (hdr_bytes == 0) {
// Wait for more input.
return 0;
}
// Make sure the entire frame (including header) fits into max_frame_size.
if (hdr.payload_len >= (max_frame_size - static_cast<size_t>(hdr_bytes))) {
CAF_LOG_DEBUG("WebSocket frame too large");
up_->abort(make_error(sec::protocol_error, "WebSocket frame too large"));
return -1;
}
// Wait for more data if necessary.
size_t frame_size = hdr_bytes + hdr.payload_len;
if (buffer.size() < frame_size) {
down_->configure_read(receive_policy::exactly(frame_size));
return 0;
}
// Decode frame.
auto payload_len = static_cast<size_t>(hdr.payload_len);
auto payload = buffer.subspan(hdr_bytes, payload_len);
if (hdr.mask_key != 0) {
detail::rfc6455::mask_data(hdr.mask_key, payload);
}
if (hdr.fin) {
if (opcode_ == nil_code) {
// Call upper layer.
if (hdr.opcode == detail::rfc6455::connection_close) {
up_->abort(make_error(sec::connection_closed));
return -1;
} else if (!handle(hdr.opcode, payload)) {
return -1;
}
} else if (hdr.opcode != detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("expected a WebSocket continuation_frame");
up_->abort(make_error(sec::protocol_error,
"negative header size on WebSocket connection"));
"expected a WebSocket continuation_frame"));
return -1;
}
if (hdr_bytes == 0) {
// Wait for more input.
down_->configure_read(receive_policy::up_to(2048));
return consumed;
}
// Make sure the entire frame (including header) fits into max_frame_size.
if (hdr.payload_len >= (max_frame_size - static_cast<size_t>(hdr_bytes))) {
CAF_LOG_DEBUG("WebSocket frame too large");
up_->abort(make_error(sec::protocol_error, "WebSocket frame too large"));
} else if (payload_buf_.size() + payload_len > max_frame_size) {
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
up_->abort(make_error(sec::protocol_error, "fragmented WebSocket payload "
"exceeds maximum size"));
return -1;
}
// Wait for more data if necessary.
size_t frame_size = hdr_bytes + hdr.payload_len;
if (buffer.size() < frame_size) {
// Ask for more data unless the upper layer called suspend_reading.
if (!down_->stopped_reading())
down_->configure_read(receive_policy::up_to(2048));
down_->configure_read(receive_policy::exactly(frame_size));
return consumed;
}
// Decode frame.
auto payload_len = static_cast<size_t>(hdr.payload_len);
auto payload = buffer.subspan(hdr_bytes, payload_len);
if (hdr.mask_key != 0) {
detail::rfc6455::mask_data(hdr.mask_key, payload);
}
if (hdr.fin) {
if (opcode_ == nil_code) {
// Call upper layer.
if (hdr.opcode == detail::rfc6455::connection_close) {
up_->abort(make_error(sec::connection_closed));
return -1;
} else if (!handle(hdr.opcode, payload)) {
return -1;
}
} else if (hdr.opcode != detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("expected a WebSocket continuation_frame");
up_->abort(make_error(sec::protocol_error,
"expected a WebSocket continuation_frame"));
return -1;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
up_->abort(make_error(sec::protocol_error,
"fragmented WebSocket payload "
"exceeds maximum size"));
} else {
if (hdr.opcode == detail::rfc6455::connection_close) {
up_->abort(make_error(sec::connection_closed));
return -1;
} else {
if (hdr.opcode == detail::rfc6455::connection_close) {
up_->abort(make_error(sec::connection_closed));
// End of fragmented input.
payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end());
if (!handle(hdr.opcode, payload_buf_)) {
return -1;
} else {
// End of fragmented input.
payload_buf_.insert(payload_buf_.end(), payload.begin(),
payload.end());
if (!handle(hdr.opcode, payload_buf_))
return -1;
opcode_ = nil_code;
payload_buf_.clear();
}
opcode_ = nil_code;
payload_buf_.clear();
}
} else {
if (opcode_ == nil_code) {
if (hdr.opcode == detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("received WebSocket continuation "
"frame without prior opcode");
up_->abort(make_error(sec::protocol_error,
"received WebSocket continuation "
"frame without prior opcode"));
return -1;
}
opcode_ = hdr.opcode;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
// Reject assembled payloads that exceed max_frame_size.
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
}
} else {
if (opcode_ == nil_code) {
if (hdr.opcode == detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("received WebSocket continuation "
"frame without prior opcode");
up_->abort(make_error(sec::protocol_error,
"fragmented WebSocket payload "
"exceeds maximum size"));
"received WebSocket continuation "
"frame without prior opcode"));
return -1;
}
payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end());
}
// Advance to next frame in the input.
buffer = buffer.subspan(frame_size);
if (buffer.empty()) {
// Ask for more data unless the upper layer called suspend_reading.
if (!down_->stopped_reading())
down_->configure_read(receive_policy::up_to(2048));
return consumed + static_cast<ptrdiff_t>(frame_size);
opcode_ = hdr.opcode;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
// Reject assembled payloads that exceed max_frame_size.
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
up_->abort(make_error(sec::protocol_error, "fragmented WebSocket payload "
"exceeds maximum size"));
return -1;
}
consumed += static_cast<ptrdiff_t>(frame_size);
payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end());
}
return static_cast<ptrdiff_t>(frame_size);
}
// -- implementation details ---------------------------------------------------
......@@ -198,7 +197,7 @@ bool framing::handle(uint8_t opcode, byte_span payload) {
// nop
return true;
default:
// nop
// error
return false;
}
}
......@@ -215,40 +214,6 @@ void framing::ship_pong(byte_span payload) {
down_->end_output();
}
void framing::ship_close(uint16_t code, std::string_view msg) {
uint32_t mask_key = 0;
byte_buffer payload;
payload.reserve(msg.size() + 2);
payload.push_back(static_cast<std::byte>((code & 0xFF00) >> 8));
payload.push_back(static_cast<std::byte>(code & 0x00FF));
for (auto c : msg)
payload.push_back(static_cast<std::byte>(c));
if (mask_outgoing_frames) {
mask_key = static_cast<uint32_t>(rng_());
detail::rfc6455::mask_data(mask_key, payload);
}
down_->begin_output();
detail::rfc6455::assemble_frame(detail::rfc6455::connection_close, mask_key,
payload, down_->output_buffer());
down_->end_output();
}
void framing::ship_close() {
uint32_t mask_key = 0;
std::byte payload[] = {// Error code 1000: normal close.
std::byte{0x03}, std::byte{0xE8},
// "EOF" string as goodbye message.
std::byte{'E'}, std::byte{'O'}, std::byte{'F'}};
if (mask_outgoing_frames) {
mask_key = static_cast<uint32_t>(rng_());
detail::rfc6455::mask_data(mask_key, payload);
}
down_->begin_output();
detail::rfc6455::assemble_frame(detail::rfc6455::connection_close, mask_key,
payload, down_->output_buffer());
down_->end_output();
}
template <class T>
void framing::ship_frame(std::vector<T>& buf) {
uint32_t mask_key = 0;
......
......@@ -13,8 +13,13 @@ lower_layer::~lower_layer() {
// nop
}
void lower_layer::send_close_message(const error& reason) {
send_close_message(status::unexpected_condition, to_string(reason));
void lower_layer::shutdown() {
using namespace std::literals;
shutdown(status::normal_close, "EOF"sv);
}
void lower_layer::shutdown(const error& reason) {
shutdown(status::unexpected_condition, to_string(reason));
}
} // namespace caf::net::web_socket
......@@ -22,9 +22,8 @@ std::unique_ptr<server> server::make(upper_layer_ptr up) {
// -- stream_oriented::upper_layer implementation ------------------------------
error server::init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& cfg) {
framing_.init(owner, down);
error server::start(stream_oriented::lower_layer* down, const settings& cfg) {
framing_.start(down);
cfg_ = cfg;
lower_layer().configure_read(receive_policy::up_to(handshake::max_http_size));
return none;
......@@ -59,28 +58,15 @@ ptrdiff_t server::consume(byte_span input, byte_span delta) {
}
} else if (!handle_header(hdr)) {
return -1;
} else if (remainder.empty()) {
CAF_ASSERT(hdr.size() == input.size());
return hdr.size();
} else {
CAF_LOG_DEBUG(CAF_ARG2("remainder.size", remainder.size()));
if (auto sub_result = framing_.consume(remainder, remainder);
sub_result >= 0) {
return hdr.size() + sub_result;
} else {
return sub_result;
}
return hdr.size();
}
}
}
void server::continue_reading() {
auto rp = receive_policy::up_to(handshake::max_http_size);
lower_layer().configure_read(rp);
}
bool server::prepare_send() {
return handshake_complete_ ? upper_layer().prepare_send() : true;
void server::prepare_send() {
if (handshake_complete_)
upper_layer().prepare_send();
}
bool server::done_sending() {
......@@ -131,7 +117,8 @@ bool server::handle_header(std::string_view http) {
put(fields, std::string{key}, std::string{val});
}
// Try to initialize the upper layer.
if (auto err = upper_layer().init(owner_, &framing_, cfg_)) {
lower_layer().configure_read(receive_policy::stop());
if (auto err = upper_layer().start(&framing_, cfg_)) {
auto descr = to_string(err);
CAF_LOG_DEBUG("upper layer rejected a WebSocket connection:" << descr);
write_response(http::status::bad_request, descr);
......
......@@ -19,8 +19,16 @@ bool mock_stream_transport::can_send_more() const noexcept {
return true;
}
void mock_stream_transport::suspend_reading() {
configure_read(net::receive_policy::stop());
bool mock_stream_transport::is_reading() const noexcept {
return max_read_size > 0;
}
void mock_stream_transport::write_later() {
// nop
}
void mock_stream_transport::shutdown() {
// nop
}
void mock_stream_transport::configure_read(net::receive_policy policy) {
......@@ -40,34 +48,41 @@ bool mock_stream_transport::end_output() {
return true;
}
bool mock_stream_transport::stopped_reading() const noexcept {
return max_read_size == 0;
}
ptrdiff_t mock_stream_transport::handle_input() {
ptrdiff_t result = 0;
while (max_read_size > 0) {
CAF_ASSERT(max_read_size > static_cast<size_t>(read_size_));
size_t len = max_read_size - static_cast<size_t>(read_size_);
CAF_LOG_DEBUG(CAF_ARG2("available capacity:", len));
auto num_bytes = std::min(input.size(), len);
if (num_bytes == 0)
// Loop until we have drained the buffer as much as we can.
while (max_read_size > 0 && input.size() >= min_read_size) {
auto n = std::min(input.size(), size_t{max_read_size});
auto bytes = make_span(input.data(), n);
auto delta = bytes.subspan(delta_offset);
auto consumed = up->consume(bytes, delta);
if (consumed < 0) {
// Negative values indicate that the application encountered an
// unrecoverable error.
up->abort(make_error(caf::sec::runtime_error, "consumed < 0"));
return result;
auto delta_offset = static_cast<ptrdiff_t>(read_buf_.size());
read_buf_.insert(read_buf_.end(), input.begin(), input.begin() + num_bytes);
input.erase(input.begin(), input.begin() + num_bytes);
read_size_ += static_cast<ptrdiff_t>(num_bytes);
if (static_cast<size_t>(read_size_) < min_read_size)
} else if (static_cast<size_t>(consumed) > n) {
// Must not happen. An application cannot handle more data then we pass
// to it.
up->abort(make_error(sec::logic_error, "consumed > buffer.size"));
return result;
auto delta = make_span(read_buf_.data() + delta_offset,
read_size_ - delta_offset);
auto consumed = up->consume(make_span(read_buf_), delta);
if (consumed > 0) {
result += static_cast<ptrdiff_t>(consumed);
read_buf_.erase(read_buf_.begin(), read_buf_.begin() + consumed);
read_size_ -= consumed;
} else if (consumed < 0) {
return -1;
} else if (consumed == 0) {
// See whether the next iteration would change what we pass to the
// application (max_read_size_ may have changed). Otherwise, we'll try
// again later.
delta_offset = static_cast<ptrdiff_t>(n);
if (n == std::min(input.size(), size_t{max_read_size})) {
return result;
} else {
// "Fall through".
}
} else {
// Shove the unread bytes to the beginning of the buffer and continue
// to the next loop iteration.
result += consumed;
auto del = static_cast<size_t>(consumed);
delta_offset = static_cast<ptrdiff_t>(n - del);
input.erase(input.begin(), input.begin() + del);
}
}
return result;
......
......@@ -32,7 +32,11 @@ public:
bool can_send_more() const noexcept override;
void suspend_reading() override;
bool is_reading() const noexcept override;
void write_later() override;
void shutdown() override;
void configure_read(caf::net::receive_policy policy) override;
......@@ -42,23 +46,21 @@ public:
bool end_output() override;
bool stopped_reading() const noexcept override;
// -- initialization ---------------------------------------------------------
caf::error init(const caf::settings& cfg) {
return up->init(nullptr, this, cfg);
caf::error start(const caf::settings& cfg) {
return up->start(this, cfg);
}
caf::error init() {
caf::error start() {
caf::settings cfg;
return init(cfg);
return start(cfg);
}
// -- buffer management ------------------------------------------------------
void push(caf::span<const std::byte> bytes) {
input.insert(input.begin(), bytes.begin(), bytes.end());
input.insert(input.end(), bytes.begin(), bytes.end());
}
void push(std::string_view str) {
......@@ -89,11 +91,11 @@ public:
uint32_t max_read_size = 0;
size_t delta_offset = 0;
private:
caf::byte_buffer read_buf_;
ptrdiff_t read_size_ = 0;
caf::error abort_reason_;
};
......
......@@ -11,6 +11,7 @@
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/net/make_actor_shell.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
......@@ -26,24 +27,20 @@ using svec = std::vector<std::string>;
class app_t : public net::stream_oriented::upper_layer {
public:
app_t() = default;
explicit app_t(actor hdl) : worker(std::move(hdl)) {
// nop
}
static auto make() {
return std::make_unique<app_t>();
explicit app_t(actor_system& sys, async::execution_context_ptr loop,
actor hdl = {})
: worker(std::move(hdl)) {
self = net::make_actor_shell(sys, loop);
}
static auto make(actor hdl) {
return std::make_unique<app_t>(std::move(hdl));
static auto make(actor_system& sys, async::execution_context_ptr loop,
actor hdl = {}) {
return std::make_unique<app_t>(sys, std::move(loop), std::move(hdl));
}
error init(net::socket_manager* mgr, net::stream_oriented::lower_layer* down,
const settings&) override {
error start(net::stream_oriented::lower_layer* down,
const settings&) override {
this->down = down;
self = mgr->make_actor_shell();
self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line));
......@@ -59,22 +56,16 @@ public:
return none;
}
bool prepare_send() override {
while (self->consume_message())
; // repeat
return !self->terminated();
}
void continue_reading() override {
CAF_FAIL("continue_reading called");
void prepare_send() override {
// nop
}
bool done_sending() override {
return self->try_block_mailbox();
return true;
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
MESSAGE("abort: " << reason);
}
ptrdiff_t consume(byte_span buf, byte_span) override {
......@@ -148,10 +139,10 @@ public:
};
struct fixture : test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) {
mpx.set_thread_id();
if (auto err = mpx.init())
CAF_FAIL("mpx.init() failed: " << err);
fixture() : mm(sys), mpx(net::multiplexer::make(&mm)) {
mpx->set_thread_id();
if (auto err = mpx->init())
CAF_FAIL("mpx->init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second);
......@@ -161,13 +152,19 @@ struct fixture : test_coordinator_fixture<> {
CAF_FAIL("nonblocking returned an error: " << err);
}
~fixture() {
while (mpx->poll_once(false)) {
// repeat
}
}
template <class Predicate>
void run_while(Predicate predicate) {
if (!predicate())
return;
for (size_t i = 0; i < 1000; ++i) {
mpx.apply_updates();
mpx.poll_once(false);
mpx->apply_updates();
mpx->poll_once(false);
std::byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0)
......@@ -186,7 +183,7 @@ struct fixture : test_coordinator_fixture<> {
}
net::middleman mm;
net::multiplexer mpx;
net::multiplexer_ptr mpx;
net::socket_guard<net::stream_socket> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard;
byte_buffer recv_buf;
......@@ -202,11 +199,11 @@ CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) {
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make();
auto app_uptr = app_t::make(sys, mpx);
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
if (auto err = mgr->init(content(cfg)))
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto hdl = app->self.as_actor();
anon_send(hdl, "line 1");
......@@ -214,6 +211,7 @@ CAF_TEST(actor shells expose their mailbox to their owners) {
anon_send(hdl, "line 3");
run_while([&] { return app->lines.size() != 3; });
CAF_CHECK_EQUAL(app->lines, svec({"line 1", "line 2", "line 3"}));
self_socket_guard.reset();
}
CAF_TEST(actor shells can send requests and receive responses) {
......@@ -223,12 +221,12 @@ CAF_TEST(actor shells can send requests and receive responses) {
};
});
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make(worker);
auto app_uptr = app_t::make(sys, mpx, worker);
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->start() failed: " << err);
send(input);
run_while([&] { return app->consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
......@@ -237,6 +235,7 @@ CAF_TEST(actor shells can send requests and receive responses) {
std::string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response);
self_socket_guard.reset();
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.consumer_adapter
#include "caf/net/consumer_adapter.hpp"
#include "net-test.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
class reader {
public:
reader(net::stream_socket fd, size_t n) : sg_(fd) {
buf_.resize(n);
}
auto fd() {
return sg_.socket();
}
void read_some() {
if (rd_pos_ < buf_.size()) {
auto res = read(fd(), make_span(buf_).subspan(rd_pos_));
if (res > 0) {
rd_pos_ += static_cast<size_t>(res);
MESSAGE(rd_pos_ << " bytes received");
} else if (res < 0 && !net::last_socket_error_is_temporary()) {
FAIL("failed to read: " << net::last_socket_error_as_string());
}
}
}
size_t remaining() const noexcept {
return buf_.size() - rd_pos_;
}
bool done() const noexcept {
return remaining() == 0;
}
auto& buf() const {
return buf_;
}
private:
size_t rd_pos_ = 0;
byte_buffer buf_;
net::socket_guard<net::stream_socket> sg_;
};
class app_t : public net::stream_oriented::upper_layer {
public:
using resource_type = async::consumer_resource<int32_t>;
using buffer_type = resource_type::buffer_type;
using adapter_ptr = net::consumer_adapter_ptr<buffer_type>;
using adapter_type = adapter_ptr::element_type;
explicit app_t(resource_type input) : input(std::move(input)) {
// nop
}
static auto make(resource_type input) {
return std::make_unique<app_t>(std::move(input));
}
error init(net::socket_manager* mgr,
net::stream_oriented::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
if (auto ptr = adapter_type::try_open(mgr, std::move(input))) {
adapter = std::move(ptr);
return none;
} else {
FAIL("unable to open the resource");
}
}
struct send_helper {
app_t* thisptr;
void on_next(int32_t item) {
thisptr->written_values.emplace_back(item);
auto offset = thisptr->written_bytes.size();
binary_serializer sink{nullptr, thisptr->written_bytes};
if (!sink.apply(item))
FAIL("sink.apply failed: " << sink.get_error());
auto bytes = make_span(thisptr->written_bytes).subspan(offset);
thisptr->down->begin_output();
auto& buf = thisptr->down->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
thisptr->down->end_output();
}
void on_complete() {
// nop
}
void on_error(const error&) {
// nop
}
};
bool prepare_send() override {
if (done || !adapter)
return true;
auto helper = send_helper{this};
while (down->can_send_more()) {
auto [again, consumed] = adapter->pull(async::delay_errors, 1, helper);
if (!again) {
MESSAGE("adapter signaled end-of-buffer");
adapter = nullptr;
done = true;
break;
} else if (consumed == 0) {
break;
}
}
MESSAGE(written_bytes.size() << " bytes written");
return true;
}
bool done_sending() override {
return done || !adapter->has_data();
}
void continue_reading() override {
CAF_FAIL("continue_reading called");
}
void abort(const error& reason) override {
MESSAGE("app::abort: " << reason);
}
ptrdiff_t consume(byte_span, byte_span) override {
FAIL("app::consume called: unexpected data");
}
net::stream_oriented::lower_layer* down = nullptr;
bool done = false;
std::vector<int32_t> written_values;
byte_buffer written_bytes;
adapter_ptr adapter;
resource_type input;
};
struct fixture : test_coordinator_fixture<> {
fixture() : mm(sys) {
mm.mpx().set_thread_id();
if (auto err = mm.mpx().init())
CAF_FAIL("mpx.init() failed: " << err);
}
bool handle_io_event() override {
return mm.mpx().poll_once(false);
}
auto mpx() {
return mm.mpx_ptr();
}
net::middleman mm;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("subscriber adapters wake up idle socket managers") {
GIVEN("an actor pushing into a buffer resource") {
static constexpr size_t num_items = 79;
auto [rd, wr] = async::make_spsc_buffer_resource<int32_t>(8, 2);
sys.spawn([wr{wr}](event_based_actor* self) {
self->make_observable().repeat(42).take(num_items).subscribe(wr);
});
WHEN("draining the buffer resource and sending its items over a socket") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
if (auto err = nonblocking(fd1, true))
FAIL("nonblocking(fd1) returned an error: " << err);
if (auto err = nonblocking(fd2, true))
FAIL("nonblocking(fd2) returned an error: " << err);
auto app = app_t::make(std::move(rd));
auto& state = *app;
auto transport = net::stream_transport::make(fd1, std::move(app));
auto mgr = net::socket_manager::make(mpx(), fd1, std::move(transport));
if (auto err = mgr->init(content(cfg)))
FAIL("mgr->init() failed: " << err);
THEN("the reader receives all items before the connection closes") {
auto remaining = num_items * sizeof(int32_t);
reader rd{fd2, remaining};
while (!rd.done()) {
if (auto new_val = rd.remaining(); remaining != new_val) {
remaining = new_val;
MESSAGE("want " << remaining << " more bytes");
}
run();
rd.read_some();
}
CHECK_EQ(state.written_values, std::vector<int32_t>(num_items, 42));
CHECK_EQ(state.written_bytes.size(), num_items * sizeof(int32_t));
CHECK_EQ(rd.buf().size(), num_items * sizeof(int32_t));
CHECK_EQ(state.written_bytes, rd.buf());
}
}
}
}
END_FIXTURE_SCOPE()
......@@ -48,9 +48,9 @@ public:
// -- implementation of http::upper_layer ------------------------------------
error init(net::socket_manager*, net::http::lower_layer* down_ptr,
const settings&) override {
error start(net::http::lower_layer* down_ptr, const settings&) override {
down = down_ptr;
down->request_messages();
return none;
}
......@@ -58,28 +58,23 @@ public:
CAF_FAIL("app::abort called: " << reason);
}
bool prepare_send() override {
return true;
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
ptrdiff_t consume(net::http::context ctx,
const net::http::header& request_hdr,
ptrdiff_t consume(const net::http::header& request_hdr,
const_byte_span body) override {
hdr = request_hdr;
auto content = "Hello world!"sv;
down->send_response(ctx, net::http::status::ok, "text/plain",
down->send_response(net::http::status::ok, "text/plain",
as_bytes(make_span(content)));
payload.assign(body.begin(), body.end());
return static_cast<ptrdiff_t>(body.size());
}
void continue_reading() override {
// nop
}
};
} // namespace
......@@ -100,7 +95,7 @@ SCENARIO("the server parses HTTP GET requests into header fields") {
auto app = app_ptr.get();
auto http_ptr = net::http::server::make(std::move(app_ptr));
auto serv = mock_stream_transport::make(std::move(http_ptr));
CHECK_EQ(serv->init(settings{}), error{});
CHECK_EQ(serv->start(settings{}), error{});
serv->push(req);
THEN("the HTTP layer parses the data and calls the application layer") {
CHECK_EQ(serv->handle_input(), static_cast<ptrdiff_t>(req.size()));
......
......@@ -17,12 +17,15 @@
#include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/net/binary/frame.hpp"
#include "caf/net/binary/lower_layer.hpp"
#include "caf/net/binary/upper_layer.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include "caf/span.hpp"
using namespace caf;
......@@ -35,54 +38,55 @@ using string_list = std::vector<std::string>;
using shared_string_list = std::shared_ptr<string_list>;
template <bool EnableSuspend>
class app_t : public net::message_oriented::upper_layer {
class app_t : public net::binary::upper_layer {
public:
static auto make(shared_string_list inputs) {
return std::make_unique<app_t>(std::move(inputs));
app_t(async::execution_context_ptr loop, shared_string_list ls_ptr)
: loop(std::move(loop)), inputs(std::move(ls_ptr)) {
// nop
}
app_t(shared_string_list ls_ptr) : inputs(std::move(ls_ptr)) {
// nop
static auto make(async::execution_context_ptr loop,
shared_string_list inputs) {
return std::make_unique<app_t>(std::move(loop), std::move(inputs));
}
caf::error init(net::socket_manager*,
net::message_oriented::lower_layer* down_ptr,
const settings&) override {
caf::error start(net::binary::lower_layer* down_ptr,
const settings&) override {
// Start reading immediately.
down = down_ptr;
down->request_messages();
return none;
}
bool prepare_send() override {
return true;
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void abort(const error&) override {
// nop
void abort(const error& err) override {
MESSAGE("abort: " << err);
}
void continue_reading() override {
down->request_messages();
void continue_reading() {
loop->schedule_fn([this] { down->request_messages(); });
}
ptrdiff_t consume(byte_span buf) override {
printf("app_t::consume %d\n", __LINE__);
auto printable = [](std::byte x) {
return ::isprint(static_cast<uint8_t>(x));
};
if (CHECK(std::all_of(buf.begin(), buf.end(), printable))) {
printf("app_t::consume %d\n", __LINE__);
auto str_buf = reinterpret_cast<char*>(buf.data());
inputs->emplace_back(std::string{str_buf, buf.size()});
printf("app_t::consume %d added %s\n", __LINE__, inputs->back().c_str());
MESSAGE("app: consumed " << inputs->back());
if constexpr (EnableSuspend)
if (inputs->back() == "pause")
if (inputs->back() == "pause") {
MESSAGE("app: suspend reading");
down->suspend_reading();
}
std::string response = "ok ";
response += std::to_string(inputs->size());
auto response_bytes = as_bytes(make_span(response));
......@@ -92,12 +96,13 @@ public:
CHECK(down->end_message());
return static_cast<ptrdiff_t>(buf.size());
} else {
printf("app_t::consume %d\n", __LINE__);
return -1;
}
}
net::message_oriented::lower_layer* down = nullptr;
async::execution_context_ptr loop;
net::binary::lower_layer* down = nullptr;
shared_string_list inputs;
};
......@@ -132,16 +137,33 @@ auto decode(byte_buffer& buf) {
return result;
}
void run_writer(net::stream_socket fd) {
net::multiplexer::block_sigpipe();
std::ignore = allow_sigpipe(fd, false);
auto guard = make_socket_guard(fd);
std::vector<std::string_view> inputs{"first", "second", "pause", "third",
"fourth"};
byte_buffer wr_buf;
byte_buffer rd_buf;
rd_buf.resize(512);
for (auto input : inputs) {
wr_buf.clear();
encode(wr_buf, input);
write(fd, wr_buf);
read(fd, rd_buf);
}
}
} // namespace
SCENARIO("length-prefix framing reads data with 32-bit size headers") {
GIVEN("a length_prefix_framing with an app that consumes strings") {
WHEN("pushing data into the unit-under-test") {
auto buf = std::make_shared<string_list>();
auto app = app_t<false>::make(buf);
auto app = app_t<false>::make(nullptr, buf);
auto framing = net::length_prefix_framing::make(std::move(app));
auto uut = mock_stream_transport::make(std::move(framing));
CHECK_EQ(uut->init(), error{});
CHECK_EQ(uut->start(), error{});
THEN("the app receives all strings as individual messages") {
encode(uut->input, "hello");
encode(uut->input, "world");
......@@ -157,57 +179,100 @@ SCENARIO("length-prefix framing reads data with 32-bit size headers") {
}
}
SCENARIO("calling suspend_reading removes message apps temporarily") {
SCENARIO("calling suspend_reading temporarily halts receiving of messages") {
using namespace std::literals;
GIVEN("a length_prefix_framing with an app that consumes strings") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
auto writer = std::thread{[fd1{fd1}] {
auto guard = make_socket_guard(fd1);
std::vector<std::string_view> inputs{"first", "second", "pause", "third",
"fourth"};
byte_buffer wr_buf;
byte_buffer rd_buf;
rd_buf.resize(512);
for (auto input : inputs) {
wr_buf.clear();
encode(wr_buf, input);
write(fd1, wr_buf);
read(fd1, rd_buf);
}
}};
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
mpx.apply_updates();
REQUIRE_EQ(mpx.num_socket_managers(), 1u);
auto writer = std::thread{run_writer, fd1};
auto mpx = net::multiplexer::make(nullptr);
mpx->set_thread_id();
if (auto err = mpx->init())
FAIL("mpx->init failed: " << err);
mpx->apply_updates();
REQUIRE_EQ(mpx->num_socket_managers(), 1u);
if (auto err = net::nonblocking(fd2, true))
CAF_FAIL("nonblocking returned an error: " << err);
auto buf = std::make_shared<string_list>();
auto app = app_t<true>::make(buf);
auto app = app_t<true>::make(mpx, buf);
auto app_ptr = app.get();
auto framing = net::length_prefix_framing::make(std::move(app));
auto transport = net::stream_transport::make(fd2, std::move(framing));
auto mgr = net::socket_manager::make(&mpx, fd2, std::move(transport));
CHECK_EQ(mgr->init(settings{}), none);
mpx.apply_updates();
REQUIRE_EQ(mpx.num_socket_managers(), 2u);
CHECK_EQ(mpx.mask_of(mgr), net::operation::read);
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
CHECK_EQ(mgr->start(settings{}), none);
mpx->apply_updates();
REQUIRE_EQ(mpx->num_socket_managers(), 2u);
CHECK_EQ(mpx->mask_of(mgr), net::operation::read);
WHEN("the app calls suspend_reading") {
while (mpx.num_socket_managers() > 1u)
mpx.poll_once(true);
CHECK_EQ(mpx.mask_of(mgr), net::operation::none);
while (mpx->num_socket_managers() > 1u)
mpx->poll_once(true);
CHECK_EQ(mpx->mask_of(mgr), net::operation::none);
if (CHECK_EQ(buf->size(), 3u)) {
CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second");
CHECK_EQ(buf->at(2), "pause");
}
THEN("users can resume it via continue_reading ") {
mgr->continue_reading();
mpx.apply_updates();
mpx.poll_once(true);
CHECK_EQ(mpx.mask_of(mgr), net::operation::read);
while (mpx.num_socket_managers() > 1u)
mpx.poll_once(true);
THEN("users can resume it manually") {
app_ptr->continue_reading();
mpx->apply_updates();
mpx->poll_once(true);
CHECK_EQ(mpx->mask_of(mgr), net::operation::read);
while (mpx->num_socket_managers() > 1u)
mpx->poll_once(true);
if (CHECK_EQ(buf->size(), 5u)) {
CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second");
CHECK_EQ(buf->at(2), "pause");
CHECK_EQ(buf->at(3), "third");
CHECK_EQ(buf->at(4), "fourth");
}
}
}
writer.join();
while (mpx->poll_once(false)) {
// repeat
}
}
}
SCENARIO("length_prefix_framing::run translates between flows and socket I/O") {
using namespace std::literals;
GIVEN("a connected socket with a writer at the other end") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
auto writer = std::thread{run_writer, fd1};
WHEN("calling length_prefix_framing::run") {
THEN("actors can consume the resulting flow") {
caf::actor_system_config cfg;
cfg.set("caf.scheduler.max-threads", 2);
cfg.set("caf.scheduler.policy", "sharing");
cfg.load<net::middleman>();
caf::actor_system sys{cfg};
auto buf = std::make_shared<std::vector<std::string>>();
caf::actor hdl;
net::length_prefix_framing::run(sys, fd2, [&](auto event) {
hdl = sys.spawn([event, buf](event_based_actor* self) {
auto [pull, push] = event.data();
pull.observe_on(self)
.do_on_error([](const error& what) { //
MESSAGE("flow aborted: " << what);
})
.do_on_complete([] { MESSAGE("flow completed"); })
.do_on_next([buf](const net::binary::frame& x) {
std::string str;
for (auto val : x.bytes())
str.push_back(static_cast<char>(val));
buf->push_back(std::move(str));
})
.map([](const net::binary::frame& x) {
std::string response = "ok ";
for (auto val : x.bytes())
response.push_back(static_cast<char>(val));
return net::binary::frame{as_bytes(make_span(response))};
})
.subscribe(push);
});
});
scoped_actor self{sys};
self->wait_for(hdl);
if (CHECK_EQ(buf->size(), 5u)) {
CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second");
......
......@@ -63,61 +63,47 @@ public:
// -- implementation of socket_event_layer -----------------------------------
error init(net::socket_manager*, const settings&) override {
error start(net::socket_manager* mgr, const settings&) override {
mgr_ = mgr;
return none;
}
read_result handle_read_event() override {
// if (trigger_handover) {
// MESSAGE(name << " triggered a handover");
// return read_result::handover;
// }
net::socket handle() const override {
return fd_;
}
void handle_read_event() override {
if (read_capacity() < 1024)
rd_buf_.resize(rd_buf_.size() + 2048);
auto num_bytes = read(fd_,
make_span(read_position_begin(), read_capacity()));
if (num_bytes > 0) {
CAF_ASSERT(num_bytes > 0);
rd_buf_pos_ += num_bytes;
return read_result::again;
} else if (num_bytes < 0 && net::last_socket_error_is_temporary()) {
return read_result::again;
} else {
return read_result::stop;
auto res = read(fd_, make_span(read_position_begin(), read_capacity()));
if (res > 0) {
CAF_ASSERT(res > 0);
rd_buf_pos_ += res;
} else if (res == 0 || !net::last_socket_error_is_temporary()) {
mgr_->deregister();
}
}
read_result handle_buffered_data() override {
return read_result::again;
}
read_result handle_continue_reading() override {
return read_result::again;
}
write_result handle_write_event() override {
// if (trigger_handover) {
// MESSAGE(name << " triggered a handover");
// return write_result::handover;
// }
if (wr_buf_.size() == 0)
return write_result::stop;
auto num_bytes = write(fd_, wr_buf_);
if (num_bytes > 0) {
wr_buf_.erase(wr_buf_.begin(), wr_buf_.begin() + num_bytes);
return wr_buf_.size() > 0 ? write_result::again : write_result::stop;
void handle_write_event() override {
if (wr_buf_.size() == 0) {
mgr_->deregister_writing();
} else if (auto res = write(fd_, wr_buf_); res > 0) {
wr_buf_.erase(wr_buf_.begin(), wr_buf_.begin() + res);
if (wr_buf_.size() == 0)
mgr_->deregister_writing();
} else if (res == 0 || !net::last_socket_error_is_temporary()) {
mgr_->deregister();
}
return num_bytes < 0 && net::last_socket_error_is_temporary()
? write_result::again
: write_result::stop;
}
void abort(const error& reason) override {
FAIL("abort called: " << reason);
abort_reason = reason;
}
std::string name;
error abort_reason;
private:
std::byte* read_position_begin() {
return rd_buf_.data() + rd_buf_pos_;
......@@ -130,7 +116,6 @@ private:
size_t read_capacity() const {
return rd_buf_.size() - rd_buf_pos_;
}
net::stream_socket fd_;
shared_count count_;
......@@ -140,46 +125,50 @@ private:
byte_buffer wr_buf_;
byte_buffer rd_buf_;
net::socket_manager* mgr_ = nullptr;
};
struct fixture {
fixture() : mpx(nullptr) {
fixture() : mpx(net::multiplexer::make(nullptr)) {
manager_count = std::make_shared<std::atomic<size_t>>(0);
mpx.set_thread_id();
mpx->set_thread_id();
}
~fixture() {
mpx.shutdown();
mpx->shutdown();
exhaust();
REQUIRE_EQ(*manager_count, 0u);
}
void exhaust() {
mpx.apply_updates();
while (mpx.poll_once(false))
mpx->apply_updates();
while (mpx->poll_once(false))
; // Repeat.
}
void apply_updates() {
mpx.apply_updates();
mpx->apply_updates();
}
std::pair<mock_event_layer*, net::socket_manager_ptr>
make_manager(net::stream_socket fd, std::string name) {
auto mock = mock_event_layer::make(fd, std::move(name), manager_count);
auto mock_ptr = mock.get();
return {mock_ptr, net::socket_manager::make(&mpx, fd, std::move(mock))};
auto mgr = net::socket_manager::make(mpx.get(), std::move(mock));
std::ignore = mgr->start(settings{});
return {mock_ptr, std::move(mgr)};
}
void init() {
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
if (auto err = mpx->init())
FAIL("mpx->init failed: " << err);
exhaust();
}
shared_count manager_count;
net::multiplexer mpx;
net::multiplexer_ptr mpx;
};
} // namespace
......@@ -190,7 +179,7 @@ SCENARIO("the multiplexer has no socket managers after default construction") {
GIVEN("a default constructed multiplexer") {
WHEN("querying the number of socket managers") {
THEN("the result is 0") {
CHECK_EQ(mpx.num_socket_managers(), 0u);
CHECK_EQ(mpx->num_socket_managers(), 0u);
}
}
}
......@@ -200,10 +189,10 @@ SCENARIO("the multiplexer constructs the pollset updater while initializing") {
GIVEN("an initialized multiplexer") {
WHEN("querying the number of socket managers") {
THEN("the result is 1") {
CHECK_EQ(mpx.num_socket_managers(), 0u);
CHECK_EQ(mpx.init(), none);
CHECK_EQ(mpx->num_socket_managers(), 0u);
CHECK_EQ(mpx->init(), none);
exhaust();
CHECK_EQ(mpx.num_socket_managers(), 1u);
CHECK_EQ(mpx->num_socket_managers(), 1u);
}
}
}
......@@ -219,7 +208,7 @@ SCENARIO("socket managers can register for read and write operations") {
alice_mgr->register_reading();
bob_mgr->register_reading();
apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 3u);
CHECK_EQ(mpx->num_socket_managers(), 3u);
THEN("the multiplexer runs callbacks on socket activity") {
alice->send("Hello Bob!");
alice_mgr->register_writing();
......@@ -235,9 +224,9 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
init();
auto go_time = std::make_shared<barrier>(2);
auto mpx_thread = std::thread{[this, go_time] {
mpx.set_thread_id();
mpx->set_thread_id();
go_time->arrive_and_wait();
mpx.run();
mpx->run();
}};
go_time->arrive_and_wait();
auto [alice_fd, bob_fd] = unbox(net::make_stream_socket_pair());
......@@ -246,11 +235,11 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
alice_mgr->register_reading();
bob_mgr->register_reading();
WHEN("calling shutdown on the multiplexer") {
mpx.shutdown();
mpx->shutdown();
THEN("the thread terminates and all socket managers get shut down") {
mpx_thread.join();
CHECK(alice_mgr->read_closed());
CHECK(bob_mgr->read_closed());
CHECK(alice_mgr->disposed());
CHECK(bob_mgr->disposed());
}
}
}
......@@ -268,7 +257,7 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
// alice->register_reading();
// bob->register_reading();
// apply_updates();
// CHECK_EQ(mpx.num_socket_managers(), 3u);
// CHECK_EQ(mpx->num_socket_managers(), 3u);
// THEN("the multiplexer swaps out the socket managers for the socket") {
// alice->send("Hello Bob!");
// alice->register_writing();
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.producer_adapter
#include "caf/net/producer_adapter.hpp"
#include "net-test.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/net/length_prefix_framing.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
class writer {
public:
explicit writer(net::stream_socket fd) : sg_(fd) {
// nop
}
auto fd() {
return sg_.socket();
}
byte_buffer encode(std::string_view msg) {
using detail::to_network_order;
auto prefix = to_network_order(static_cast<uint32_t>(msg.size()));
auto prefix_bytes = as_bytes(make_span(&prefix, 1));
byte_buffer buf;
buf.insert(buf.end(), prefix_bytes.begin(), prefix_bytes.end());
auto bytes = as_bytes(make_span(msg));
buf.insert(buf.end(), bytes.begin(), bytes.end());
return buf;
}
void write(std::string_view msg) {
auto buf = encode(msg);
if (net::write(fd(), buf) < 0)
FAIL("failed to write: " << net::last_socket_error_as_string());
}
private:
net::socket_guard<net::stream_socket> sg_;
};
class app_t : public net::message_oriented::upper_layer {
public:
using resource_type = async::producer_resource<int32_t>;
using buffer_type = resource_type::buffer_type;
using adapter_ptr = net::producer_adapter_ptr<buffer_type>;
using adapter_type = adapter_ptr::element_type;
explicit app_t(resource_type output) : output_(std::move(output)) {
// nop
}
static auto make(resource_type output) {
return std::make_unique<app_t>(std::move(output));
}
error init(net::socket_manager* mgr,
net::message_oriented::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
down->request_messages();
if (auto ptr = adapter_type::try_open(mgr, std::move(output_))) {
adapter_ = std::move(ptr);
return none;
} else {
FAIL("unable to open the resource");
}
}
void continue_reading() override {
down->request_messages();
}
bool prepare_send() override {
return true;
}
bool done_sending() override {
return true;
}
void abort(const error& reason) override {
if (reason == caf::sec::socket_disconnected || reason == caf::sec::disposed)
adapter_->close();
else
adapter_->abort(reason);
}
ptrdiff_t consume(byte_span buf) override {
auto val = int32_t{0};
auto str = std::string_view{reinterpret_cast<char*>(buf.data()),
buf.size()};
if (auto err = detail::parse(str, val))
FAIL("unable to parse input: " << err);
++received_messages;
if (auto capacity_left = adapter_->push(val); capacity_left == 0) {
down->suspend_reading();
}
return static_cast<ptrdiff_t>(buf.size());
}
net::message_oriented::lower_layer* down = nullptr;
size_t received_messages = 0;
adapter_ptr adapter_;
resource_type output_;
};
struct fixture : test_coordinator_fixture<> {
fixture() : mm(sys) {
if (auto err = mm.mpx().init())
CAF_FAIL("mpx.init() failed: " << err);
}
bool handle_io_event() override {
mm.mpx().apply_updates();
return mm.mpx().poll_once(false);
}
auto mpx() {
return mm.mpx_ptr();
}
net::middleman mm;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("publisher adapters suspend reads if the buffer becomes full") {
GIVEN("an actor reading from a buffer resource") {
static constexpr size_t num_items = 13;
std::vector<int32_t> outputs;
auto [rd, wr] = async::make_spsc_buffer_resource<int32_t>(8, 2);
sys.spawn([rd{rd}, &outputs](event_based_actor* self) {
self //
->make_observable()
.from_resource(rd)
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
});
WHEN("a producer reads from a socket and publishes to the buffer") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
auto writer_thread = std::thread{[fd1{fd1}] {
writer out{fd1};
for (size_t i = 0; i < num_items; ++i)
out.write(std::to_string(i));
}};
if (auto err = nonblocking(fd2, true))
FAIL("nonblocking(fd2) returned an error: " << err);
auto app = app_t::make(std::move(wr));
auto framing = net::length_prefix_framing::make(std::move(app));
auto transport = net::stream_transport::make(fd2, std::move(framing));
auto mgr = net::socket_manager::make(mpx(), fd2, std::move(transport));
if (auto err = mgr->init(content(cfg)))
FAIL("mgr->init() failed: " << err);
THEN("the actor receives all items from the writer (socket)") {
while (outputs.size() < num_items)
run();
auto ls = [](auto... xs) { return std::vector<int32_t>{xs...}; };
CHECK_EQ(outputs, ls(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
}
writer_thread.join();
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// prometheuss://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.prometheus.server
#include "caf/net/prometheus/server.hpp"
#include "net-test.hpp"
#include "caf/net/http/server.hpp"
#include "caf/telemetry/metric_registry.hpp"
using namespace caf;
using namespace caf::net;
using namespace std::literals;
constexpr std::string_view request_str
= "GET /metrics HTTP/1.1\r\n"
"Host: localhost:8090\r\n"
"User-Agent: Prometheus/2.18.1\r\n"
"Accept: text/plain;version=0.0.4\r\n"
"Accept-Encoding: gzip\r\n"
"X-Prometheus-Scrape-Timeout-Seconds: 5.000000\r\n\r\n";
SCENARIO("the Prometheus server responds to requests with scrape results") {
GIVEN("a valid Prometheus GET request") {
WHEN("sending it to an prometheus server") {
THEN("the Prometheus server responds with metrics text data") {
telemetry::metric_registry registry;
auto fb = registry.counter_singleton("foo", "bar", "test metric");
auto bf = registry.counter_singleton("bar", "foo", "test metric");
fb->inc(3);
bf->inc(7);
auto prom_state = prometheus::server::scrape_state::make(&registry);
auto prom_serv = prometheus::server::make(prom_state);
auto http_serv = net::http::server::make(std::move(prom_serv));
auto serv = mock_stream_transport::make(std::move(http_serv));
CHECK_EQ(serv->start(settings{}), error{});
serv->push(request_str);
CHECK_EQ(serv->handle_input(),
static_cast<ptrdiff_t>(request_str.size()));
auto out = serv->output_as_str();
CHECK(out.find("foo_bar 3"sv) != std::string_view::npos);
CHECK(out.find("bar_foo 7"sv) != std::string_view::npos);
}
}
}
}
......@@ -59,8 +59,8 @@ public:
*done_ = true;
}
error init(socket_manager*, net::stream_oriented::lower_layer* down,
const settings&) override {
error start(net::stream_oriented::lower_layer* down,
const settings&) override {
MESSAGE("initialize dummy app");
down_ = down;
down->configure_read(receive_policy::exactly(4));
......@@ -72,18 +72,14 @@ public:
return none;
}
bool prepare_send() override {
return true;
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void continue_reading() override {
// nop
}
ptrdiff_t consume(byte_span data, byte_span) override {
MESSAGE("dummy app received " << data.size() << " bytes");
// Store the received bytes.
......@@ -191,8 +187,9 @@ SCENARIO("ssl::transport::make_client performs the client handshake") {
key_1_pem_path};
WHEN("connecting as a client to an SSL server") {
THEN("CAF transparently calls SSL_connect") {
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto mpx = net::multiplexer::make(nullptr);
mpx->set_thread_id();
std::ignore = mpx->init();
auto ctx = unbox(ssl::context::make_client(ssl::tls::any));
auto conn = unbox(ctx.new_connection(client_fd));
auto done = std::make_shared<bool>(false);
......@@ -200,12 +197,11 @@ SCENARIO("ssl::transport::make_client performs the client handshake") {
auto mock = mock_application::make(done, buf);
auto transport = ssl::transport::make_client(std::move(conn),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, client_fd,
std::move(transport));
mpx.init(mgr);
mpx.apply_updates();
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
mpx->start(mgr);
mpx->apply_updates();
while (!*done)
mpx.poll_once(true);
mpx->poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) {
......@@ -214,6 +210,9 @@ SCENARIO("ssl::transport::make_client performs the client handshake") {
CHECK_EQ(value, 10);
}
}
while (mpx->poll_once(false)) {
// repeat
}
}
}
server.join();
......@@ -228,8 +227,9 @@ SCENARIO("ssl::transport::make_server performs the server handshake") {
std::thread client{dummy_tls_client, client_fd};
WHEN("acting as the SSL server") {
THEN("CAF transparently calls SSL_accept") {
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto mpx = net::multiplexer::make(nullptr);
mpx->set_thread_id();
std::ignore = mpx->init();
auto ctx = unbox(ssl::context::make_server(ssl::tls::any));
REQUIRE(ctx.use_certificate_from_file(cert_1_pem_path, //
ssl::format::pem));
......@@ -241,12 +241,11 @@ SCENARIO("ssl::transport::make_server performs the server handshake") {
auto mock = mock_application::make(done, buf);
auto transport = ssl::transport::make_server(std::move(conn),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, server_fd,
std::move(transport));
mpx.init(mgr);
mpx.apply_updates();
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
mpx->start(mgr);
mpx->apply_updates();
while (!*done)
mpx.poll_once(true);
mpx->poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) {
......@@ -255,6 +254,9 @@ SCENARIO("ssl::transport::make_server performs the server handshake") {
CHECK_EQ(value, 10);
}
}
while (mpx->poll_once(false)) {
// repeat
}
}
}
client.join();
......
......@@ -30,15 +30,15 @@ struct fixture : test_coordinator_fixture<> {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
fixture()
: mpx(nullptr),
: mpx(net::multiplexer::make(nullptr)),
recv_buf(1024),
shared_recv_buf{std::make_shared<byte_buffer>()},
shared_send_buf{std::make_shared<byte_buffer>()} {
mpx.set_thread_id();
mpx.apply_updates();
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
REQUIRE_EQ(mpx.num_socket_managers(), 1u);
mpx->set_thread_id();
mpx->apply_updates();
if (auto err = mpx->init())
FAIL("mpx->init failed: " << err);
REQUIRE_EQ(mpx->num_socket_managers(), 1u);
auto sockets = unbox(net::make_stream_socket_pair());
send_socket_guard.reset(sockets.first);
recv_socket_guard.reset(sockets.second);
......@@ -47,11 +47,11 @@ struct fixture : test_coordinator_fixture<> {
}
bool handle_io_event() override {
return mpx.poll_once(false);
return mpx->poll_once(false);
}
settings config;
net::multiplexer mpx;
net::multiplexer_ptr mpx;
byte_buffer recv_buf;
net::socket_guard<net::stream_socket> send_socket_guard;
net::socket_guard<net::stream_socket> recv_socket_guard;
......@@ -75,8 +75,8 @@ public:
net::stream_oriented::lower_layer* down;
error init(net::socket_manager*, net::stream_oriented::lower_layer* down_ptr,
const settings&) override {
error start(net::stream_oriented::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
down->configure_read(net::receive_policy::exactly(hello_manager.size()));
return none;
......@@ -93,16 +93,11 @@ public:
return static_cast<ptrdiff_t>(recv_buf_->size());
}
void continue_reading() override {
FAIL("continue_reading called");
}
bool prepare_send() override {
void prepare_send() override {
MESSAGE("prepare_send called");
auto& buf = down->output_buffer();
auto data = as_bytes(make_span(hello_manager));
buf.insert(buf.end(), data.begin(), data.end());
return true;
}
bool done_sending() override {
......@@ -121,13 +116,12 @@ BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(receive) {
auto mock = mock_application::make(shared_recv_buf, shared_send_buf);
auto transport = net::stream_transport::make(recv_socket_guard.get(),
auto transport = net::stream_transport::make(recv_socket_guard.release(),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, recv_socket_guard.release(),
std::move(transport));
CHECK_EQ(mgr->init(config), none);
mpx.apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 2u);
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
CHECK_EQ(mgr->start(config), none);
mpx->apply_updates();
CHECK_EQ(mpx->num_socket_managers(), 2u);
CHECK_EQ(static_cast<size_t>(write(send_socket_guard.socket(),
as_bytes(make_span(hello_manager)))),
hello_manager.size());
......@@ -140,15 +134,14 @@ CAF_TEST(receive) {
CAF_TEST(send) {
auto mock = mock_application::make(shared_recv_buf, shared_send_buf);
auto transport = net::stream_transport::make(recv_socket_guard.get(),
auto transport = net::stream_transport::make(recv_socket_guard.release(),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, recv_socket_guard.release(),
std::move(transport));
CHECK_EQ(mgr->init(config), none);
mpx.apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 2u);
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
CHECK_EQ(mgr->start(config), none);
mpx->apply_updates();
CHECK_EQ(mpx->num_socket_managers(), 2u);
mgr->register_writing();
mpx.apply_updates();
mpx->apply_updates();
while (handle_io_event())
;
recv_buf.resize(hello_manager.size());
......
......@@ -11,6 +11,7 @@
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/net/make_actor_shell.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
......@@ -29,24 +30,20 @@ using string_consumer = typed_actor<result<void>(std::string)>;
class app_t : public net::stream_oriented::upper_layer {
public:
app_t() = default;
explicit app_t(actor hdl) : worker(std::move(hdl)) {
// nop
}
static auto make() {
return std::make_unique<app_t>();
explicit app_t(actor_system& sys, async::execution_context_ptr loop,
actor hdl = {})
: worker(std::move(hdl)) {
self = net::make_actor_shell<string_consumer>(sys, loop);
}
static auto make(actor hdl) {
return std::make_unique<app_t>(std::move(hdl));
static auto make(actor_system& sys, async::execution_context_ptr loop,
actor hdl = {}) {
return std::make_unique<app_t>(sys, std::move(loop), std::move(hdl));
}
error init(net::socket_manager* mgr, net::stream_oriented::lower_layer* down,
const settings&) override {
error start(net::stream_oriented::lower_layer* down,
const settings&) override {
this->down = down;
self = mgr->make_actor_shell<string_consumer>();
self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line));
......@@ -59,22 +56,16 @@ public:
return none;
}
bool prepare_send() override {
while (self->consume_message())
; // repeat
return !self->terminated();
}
void continue_reading() override {
CAF_FAIL("continue_reading called");
void prepare_send() override {
// nop
}
bool done_sending() override {
return self->try_block_mailbox();
return true;
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
void abort(const error&) override {
// nop
}
ptrdiff_t consume(byte_span buf, byte_span) override {
......@@ -138,7 +129,7 @@ public:
std::vector<std::string> lines;
// Actor shell representing this app.
net::typed_actor_shell_ptr_t<string_consumer> self;
net::actor_shell_ptr_t<string_consumer> self;
// Counts how many bytes we've consumed in total.
size_t consumed_bytes = 0;
......@@ -148,10 +139,10 @@ public:
};
struct fixture : test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) {
mpx.set_thread_id();
if (auto err = mpx.init())
CAF_FAIL("mpx.init() failed: " << err);
fixture() : mm(sys), mpx(net::multiplexer::make(&mm)) {
mpx->set_thread_id();
if (auto err = mpx->init())
CAF_FAIL("mpx->init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second);
......@@ -161,13 +152,19 @@ struct fixture : test_coordinator_fixture<> {
CAF_FAIL("nonblocking returned an error: " << err);
}
~fixture() {
while (mpx->poll_once(false)) {
// repeat
}
}
template <class Predicate>
void run_while(Predicate predicate) {
if (!predicate())
return;
for (size_t i = 0; i < 1000; ++i) {
mpx.apply_updates();
mpx.poll_once(false);
mpx->apply_updates();
mpx->poll_once(false);
std::byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0)
......@@ -186,7 +183,7 @@ struct fixture : test_coordinator_fixture<> {
}
net::middleman mm;
net::multiplexer mpx;
net::multiplexer_ptr mpx;
net::socket_guard<net::stream_socket> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard;
byte_buffer recv_buf;
......@@ -202,18 +199,19 @@ CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) {
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make();
auto app_uptr = app_t::make(sys, mpx);
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->start() failed: " << err);
auto hdl = app->self.as_actor();
anon_send(hdl, "line 1");
anon_send(hdl, "line 2");
anon_send(hdl, "line 3");
run_while([&] { return app->lines.size() != 3; });
CAF_CHECK_EQUAL(app->lines, svec({"line 1", "line 2", "line 3"}));
self_socket_guard.reset();
}
CAF_TEST(actor shells can send requests and receive responses) {
......@@ -223,12 +221,12 @@ CAF_TEST(actor shells can send requests and receive responses) {
};
});
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make(worker);
auto app_uptr = app_t::make(sys, mpx, worker);
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->start() failed: " << err);
send(input);
run_while([&] { return app->consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
......@@ -237,6 +235,7 @@ CAF_TEST(actor shells can send requests and receive responses) {
std::string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response);
self_socket_guard.reset();
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -26,24 +26,19 @@ public:
settings cfg;
error init(net::socket_manager*, net::web_socket::lower_layer*,
const settings& init_cfg) override {
cfg = init_cfg;
error start(net::web_socket::lower_layer*, const settings& args) override {
cfg = args;
return none;
}
bool prepare_send() override {
return true;
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void continue_reading() override {
// nop
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
......@@ -106,7 +101,7 @@ SCENARIO("the client performs the WebSocket handshake on startup") {
auto& ws_state = *ws;
auto uut = mock_stream_transport::make(std::move(ws));
THEN("the client sends its HTTP request when initializing it") {
CHECK_EQ(uut->init(), error{});
CHECK_EQ(uut->start(), error{});
CHECK_EQ(uut->output_as_str(), http_request);
}
AND("the client waits for the server handshake and validates it") {
......
......@@ -33,24 +33,21 @@ public:
return std::make_unique<app_t>();
}
error init(net::socket_manager*, net::web_socket::lower_layer*,
const settings& init_cfg) override {
cfg = init_cfg;
error start(net::web_socket::lower_layer* down,
const settings& args) override {
down->request_messages();
cfg = args;
return none;
}
bool prepare_send() override {
return true;
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void continue_reading() override {
// nop
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
......@@ -74,7 +71,7 @@ struct fixture {
auto ws_ptr = net::web_socket::server::make(std::move(app_ptr));
ws = ws_ptr.get();
transport = mock_stream_transport::make(std::move(ws_ptr));
if (auto err = transport->init())
if (auto err = transport->start())
CAF_FAIL("failed to initialize mock transport: " << err);
rng.seed(0xD3ADC0D3);
}
......
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