Commit 823cfbea authored by Dominik Charousset's avatar Dominik Charousset

Iterate on the caf-net design, add Prometheus

parent 1588071c
...@@ -139,5 +139,9 @@ else() ...@@ -139,5 +139,9 @@ else()
endfunction() endfunction()
endif() 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 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) { ...@@ -50,8 +50,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
.for_each([self](const event_t& event) { .for_each([self](const event_t& event) {
// ... that simply pushes data back to the sender. // ... that simply pushes data back to the sender.
auto [pull, push] = event.data(); auto [pull, push] = event.data();
self->make_observable() pull.observe_on(self)
.from_resource(pull)
.do_on_next([](const ws::frame& x) { .do_on_next([](const ws::frame& x) {
if (x.is_binary()) { if (x.is_binary()) {
std::cout << "*** received a binary WebSocket frame of size " 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) { ...@@ -102,7 +102,8 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
// Open up a TCP port for incoming connections. // Open up a TCP port for incoming connections.
auto port = caf::get_or(cfg, "port", default_port); auto port = caf::get_or(cfg, "port", default_port);
cn::tcp_accept_socket fd; 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 " std::cout << "*** started listening for incoming connections on port "
<< port << '\n'; << port << '\n';
fd = std::move(*maybe_fd); fd = std::move(*maybe_fd);
...@@ -127,7 +128,8 @@ int caf_main(caf::actor_system& sys, const config& cfg) { ...@@ -127,7 +128,8 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
auto quote = quotes.empty() ? "Try /epictetus, /seneca or /plato." auto quote = quotes.empty() ? "Try /epictetus, /seneca or /plato."
: f(quotes); : f(quotes);
self->make_observable().just(ws::frame{quote}).subscribe(push); 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. // 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( ...@@ -224,6 +224,10 @@ caf_add_component(
actor_system_config actor_system_config
actor_termination actor_termination
aout aout
async.blocking_consumer
async.blocking_producer
async.consumer_adapter
async.producer_adapter
async.promise async.promise
async.spsc_buffer async.spsc_buffer
behavior behavior
......
...@@ -33,20 +33,24 @@ public: ...@@ -33,20 +33,24 @@ public:
template <class ErrorPolicy, class TimePoint> template <class ErrorPolicy, class TimePoint>
read_result pull(ErrorPolicy policy, T& item, TimePoint timeout) { read_result pull(ErrorPolicy policy, T& item, TimePoint timeout) {
if (!buf_) {
return abort_reason_ ? read_result::abort : read_result::stop;
}
val_ = &item; val_ = &item;
std::unique_lock guard{buf_->mtx()}; std::unique_lock guard{buf_->mtx()};
if constexpr (std::is_same_v<TimePoint, none_t>) { if constexpr (std::is_same_v<TimePoint, none_t>) {
buf_->await_consumer_ready(guard, cv_); buf_->await_consumer_ready(guard, cv_);
} else { } else {
if (!buf_->await_consumer_ready(guard, cv_, timeout)) 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); auto [again, n] = buf_->pull_unsafe(guard, policy, 1u, *this);
CAF_IGNORE_UNUSED(again); if (!again) {
CAF_ASSERT(!again || n == 1); buf_ = nullptr;
}
if (n == 1) { if (n == 1) {
return read_result::ok; return read_result::ok;
} else if (buf_->abort_reason_unsafe()) { } else if (abort_reason_) {
return read_result::abort; return read_result::abort;
} else { } else {
return read_result::stop; return read_result::stop;
...@@ -58,19 +62,18 @@ public: ...@@ -58,19 +62,18 @@ public:
return pull(policy, item, none); return pull(policy, item, none);
} }
void on_next(span<const T> items) { void on_next(const T& item) {
CAF_ASSERT(items.size() == 1); *val_ = item;
*val_ = items[0];
} }
void on_complete() { void on_complete() {
} }
void on_error(const caf::error&) { void on_error(const caf::error& abort_reason) {
// nop abort_reason_ = abort_reason;
} }
void dispose() { void cancel() {
if (buf_) { if (buf_) {
buf_->cancel(); buf_->cancel();
buf_ = nullptr; buf_ = nullptr;
...@@ -94,8 +97,8 @@ public: ...@@ -94,8 +97,8 @@ public:
deref(); deref();
} }
error abort_reason() const { const error& abort_reason() const {
buf_->abort_reason()(); return abort_reason_;
} }
CAF_INTRUSIVE_PTR_FRIENDS(impl) CAF_INTRUSIVE_PTR_FRIENDS(impl)
...@@ -104,6 +107,7 @@ public: ...@@ -104,6 +107,7 @@ public:
spsc_buffer_ptr<T> buf_; spsc_buffer_ptr<T> buf_;
std::condition_variable cv_; std::condition_variable cv_;
T* val_ = nullptr; T* val_ = nullptr;
error abort_reason_;
}; };
using impl_ptr = intrusive_ptr<impl>; using impl_ptr = intrusive_ptr<impl>;
...@@ -122,6 +126,10 @@ public: ...@@ -122,6 +126,10 @@ public:
// nop // nop
} }
explicit blocking_consumer(spsc_buffer_ptr<T> buf) {
impl_.emplace(std::move(buf));
}
~blocking_consumer() { ~blocking_consumer() {
if (impl_) if (impl_)
impl_->cancel(); impl_->cancel();
......
...@@ -4,14 +4,15 @@ ...@@ -4,14 +4,15 @@
#pragma once #pragma once
#include <condition_variable>
#include <type_traits>
#include "caf/async/producer.hpp" #include "caf/async/producer.hpp"
#include "caf/async/spsc_buffer.hpp" #include "caf/async/spsc_buffer.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/intrusive_ptr.hpp" #include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp" #include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include <condition_variable>
#include <optional>
#include <type_traits>
namespace caf::async { namespace caf::async {
...@@ -19,7 +20,7 @@ namespace caf::async { ...@@ -19,7 +20,7 @@ namespace caf::async {
template <class T> template <class T>
class blocking_producer { class blocking_producer {
public: public:
class impl : public ref_counted, public producer { class impl : public detail::atomic_ref_counted, public producer {
public: public:
impl() = delete; impl() = delete;
impl(const impl&) = delete; impl(const impl&) = delete;
...@@ -99,8 +100,6 @@ public: ...@@ -99,8 +100,6 @@ public:
deref(); deref();
} }
CAF_INTRUSIVE_PTR_FRIENDS(impl)
private: private:
spsc_buffer_ptr<T> buf_; spsc_buffer_ptr<T> buf_;
mutable std::mutex mtx_; mutable std::mutex mtx_;
...@@ -169,18 +168,30 @@ public: ...@@ -169,18 +168,30 @@ public:
return impl_->canceled(); return impl_->canceled();
} }
explicit operator bool() const noexcept {
return static_cast<bool>(impl_);
}
private: private:
intrusive_ptr<impl> impl_; intrusive_ptr<impl> impl_;
}; };
/// @pre `buf != nullptr`
/// @relates blocking_producer
template <class T> template <class T>
expected<blocking_producer<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>
std::optional<blocking_producer<T>>
make_blocking_producer(producer_resource<T> res) { make_blocking_producer(producer_resource<T> res) {
if (auto buf = res.try_open()) { if (auto buf = res.try_open()) {
using impl_t = typename blocking_producer<T>::impl; return {make_blocking_producer(std::move(buf))};
return {blocking_producer<T>{make_counted<impl_t>(std::move(buf))}};
} else { } 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: ...@@ -109,12 +109,24 @@ public:
return {ctx, std::move(cell_)}; 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. /// Binds this future to an @ref execution_context to run callbacks.
/// @pre `valid()` /// @pre `valid()`
bound_future<T> bind_to(execution_context* ctx) const& { bound_future<T> bind_to(execution_context* ctx) const& {
return {ctx, cell_}; 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 /// Queries whether the result of the asynchronous computation is still
/// pending, i.e., neither `set_value` nor `set_error` has been called on the /// pending, i.e., neither `set_value` nor `set_error` has been called on the
/// @ref promise. /// @ref promise.
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <caf/fwd.hpp> #include "caf/fwd.hpp"
namespace caf::async { namespace caf::async {
...@@ -12,6 +12,7 @@ namespace caf::async { ...@@ -12,6 +12,7 @@ namespace caf::async {
class batch; class batch;
class consumer; class consumer;
class execution_context;
class producer; class producer;
// -- template classes --------------------------------------------------------- // -- template classes ---------------------------------------------------------
...@@ -31,6 +32,10 @@ class promise; ...@@ -31,6 +32,10 @@ class promise;
template <class T> template <class T>
class future; class future;
// -- smart pointer aliases ----------------------------------------------------
using execution_context_ptr = intrusive_ptr<execution_context>;
// -- free function templates -------------------------------------------------- // -- free function templates --------------------------------------------------
template <class T> 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 { ...@@ -20,8 +20,9 @@ enum class read_result {
stop, stop,
/// Signals that the source failed with an error. /// Signals that the source failed with an error.
abort, abort,
/// Signals that the read operation timed out. /// Signals that the read operation cannot produce a result at the moment,
timeout, /// e.g., because of a timeout.
try_again_later,
}; };
/// @relates read_result /// @relates read_result
......
...@@ -329,7 +329,7 @@ struct resource_ctrl : ref_counted { ...@@ -329,7 +329,7 @@ struct resource_ctrl : ref_counted {
~resource_ctrl() { ~resource_ctrl() {
if (buf) { if (buf) {
if constexpr (IsProducer) { if constexpr (IsProducer) {
auto err = make_error(sec::invalid_upstream, auto err = make_error(sec::disposed,
"producer_resource destroyed without opening it"); "producer_resource destroyed without opening it");
buf->abort(err); buf->abort(err);
} else { } else {
...@@ -397,11 +397,19 @@ public: ...@@ -397,11 +397,19 @@ public:
} }
} }
/// Convenience function for calling
/// `ctx->make_observable().from_resource(*this)`.
template <class Coordinator> template <class Coordinator>
auto observe_on(Coordinator* ctx) const { auto observe_on(Coordinator* ctx) const {
return ctx->make_observable().from_resource(*this); 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 { explicit operator bool() const noexcept {
return ctrl_ != nullptr; return ctrl_ != nullptr;
} }
...@@ -454,6 +462,12 @@ public: ...@@ -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 { explicit operator bool() const noexcept {
return ctrl_ != nullptr; return ctrl_ != nullptr;
} }
......
...@@ -17,6 +17,23 @@ ...@@ -17,6 +17,23 @@
// -- hard-coded default values for various CAF options ------------------------ // -- 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 { namespace caf::defaults::stream {
constexpr auto max_batch_delay = timespan{1'000'000}; constexpr auto max_batch_delay = timespan{1'000'000};
...@@ -108,12 +125,12 @@ constexpr auto format = std::string_view{"[%c:%p] %d %m"}; ...@@ -108,12 +125,12 @@ constexpr auto format = std::string_view{"[%c:%p] %d %m"};
namespace caf::defaults::middleman { namespace caf::defaults::middleman {
constexpr auto app_identifier = std::string_view{"generic-caf-app"}; 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 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 max_pending_msgs = size_t{10};
constexpr auto network_backend = std::string_view{"default"};
} // namespace caf::defaults::middleman } // namespace caf::defaults::middleman
...@@ -124,3 +141,12 @@ constexpr auto batch_size = size_t{32}; ...@@ -124,3 +141,12 @@ constexpr auto batch_size = size_t{32};
constexpr auto buffer_size = size_t{128}; constexpr auto buffer_size = size_t{128};
} // namespace caf::defaults::flow } // 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: ...@@ -131,6 +131,12 @@ public:
return pimpl_.compare(other.pimpl_); 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: private:
intrusive_ptr<impl> pimpl_; intrusive_ptr<impl> pimpl_;
}; };
......
...@@ -203,14 +203,13 @@ public: ...@@ -203,14 +203,13 @@ public:
bool value(span<std::byte> x) override; bool value(span<std::byte> x) override;
/// @private
std::string current_field_name();
private: private:
[[nodiscard]] position pos() const noexcept; [[nodiscard]] position pos() const noexcept;
void append_current_field_name(std::string& str); void append_current_field_name(std::string& str);
std::string current_field_name();
std::string mandatory_field_missing_str(std::string_view name); std::string mandatory_field_missing_str(std::string_view name);
template <bool PopOrAdvanceOnSuccess, class F> template <bool PopOrAdvanceOnSuccess, class F>
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <string_view> #include <string_view>
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/detail/move_if_not_ptr.hpp" #include "caf/detail/move_if_not_ptr.hpp"
#include "caf/dictionary.hpp" #include "caf/dictionary.hpp"
...@@ -68,6 +69,12 @@ auto get_or(const settings& xs, std::string_view name, Fallback&& fallback) { ...@@ -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 /// Tries to retrieve the value associated to `name` from `xs` as an instance of
/// type `T`. /// type `T`.
/// @relates actor_system_config /// @relates actor_system_config
......
...@@ -31,6 +31,7 @@ private: ...@@ -31,6 +31,7 @@ private:
telemetry::int_gauge* rss_ = nullptr; telemetry::int_gauge* rss_ = nullptr;
telemetry::int_gauge* vms_ = nullptr; telemetry::int_gauge* vms_ = nullptr;
telemetry::dbl_gauge* cpu_ = nullptr; telemetry::dbl_gauge* cpu_ = nullptr;
telemetry::int_gauge* fds_ = nullptr;
}; };
} // namespace caf::telemetry::importer } // namespace caf::telemetry::importer
...@@ -238,100 +238,27 @@ std::ostream& operator<<(std::ostream& out, indentation indent) { ...@@ -238,100 +238,27 @@ std::ostream& operator<<(std::ostream& out, indentation indent) {
return out; 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) { void print(const config_value::dictionary& xs, indentation indent) {
out_buf out;
using std::cout; using std::cout;
bool top_level = indent.size == 0; for (const auto& kvp : xs) {
for (const auto& [key, val] : xs) { if (kvp.first == "dump-config")
if (!top_level || (key != "dump-config" && key != "config-file")) { continue;
cout << indent; if (auto submap = get_if<config_value::dictionary>(&kvp.second)) {
if (!needs_quotes(key)) { cout << indent << kvp.first << " {\n";
cout << key; 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 { } else {
detail::print_escaped(out, key); cout << indent << kvp.first << " = [\n";
auto list_indent = indent + 2;
for (auto& x : *lst)
cout << list_indent << to_string(x) << ",\n";
cout << indent << "]\n";
} }
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";
} else { } else {
cout << " {\n"; cout << indent << kvp.first << " = " << to_string(kvp.second) << '\n';
print(get<config_value::dictionary>(val), indent + 2);
cout << indent << "}\n";
}
} }
} }
} }
......
...@@ -10,6 +10,18 @@ ...@@ -10,6 +10,18 @@
namespace caf { namespace caf {
// -- member types -------------------------------------------------------------
disposable::impl::~impl() {
// nop
}
disposable disposable::impl::as_disposable() noexcept {
return disposable{intrusive_ptr<impl>{this}};
}
// -- factories ----------------------------------------------------------------
namespace { namespace {
class composite_impl : public ref_counted, public disposable::impl { class composite_impl : public ref_counted, public disposable::impl {
...@@ -52,14 +64,6 @@ private: ...@@ -52,14 +64,6 @@ private:
} // namespace } // 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) { disposable disposable::make_composite(std::vector<disposable> entries) {
if (entries.empty()) if (entries.empty())
return {}; return {};
...@@ -67,4 +71,18 @@ disposable disposable::make_composite(std::vector<disposable> entries) { ...@@ -67,4 +71,18 @@ disposable disposable::make_composite(std::vector<disposable> entries) {
return disposable{make_counted<composite_impl>(std::move(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 } // namespace caf
...@@ -634,15 +634,10 @@ json_reader::position json_reader::pos() const noexcept { ...@@ -634,15 +634,10 @@ json_reader::position json_reader::pos() const noexcept {
} }
void json_reader::append_current_field_name(std::string& result) { void json_reader::append_current_field_name(std::string& result) {
if (field_.empty()) { result += "ROOT";
result += "null"; for (auto& key : field_) {
} else {
auto i = field_.begin();
result += *i++;
while (i != field_.end()) {
result += '.'; result += '.';
result += *i++; result.insert(result.end(), key.begin(), key.end());
}
} }
} }
......
...@@ -752,12 +752,9 @@ void scheduled_actor::run_actions() { ...@@ -752,12 +752,9 @@ void scheduled_actor::run_actions() {
void scheduled_actor::update_watched_disposables() { void scheduled_actor::update_watched_disposables() {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
auto disposed = [](auto& hdl) { return hdl.disposed(); }; [[maybe_unused]] auto n = disposable::erase_disposed(watched_disposables_);
auto& xs = watched_disposables_; CAF_LOG_DEBUG_IF(n > 0, "now watching" << watched_disposables_.size()
if (auto e = std::remove_if(xs.begin(), xs.end(), disposed); e != xs.end()) { << "disposables");
xs.erase(e, xs.end());
CAF_LOG_DEBUG("now watching" << xs.size() << "disposables");
}
} }
} // namespace caf } // namespace caf
...@@ -21,18 +21,19 @@ ...@@ -21,18 +21,19 @@
namespace { namespace {
struct sys_stats { struct sys_stats {
int64_t rss; int64_t rss = 0;
int64_t vms; int64_t vms = 0;
double cpu_time; double cpu_time = 0.0;
int64_t fds = 0;
}; };
sys_stats read_sys_stats(); 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, 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", rss = reg.gauge_singleton("process", "resident_memory",
"Resident memory size.", "bytes"); "Resident memory size.", "bytes");
vms = reg.gauge_singleton("process", "virtual_memory", "Virtual memory size.", 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, ...@@ -40,15 +41,19 @@ void sys_stats_init(caf::telemetry::metric_registry& reg, RssPtr& rss,
cpu = reg.gauge_singleton<double>("process", "cpu", cpu = reg.gauge_singleton<double>("process", "cpu",
"Total user and system CPU time spent.", "Total user and system CPU time spent.",
"seconds", true); "seconds", true);
fds = reg.gauge_singleton("process", "open_fds",
"Number of open file descriptors.");
} }
void update_impl(caf::telemetry::int_gauge* rss_gauge, void update_impl(caf::telemetry::int_gauge* rss_gauge,
caf::telemetry::int_gauge* vms_gauge, caf::telemetry::int_gauge* vms_gauge,
caf::telemetry::dbl_gauge* cpu_gauge) { caf::telemetry::dbl_gauge* cpu_gauge,
auto [rss, vmsize, cpu] = read_sys_stats(); caf::telemetry::int_gauge* fds_gauge) {
auto [rss, vmsize, cpu, fds] = read_sys_stats();
rss_gauge->value(rss); rss_gauge->value(rss);
vms_gauge->value(vmsize); vms_gauge->value(vmsize);
cpu_gauge->value(cpu); cpu_gauge->value(cpu);
fds_gauge->value(fds);
} }
} // namespace } // namespace
...@@ -57,7 +62,7 @@ void update_impl(caf::telemetry::int_gauge* rss_gauge, ...@@ -57,7 +62,7 @@ void update_impl(caf::telemetry::int_gauge* rss_gauge,
namespace { namespace {
static constexpr bool platform_supported_v = false; constexpr bool platform_supported_v = false;
template <class... Ts> template <class... Ts>
void sys_stats_init(Ts&&...) { void sys_stats_init(Ts&&...) {
...@@ -77,14 +82,16 @@ void update_impl(Ts&&...) { ...@@ -77,14 +82,16 @@ void update_impl(Ts&&...) {
#ifdef CAF_MACOS #ifdef CAF_MACOS
# include <libproc.h>
# include <mach/mach.h> # include <mach/mach.h>
# include <mach/task.h> # include <mach/task.h>
# include <sys/resource.h> # include <sys/resource.h>
# include <unistd.h>
namespace { namespace {
sys_stats read_sys_stats() { sys_stats read_sys_stats() {
sys_stats result{0, 0, 0}; sys_stats result;
// Fetch memory usage. // Fetch memory usage.
{ {
mach_task_basic_info info; mach_task_basic_info info;
...@@ -110,6 +117,20 @@ sys_stats read_sys_stats() { ...@@ -110,6 +117,20 @@ sys_stats read_sys_stats() {
result.cpu_time += ceil(info.system_time.microseconds / 1000.0) / 1000.0; 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; return result;
} }
...@@ -121,8 +142,22 @@ sys_stats read_sys_stats() { ...@@ -121,8 +142,22 @@ sys_stats read_sys_stats() {
#if defined(CAF_LINUX) || defined(CAF_NET_BSD) #if defined(CAF_LINUX) || defined(CAF_NET_BSD)
# include <dirent.h>
# include <unistd.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 /// 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, /// 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 /// `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, ...@@ -160,6 +195,7 @@ bool load_system_setting(std::atomic<long>& cache_var, long& var, int name,
#ifdef CAF_LINUX #ifdef CAF_LINUX
# include <cstdio> # include <cstdio>
# include <dirent.h>
namespace { namespace {
...@@ -168,7 +204,7 @@ std::atomic<long> global_ticks_per_second; ...@@ -168,7 +204,7 @@ std::atomic<long> global_ticks_per_second;
std::atomic<long> global_page_size; std::atomic<long> global_page_size;
sys_stats read_sys_stats() { sys_stats read_sys_stats() {
sys_stats result{0, 0, 0}; sys_stats result;
long ticks_per_second = 0; long ticks_per_second = 0;
long page_size = 0; long page_size = 0;
if (!TRY_LOAD(ticks_per_second, _SC_CLK_TCK) if (!TRY_LOAD(ticks_per_second, _SC_CLK_TCK)
...@@ -218,6 +254,7 @@ sys_stats read_sys_stats() { ...@@ -218,6 +254,7 @@ sys_stats read_sys_stats() {
result.cpu_time += stime_ticks; result.cpu_time += stime_ticks;
result.cpu_time /= ticks_per_second; result.cpu_time /= ticks_per_second;
} }
result.fds = count_entries_in_directory("/proc/self/fd");
return result; return result;
} }
...@@ -234,7 +271,7 @@ namespace { ...@@ -234,7 +271,7 @@ namespace {
std::atomic<long> global_page_size; std::atomic<long> global_page_size;
sys_stats read_sys_stats() { sys_stats read_sys_stats() {
auto result = sys_stats{0, 0, 0}; auto result = sys_stats;
auto kip2 = kinfo_proc2{}; auto kip2 = kinfo_proc2{};
auto kip2_size = sizeof(kip2); auto kip2_size = sizeof(kip2);
int mib[6] = { int mib[6] = {
...@@ -263,7 +300,7 @@ sys_stats read_sys_stats() { ...@@ -263,7 +300,7 @@ sys_stats read_sys_stats() {
namespace caf::telemetry::importer { namespace caf::telemetry::importer {
process::process(metric_registry& reg) { 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 { bool process::platform_supported() noexcept {
...@@ -271,7 +308,7 @@ bool process::platform_supported() noexcept { ...@@ -271,7 +308,7 @@ bool process::platform_supported() noexcept {
} }
void process::update() { void process::update() {
update_impl(rss_, vms_, cpu_); update_impl(rss_, vms_, cpu_, fds_);
} }
} // namespace caf::telemetry::importer } // 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/fwd.hpp"
#include "caf/result.hpp" #include "caf/result.hpp"
#include "caf/test/bdd_dsl.hpp" #include "caf/test/bdd_dsl.hpp"
...@@ -418,6 +419,7 @@ bool inspect(Inspector& f, phone_book& x) { ...@@ -418,6 +419,7 @@ bool inspect(Inspector& f, phone_book& x) {
CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id) 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((circle))
ADD_TYPE_ID((dummy_enum)) ADD_TYPE_ID((dummy_enum))
ADD_TYPE_ID((dummy_enum_class)) ADD_TYPE_ID((dummy_enum_class))
......
...@@ -124,64 +124,4 @@ CAF_TEST(json baselines) { ...@@ -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() END_FIXTURE_SCOPE()
...@@ -421,7 +421,9 @@ strong_actor_ptr middleman::remote_lookup(std::string name, ...@@ -421,7 +421,9 @@ strong_actor_ptr middleman::remote_lookup(std::string name,
void middleman::start() { void middleman::start() {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
// Launch background tasks. // 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>( if (auto prom = get_if<config_value::dictionary>(
&system().config(), "caf.middleman.prometheus-http")) { &system().config(), "caf.middleman.prometheus-http")) {
auto ptr = std::make_unique<prometheus_scraping>(system()); auto ptr = std::make_unique<prometheus_scraping>(system());
...@@ -431,6 +433,7 @@ void middleman::start() { ...@@ -431,6 +433,7 @@ void middleman::start() {
background_tasks_.emplace_back(std::move(ptr)); background_tasks_.emplace_back(std::move(ptr));
} }
} }
}
// Launch backend. // Launch backend.
if (!get_or(config(), "caf.middleman.manual-multiplexing", false)) if (!get_or(config(), "caf.middleman.manual-multiplexing", false))
backend_supervisor_ = backend().make_supervisor(); backend_supervisor_ = backend().make_supervisor();
......
...@@ -33,24 +33,31 @@ caf_add_component( ...@@ -33,24 +33,31 @@ caf_add_component(
src/detail/rfc6455.cpp src/detail/rfc6455.cpp
src/net/abstract_actor_shell.cpp src/net/abstract_actor_shell.cpp
src/net/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/datagram_socket.cpp
src/net/generic_lower_layer.cpp src/net/generic_lower_layer.cpp
src/net/generic_upper_layer.cpp src/net/generic_upper_layer.cpp
src/net/http/header.cpp src/net/http/header.cpp
src/net/http/lower_layer.cpp src/net/http/lower_layer.cpp
src/net/http/method.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/server.cpp
src/net/http/status.cpp src/net/http/status.cpp
src/net/http/upper_layer.cpp src/net/http/upper_layer.cpp
src/net/http/v1.cpp src/net/http/v1.cpp
src/net/ip.cpp src/net/ip.cpp
src/net/length_prefix_framing.cpp src/net/length_prefix_framing.cpp
src/net/message_oriented.cpp
src/net/middleman.cpp src/net/middleman.cpp
src/net/multiplexer.cpp src/net/multiplexer.cpp
src/net/network_socket.cpp src/net/network_socket.cpp
src/net/pipe_socket.cpp src/net/pipe_socket.cpp
src/net/pollset_updater.cpp src/net/pollset_updater.cpp
src/net/prometheus/server.cpp
src/net/socket.cpp src/net/socket.cpp
src/net/socket_event_layer.cpp src/net/socket_event_layer.cpp
src/net/socket_manager.cpp src/net/socket_manager.cpp
...@@ -84,7 +91,6 @@ caf_add_component( ...@@ -84,7 +91,6 @@ caf_add_component(
detail.rfc6455 detail.rfc6455
net.accept_socket net.accept_socket
net.actor_shell net.actor_shell
net.consumer_adapter
net.datagram_socket net.datagram_socket
net.http.server net.http.server
net.ip net.ip
...@@ -93,7 +99,7 @@ caf_add_component( ...@@ -93,7 +99,7 @@ caf_add_component(
net.network_socket net.network_socket
net.operation net.operation
net.pipe_socket net.pipe_socket
net.producer_adapter net.prometheus.server
net.socket net.socket
net.socket_guard net.socket_guard
net.ssl.transport net.ssl.transport
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#pragma once #pragma once
#include "caf/actor_traits.hpp" #include "caf/actor_traits.hpp"
#include "caf/async/execution_context.hpp"
#include "caf/callback.hpp" #include "caf/callback.hpp"
#include "caf/detail/net_export.hpp" #include "caf/detail/net_export.hpp"
#include "caf/extend.hpp" #include "caf/extend.hpp"
...@@ -46,7 +47,7 @@ public: ...@@ -46,7 +47,7 @@ public:
// -- constructors, destructors, and assignment operators -------------------- // -- 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; ~abstract_actor_shell() override;
...@@ -56,7 +57,7 @@ public: ...@@ -56,7 +57,7 @@ public:
// -- state modifiers -------------------------------------------------------- // -- 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); void quit(error reason);
/// Overrides the default handler for unexpected messages. /// Overrides the default handler for unexpected messages.
...@@ -94,11 +95,6 @@ public: ...@@ -94,11 +95,6 @@ public:
// -- message processing ----------------------------------------------------- // -- 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. /// Adds a callback for a multiplexed response.
void add_multiplexed_response_handler(message_id response_id, behavior bhvr); void add_multiplexed_response_handler(message_id response_id, behavior bhvr);
...@@ -117,23 +113,35 @@ public: ...@@ -117,23 +113,35 @@ public:
bool cleanup(error&& fail_state, execution_unit* host) override; bool cleanup(error&& fail_state, execution_unit* host) override;
protected: protected:
// Stores incoming actor messages. void set_behavior_impl(behavior bhvr) {
bhvr_ = std::move(bhvr);
}
private:
/// Stores incoming actor messages.
mailbox_type mailbox_; mailbox_type mailbox_;
// Guards access to owner_. /// Guards access to loop_.
std::mutex owner_mtx_; std::mutex loop_mtx_;
// Points to the owning manager (nullptr after quit was called). /// Points to the loop in which this "actor" runs (nullptr after calling
socket_manager* owner_; /// quit).
async::execution_context_ptr loop_;
// Handler for consuming messages from the mailbox. /// Handler for consuming messages from the mailbox.
behavior bhvr_; behavior bhvr_;
// Handler for unexpected messages. /// Handler for unexpected messages.
fallback_handler fallback_; fallback_handler fallback_;
// Stores callbacks for multiplexed responses. /// Stores callbacks for multiplexed responses.
unordered_flat_map<message_id, behavior> 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 } // namespace caf::net
...@@ -38,7 +38,7 @@ public: ...@@ -38,7 +38,7 @@ public:
// -- constructors, destructors, and assignment operators -------------------- // -- 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; ~actor_shell() override;
...@@ -47,7 +47,7 @@ public: ...@@ -47,7 +47,7 @@ public:
/// Overrides the callbacks for incoming messages. /// Overrides the callbacks for incoming messages.
template <class... Fs> template <class... Fs>
void set_behavior(Fs... fs) { void set_behavior(Fs... fs) {
bhvr_ = behavior{std::move(fs)...}; set_behavior_impl(behavior{std::move(fs)...});
} }
// -- overridden functions of local_actor ------------------------------------ // -- overridden functions of local_actor ------------------------------------
...@@ -61,7 +61,9 @@ class CAF_NET_EXPORT actor_shell_ptr { ...@@ -61,7 +61,9 @@ class CAF_NET_EXPORT actor_shell_ptr {
public: public:
// -- friends ---------------------------------------------------------------- // -- 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 ----------------------------------------------------------- // -- 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 @@ ...@@ -4,9 +4,10 @@
#pragma once #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 frame;
class context {}; class lower_layer;
class upper_layer;
} // namespace caf::net::http } // namespace caf::net::binary
...@@ -7,39 +7,13 @@ ...@@ -7,39 +7,13 @@
#include "caf/detail/net_export.hpp" #include "caf/detail/net_export.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/fwd.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_lower_layer.hpp"
#include "caf/net/generic_upper_layer.hpp"
namespace caf::net::message_oriented { namespace caf::net::binary {
class lower_layer; /// Provides access to a resource that operates on the granularity of binary
/// messages.
/// 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.
class CAF_NET_EXPORT lower_layer : public generic_lower_layer { class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public: public:
virtual ~lower_layer(); virtual ~lower_layer();
...@@ -47,6 +21,9 @@ public: ...@@ -47,6 +21,9 @@ public:
/// Pulls messages from the transport until calling `suspend_reading`. /// Pulls messages from the transport until calling `suspend_reading`.
virtual void request_messages() = 0; 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 /// Prepares the layer for an outgoing message, e.g., by allocating an
/// output buffer as necessary. /// output buffer as necessary.
virtual void begin_message() = 0; virtual void begin_message() = 0;
...@@ -62,17 +39,6 @@ public: ...@@ -62,17 +39,6 @@ public:
/// @note When returning `false`, clients must also call /// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code. /// `down.set_read_error(...)` with an appropriate error code.
virtual bool end_message() = 0; 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 @@ ...@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "caf/net/connection_factory.hpp"
#include "caf/net/socket_event_layer.hpp" #include "caf/net/socket_event_layer.hpp"
#include "caf/net/socket_manager.hpp" #include "caf/net/socket_manager.hpp"
#include "caf/settings.hpp" #include "caf/settings.hpp"
...@@ -12,99 +13,116 @@ namespace caf::net { ...@@ -12,99 +13,116 @@ namespace caf::net {
/// A connection_acceptor accepts connections from an accept socket and creates /// A connection_acceptor accepts connections from an accept socket and creates
/// socket managers to handle them via its factory. /// socket managers to handle them via its factory.
template <class Socket, class Factory> template <class Socket>
class connection_acceptor : public socket_event_layer { class connection_acceptor : public socket_event_layer {
public: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
using socket_type = Socket; 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 factory_type = connection_factory<connected_socket_type>;
using write_result = typename socket_event_layer::write_result;
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
template <class... Ts> template <class FactoryPtr, class... Ts>
explicit connection_acceptor(Socket fd, size_t limit, Ts&&... xs) connection_acceptor(Socket fd, FactoryPtr fptr, size_t max_connections)
: fd_(fd), limit_(limit), factory_(std::forward<Ts>(xs)...) { : fd_(fd),
// nop 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 -------------------------------------------------------------- // -- factories --------------------------------------------------------------
template <class... Ts> template <class FactoryPtr, class... Ts>
static std::unique_ptr<connection_acceptor> static std::unique_ptr<connection_acceptor>
make(Socket fd, size_t limit, Ts&&... xs) { make(Socket fd, FactoryPtr fptr, size_t max_connections) {
return std::make_unique<connection_acceptor>(fd, limit, return std::make_unique<connection_acceptor>(fd, std::move(fptr),
std::forward<Ts>(xs)...); max_connections);
} }
// -- implementation of socket_event_layer ----------------------------------- // -- 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(""); CAF_LOG_TRACE("");
owner_ = owner; owner_ = owner;
cfg_ = cfg; 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; return err;
}
on_conn_close_ = make_action([this] { connection_closed(); });
owner->register_reading(); owner->register_reading();
return none; return none;
} }
read_result handle_read_event() override { socket handle() const override {
return fd_;
}
void handle_read_event() override {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
if (auto x = accept(fd_)) { CAF_ASSERT(owner_ != nullptr);
socket_manager_ptr child = factory_.make(owner_->mpx_ptr(), *x); 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) { if (!child) {
CAF_LOG_ERROR("factory failed to create a new child"); CAF_LOG_ERROR("factory failed to create a new child");
return read_result::abort; on_conn_close_.dispose();
} owner_->shutdown();
if (auto err = child->init(cfg_)) { return;
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;
} }
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 { } else {
CAF_LOG_ERROR("accept failed:" << x.error()); // Encountered a "hard" error: stop.
return read_result::stop; abort(x.error());
} owner_->deregister_reading();
}
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 {
CAF_LOG_ERROR("connection_acceptor received write event"); CAF_LOG_ERROR("connection_acceptor received write event");
return write_result::stop; owner_->deregister_writing();
} }
void abort(const error& reason) override { void abort(const error& reason) override {
CAF_LOG_ERROR("connection_acceptor aborts due to an error: " << reason); CAF_LOG_ERROR("connection_acceptor aborts due to an error:" << reason);
factory_.abort(reason); factory_->abort(reason);
on_conn_close_.dispose();
} }
private: private:
void connection_closed() {
if (open_connections_-- == max_connections_)
owner_->register_reading();
}
Socket fd_; Socket fd_;
size_t limit_; connection_factory_ptr<connected_socket_type> factory_;
size_t max_connections_;
factory_type factory_; size_t open_connections_ = 0;
socket_manager* owner_ = nullptr; socket_manager* owner_ = nullptr;
size_t accepted_ = 0; action on_conn_close_;
settings cfg_; 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/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: ...@@ -56,9 +56,9 @@ public:
// -- public member functions ------------------------------------------------ // -- public member functions ------------------------------------------------
error init(endpoint_manager& manager) override { error start(endpoint_manager& manager) override {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
if (auto err = super::init(manager)) if (auto err = super::start(manager))
return err; return err;
prepare_next_read(); prepare_next_read();
return none; 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 @@ ...@@ -4,6 +4,8 @@
#pragma once #pragma once
#include "caf/async/fwd.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp" #include "caf/intrusive_ptr.hpp"
#include "caf/type_id.hpp" #include "caf/type_id.hpp"
...@@ -25,6 +27,9 @@ class typed_actor_shell; ...@@ -25,6 +27,9 @@ class typed_actor_shell;
template <class... Sigs> template <class... Sigs>
class typed_actor_shell_ptr; class typed_actor_shell_ptr;
template <class Trait>
class flow_connector;
// -- classes ------------------------------------------------------------------ // -- classes ------------------------------------------------------------------
class actor_shell; class actor_shell;
...@@ -48,12 +53,36 @@ struct udp_datagram_socket; ...@@ -48,12 +53,36 @@ struct udp_datagram_socket;
// -- smart pointer aliases ---------------------------------------------------- // -- 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 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 ---------------------------------------------------- // -- miscellaneous aliases ----------------------------------------------------
using text_buffer = std::vector<char>; 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 } // namespace caf::net
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#pragma once #pragma once
#include "caf/detail/net_export.hpp" #include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
namespace caf::net { namespace caf::net {
...@@ -17,12 +18,22 @@ public: ...@@ -17,12 +18,22 @@ public:
/// Queries whether the output device can accept more data straight away. /// Queries whether the output device can accept more data straight away.
[[nodiscard]] virtual bool can_send_more() const noexcept = 0; [[nodiscard]] virtual bool can_send_more() const noexcept = 0;
/// Halt receiving of additional bytes or messages. /// Queries whether the lower layer is currently reading from its input
virtual void suspend_reading() = 0; /// device.
[[nodiscard]] virtual bool is_reading() const noexcept = 0;
/// Queries whether the lower layer is currently configured to halt receiving /// Triggers a write callback after the write device signals downstream
/// of additional bytes or messages. /// capacity. Does nothing if this layer is already writing.
[[nodiscard]] virtual bool stopped_reading() const noexcept = 0; 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 } // namespace caf::net
...@@ -15,21 +15,16 @@ class CAF_NET_EXPORT generic_upper_layer { ...@@ -15,21 +15,16 @@ class CAF_NET_EXPORT generic_upper_layer {
public: public:
virtual ~generic_upper_layer(); virtual ~generic_upper_layer();
/// Prepares any pending data for sending. /// Gives the upper layer an opportunity to add additional data to the output
/// @returns `false` in case of an error to cause the lower layer to stop, /// buffer.
/// `true` otherwise. virtual void prepare_send() = 0;
[[nodiscard]] virtual bool prepare_send() = 0;
/// Queries whether all pending data has been sent. The lower calls this /// 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. /// function to decide whether it has to wait for write events on the socket.
[[nodiscard]] virtual bool done_sending() = 0; [[nodiscard]] virtual bool done_sending() = 0;
/// Called by the lower layer after some event triggered re-registering the /// Called by the lower layer for cleaning up any state in case of an error or
/// socket manager for read operations after it has been stopped previously /// when disposed.
/// 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.
virtual void abort(const error& reason) = 0; virtual void abort(const error& reason) = 0;
}; };
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
namespace caf::net::http { namespace caf::net::http {
class context;
class header; class header;
class lower_layer; class lower_layer;
class server; class server;
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
#include "caf/net/generic_lower_layer.hpp" #include "caf/net/generic_lower_layer.hpp"
#include "caf/net/http/fwd.hpp" #include "caf/net/http/fwd.hpp"
#include <string_view>
namespace caf::net::http { namespace caf::net::http {
/// Parses HTTP requests and passes them to the upper layer. /// Parses HTTP requests and passes them to the upper layer.
...@@ -18,28 +20,40 @@ class CAF_NET_EXPORT lower_layer : public generic_lower_layer { ...@@ -18,28 +20,40 @@ class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public: public:
virtual ~lower_layer(); virtual ~lower_layer();
/// Sends the next header to the client. /// Start or re-start reading data from the client.
virtual bool virtual void request_messages() = 0;
send_header(context ctx, status code, const header_fields_map& fields)
= 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. /// 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 /// Sends a chunk of data if the full payload is unknown when starting to
/// send. /// 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. /// Sends the last chunk, completing a chunked payload.
virtual bool send_end_of_chunks() = 0; 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 /// Convenience function for sending header and payload. Automatically sets
/// the header fields `Content-Type` and `Content-Length`. /// 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); 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 } // 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 @@ ...@@ -6,11 +6,11 @@
#include "caf/byte_span.hpp" #include "caf/byte_span.hpp"
#include "caf/detail/append_hex.hpp" #include "caf/detail/append_hex.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/logger.hpp" #include "caf/logger.hpp"
#include "caf/net/connection_acceptor.hpp" #include "caf/net/connection_acceptor.hpp"
#include "caf/net/fwd.hpp" #include "caf/net/fwd.hpp"
#include "caf/net/http/context.hpp"
#include "caf/net/http/header.hpp" #include "caf/net/http/header.hpp"
#include "caf/net/http/header_fields_map.hpp" #include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/lower_layer.hpp" #include "caf/net/http/lower_layer.hpp"
...@@ -31,7 +31,8 @@ ...@@ -31,7 +31,8 @@
namespace caf::net::http { namespace caf::net::http {
/// Implements the server part for the HTTP Protocol as defined in RFC 7231. /// 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: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
...@@ -39,8 +40,6 @@ public: ...@@ -39,8 +40,6 @@ public:
using status_code_type = status; using status_code_type = status;
using context_type = context;
using header_type = header; using header_type = header;
enum class mode { enum class mode {
...@@ -80,34 +79,39 @@ public: ...@@ -80,34 +79,39 @@ public:
bool can_send_more() const noexcept override; 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; void suspend_reading() override;
bool stopped_reading() const noexcept override; void begin_header(status code) override;
bool send_header(context, status code, void add_header_field(std::string_view key, std::string_view val) override;
const header_fields_map& fields) 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 ---------------------------- // -- stream_oriented::upper_layer implementation ----------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down, error start(stream_oriented::lower_layer* down,
const settings& config) override; const settings& config) override;
void abort(const error& reason) override; void abort(const error& reason) override;
bool prepare_send() override; void prepare_send() override;
bool done_sending() override; bool done_sending() override;
void continue_reading() override;
ptrdiff_t consume(byte_span input, byte_span) override; ptrdiff_t consume(byte_span input, byte_span) override;
private: private:
......
...@@ -18,28 +18,17 @@ public: ...@@ -18,28 +18,17 @@ public:
virtual ~upper_layer(); virtual ~upper_layer();
/// Initializes the 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 /// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer. /// lifetime of the upper layer.
virtual error virtual error start(lower_layer* down, const settings& config) = 0;
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
/// Consumes an HTTP message. /// 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 hdr The header fields for the received message.
/// @param payload The payload of the received message. /// @param payload The payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an /// @returns The number of consumed bytes or a negative value to signal an
/// error. /// error.
/// @note Discarded data is lost permanently. /// @note Discarded data is lost permanently.
virtual ptrdiff_t virtual ptrdiff_t consume(const header& hdr, const_byte_span payload) = 0;
consume(context ctx, const header& hdr, const_byte_span payload)
= 0;
}; };
} // namespace caf::net::http } // namespace caf::net::http
...@@ -20,18 +20,27 @@ namespace caf::net::http::v1 { ...@@ -20,18 +20,27 @@ namespace caf::net::http::v1 {
CAF_NET_EXPORT std::pair<std::string_view, byte_span> CAF_NET_EXPORT std::pair<std::string_view, byte_span>
split_header(byte_span bytes); 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, CAF_NET_EXPORT void write_header(status code, const header_fields_map& fields,
byte_buffer& buf); byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets /// Write the status code for an HTTP header to @p buf.
/// Content-Type and Content-Length header fields. 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, CAF_NET_EXPORT void write_response(status code, std::string_view content_type,
std::string_view content, byte_buffer& buf); std::string_view content, byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets /// Writes a complete HTTP response to @p buf. Automatically sets Content-Type
/// Content-Type and Content-Length header fields followed by the user-defined /// and Content-Length header fields followed by the user-defined @p fields.
/// @p fields.
CAF_NET_EXPORT void write_response(status code, std::string_view content_type, CAF_NET_EXPORT void write_response(status code, std::string_view content_type,
std::string_view content, std::string_view content,
const header_fields_map& fields, const header_fields_map& fields,
......
...@@ -4,9 +4,17 @@ ...@@ -4,9 +4,17 @@
#pragma once #pragma once
#include "caf/actor_system.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/byte_span.hpp" #include "caf/byte_span.hpp"
#include "caf/cow_tuple.hpp"
#include "caf/detail/net_export.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 "caf/net/stream_oriented.hpp"
#include <cstdint> #include <cstdint>
...@@ -23,11 +31,11 @@ namespace caf::net { ...@@ -23,11 +31,11 @@ namespace caf::net {
/// on 32-bit platforms. /// on 32-bit platforms.
class CAF_NET_EXPORT length_prefix_framing class CAF_NET_EXPORT length_prefix_framing
: public stream_oriented::upper_layer, : public stream_oriented::upper_layer,
public message_oriented::lower_layer { public binary::lower_layer {
public: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
using upper_layer_ptr = std::unique_ptr<message_oriented::upper_layer>; using upper_layer_ptr = std::unique_ptr<binary::upper_layer>;
// -- constants -------------------------------------------------------------- // -- constants --------------------------------------------------------------
...@@ -45,49 +53,70 @@ public: ...@@ -45,49 +53,70 @@ public:
// -- high-level factory functions ------------------------------------------- // -- high-level factory functions -------------------------------------------
// /// Runs a WebSocket server on the connected socket `fd`. /// Describes the one-time connection event.
// /// @param mpx The multiplexer that takes ownership of the socket. using connect_event_t
// /// @param fd A connected stream socket. = cow_tuple<async::consumer_resource<binary::frame>, // Socket to App.
// /// @param cfg Additional configuration parameters for the protocol async::producer_resource<binary::frame>>; // App to Socket.
// stack.
// /// @param in Inputs for writing to the socket. /// Runs length-prefix framing on given connection.
// /// @param out Outputs from the socket. /// @param sys The host system.
// /// @param trait Converts between the native and the wire format. /// @param conn A connected stream socket or SSL connection, depending on the
// /// @relates length_prefix_framing /// `Transport`.
// template <template <class> class Transport = stream_transport, class /// @param init Function object for setting up the created flows.
// Socket, template <class Transport = stream_transport, class Connection, class Init>
// class T, class Trait, class... TransportArgs> static disposable run(actor_system& sys, Connection conn, Init init) {
// error run(actor_system&sys,Socket fd, using trait_t = binary::default_trait;
// const settings& cfg, using frame_t = binary::frame;
// async::consumer_resource<T> in, static_assert(std::is_invocable_v<Init, connect_event_t&&>,
// async::producer_resource<T> out, "invalid signature found for init");
// Trait trait, TransportArgs&&... auto [fd_pull, app_push] = async::make_spsc_buffer_resource<frame_t>();
// args) { auto [app_pull, fd_push] = async::make_spsc_buffer_resource<frame_t>();
// using app_t auto mpx = sys.network_manager().mpx_ptr();
// = Transport<length_prefix_framing<message_flow_bridge<T, Trait>>>; auto fc = flow_connector<trait_t>::make_trivial(std::move(fd_pull),
// auto mgr = make_socket_manager<app_t>(fd, &mpx, std::move(fd_push));
// std::forward<TransportArgs>(args)..., auto bridge = binary::flow_bridge<trait_t>::make(mpx, std::move(fc));
// std::move(in), std::move(out), auto bridge_ptr = bridge.get();
// std::move(trait)); auto impl = length_prefix_framing::make(std::move(bridge));
// return mgr->init(cfg); 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 ------------------------- // -- implementation of stream_oriented::upper_layer -------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down, error start(stream_oriented::lower_layer* down,
const settings& config) override; const settings& config) override;
void abort(const error& reason) override; void abort(const error& reason) override;
ptrdiff_t consume(byte_span buffer, byte_span delta) override; ptrdiff_t consume(byte_span buffer, byte_span delta) override;
void continue_reading() override; void prepare_send() override;
bool prepare_send() override;
bool done_sending() override; bool done_sending() override;
// -- implementation of message_oriented::lower_layer ------------------------ // -- implementation of binary::lower_layer ----------------------------------
bool can_send_more() const noexcept override; bool can_send_more() const noexcept override;
...@@ -95,7 +124,9 @@ public: ...@@ -95,7 +124,9 @@ public:
void suspend_reading() override; void suspend_reading() override;
bool stopped_reading() const noexcept override; bool is_reading() const noexcept override;
void write_later() override;
void begin_message() override; void begin_message() override;
...@@ -103,9 +134,7 @@ public: ...@@ -103,9 +134,7 @@ public:
bool end_message() override; bool end_message() override;
void send_close_message() override; void shutdown() override;
void send_close_message(const error& reason) override;
// -- utility functions ------------------------------------------------------ // -- 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 @@ ...@@ -5,11 +5,11 @@
#pragma once #pragma once
#include "caf/actor_system.hpp" #include "caf/actor_system.hpp"
#include "caf/async/spsc_buffer.hpp" #include "caf/async/consumer_adapter.hpp"
#include "caf/net/consumer_adapter.hpp" #include "caf/async/producer_adapter.hpp"
#include "caf/net/message_oriented.hpp" #include "caf/net/binary/lower_layer.hpp"
#include "caf/net/binary/upper_layer.hpp"
#include "caf/net/multiplexer.hpp" #include "caf/net/multiplexer.hpp"
#include "caf/net/producer_adapter.hpp"
#include "caf/net/socket_manager.hpp" #include "caf/net/socket_manager.hpp"
#include "caf/sec.hpp" #include "caf/sec.hpp"
#include "caf/settings.hpp" #include "caf/settings.hpp"
...@@ -32,7 +32,7 @@ namespace caf::net { ...@@ -32,7 +32,7 @@ namespace caf::net {
/// }; /// };
/// ~~~ /// ~~~
template <class Trait> template <class Trait>
class message_flow_bridge : public message_oriented::upper_layer { class message_flow_bridge : public binary::upper_layer {
public: public:
/// The input type for the application. /// The input type for the application.
using input_type = typename Trait::input_type; using input_type = typename Trait::input_type;
...@@ -64,15 +64,17 @@ public: ...@@ -64,15 +64,17 @@ public:
// nop // nop
} }
error init(net::socket_manager* mgr, message_oriented::lower_layer* down, error start(net::socket_manager* mgr, binary::lower_layer* down,
const settings&) { const settings&) {
down_ = down; down_ = down;
if (in_res_) { if (auto in = make_consumer_adapter(in_res_, mgr->mpx_ptr(),
in_ = consumer_adapter<pull_buffer_t>::try_open(mgr, in_res_); do_wakeup_cb())) {
in_ = std::move(*in);
in_res_ = nullptr; in_res_ = nullptr;
} }
if (out_res_) { if (auto out = make_producer_adapter(out_res_, mgr->mpx_ptr(),
out_ = producer_adapter<push_buffer_t>::try_open(mgr, out_res_); do_resume_cb(), do_cancel_cb())) {
out_ = std::move(*out);
out_res_ = nullptr; out_res_ = nullptr;
} }
if (!in_ && !out_) if (!in_ && !out_)
...@@ -87,63 +89,39 @@ public: ...@@ -87,63 +89,39 @@ public:
return trait_.convert(item, buf) && down_->end_message(); return trait_.convert(item, buf) && down_->end_message();
} }
struct write_helper { void prepare_send() override {
message_flow_bridge* bridge; input_type tmp;
bool aborted = false; while (down_->can_send_more()) {
error err; switch (in_.pull(async::delay_errors, tmp)) {
case async::read_result::ok:
write_helper(message_flow_bridge* bridge) : bridge(bridge) { if (!write(tmp)) {
// nop down_->shutdown(trait_.last_error());
} return;
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;
} }
}; break;
case async::read_result::stop:
bool prepare_send() override { down_->shutdown();
write_helper helper{this}; break;
while (down_->can_send_more() && in_) { case async::read_result::abort:
auto [again, consumed] = in_->pull(async::delay_errors, 1, helper); down_->shutdown(in_.abort_reason());
if (!again) { break;
if (helper.err) { default: // try later
down_->send_close_message(helper.err); return;
} else {
down_->send_close_message();
}
in_ = nullptr;
} else if (helper.aborted) {
in_->cancel();
in_ = nullptr;
return false;
} else if (consumed == 0) {
return true;
} }
} }
return true;
} }
bool done_sending() override { bool done_sending() override {
return !in_ || !in_->has_data(); return !in_->has_consumer_event();
} }
void abort(const error& reason) override { void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason)); CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) { if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed) if (reason == sec::socket_disconnected || reason == sec::disposed)
out_->close(); out_.close();
else else
out_->abort(reason); out_ > abort(reason);
out_ = nullptr;
} }
if (in_) { if (in_) {
in_->cancel(); in_->cancel();
...@@ -163,14 +141,31 @@ public: ...@@ -163,14 +141,31 @@ public:
} }
private: 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. /// 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. /// 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. /// 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. /// Converts between raw bytes and items.
Trait trait_; Trait trait_;
......
...@@ -69,19 +69,15 @@ public: ...@@ -69,19 +69,15 @@ public:
} }
multiplexer& mpx() noexcept { multiplexer& mpx() noexcept {
return mpx_; return *mpx_;
} }
const multiplexer& mpx() const noexcept { const multiplexer& mpx() const noexcept {
return mpx_; return *mpx_;
} }
multiplexer* mpx_ptr() noexcept { multiplexer* mpx_ptr() const noexcept {
return &mpx_; return mpx_.get();
}
const multiplexer* mpx_ptr() const noexcept {
return &mpx_;
} }
private: private:
...@@ -91,7 +87,7 @@ private: ...@@ -91,7 +87,7 @@ private:
actor_system& sys_; actor_system& sys_;
/// Stores the global socket I/O multiplexer. /// Stores the global socket I/O multiplexer.
multiplexer mpx_; multiplexer_ptr mpx_;
/// Runs the multiplexer's event loop /// Runs the multiplexer's event loop
std::thread mpx_thread_; std::thread mpx_thread_;
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include <thread> #include <thread>
#include "caf/action.hpp" #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/detail/net_export.hpp"
#include "caf/net/fwd.hpp" #include "caf/net/fwd.hpp"
#include "caf/net/operation.hpp" #include "caf/net/operation.hpp"
...@@ -28,7 +30,8 @@ namespace caf::net { ...@@ -28,7 +30,8 @@ namespace caf::net {
class pollset_updater; class pollset_updater;
/// Multiplexes any number of ::socket_manager objects with a ::socket. /// 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: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
...@@ -55,13 +58,18 @@ public: ...@@ -55,13 +58,18 @@ public:
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
~multiplexer();
// -- factories --------------------------------------------------------------
/// @param parent Points to the owning middleman instance. May be `nullptr` /// @param parent Points to the owning middleman instance. May be `nullptr`
/// only for the purpose of unit testing if no @ref /// only for the purpose of unit testing if no @ref
/// socket_manager requires access to the @ref middleman or the /// socket_manager requires access to the @ref middleman or the
/// @ref actor_system. /// @ref actor_system.
explicit multiplexer(middleman* parent); static multiplexer_ptr make(middleman* parent) {
auto ptr = new multiplexer(parent);
~multiplexer(); return multiplexer_ptr{ptr, false};
}
// -- initialization --------------------------------------------------------- // -- initialization ---------------------------------------------------------
...@@ -73,10 +81,10 @@ public: ...@@ -73,10 +81,10 @@ public:
size_t num_socket_managers() const noexcept; size_t num_socket_managers() const noexcept;
/// Returns the index of `mgr` in the pollset or `-1`. /// 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`. /// 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. /// Returns the owning @ref middleman instance.
middleman& owner(); middleman& owner();
...@@ -87,54 +95,48 @@ public: ...@@ -87,54 +95,48 @@ public:
/// Computes the current mask for the manager. Mostly useful for testing. /// Computes the current mask for the manager. Mostly useful for testing.
operation mask_of(const socket_manager_ptr& mgr); operation mask_of(const socket_manager_ptr& mgr);
// -- thread-safe signaling -------------------------------------------------- // -- implementation of execution_context ------------------------------------
/// Registers `mgr` for read events. void ref_execution_context() const noexcept override;
/// @thread-safe
void register_reading(const socket_manager_ptr& mgr);
/// Registers `mgr` for write events. void deref_execution_context() const noexcept override;
/// @thread-safe
void register_writing(const socket_manager_ptr& mgr);
/// Triggers a continue reading event for `mgr`. void schedule(action what) override;
/// @thread-safe
void continue_reading(const socket_manager_ptr& mgr);
/// Triggers a continue writing event for `mgr`. void watch(disposable what) override;
/// @thread-safe
void continue_writing(const socket_manager_ptr& mgr);
/// Schedules a call to `mgr->handle_error(sec::discarded)`. // -- thread-safe signaling --------------------------------------------------
/// @thread-safe
void discard(const socket_manager_ptr& mgr);
/// Stops further reading by `mgr`. /// Registers `mgr` for initialization in the multiplexer's thread.
/// @thread-safe /// @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 /// @thread-safe
void shutdown_writing(const socket_manager_ptr& mgr); void shutdown();
/// Schedules an action for execution on this multiplexer. // -- callbacks for socket managers ------------------------------------------
/// @thread-safe
void schedule(const action& what);
/// Schedules an action for execution on this multiplexer. /// Registers `mgr` for read events.
/// @thread-safe void register_reading(socket_manager* mgr);
template <class F>
void schedule_fn(F f) {
schedule(make_action(std::move(f)));
}
/// Registers `mgr` for initialization in the multiplexer's thread. /// Registers `mgr` for write events.
/// @thread-safe void register_writing(socket_manager* mgr);
void init(const socket_manager_ptr& mgr);
/// Signals the multiplexer to initiate shutdown. /// Deregisters `mgr` from read events.
/// @thread-safe void deregister_reading(socket_manager* mgr);
void shutdown();
/// 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 ----------------------------------------------------------- // -- control flow -----------------------------------------------------------
...@@ -160,18 +162,16 @@ protected: ...@@ -160,18 +162,16 @@ protected:
/// Handles an I/O event on given manager. /// Handles an I/O event on given manager.
void handle(const socket_manager_ptr& mgr, short events, short revents); 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 /// Returns a change entry for the socket at given index. Lazily creates a new
/// entry before returning if necessary. /// entry before returning if necessary.
poll_update& update_for(ptrdiff_t index); poll_update& update_for(ptrdiff_t index);
/// Returns a change entry for the socket of the manager. /// 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 /// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater. /// later via the pollset updater.
/// @warning assumes ownership of @p ptr.
template <class T> template <class T>
void write_to_pipe(uint8_t opcode, T* ptr); void write_to_pipe(uint8_t opcode, T* ptr);
...@@ -182,13 +182,7 @@ protected: ...@@ -182,13 +182,7 @@ protected:
} }
/// Queries the currently active event bitmask for `mgr`. /// Queries the currently active event bitmask for `mgr`.
short active_mask_of(const socket_manager_ptr& mgr); short active_mask_of(const socket_manager* mgr) const noexcept;
/// 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);
// -- member variables ------------------------------------------------------- // -- member variables -------------------------------------------------------
...@@ -219,26 +213,21 @@ protected: ...@@ -219,26 +213,21 @@ protected:
/// Signals whether shutdown has been requested. /// Signals whether shutdown has been requested.
bool shutting_down_ = false; bool shutting_down_ = false;
/// Keeps track of watched disposables.
std::vector<disposable> watched_;
private: private:
// -- constructors, destructors, and assignment operators --------------------
explicit multiplexer(middleman* parent);
// -- internal callbacks the pollset updater --------------------------------- // -- internal callbacks the pollset updater ---------------------------------
void do_shutdown(); void do_shutdown();
void do_register_reading(const socket_manager_ptr& mgr); void do_register_reading(const socket_manager_ptr& mgr);
void do_register_writing(const socket_manager_ptr& mgr); void do_start(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);
}; };
} // namespace caf::net } // namespace caf::net
...@@ -25,12 +25,7 @@ public: ...@@ -25,12 +25,7 @@ public:
using msg_buf = std::array<std::byte, sizeof(intptr_t) + 1>; using msg_buf = std::array<std::byte, sizeof(intptr_t) + 1>;
enum class code : uint8_t { enum class code : uint8_t {
register_reading, start_manager,
continue_reading,
register_writing,
continue_writing,
init_manager,
discard_manager,
shutdown_reading, shutdown_reading,
shutdown_writing, shutdown_writing,
run_action, run_action,
...@@ -47,20 +42,19 @@ public: ...@@ -47,20 +42,19 @@ public:
// -- implementation of socket_event_layer ----------------------------------- // -- 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; void handle_write_event() override;
write_result handle_write_event() override;
void abort(const error& reason) override; void abort(const error& reason) override;
private: private:
pipe_socket fd_; pipe_socket fd_;
socket_manager* owner_ = nullptr;
multiplexer* mpx_ = nullptr; multiplexer* mpx_ = nullptr;
msg_buf buf_; msg_buf buf_;
size_t buf_size_ = 0; 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> { ...@@ -37,6 +37,14 @@ struct CAF_NET_EXPORT socket : detail::comparable<socket> {
return static_cast<signed_socket_id>(id) return static_cast<signed_socket_id>(id)
- static_cast<signed_socket_id>(other.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 /// @relates socket
......
...@@ -16,64 +16,27 @@ class CAF_NET_EXPORT socket_event_layer { ...@@ -16,64 +16,27 @@ class CAF_NET_EXPORT socket_event_layer {
public: public:
virtual ~socket_event_layer(); virtual ~socket_event_layer();
/// Encodes how to proceed after a read operation. /// Starts processing on this layer.
enum class read_result { virtual error start(socket_manager* owner, const settings& cfg) = 0;
/// 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,
};
/// Encodes how to proceed after a write operation. /// Returns the handle for the managed socket.
enum class write_result { virtual socket handle() const = 0;
/// 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;
/// Handles a read event on the managed socket. /// Handles a read event on the managed socket.
virtual read_result handle_read_event() = 0; virtual void 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;
/// Handles a write event on the managed socket. /// 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. /// Called after returning `handover` from a read or write handler.
virtual bool do_handover(std::unique_ptr<socket_event_layer>& next); 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; 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 } // namespace caf::net
...@@ -44,6 +44,13 @@ public: ...@@ -44,6 +44,13 @@ public:
fd_ = x; fd_ = x;
} }
void reset() noexcept {
if (fd_.id != invalid_socket_id) {
close(fd_);
fd_.id = invalid_socket_id;
}
}
Socket release() noexcept { Socket release() noexcept {
auto sock = fd_; auto sock = fd_;
fd_.id = invalid_socket_id; fd_.id = invalid_socket_id;
......
...@@ -4,11 +4,14 @@ ...@@ -4,11 +4,14 @@
#pragma once #pragma once
#include "caf/action.hpp"
#include "caf/actor.hpp" #include "caf/actor.hpp"
#include "caf/actor_system.hpp" #include "caf/actor_system.hpp"
#include "caf/callback.hpp" #include "caf/callback.hpp"
#include "caf/detail/atomic_ref_counted.hpp"
#include "caf/detail/infer_actor_shell_ptr_type.hpp" #include "caf/detail/infer_actor_shell_ptr_type.hpp"
#include "caf/detail/net_export.hpp" #include "caf/detail/net_export.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/net/actor_shell.hpp" #include "caf/net/actor_shell.hpp"
...@@ -16,7 +19,6 @@ ...@@ -16,7 +19,6 @@
#include "caf/net/socket.hpp" #include "caf/net/socket.hpp"
#include "caf/net/socket_event_layer.hpp" #include "caf/net/socket_event_layer.hpp"
#include "caf/net/typed_actor_shell.hpp" #include "caf/net/typed_actor_shell.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp" #include "caf/sec.hpp"
#include <type_traits> #include <type_traits>
...@@ -24,27 +26,18 @@ ...@@ -24,27 +26,18 @@
namespace caf::net { namespace caf::net {
/// Manages the lifetime of a single socket and handles any I/O events on it. /// 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: public:
// -- member types ----------------------------------------------------------- // -- 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>; 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 -------------------- // -- constructors, destructors, and assignment operators --------------------
/// @pre `handle != invalid_socket` /// @pre `handle != invalid_socket`
/// @pre `mpx!= nullptr` /// @pre `mpx!= nullptr`
socket_manager(multiplexer* mpx, socket handle, event_handler_ptr handler); socket_manager(multiplexer* mpx, event_handler_ptr handler);
~socket_manager() override; ~socket_manager() override;
...@@ -54,28 +47,7 @@ public: ...@@ -54,28 +47,7 @@ public:
// -- factories -------------------------------------------------------------- // -- factories --------------------------------------------------------------
static socket_manager_ptr make(multiplexer* mpx, socket handle, static socket_manager_ptr make(multiplexer* mpx, event_handler_ptr handler);
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));
}
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
...@@ -112,92 +84,112 @@ public: ...@@ -112,92 +84,112 @@ public:
return mpx_; return mpx_;
} }
/// Returns whether the manager closed read operations on the socket. /// Queries whether the manager is registered for reading.
[[nodiscard]] bool read_closed() const noexcept { bool is_reading() const noexcept;
return flags_.read_closed;
}
/// Returns whether the manager closed write operations on the socket. /// Queries whether the manager is registered for writing.
[[nodiscard]] bool write_closed() const noexcept { bool is_writing() const noexcept;
return flags_.write_closed;
}
// -- event loop management -------------------------------------------------- // -- event loop management --------------------------------------------------
/// Registers the manager for read operations on the @ref multiplexer. /// Registers the manager for read operations.
/// @thread-safe
void register_reading(); void register_reading();
/// Registers the manager for write operations on the @ref multiplexer. /// Registers the manager for write operations.
/// @thread-safe
void register_writing(); void register_writing();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer. /// Deregisters the manager from read operations.
/// This mechanism allows users to signal changes in the environment to the void deregister_reading();
/// 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();
// -- callbacks for the multiplexer ------------------------------------------ /// Deregisters the manager from write operations.
void deregister_writing();
/// Closes the read channel of the socket. /// Deregisters the manager from both read and write operations.
void close_read() noexcept; void deregister();
/// Closes the write channel of the socket. /// Schedules a call to `fn` on the multiplexer when this socket manager
void close_write() noexcept; /// 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. // -- callbacks for the handler ----------------------------------------------
error init(const settings& cfg);
/// Called whenever the socket received new data. /// Schedules a call to `do_handover` on the handler.
read_result handle_read_event(); void schedule_handover();
/// Called after handovers to allow the manager to process any data that is /// Schedules @p what to run later.
/// already buffered at the transport policy and thus would not trigger a read void schedule(action what);
/// event on the socket.
read_result handle_buffered_data();
/// Restarts a socket manager that suspended reads. Calling this member /// Schedules @p what to run later.
/// function on active managers is a no-op. This function also should read any template <class F>
/// data buffered outside of the socket. void schedule_fn(F&& what) {
read_result handle_continue_reading(); 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. /// 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. /// Called when the remote side becomes unreachable due to an error or after
/// @param code The error code as reported by the operating system. /// calling @ref dispose.
/// @param code The error code as reported by the operating system or
/// @ref sec::disposed.
void handle_error(sec code); void handle_error(sec code);
/// Performs a handover to another transport after `handle_read_event` or // -- implementation of disposable_impl --------------------------------------
/// `handle_read_event` returned `handover`.
[[nodiscard]] bool do_handover(); void dispose() override;
bool disposed() const noexcept override;
void ref_disposable() const noexcept override;
void deref_disposable() const noexcept override;
private: private:
// -- utility functions ------------------------------------------------------
void cleanup();
socket_manager_ptr strong_this();
// -- member variables ------------------------------------------------------- // -- member variables -------------------------------------------------------
/// Stores the socket file descriptor. The socket manager automatically closes /// Stores the socket file descriptor. The socket manager automatically closes
/// the socket in its destructor. /// the socket in its destructor.
socket fd_; 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_; multiplexer* mpx_;
/// Stores flags for the socket file descriptor.
flags_t flags_;
/// Stores the event handler that operators on the socket file descriptor. /// Stores the event handler that operators on the socket file descriptor.
event_handler_ptr handler_; 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 /// @relates socket_manager
......
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
namespace caf::net::ssl { 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. /// SSL state, shared by multiple connections.
class CAF_NET_EXPORT context { class CAF_NET_EXPORT context {
public: public:
...@@ -71,8 +75,15 @@ public: ...@@ -71,8 +75,15 @@ public:
// -- connections ------------------------------------------------------------ // -- 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); 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 -------------------------------------------------- // -- certificates and keys --------------------------------------------------
/// Configure the context to use the default locations for loading CA /// Configure the context to use the default locations for loading CA
......
...@@ -21,14 +21,9 @@ public: ...@@ -21,14 +21,9 @@ public:
virtual ~upper_layer(); virtual ~upper_layer();
/// Initializes the 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 /// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer. /// lifetime of the upper layer.
virtual error virtual error start(lower_layer* down, const settings& config) = 0;
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
/// Consumes bytes from the lower layer. /// Consumes bytes from the lower layer.
/// @param buffer Available bytes to read. /// @param buffer Available bytes to read.
...@@ -45,6 +40,10 @@ class CAF_NET_EXPORT lower_layer : public generic_lower_layer { ...@@ -45,6 +40,10 @@ class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public: public:
virtual ~lower_layer(); 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 /// Configures threshold for the next receive operations. Policies remain
/// active until calling this function again. /// active until calling this function again.
/// @warning Calling this function in `consume` invalidates both byte spans. /// @warning Calling this function in `consume` invalidates both byte spans.
......
...@@ -85,6 +85,9 @@ public: ...@@ -85,6 +85,9 @@ public:
/// Stores whether we left a write handler due to want_read. /// Stores whether we left a write handler due to want_read.
bool wanted_write_from_read_event : 1; bool wanted_write_from_read_event : 1;
/// Stores whether the application has asked to shut down.
bool shutting_down : 1;
}; };
// -- constants -------------------------------------------------------------- // -- constants --------------------------------------------------------------
...@@ -118,9 +121,11 @@ public: ...@@ -118,9 +121,11 @@ public:
bool end_output() override; 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 ------------------------------------------------------------- // -- properties -------------------------------------------------------------
...@@ -150,19 +155,29 @@ public: ...@@ -150,19 +155,29 @@ public:
// -- implementation of socket_event_layer ----------------------------------- // -- 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; void handle_write_event() override;
read_result handle_continue_reading() override;
void abort(const error& reason) override; void abort(const error& reason) override;
bool finalized() const noexcept override;
protected: 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. /// The socket file descriptor.
stream_socket fd_; stream_socket fd_;
......
...@@ -24,8 +24,8 @@ struct CAF_NET_EXPORT tcp_accept_socket : network_socket { ...@@ -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. /// 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. /// @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 the address `0.0.0.0` will accept incoming connection
/// Passing port 0 lets the OS choose the port. /// from any host. Passing port 0 lets the OS choose the port.
/// @relates tcp_accept_socket /// @relates tcp_accept_socket
expected<tcp_accept_socket> expected<tcp_accept_socket>
CAF_NET_EXPORT make_tcp_accept_socket(ip_endpoint node, CAF_NET_EXPORT make_tcp_accept_socket(ip_endpoint node,
...@@ -33,14 +33,24 @@ expected<tcp_accept_socket> ...@@ -33,14 +33,24 @@ expected<tcp_accept_socket>
/// Creates a new TCP socket to accept connections on a given port. /// 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. /// @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 the address `0.0.0.0` will accept incoming connection
/// Passing port 0 lets the OS choose the port. /// from any host. Passing port 0 lets the OS choose the port.
/// @param reuse_addr Optionally sets the SO_REUSEADDR option on the socket. /// @param reuse_addr Optionally sets the SO_REUSEADDR option on the socket.
/// @relates tcp_accept_socket /// @relates tcp_accept_socket
expected<tcp_accept_socket> expected<tcp_accept_socket>
CAF_NET_EXPORT make_tcp_accept_socket(const uri::authority_type& node, CAF_NET_EXPORT make_tcp_accept_socket(const uri::authority_type& node,
bool reuse_addr = false); 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`. /// Accepts a connection on `x`.
/// @param x Listening endpoint. /// @param x Listening endpoint.
/// @returns The socket that handles the accepted connection on success, an /// @returns The socket that handles the accepted connection on success, an
......
...@@ -55,7 +55,7 @@ public: ...@@ -55,7 +55,7 @@ public:
template <class... Fs> template <class... Fs>
void set_behavior(Fs... fs) { void set_behavior(Fs... fs) {
auto new_bhvr = behavior_type{std::move(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 ------------------------------------ // -- overridden functions of local_actor ------------------------------------
...@@ -72,7 +72,9 @@ class typed_actor_shell_ptr { ...@@ -72,7 +72,9 @@ class typed_actor_shell_ptr {
public: public:
// -- friends ---------------------------------------------------------------- // -- 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 ----------------------------------------------------------- // -- member types -----------------------------------------------------------
...@@ -154,23 +156,3 @@ private: ...@@ -154,23 +156,3 @@ private:
}; };
} // namespace caf::net } // 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 @@ ...@@ -6,9 +6,10 @@
#include "caf/actor_system.hpp" #include "caf/actor_system.hpp"
#include "caf/cow_tuple.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/default_trait.hpp"
#include "caf/net/web_socket/flow_bridge.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/frame.hpp"
#include "caf/net/web_socket/request.hpp" #include "caf/net/web_socket/request.hpp"
#include "caf/net/web_socket/server.hpp" #include "caf/net/web_socket/server.hpp"
...@@ -18,29 +19,23 @@ ...@@ -18,29 +19,23 @@
namespace caf::detail { namespace caf::detail {
template <class Transport, class Trait> template <class Transport, class Trait>
class ws_acceptor_factory { class ws_acceptor_factory
: public net::connection_factory<typename Transport::socket_type> {
public: 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) explicit ws_acceptor_factory(connector_pointer connector)
: connector_(std::move(connector)) { : connector_(std::move(connector)) {
// nop // nop
} }
error init(net::socket_manager*, const settings&) { net::socket_manager_ptr make(net::multiplexer* mpx, socket_type fd) override {
return none; auto app = net::web_socket::flow_bridge<Trait>::make(mpx, connector_);
}
template <class Socket>
net::socket_manager_ptr make(net::multiplexer* mpx, Socket fd) {
auto app = net::web_socket::flow_bridge<Trait>::make(connector_);
auto ws = net::web_socket::server::make(std::move(app)); auto ws = net::web_socket::server::make(std::move(app));
auto transport = Transport::make(fd, std::move(ws)); auto transport = Transport::make(fd, std::move(ws));
return net::socket_manager::make(mpx, fd, std::move(transport)); return net::socket_manager::make(mpx, std::move(transport));
}
void abort(const error&) {
// nop
} }
private: private:
...@@ -79,28 +74,30 @@ auto make_accept_event_resources() { ...@@ -79,28 +74,30 @@ auto make_accept_event_resources() {
/// @param out A buffer resource that connects the server to a listener that /// @param out A buffer resource that connects the server to a listener that
/// processes the buffer pairs for each incoming connection. /// processes the buffer pairs for each incoming connection.
/// @param on_request Function object for accepting incoming requests. /// @param on_request Function object for accepting incoming requests.
/// @param limit The maximum amount of connections before closing @p fd. Passing /// @param cfg Configuration parameters for the acceptor.
/// 0 means "no limit".
template <class Transport = stream_transport, class Socket, class... Ts, template <class Transport = stream_transport, class Socket, class... Ts,
class OnRequest> class OnRequest>
void accept(actor_system& sys, Socket fd, acceptor_resource_t<Ts...> out, disposable accept(actor_system& sys, Socket fd, acceptor_resource_t<Ts...> out,
OnRequest on_request, size_t limit = 0) { OnRequest on_request, const settings& cfg = {}) {
using trait_t = default_trait; using trait_t = default_trait;
using request_t = request<default_trait, Ts...>; using request_t = request<default_trait, Ts...>;
static_assert(std::is_invocable_v<OnRequest, const settings&, request_t&>, static_assert(std::is_invocable_v<OnRequest, const settings&, request_t&>,
"invalid signature found for on_request"); "invalid signature found for on_request");
using factory_t = detail::ws_acceptor_factory<Transport, trait_t>; using factory_t = detail::ws_acceptor_factory<Transport, trait_t>;
using impl_t = connection_acceptor<Socket, factory_t>; using impl_t = connection_acceptor<Socket>;
using conn_t = flow_connector_impl<OnRequest, trait_t, Ts...>; 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()) { if (auto buf = out.try_open()) {
auto& mpx = sys.network_manager().mpx(); auto& mpx = sys.network_manager().mpx();
auto conn = std::make_shared<conn_t>(std::move(on_request), std::move(buf)); auto conn = std::make_shared<conn_t>(std::move(on_request), std::move(buf));
auto factory = factory_t{std::move(conn)}; auto factory = std::make_unique<factory_t>(std::move(conn));
auto impl = impl_t::make(fd, limit, std::move(factory)); auto impl = impl_t::make(fd, std::move(factory), max_connections);
auto ptr = socket_manager::make(&mpx, fd, std::move(impl)); auto ptr = socket_manager::make(&mpx, std::move(impl));
mpx.init(ptr); 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 } // namespace caf::net::web_socket
...@@ -27,7 +27,7 @@ namespace caf::net::web_socket { ...@@ -27,7 +27,7 @@ namespace caf::net::web_socket {
/// 6455. Initially, the layer performs the WebSocket handshake. Once completed, /// 6455. Initially, the layer performs the WebSocket handshake. Once completed,
/// this layer decodes RFC 6455 frames and forwards binary and text messages to /// this layer decodes RFC 6455 frames and forwards binary and text messages to
/// `UpperLayer`. /// `UpperLayer`.
class client : public stream_oriented::upper_layer { class CAF_NET_EXPORT client : public stream_oriented::upper_layer {
public: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
...@@ -43,6 +43,10 @@ public: ...@@ -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_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 ------------------------------------------------------------- // -- properties -------------------------------------------------------------
web_socket::upper_layer& upper_layer() noexcept { web_socket::upper_layer& upper_layer() noexcept {
...@@ -67,16 +71,14 @@ public: ...@@ -67,16 +71,14 @@ public:
// -- implementation of stream_oriented::upper_layer ------------------------- // -- implementation of stream_oriented::upper_layer -------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down, error start(stream_oriented::lower_layer* down,
const settings& config) override; const settings& config) override;
void abort(const error& reason) override; void abort(const error& reason) override;
ptrdiff_t consume(byte_span buffer, byte_span delta) override; ptrdiff_t consume(byte_span buffer, byte_span delta) override;
void continue_reading() override; void prepare_send() override;
bool prepare_send() override;
bool done_sending() override; bool done_sending() override;
...@@ -93,9 +95,6 @@ private: ...@@ -93,9 +95,6 @@ private:
/// Stores the upper layer. /// Stores the upper layer.
framing framing_; framing framing_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
settings cfg_; 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: ...@@ -32,6 +32,8 @@ public:
bool convert(const_byte_span bytes, input_type& x); bool convert(const_byte_span bytes, input_type& x);
bool convert(std::string_view text, input_type& x); bool convert(std::string_view text, input_type& x);
error last_error();
}; };
} // namespace caf::net::web_socket } // namespace caf::net::web_socket
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
#pragma once #pragma once
#include "caf/async/consumer_adapter.hpp"
#include "caf/async/producer_adapter.hpp"
#include "caf/async/spsc_buffer.hpp" #include "caf/async/spsc_buffer.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/net/consumer_adapter.hpp" #include "caf/net/flow_connector.hpp"
#include "caf/net/producer_adapter.hpp"
#include "caf/net/web_socket/flow_connector.hpp"
#include "caf/net/web_socket/lower_layer.hpp" #include "caf/net/web_socket/lower_layer.hpp"
#include "caf/net/web_socket/request.hpp" #include "caf/net/web_socket/request.hpp"
#include "caf/net/web_socket/upper_layer.hpp" #include "caf/net/web_socket/upper_layer.hpp"
...@@ -27,21 +27,24 @@ public: ...@@ -27,21 +27,24 @@ public:
using output_type = typename Trait::output_type; using output_type = typename Trait::output_type;
/// Type for the consumer adapter. We consume the output of the application. /// 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. /// 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 request_type = request<Trait>;
using connector_pointer = flow_connector_ptr<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 // nop
} }
static std::unique_ptr<flow_bridge> make(connector_pointer conn) { static std::unique_ptr<flow_bridge> make(async::execution_context_ptr loop,
return std::make_unique<flow_bridge>(std::move(conn)); connector_pointer conn) {
return std::make_unique<flow_bridge>(std::move(loop), std::move(conn));
} }
bool write(const output_type& item) { bool write(const output_type& item) {
...@@ -56,92 +59,77 @@ public: ...@@ -56,92 +59,77 @@ public:
} }
} }
struct write_helper { bool running() const noexcept {
flow_bridge* thisptr; return in_ || out_;
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
}
void on_error(const error& x) {
err = x;
} }
};
// -- implementation of web_socket::lower_layer ------------------------------ // -- implementation of web_socket::lower_layer ------------------------------
error init(net::socket_manager* mgr, web_socket::lower_layer* down, error start(web_socket::lower_layer* down, const settings& cfg) override {
const settings& cfg) override {
down_ = down; down_ = down;
auto [err, pull, push] = conn_->on_request(cfg); auto [err, pull, push] = conn_->on_request(cfg);
if (!err) { if (!err) {
in_ = consumer_type::try_open(mgr, pull); auto do_wakeup = make_action([this] {
out_ = producer_type::try_open(mgr, push); prepare_send();
CAF_ASSERT(in_ != nullptr); if (!running())
CAF_ASSERT(out_ != nullptr); 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; conn_ = nullptr;
if (running())
return none; return none;
else
return make_error(sec::runtime_error,
"cannot init flow bridge: no buffers");
} else { } else {
conn_ = nullptr; conn_ = nullptr;
return err; return err;
} }
} }
void continue_reading() override { void prepare_send() override {
// nop input_type tmp;
} while (down_->can_send_more()) {
switch (in_.pull(async::delay_errors, tmp)) {
bool prepare_send() override { case async::read_result::ok:
write_helper helper{this}; if (!write(tmp)) {
while (down_->can_send_more() && in_) { down_->shutdown(trait_.last_error());
auto [again, consumed] = in_->pull(async::delay_errors, 1, helper); return;
if (!again) {
if (helper.err) {
down_->send_close_message(helper.err);
} else {
down_->send_close_message();
} }
in_ = nullptr; break;
return false; case async::read_result::stop:
} else if (helper.aborted) { down_->shutdown();
in_->cancel(); break;
in_ = nullptr; case async::read_result::abort:
return false; down_->shutdown(in_.abort_reason());
} else if (consumed == 0) { break;
return true; default: // try later
return;
} }
} }
return true;
} }
bool done_sending() override { bool done_sending() override {
return !in_ || !in_->has_data(); return !in_.has_consumer_event();
} }
void abort(const error& reason) override { void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason)); CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) { if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed) if (reason == sec::connection_closed || reason == sec::socket_disconnected
out_->close(); || reason == sec::disposed)
out_.close();
else else
out_->abort(reason); out_.abort(reason);
out_ = nullptr;
}
if (in_) {
in_->cancel();
in_ = nullptr;
} }
in_.cancel();
} }
ptrdiff_t consume_binary(byte_span buf) override { ptrdiff_t consume_binary(byte_span buf) override {
...@@ -150,7 +138,7 @@ public: ...@@ -150,7 +138,7 @@ public:
input_type val; input_type val;
if (!trait_.convert(buf, val)) if (!trait_.convert(buf, val))
return -1; return -1;
if (out_->push(std::move(val)) == 0) if (out_.push(std::move(val)) == 0)
down_->suspend_reading(); down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size()); return static_cast<ptrdiff_t>(buf.size());
} }
...@@ -161,7 +149,7 @@ public: ...@@ -161,7 +149,7 @@ public:
input_type val; input_type val;
if (!trait_.convert(buf, val)) if (!trait_.convert(buf, val))
return -1; return -1;
if (out_->push(std::move(val)) == 0) if (out_.push(std::move(val)) == 0)
down_->suspend_reading(); down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size()); return static_cast<ptrdiff_t>(buf.size());
} }
...@@ -170,14 +158,17 @@ private: ...@@ -170,14 +158,17 @@ private:
web_socket::lower_layer* down_; web_socket::lower_layer* down_;
/// The output of the application. Serialized to the socket. /// 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. /// 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. /// Converts between raw bytes and native C++ objects.
Trait trait_; Trait trait_;
/// Runs callbacks in the I/O event loop.
async::execution_context_ptr loop_;
/// Initializes the bridge. Disposed (set to null) after initializing. /// Initializes the bridge. Disposed (set to null) after initializing.
connector_pointer conn_; connector_pointer conn_;
}; };
......
...@@ -4,37 +4,12 @@ ...@@ -4,37 +4,12 @@
#pragma once #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 { namespace caf::net::web_socket {
template <class Trait> /// Calls an `OnRequest` handler with a @ref request object and passes the
class flow_connector { /// generated buffers to the @ref flow_bridge.
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>>;
template <class OnRequest, class Trait, class... Ts> 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: public:
using super = flow_connector<Trait>; using super = flow_connector<Trait>;
...@@ -47,7 +22,7 @@ public: ...@@ -47,7 +22,7 @@ public:
using producer_type = async::blocking_producer<app_res_type>; using producer_type = async::blocking_producer<app_res_type>;
template <class T> 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)) { : on_request_(std::move(on_request)), out_(std::forward<T>(out)) {
// nop // nop
} }
......
...@@ -72,7 +72,6 @@ public: ...@@ -72,7 +72,6 @@ public:
std::string_view as_text() const noexcept; std::string_view as_text() const noexcept;
private:
class data { class data {
public: public:
data() = delete; data() = delete;
...@@ -133,6 +132,7 @@ private: ...@@ -133,6 +132,7 @@ private:
std::byte storage_[]; std::byte storage_[];
}; };
private:
explicit frame(intrusive_ptr<data> ptr) : data_(std::move(ptr)) { explicit frame(intrusive_ptr<data> ptr) : data_(std::move(ptr)) {
// nop // nop
} }
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
namespace caf::net::web_socket { namespace caf::net::web_socket {
/// Implements the WebSocket framing protocol as defined in RFC-6455. /// 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: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
...@@ -49,7 +49,7 @@ public: ...@@ -49,7 +49,7 @@ public:
// -- initialization --------------------------------------------------------- // -- initialization ---------------------------------------------------------
void init(socket_manager* owner, stream_oriented::lower_layer* down); void start(stream_oriented::lower_layer* down);
// -- properties ------------------------------------------------------------- // -- properties -------------------------------------------------------------
...@@ -77,11 +77,17 @@ public: ...@@ -77,11 +77,17 @@ public:
// -- web_socket::lower_layer implementation --------------------------------- // -- web_socket::lower_layer implementation ---------------------------------
using web_socket::lower_layer::shutdown;
bool can_send_more() const noexcept override; bool can_send_more() const noexcept override;
void suspend_reading() 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; void request_messages() override;
...@@ -97,17 +103,8 @@ public: ...@@ -97,17 +103,8 @@ public:
bool end_text_message() override; 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 ------------------------------------------ // -- 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); ptrdiff_t consume(byte_span input, byte_span);
private: private:
...@@ -117,10 +114,6 @@ private: ...@@ -117,10 +114,6 @@ private:
void ship_pong(byte_span payload); void ship_pong(byte_span payload);
void ship_close(uint16_t code, std::string_view msg);
void ship_close();
template <class T> template <class T>
void ship_frame(std::vector<T>& buf); void ship_frame(std::vector<T>& buf);
......
...@@ -6,11 +6,8 @@ ...@@ -6,11 +6,8 @@
namespace caf::net::web_socket { namespace caf::net::web_socket {
template <class Trait>
class flow_connector;
template <class OnRequest, class Trait, class... Ts> template <class OnRequest, class Trait, class... Ts>
class flow_connector_impl; class flow_connector_request_impl;
template <class Trait> template <class Trait>
class flow_bridge; class flow_bridge;
......
...@@ -22,6 +22,9 @@ public: ...@@ -22,6 +22,9 @@ public:
/// Pulls messages from the transport until calling `suspend_reading`. /// Pulls messages from the transport until calling `suspend_reading`.
virtual void request_messages() = 0; virtual void request_messages() = 0;
/// Stops reading messages until calling `request_messages`.
virtual void suspend_reading() = 0;
/// Begins transmission of a binary message. /// Begins transmission of a binary message.
virtual void begin_binary_message() = 0; virtual void begin_binary_message() = 0;
...@@ -42,14 +45,12 @@ public: ...@@ -42,14 +45,12 @@ public:
/// Seals the current text message buffer and ships a new WebSocket frame. /// Seals the current text message buffer and ships a new WebSocket frame.
virtual bool end_text_message() = 0; virtual bool end_text_message() = 0;
/// Sends the close message with @ref status `normal_close`. void shutdown() override;
virtual void send_close_message() = 0;
/// Sends the close message with an custom @ref status. void shutdown(const error& reason) override;
virtual void send_close_message(status code, std::string_view desc) = 0;
/// Sends the close message with @ref status `unexpected_condition`. /// Sends a shutdown message with custom @ref status @p code and @p msg text.
void send_close_message(const error& reason); virtual void shutdown(status code, std::string_view msg) = 0;
}; };
} // namespace caf::net::web_socket } // namespace caf::net::web_socket
...@@ -19,7 +19,7 @@ template <class Trait, class... Ts> ...@@ -19,7 +19,7 @@ template <class Trait, class... Ts>
class request { class request {
public: public:
template <class, class, class...> template <class, class, class...>
friend class flow_connector_impl; friend class flow_connector_request_impl;
using input_type = typename Trait::input_type; using input_type = typename Trait::input_type;
......
...@@ -74,16 +74,14 @@ public: ...@@ -74,16 +74,14 @@ public:
// -- stream_oriented::upper_layer implementation ---------------------------- // -- stream_oriented::upper_layer implementation ----------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down, error start(stream_oriented::lower_layer* down,
const settings& config) override; const settings& config) override;
void abort(const error& reason) override; void abort(const error& reason) override;
ptrdiff_t consume(byte_span input, byte_span) override; ptrdiff_t consume(byte_span input, byte_span) override;
void continue_reading() override; void prepare_send() override;
bool prepare_send() override;
bool done_sending() override; bool done_sending() override;
...@@ -100,9 +98,6 @@ private: ...@@ -100,9 +98,6 @@ private:
/// Stores the upper layer. /// Stores the upper layer.
framing framing_; 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 /// 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 /// layer until the handshake completed. We also fill this dictionary with the
/// contents of the HTTP GET header. /// contents of the HTTP GET header.
......
...@@ -16,9 +16,7 @@ class CAF_NET_EXPORT upper_layer : public generic_upper_layer { ...@@ -16,9 +16,7 @@ class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public: public:
virtual ~upper_layer(); virtual ~upper_layer();
virtual error virtual error start(lower_layer* down, const settings& cfg) = 0;
init(net::socket_manager* mgr, lower_layer* down, const settings& cfg)
= 0;
virtual ptrdiff_t consume_binary(byte_span buf) = 0; virtual ptrdiff_t consume_binary(byte_span buf) = 0;
virtual ptrdiff_t consume_text(std::string_view buf) = 0; virtual ptrdiff_t consume_text(std::string_view buf) = 0;
}; };
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "caf/net/abstract_actor_shell.hpp" #include "caf/net/abstract_actor_shell.hpp"
#include "caf/action.hpp"
#include "caf/callback.hpp" #include "caf/callback.hpp"
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/detail/default_invoke_result_visitor.hpp" #include "caf/detail/default_invoke_result_visitor.hpp"
...@@ -18,9 +19,15 @@ namespace caf::net { ...@@ -18,9 +19,15 @@ namespace caf::net {
// -- constructors, destructors, and assignment operators ---------------------- // -- constructors, destructors, and assignment operators ----------------------
abstract_actor_shell::abstract_actor_shell(actor_config& cfg, abstract_actor_shell::abstract_actor_shell(actor_config& cfg,
socket_manager* owner) async::execution_context_ptr loop)
: super(cfg), mailbox_(policy::normal_messages{}), owner_(owner) { : super(cfg), mailbox_(policy::normal_messages{}), loop_(loop) {
mailbox_.try_block(); mailbox_.try_block();
resume_ = make_action([this] {
for (;;) {
if (!consume_message() && try_block_mailbox())
return;
}
});
} }
abstract_actor_shell::~abstract_actor_shell() { abstract_actor_shell::~abstract_actor_shell() {
...@@ -117,13 +124,13 @@ bool abstract_actor_shell::enqueue(mailbox_element_ptr ptr, execution_unit*) { ...@@ -117,13 +124,13 @@ bool abstract_actor_shell::enqueue(mailbox_element_ptr ptr, execution_unit*) {
switch (mailbox().push_back(std::move(ptr))) { switch (mailbox().push_back(std::move(ptr))) {
case intrusive::inbox_result::unblocked_reader: { case intrusive::inbox_result::unblocked_reader: {
CAF_LOG_ACCEPT_EVENT(true); CAF_LOG_ACCEPT_EVENT(true);
std::unique_lock<std::mutex> guard{owner_mtx_}; std::unique_lock<std::mutex> guard{loop_mtx_};
// The owner can only be null if this enqueue succeeds, then we close the // The loop can only be null if this enqueue succeeds, then we close the
// mailbox and reset owner_ in cleanup() before acquiring the mutex here. // mailbox and reset loop_ in cleanup() before acquiring the mutex here.
// Hence, the mailbox element has already been disposed and we can simply // Hence, the mailbox element has already been disposed and we can simply
// skip any further processing. // skip any further processing.
if (owner_) if (loop_)
owner_->mpx().register_writing(owner_); loop_->schedule(resume_);
return true; return true;
} }
case intrusive::inbox_result::success: case intrusive::inbox_result::success:
...@@ -175,8 +182,9 @@ bool abstract_actor_shell::cleanup(error&& fail_state, execution_unit* host) { ...@@ -175,8 +182,9 @@ bool abstract_actor_shell::cleanup(error&& fail_state, execution_unit* host) {
} }
// Detach from owner. // Detach from owner.
{ {
std::unique_lock<std::mutex> guard{owner_mtx_}; std::unique_lock<std::mutex> guard{loop_mtx_};
owner_ = nullptr; loop_ = nullptr;
resume_.dispose();
} }
// Dispatch to parent's `cleanup` function. // Dispatch to parent's `cleanup` function.
return super::cleanup(std::move(fail_state), host); return super::cleanup(std::move(fail_state), host);
......
...@@ -8,8 +8,8 @@ namespace caf::net { ...@@ -8,8 +8,8 @@ namespace caf::net {
// -- actor_shell -------------------------------------------------------------- // -- actor_shell --------------------------------------------------------------
actor_shell::actor_shell(actor_config& cfg, socket_manager* owner) actor_shell::actor_shell(actor_config& cfg, async::execution_context_ptr loop)
: super(cfg, owner) { : super(cfg, loop) {
// nop // 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 @@ ...@@ -2,16 +2,12 @@
// the main distribution directory for license terms and copyright or visit // the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE. // 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 { namespace caf::net::binary {
upper_layer::~upper_layer() {
// nop
}
lower_layer::~lower_layer() { lower_layer::~lower_layer() {
// nop // 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() { ...@@ -10,4 +10,8 @@ generic_lower_layer::~generic_lower_layer() {
// nop // nop
} }
void generic_lower_layer::shutdown(const error&) {
shutdown();
}
} // namespace caf::net } // namespace caf::net
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
#include "caf/net/http/lower_layer.hpp" #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/header_fields_map.hpp"
#include "caf/net/http/status.hpp" #include "caf/net/http/status.hpp"
...@@ -19,14 +18,18 @@ lower_layer::~lower_layer() { ...@@ -19,14 +18,18 @@ lower_layer::~lower_layer() {
// nop // nop
} }
bool lower_layer::send_response(context ctx, status code, bool lower_layer::send_response(status code, std::string_view content_type,
std::string_view content_type,
const_byte_span content) { const_byte_span content) {
auto content_size = std::to_string(content.size()); auto content_size = std::to_string(content.size());
header_fields_map fields; begin_header(code);
fields.emplace("Content-Type"sv, content_type); add_header_field("Content-Type"sv, content_type);
fields.emplace("Content-Length"sv, content_size); add_header_field("Content-Length"sv, content_size);
return send_header(ctx, code, fields) && send_payload(ctx, content); 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 } // 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 { ...@@ -14,23 +14,41 @@ bool server::can_send_more() const noexcept {
return down_->can_send_more(); return down_->can_send_more();
} }
void server::suspend_reading() { bool server::is_reading() const noexcept {
return down_->suspend_reading(); return down_->is_reading();
}
void server::write_later() {
down_->write_later();
}
void server::shutdown() {
down_->shutdown();
}
void server::request_messages() {
if (!down_->is_reading())
down_->configure_read(receive_policy::up_to(max_request_size_));
} }
bool server::stopped_reading() const noexcept { void server::suspend_reading() {
return down_->stopped_reading(); down_->configure_read(receive_policy::stop());
} }
bool server::send_header(context, status code, void server::begin_header(status code) {
const header_fields_map& fields) {
down_->begin_output(); down_->begin_output();
v1::write_header(code, fields, down_->output_buffer()); v1::begin_header(code, down_->output_buffer());
down_->end_output();
return true;
} }
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(); down_->begin_output();
auto& buf = down_->output_buffer(); auto& buf = down_->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end()); buf.insert(buf.end(), bytes.begin(), bytes.end());
...@@ -38,7 +56,7 @@ bool server::send_payload(context, const_byte_span bytes) { ...@@ -38,7 +56,7 @@ bool server::send_payload(context, const_byte_span bytes) {
return true; return true;
} }
bool server::send_chunk(context, const_byte_span bytes) { bool server::send_chunk(const_byte_span bytes) {
down_->begin_output(); down_->begin_output();
auto& buf = down_->output_buffer(); auto& buf = down_->output_buffer();
auto size = bytes.size(); auto size = bytes.size();
...@@ -60,20 +78,15 @@ bool server::send_end_of_chunks() { ...@@ -60,20 +78,15 @@ bool server::send_end_of_chunks() {
return down_->end_output(); return down_->end_output();
} }
void server::fin(context) {
// nop
}
// -- stream_oriented::upper_layer implementation ------------------------------ // -- stream_oriented::upper_layer implementation ------------------------------
error server::init(socket_manager* owner, stream_oriented::lower_layer* down, error server::start(stream_oriented::lower_layer* down, const settings& cfg) {
const settings& cfg) {
down_ = down; 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")) if (auto max_size = get_as<uint32_t>(cfg, "http.max-request-size"))
max_request_size_ = *max_size; max_request_size_ = *max_size;
down_->configure_read(receive_policy::up_to(max_request_size_)); if (auto err = up_->start(this, cfg))
return err;
else
return none; return none;
} }
...@@ -81,18 +94,14 @@ void server::abort(const error& reason) { ...@@ -81,18 +94,14 @@ void server::abort(const error& reason) {
up_->abort(reason); up_->abort(reason);
} }
bool server::prepare_send() { void server::prepare_send() {
return up_->prepare_send(); up_->prepare_send();
} }
bool server::done_sending() { bool server::done_sending() {
return up_->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) { ptrdiff_t server::consume(byte_span input, byte_span) {
using namespace literals; using namespace literals;
CAF_LOG_TRACE(CAF_ARG2("bytes", input.size())); CAF_LOG_TRACE(CAF_ARG2("bytes", input.size()));
...@@ -174,7 +183,7 @@ void server::write_response(status code, std::string_view content) { ...@@ -174,7 +183,7 @@ void server::write_response(status code, std::string_view content) {
} }
bool server::invoke_upper_layer(const_byte_span payload) { 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) { bool server::handle_header(std::string_view http) {
......
...@@ -61,6 +61,24 @@ void write_header(status code, const header_fields_map& fields, ...@@ -61,6 +61,24 @@ void write_header(status code, const header_fields_map& fields,
out << "\r\n"sv; 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, void write_response(status code, std::string_view content_type,
std::string_view content, byte_buffer& buf) { std::string_view content, byte_buffer& buf) {
header_fields_map fields; header_fields_map fields;
......
...@@ -24,11 +24,10 @@ length_prefix_framing::make(upper_layer_ptr up) { ...@@ -24,11 +24,10 @@ length_prefix_framing::make(upper_layer_ptr up) {
// -- implementation of stream_oriented::upper_layer --------------------------- // -- implementation of stream_oriented::upper_layer ---------------------------
error length_prefix_framing::init(socket_manager* owner, error length_prefix_framing::start(stream_oriented::lower_layer* down,
stream_oriented::lower_layer* down,
const settings& cfg) { const settings& cfg) {
down_ = down; down_ = down;
return up_->init(owner, this, cfg); return up_->start(this, cfg);
} }
void length_prefix_framing::abort(const error& reason) { void length_prefix_framing::abort(const error& reason) {
...@@ -36,7 +35,7 @@ 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) { 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)) { if (input.size() < sizeof(uint32_t)) {
CAF_LOG_ERROR("received too few bytes from underlying transport"); CAF_LOG_ERROR("received too few bytes from underlying transport");
up_->abort(make_error(sec::logic_error, up_->abort(make_error(sec::logic_error,
...@@ -48,8 +47,10 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) { ...@@ -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)); auto msg_size = static_cast<size_t>(detail::from_network_order(u32_size));
if (msg_size == 0) { if (msg_size == 0) {
// Ignore empty messages. // Ignore empty messages.
CAF_LOG_DEBUG("received empty message"); CAF_LOG_ERROR("received empty message");
return static_cast<ptrdiff_t>(input.size()); up_->abort(make_error(sec::logic_error,
"received empty buffer from stream layer"));
return -1;
} else if (msg_size > max_message_length) { } else if (msg_size > max_message_length) {
CAF_LOG_DEBUG("exceeded maximum message size"); CAF_LOG_DEBUG("exceeded maximum message size");
up_->abort( up_->abort(
...@@ -65,7 +66,7 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) { ...@@ -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()) { if (msg_size == msg.size() && msg_size + hdr_size == input.size()) {
CAF_LOG_DEBUG("got message of size" << msg_size); CAF_LOG_DEBUG("got message of size" << msg_size);
if (up_->consume(msg) >= 0) { if (up_->consume(msg) >= 0) {
if (!down_->stopped_reading()) if (down_->is_reading())
down_->configure_read(receive_policy::exactly(hdr_size)); down_->configure_read(receive_policy::exactly(hdr_size));
return static_cast<ptrdiff_t>(input.size()); return static_cast<ptrdiff_t>(input.size());
} else { } else {
...@@ -79,34 +80,34 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) { ...@@ -79,34 +80,34 @@ ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) {
} }
} }
void length_prefix_framing::continue_reading() { void length_prefix_framing::prepare_send() {
up_->continue_reading(); up_->prepare_send();
}
bool length_prefix_framing::prepare_send() {
return up_->prepare_send();
} }
bool length_prefix_framing::done_sending() { bool length_prefix_framing::done_sending() {
return up_->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 { bool length_prefix_framing::can_send_more() const noexcept {
return down_->can_send_more(); return down_->can_send_more();
} }
void length_prefix_framing::suspend_reading() { void length_prefix_framing::suspend_reading() {
down_->suspend_reading(); down_->configure_read(receive_policy::stop());
} }
bool length_prefix_framing::stopped_reading() const noexcept { bool length_prefix_framing::is_reading() const noexcept {
return down_->stopped_reading(); return down_->is_reading();
}
void length_prefix_framing::write_later() {
down_->write_later();
} }
void length_prefix_framing::request_messages() { void length_prefix_framing::request_messages() {
if (down_->stopped_reading()) if (!down_->is_reading())
down_->configure_read(receive_policy::exactly(hdr_size)); down_->configure_read(receive_policy::exactly(hdr_size));
} }
...@@ -139,12 +140,8 @@ bool length_prefix_framing::end_message() { ...@@ -139,12 +140,8 @@ bool length_prefix_framing::end_message() {
} }
} }
void length_prefix_framing::send_close_message() { void length_prefix_framing::shutdown() {
// nop: this layer has no close message down_->shutdown();
}
void length_prefix_framing::send_close_message(const error&) {
// nop: this layer has no close message
} }
// -- utility functions ------------------------------------------------------ // -- utility functions ------------------------------------------------------
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
#include "caf/actor_system_config.hpp" #include "caf/actor_system_config.hpp"
#include "caf/detail/set_thread_name.hpp" #include "caf/detail/set_thread_name.hpp"
#include "caf/expected.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/raise_error.hpp"
#include "caf/sec.hpp" #include "caf/sec.hpp"
#include "caf/send.hpp" #include "caf/send.hpp"
...@@ -14,11 +17,46 @@ ...@@ -14,11 +17,46 @@
namespace caf::net { 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() { void middleman::init_global_meta_objects() {
// nop // nop
} }
middleman::middleman(actor_system& sys) : sys_(sys), mpx_(this) { middleman::middleman(actor_system& sys)
: sys_(sys), mpx_(multiplexer::make(this)) {
// nop // nop
} }
...@@ -29,26 +67,27 @@ middleman::~middleman() { ...@@ -29,26 +67,27 @@ middleman::~middleman() {
void middleman::start() { void middleman::start() {
if (!get_or(config(), "caf.middleman.manual-multiplexing", false)) { if (!get_or(config(), "caf.middleman.manual-multiplexing", false)) {
mpx_thread_ = sys_.launch_thread("caf.net.mpx", [this] { mpx_thread_ = sys_.launch_thread("caf.net.mpx", [this] {
mpx_.set_thread_id(); mpx_->set_thread_id();
mpx_.run(); launch_background_tasks(sys_);
mpx_->run();
}); });
} else { } else {
mpx_.set_thread_id(); mpx_->set_thread_id();
} }
} }
void middleman::stop() { void middleman::stop() {
mpx_.shutdown(); mpx_->shutdown();
if (mpx_thread_.joinable()) if (mpx_thread_.joinable())
mpx_thread_.join(); mpx_thread_.join();
else else
mpx_.run(); mpx_->run();
} }
void middleman::init(actor_system_config& cfg) { void middleman::init(actor_system_config& cfg) {
if (auto err = mpx_.init()) { if (auto err = mpx_->init()) {
CAF_LOG_ERROR("mpx_.init() failed: " << err); CAF_LOG_ERROR("mpx_->init() failed: " << err);
CAF_RAISE_ERROR("mpx_.init() failed"); CAF_RAISE_ERROR("mpx_->init() failed");
} }
if (auto node_uri = get_if<uri>(&cfg, "caf.middleman.this-node")) { if (auto node_uri = get_if<uri>(&cfg, "caf.middleman.this-node")) {
auto this_node = make_node_id(std::move(*node_uri)); auto this_node = make_node_id(std::move(*node_uri));
...@@ -85,6 +124,9 @@ void middleman::add_module_options(actor_system_config& cfg) { ...@@ -85,6 +124,9 @@ void middleman::add_module_options(actor_system_config& cfg) {
"max. time between messages before declaring a node dead " "max. time between messages before declaring a node dead "
"(disabled if 0, ignored if heartbeats are disabled)") "(disabled if 0, ignored if heartbeats are disabled)")
.add<std::string>("network-backend", "legacy option"); .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 } // namespace caf::net
...@@ -57,13 +57,8 @@ const short output_mask = POLLOUT; ...@@ -57,13 +57,8 @@ const short output_mask = POLLOUT;
// | (is_writing(mask) ? output_mask : 0)); // | (is_writing(mask) ? output_mask : 0));
// } // }
operation to_operation(const socket_manager_ptr& mgr, operation to_operation(const socket_manager_ptr&, std::optional<short> mask) {
std::optional<short> mask) {
operation res = operation::none; operation res = operation::none;
if (mgr->read_closed())
res = block_reads(res);
if (mgr->write_closed())
res = block_writes(res);
if (mask) { if (mask) {
if ((*mask & input_mask) != 0) if ((*mask & input_mask) != 0)
res = add_read_flag(res); res = add_read_flag(res);
...@@ -115,9 +110,8 @@ error multiplexer::init() { ...@@ -115,9 +110,8 @@ error multiplexer::init() {
if (!pipe_handles) if (!pipe_handles)
return std::move(pipe_handles.error()); return std::move(pipe_handles.error());
auto updater = pollset_updater::make(pipe_handles->first); auto updater = pollset_updater::make(pipe_handles->first);
auto mgr = socket_manager::make(this, pipe_handles->first, auto mgr = socket_manager::make(this, std::move(updater));
std::move(updater)); if (auto err = mgr->start(settings{}))
if (auto err = mgr->init(settings{}))
return err; return err;
write_handle_ = pipe_handles->second; write_handle_ = pipe_handles->second;
pollset_.emplace_back(pollfd{pipe_handles->first.id, input_mask, 0}); pollset_.emplace_back(pollfd{pipe_handles->first.id, input_mask, 0});
...@@ -131,17 +125,18 @@ size_t multiplexer::num_socket_managers() const noexcept { ...@@ -131,17 +125,18 @@ size_t multiplexer::num_socket_managers() const noexcept {
return managers_.size(); 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 first = managers_.begin();
auto last = managers_.end(); auto last = managers_.end();
auto i = std::find(first, last, mgr); auto i = std::find(first, last, mgr);
return i == last ? -1 : std::distance(first, i); 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 first = pollset_.begin();
auto last = pollset_.end(); 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); return i == last ? -1 : std::distance(first, i);
} }
...@@ -164,91 +159,80 @@ operation multiplexer::mask_of(const socket_manager_ptr& mgr) { ...@@ -164,91 +159,80 @@ operation multiplexer::mask_of(const socket_manager_ptr& mgr) {
return to_operation(mgr, std::nullopt); return to_operation(mgr, std::nullopt);
} }
// -- thread-safe signaling ---------------------------------------------------- // -- implementation of execution_context --------------------------------------
void multiplexer::register_reading(const socket_manager_ptr& mgr) { void multiplexer::ref_execution_context() const noexcept {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id)); ref();
if (std::this_thread::get_id() == tid_) {
do_register_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::register_reading, mgr.get());
}
} }
void multiplexer::register_writing(const socket_manager_ptr& mgr) { void multiplexer::deref_execution_context() const noexcept {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id)); deref();
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::continue_reading(const socket_manager_ptr& mgr) { void multiplexer::schedule(action what) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id)); CAF_LOG_TRACE("");
write_to_pipe(pollset_updater::code::continue_reading, mgr.get()); auto ptr = std::move(what).as_intrusive_ptr().release();
write_to_pipe(pollset_updater::code::run_action, ptr);
} }
void multiplexer::continue_writing(const socket_manager_ptr& mgr) { void multiplexer::watch(disposable what) {
watched_.emplace_back(what);
}
// -- thread-safe signaling ----------------------------------------------------
void multiplexer::start(socket_manager_ptr mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id)); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
if (std::this_thread::get_id() == tid_) { if (std::this_thread::get_id() == tid_) {
do_continue_writing(mgr); do_start(mgr);
} else { } 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)); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) { update_for(mgr).events |= input_mask;
do_discard(mgr);
} else {
write_to_pipe(pollset_updater::code::discard_manager, mgr.get());
}
} }
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)); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) { update_for(mgr).events |= output_mask;
do_shutdown_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::shutdown_reading, mgr.get());
}
} }
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)); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) { update_for(mgr).events &= ~input_mask;
do_shutdown_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::shutdown_writing, mgr.get());
}
} }
void multiplexer::schedule(const action& what) { void multiplexer::deregister_writing(socket_manager* mgr) {
CAF_LOG_TRACE(""); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
write_to_pipe(pollset_updater::code::run_action, what.ptr()); 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)); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) { update_for(mgr).events = 0;
do_init(mgr);
} else {
write_to_pipe(pollset_updater::code::init_manager, mgr.get());
}
} }
void multiplexer::shutdown() { bool multiplexer::is_reading(const socket_manager* mgr) const noexcept {
CAF_LOG_TRACE(""); return (active_mask_of(mgr) & input_mask) != 0;
// 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. bool multiplexer::is_writing(const socket_manager* mgr) const noexcept {
CAF_LOG_DEBUG("push shutdown event to pipe"); return (active_mask_of(mgr) & output_mask) != 0;
write_to_pipe(pollset_updater::code::shutdown,
static_cast<socket_manager*>(nullptr));
} }
// -- control flow ------------------------------------------------------------- // -- control flow -------------------------------------------------------------
...@@ -271,7 +255,6 @@ bool multiplexer::poll_once(bool blocking) { ...@@ -271,7 +255,6 @@ bool multiplexer::poll_once(bool blocking) {
CAF_LOG_DEBUG("poll() on" << pollset_.size() << "sockets reported" CAF_LOG_DEBUG("poll() on" << pollset_.size() << "sockets reported"
<< presult << "event(s)"); << presult << "event(s)");
// Scan pollset for events. // Scan pollset for events.
CAF_LOG_DEBUG("scan pollset for socket events");
if (auto revents = pollset_[0].revents; revents != 0) { if (auto revents = pollset_[0].revents; revents != 0) {
// Index 0 is always the pollset updater. This is the only handler that // 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 // is allowed to modify pollset_ and managers_. Since this may very well
...@@ -280,6 +263,7 @@ bool multiplexer::poll_once(bool blocking) { ...@@ -280,6 +263,7 @@ bool multiplexer::poll_once(bool blocking) {
handle(mgr, pollset_[0].events, revents); handle(mgr, pollset_[0].events, revents);
--presult; --presult;
} }
apply_updates();
for (size_t i = 1; i < pollset_.size() && presult > 0; ++i) { for (size_t i = 1; i < pollset_.size() && presult > 0; ++i) {
if (auto revents = pollset_[i].revents; revents != 0) { if (auto revents = pollset_[i].revents; revents != 0) {
handle(managers_[i], pollset_[i].events, revents); handle(managers_[i], pollset_[i].events, revents);
...@@ -352,12 +336,16 @@ void multiplexer::set_thread_id() { ...@@ -352,12 +336,16 @@ void multiplexer::set_thread_id() {
void multiplexer::run() { void multiplexer::run() {
CAF_LOG_TRACE(""); 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 // 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) // need to block the signal at thread level since some APIs (such as OpenSSL)
// are unsafe to call otherwise. // are unsafe to call otherwise.
block_sigpipe(); block_sigpipe();
while (!shutting_down_ || pollset_.size() > 1) while (!shutting_down_ || pollset_.size() > 1 || !watched_.empty()) {
poll_once(true); poll_once(true);
disposable::erase_disposed(watched_);
}
// Close the pipe to block any future event. // Close the pipe to block any future event.
std::lock_guard<std::mutex> guard{write_lock_}; std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket) { if (write_handle_ != invalid_socket) {
...@@ -374,45 +362,19 @@ void multiplexer::handle(const socket_manager_ptr& mgr, ...@@ -374,45 +362,19 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
<< CAF_ARG(events) << CAF_ARG(revents)); << CAF_ARG(events) << CAF_ARG(revents));
CAF_ASSERT(mgr != nullptr); CAF_ASSERT(mgr != nullptr);
bool checkerror = true; 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 // Note: we double-check whether the manager is actually reading because a
// previous action from the pipe may have called shutdown_reading. // previous action from the pipe may have disabled reading.
if ((events & revents & input_mask) != 0) { if ((revents & input_mask) != 0 && is_reading(mgr.get())) {
checkerror = false; checkerror = false;
switch (mgr->handle_read_event()) { 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;
}
}
} }
// Similar reasoning than before: double-check whether this event should still // Similar reasoning than before: double-check whether this event should still
// get dispatched. // get dispatched.
if ((events & revents & output_mask) != 0) { if ((revents & output_mask) != 0 && is_writing(mgr.get())) {
checkerror = false; checkerror = false;
switch (mgr->handle_write_event()) { 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;
}
} }
if (checkerror && ((revents & error_mask) != 0)) { if (checkerror && ((revents & error_mask) != 0)) {
if (revents & POLLNVAL) if (revents & POLLNVAL)
...@@ -421,38 +383,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr, ...@@ -421,38 +383,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
mgr->handle_error(sec::socket_disconnected); mgr->handle_error(sec::socket_disconnected);
else else
mgr->handle_error(sec::socket_operation_failed); mgr->handle_error(sec::socket_operation_failed);
update_for(mgr).events = 0; update_for(mgr.get()).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);
}
}
}
} }
} }
...@@ -467,14 +398,13 @@ multiplexer::poll_update& multiplexer::update_for(ptrdiff_t index) { ...@@ -467,14 +398,13 @@ multiplexer::poll_update& multiplexer::update_for(ptrdiff_t index) {
} }
} }
multiplexer::poll_update& multiplexer::poll_update& multiplexer::update_for(socket_manager* mgr) {
multiplexer::update_for(const socket_manager_ptr& mgr) {
auto fd = mgr->handle(); auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) { if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second; return i->second;
} else if (auto index = index_of(fd); index != -1) { } else if (auto index = index_of(fd); index != -1) {
updates_.container().emplace_back(fd, updates_.container().emplace_back(fd, poll_update{pollset_[index].events,
poll_update{pollset_[index].events, mgr}); socket_manager_ptr{mgr}});
return updates_.container().back().second; return updates_.container().back().second;
} else { } else {
updates_.container().emplace_back(fd, poll_update{0, mgr}); updates_.container().emplace_back(fd, poll_update{0, mgr});
...@@ -485,9 +415,7 @@ multiplexer::update_for(const socket_manager_ptr& mgr) { ...@@ -485,9 +415,7 @@ multiplexer::update_for(const socket_manager_ptr& mgr) {
template <class T> template <class T>
void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) { void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
pollset_updater::msg_buf buf; pollset_updater::msg_buf buf;
if (ptr) { // Note: no intrusive_ptr_add_ref(ptr) since we take ownership of `ptr`.
intrusive_ptr_add_ref(ptr);
}
buf[0] = static_cast<std::byte>(opcode); buf[0] = static_cast<std::byte>(opcode);
auto value = reinterpret_cast<intptr_t>(ptr); auto value = reinterpret_cast<intptr_t>(ptr);
memcpy(buf.data() + 1, &value, sizeof(intptr_t)); memcpy(buf.data() + 1, &value, sizeof(intptr_t));
...@@ -501,7 +429,7 @@ void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) { ...@@ -501,7 +429,7 @@ void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
intrusive_ptr_release(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(); auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) { if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second.events; return i->second.events;
...@@ -512,14 +440,6 @@ short multiplexer::active_mask_of(const socket_manager_ptr& mgr) { ...@@ -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 ----------------------------------- // -- internal callbacks the pollset updater -----------------------------------
void multiplexer::do_shutdown() { void multiplexer::do_shutdown() {
...@@ -529,96 +449,25 @@ void multiplexer::do_shutdown() { ...@@ -529,96 +449,25 @@ void multiplexer::do_shutdown() {
shutting_down_ = true; shutting_down_ = true;
apply_updates(); apply_updates();
// Skip the first manager (the pollset updater). // Skip the first manager (the pollset updater).
for (size_t i = 1; i < managers_.size(); ++i) { for (size_t i = 1; i < managers_.size(); ++i)
auto& mgr = managers_[i]; managers_[i]->dispose();
mgr->close_read();
update_for(static_cast<ptrdiff_t>(i)).events &= ~input_mask;
}
apply_updates(); apply_updates();
} }
void multiplexer::do_register_reading(const socket_manager_ptr& mgr) { void multiplexer::do_start(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) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id)); CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_) { if (!shutting_down_) {
error err; error err;
if (owner_) if (owner_)
err = mgr->init(content(system().config())); err = mgr->start(content(system().config()));
else else
err = mgr->init(settings{}); err = mgr->start(settings{});
if (err) { if (err) {
CAF_LOG_DEBUG("mgr->init failed: " << err); CAF_LOG_DEBUG("mgr->init failed: " << err);
// The socket manager should not register itself for any events if // The socket manager should not register itself for any events if
// initialization fails. Purge any state just in case. // 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) { ...@@ -29,13 +29,18 @@ std::unique_ptr<pollset_updater> pollset_updater::make(pipe_socket fd) {
// -- interface functions ------------------------------------------------------ // -- interface functions ------------------------------------------------------
error pollset_updater::init(socket_manager* owner, const settings&) { error pollset_updater::start(socket_manager* owner, const settings&) {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
owner_ = owner;
mpx_ = owner->mpx_ptr(); mpx_ = owner->mpx_ptr();
return nonblocking(fd_, true); 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(""); CAF_LOG_TRACE("");
auto as_mgr = [](intptr_t ptr) { auto as_mgr = [](intptr_t ptr) {
return intrusive_ptr{reinterpret_cast<socket_manager*>(ptr), false}; return intrusive_ptr{reinterpret_cast<socket_manager*>(ptr), false};
...@@ -56,29 +61,8 @@ pollset_updater::read_result pollset_updater::handle_read_event() { ...@@ -56,29 +61,8 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
intptr_t ptr; intptr_t ptr;
memcpy(&ptr, buf_.data() + 1, sizeof(intptr_t)); memcpy(&ptr, buf_.data() + 1, sizeof(intptr_t));
switch (static_cast<code>(opcode)) { switch (static_cast<code>(opcode)) {
case code::register_reading: case code::start_manager:
mpx_->do_register_reading(as_mgr(ptr)); mpx_->do_start(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));
break; break;
case code::run_action: case code::run_action:
run_action(ptr); run_action(ptr);
...@@ -94,25 +78,20 @@ pollset_updater::read_result pollset_updater::handle_read_event() { ...@@ -94,25 +78,20 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
} }
} else if (num_bytes == 0) { } else if (num_bytes == 0) {
CAF_LOG_DEBUG("pipe closed, assume shutdown"); CAF_LOG_DEBUG("pipe closed, assume shutdown");
return read_result::stop; owner_->deregister();
return;
} else if (last_socket_error_is_temporary()) { } else if (last_socket_error_is_temporary()) {
return read_result::again; return;
} else { } 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() { void pollset_updater::handle_write_event() {
return read_result::again; owner_->deregister_writing();
}
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::abort(const error&) { 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>&) { ...@@ -14,4 +14,8 @@ bool socket_event_layer::do_handover(std::unique_ptr<socket_event_layer>&) {
return false; return false;
} }
bool socket_event_layer::finalized() const noexcept {
return true;
}
} // namespace caf::net } // namespace caf::net
...@@ -13,24 +13,27 @@ namespace caf::net { ...@@ -13,24 +13,27 @@ namespace caf::net {
// -- constructors, destructors, and assignment operators ---------------------- // -- constructors, destructors, and assignment operators ----------------------
socket_manager::socket_manager(multiplexer* mpx, socket fd, socket_manager::socket_manager(multiplexer* mpx, event_handler_ptr handler)
event_handler_ptr handler) : fd_(handler->handle()),
: fd_(fd), mpx_(mpx), handler_(std::move(handler)) { mpx_(mpx),
handler_(std::move(handler)),
disposed_(false) {
CAF_ASSERT(fd_ != invalid_socket); CAF_ASSERT(fd_ != invalid_socket);
CAF_ASSERT(mpx_ != nullptr); CAF_ASSERT(mpx_ != nullptr);
CAF_ASSERT(handler_ != nullptr); CAF_ASSERT(handler_ != nullptr);
memset(&flags_, 0, sizeof(flags_t));
} }
socket_manager::~socket_manager() { socket_manager::~socket_manager() {
if (fd_)
close(fd_); close(fd_);
} }
// -- factories ---------------------------------------------------------------- // -- factories ----------------------------------------------------------------
socket_manager_ptr socket_manager::make(multiplexer* mpx, socket handle, socket_manager_ptr socket_manager::make(multiplexer* mpx,
event_handler_ptr handler) { 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 --------------------------------------------------------------- // -- properties ---------------------------------------------------------------
...@@ -40,88 +43,160 @@ actor_system& socket_manager::system() noexcept { ...@@ -40,88 +43,160 @@ actor_system& socket_manager::system() noexcept {
return mpx_->system(); 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 ---------------------------------------------------- // -- event loop management ----------------------------------------------------
void socket_manager::register_reading() { void socket_manager::register_reading() {
mpx_->register_reading(this); mpx_->register_reading(this);
} }
void socket_manager::continue_reading() {
mpx_->continue_reading(this);
}
void socket_manager::register_writing() { void socket_manager::register_writing() {
mpx_->register_writing(this); mpx_->register_writing(this);
} }
void socket_manager::continue_writing() { void socket_manager::deregister_reading() {
mpx_->continue_writing(this); 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::close_write() noexcept { void socket_manager::deregister() {
// TODO: extend transport API for closing write operations. mpx_->deregister(this);
flags_.write_closed = true;
} }
bool socket_manager::do_handover() { // -- callbacks for the handler ------------------------------------------------
void socket_manager::schedule_handover() {
deregister();
mpx_->schedule_fn([ptr = strong_this()] {
event_handler_ptr next; event_handler_ptr next;
if (handler_->do_handover(next)) { if (ptr->handler_->do_handover(next)) {
handler_.swap(next); ptr->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 { } 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)); CAF_LOG_TRACE(CAF_ARG(cfg));
if (auto err = nonblocking(fd_, true)) { if (auto err = nonblocking(fd_, true)) {
CAF_LOG_ERROR("failed to set nonblocking flag in socket:" << err); CAF_LOG_ERROR("failed to set nonblocking flag in socket:" << err);
close(fd_);
return err; return err;
} else if (err = handler_->start(this, cfg)) {
CAF_LOG_DEBUG("failed to initialize handler:" << err);
close(fd_);
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;
}
return result;
}
socket_manager::read_result socket_manager::handle_buffered_data() {
return handler_->handle_buffered_data();
} }
socket_manager::read_result socket_manager::handle_continue_reading() { void socket_manager::handle_read_event() {
return handler_->handle_continue_reading(); if (handler_)
handler_->handle_read_event();
else
deregister();
} }
socket_manager::write_result socket_manager::handle_write_event() { void socket_manager::handle_write_event() {
return handler_->handle_write_event(); if (handler_)
handler_->handle_write_event();
else
deregister();
} }
void socket_manager::handle_error(sec code) { void socket_manager::handle_error(sec code) {
if (!disposed_)
disposed_ = true;
if (handler_) { if (handler_) {
if (!shutting_down_) {
handler_->abort(make_error(code)); handler_->abort(make_error(code));
handler_ = nullptr; 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();
}
}
}
// -- 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);
});
}
}
bool socket_manager::disposed() const noexcept {
return disposed_.load();
}
void socket_manager::ref_disposable() const noexcept {
ref();
}
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 } // namespace caf::net
...@@ -132,7 +132,7 @@ size_t connection::buffered() const noexcept { ...@@ -132,7 +132,7 @@ size_t connection::buffered() const noexcept {
} }
stream_socket connection::fd() 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)}; return stream_socket{static_cast<socket_id>(id)};
else else
return stream_socket{invalid_socket_id}; return stream_socket{invalid_socket_id};
......
...@@ -183,6 +183,21 @@ bool context::has_last_error() noexcept { ...@@ -183,6 +183,21 @@ bool context::has_last_error() noexcept {
// -- connections -------------------------------------------------------------- // -- connections --------------------------------------------------------------
expected<connection> context::new_connection(stream_socket fd) { 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_))) { if (auto ptr = SSL_new(native(pimpl_))) {
auto conn = connection::from_native(ptr); auto conn = connection::from_native(ptr);
if (SSL_set_fd(ptr, fd.id) == 1) if (SSL_set_fd(ptr, fd.id) == 1)
......
...@@ -26,10 +26,6 @@ public: ...@@ -26,10 +26,6 @@ public:
using super = socket_event_layer; 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; using upper_layer_ptr = transport::upper_layer_ptr;
handshake_worker(connection conn, bool is_server, upper_layer_ptr up) handshake_worker(connection conn, bool is_server, upper_layer_ptr up)
...@@ -39,69 +35,74 @@ public: ...@@ -39,69 +35,74 @@ public:
// -- interface functions ---------------------------------------------------- // -- interface functions ----------------------------------------------------
error init(socket_manager* owner, const settings& cfg) override { error start(socket_manager* owner, const settings& cfg) override {
owner_ = owner; owner_ = owner;
cfg_ = cfg; cfg_ = cfg;
owner->register_writing(); owner->register_writing();
return caf::none; 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) { if (auto res = advance_handshake(); res > 0) {
return read_result::handover; owner_->deregister();
owner_->schedule_handover();
} else if (res == 0) { } else if (res == 0) {
up_->abort(make_error(sec::connection_closed)); up_->abort(make_error(sec::connection_closed));
return read_result::stop; owner_->deregister();
} else { } else {
auto err = policy_.last_error(policy_.conn.fd(), res); auto err = policy_.last_error(policy_.conn.fd(), res);
switch (err) { switch (err) {
case stream_transport_error::want_read: case stream_transport_error::want_read:
case stream_transport_error::temporary: case stream_transport_error::temporary:
return read_result::again; break;
case stream_transport_error::want_write: case stream_transport_error::want_write:
return read_result::want_write; owner_->deregister_reading();
default: owner_->register_writing();
break;
default: {
auto err = make_error(sec::cannot_connect_to_node, auto err = make_error(sec::cannot_connect_to_node,
policy_.conn.last_error_string(res)); policy_.conn.last_error_string(res));
up_->abort(std::move(err)); 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) { if (auto res = advance_handshake(); res > 0) {
return write_result::handover; owner_->deregister();
owner_->schedule_handover();
return;
} else if (res == 0) { } else if (res == 0) {
up_->abort(make_error(sec::connection_closed)); up_->abort(make_error(sec::connection_closed));
return write_result::stop; owner_->deregister();
} else { } else {
switch (policy_.last_error(policy_.conn.fd(), res)) { switch (policy_.last_error(policy_.conn.fd(), res)) {
case stream_transport_error::want_write: case stream_transport_error::want_write:
case stream_transport_error::temporary: case stream_transport_error::temporary:
return write_result::again; break;
case stream_transport_error::want_read: case stream_transport_error::want_read:
return write_result::want_read; owner_->deregister_writing();
default: owner_->register_reading();
break;
default: {
auto err = make_error(sec::cannot_connect_to_node, auto err = make_error(sec::cannot_connect_to_node,
policy_.conn.last_error_string(res)); policy_.conn.last_error_string(res));
up_->abort(std::move(err)); up_->abort(std::move(err));
return write_result::stop; owner_->deregister();
}
} }
} }
} }
bool do_handover(std::unique_ptr<socket_event_layer>& next) override { bool do_handover(std::unique_ptr<socket_event_layer>& next) override {
next = transport::make(std::move(policy_.conn), std::move(up_)); 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; return false;
else else
return true; return true;
......
...@@ -49,14 +49,14 @@ size_t stream_transport::default_policy::buffered() const noexcept { ...@@ -49,14 +49,14 @@ size_t stream_transport::default_policy::buffered() const noexcept {
// -- constructors, destructors, and assignment operators ---------------------- // -- constructors, destructors, and assignment operators ----------------------
stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up) stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up)
: fd_(fd), up_(std::move(up)) { : fd_(fd), up_(std::move(up)), policy_(&default_policy_) {
policy_ = &default_policy_; memset(&flags_, 0, sizeof(flags_t));
} }
stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up, stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up,
policy* policy) policy* policy)
: fd_(fd), up_(std::move(up)), policy_(policy) { : fd_(fd), up_(std::move(up)), policy_(policy) {
// nop memset(&flags_, 0, sizeof(flags_t));
} }
// -- factories ---------------------------------------------------------------- // -- factories ----------------------------------------------------------------
...@@ -73,11 +73,25 @@ bool stream_transport::can_send_more() const noexcept { ...@@ -73,11 +73,25 @@ bool stream_transport::can_send_more() const noexcept {
} }
void stream_transport::configure_read(receive_policy rd) { void stream_transport::configure_read(receive_policy rd) {
if (rd.max_size > 0 && max_read_size_ == 0) { auto restarting = rd.max_size > 0 && max_read_size_ == 0;
parent_->register_reading();
}
min_read_size_ = rd.min_size; min_read_size_ = rd.min_size;
max_read_size_ = rd.max_size; max_read_size_ = rd.max_size;
if (restarting && !parent_->is_reading()) {
if (buffered_ > 0 && buffered_ >= min_read_size_) {
// 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() { void stream_transport::begin_output() {
...@@ -93,17 +107,27 @@ bool stream_transport::end_output() { ...@@ -93,17 +107,27 @@ bool stream_transport::end_output() {
return true; return true;
} }
void stream_transport::suspend_reading() { bool stream_transport::is_reading() const noexcept {
configure_read(receive_policy::stop()); return max_read_size_ > 0;
} }
bool stream_transport::stopped_reading() const noexcept { void stream_transport::write_later() {
return max_read_size_ == 0; 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 ---------------------------------------------- // -- 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; parent_ = owner;
namespace mm = defaults::middleman; namespace mm = defaults::middleman;
auto default_max_reads = static_cast<uint32_t>(mm::max_consecutive_reads); auto default_max_reads = static_cast<uint32_t>(mm::max_consecutive_reads);
...@@ -121,35 +145,35 @@ error stream_transport::init(socket_manager* owner, const settings& config) { ...@@ -121,35 +145,35 @@ error stream_transport::init(socket_manager* owner, const settings& config) {
CAF_LOG_ERROR("send_buffer_size: " << socket_buf_size.error()); CAF_LOG_ERROR("send_buffer_size: " << socket_buf_size.error());
return std::move(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)); 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 // Resume a write operation if the transport waited for the socket to be
// readable from the last call to handle_write_event. // readable from the last call to handle_write_event.
if (flags_.wanted_read_from_write_event) { if (flags_.wanted_read_from_write_event) {
flags_.wanted_read_from_write_event = false; flags_.wanted_read_from_write_event = false;
switch (handle_write_event()) { // The subsequent call to handle_write_event expects a writing manager.
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(); parent_->register_writing();
break; handle_write_event();
default: if (!parent_->is_reading()) {
break; // 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_) if (read_buf_.size() < max_read_size_)
read_buf_.resize(max_read_size_); read_buf_.resize(max_read_size_);
// Fill up our buffer. // Fill up our buffer.
...@@ -160,19 +184,23 @@ stream_transport::read_result stream_transport::handle_read_event() { ...@@ -160,19 +184,23 @@ stream_transport::read_result stream_transport::handle_read_event() {
switch (policy_->last_error(fd_, rd)) { switch (policy_->last_error(fd_, rd)) {
case stream_transport_error::temporary: case stream_transport_error::temporary:
case stream_transport_error::want_read: case stream_transport_error::want_read:
return read_result::again; // Try again later.
return;
case stream_transport_error::want_write: case stream_transport_error::want_write:
// Wait for writable socket and then call handle_read_event again.
flags_.wanted_write_from_read_event = true; flags_.wanted_write_from_read_event = true;
return read_result::want_write; parent_->register_writing();
parent_->deregister_reading();
return;
default: default:
return fail(sec::socket_operation_failed); return fail(make_error(sec::socket_operation_failed));
} }
} else if (rd == 0) { } 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 // 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 // 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); buffered_ += static_cast<size_t>(rd);
if (auto policy_buffered = policy_->buffered(); policy_buffered > 0) { if (auto policy_buffered = policy_->buffered(); policy_buffered > 0) {
if (auto n = buffered_ + policy_buffered; n > read_buf_.size()) if (auto n = buffered_ + policy_buffered; n > read_buf_.size())
...@@ -181,27 +209,19 @@ stream_transport::read_result stream_transport::handle_read_event() { ...@@ -181,27 +209,19 @@ stream_transport::read_result stream_transport::handle_read_event() {
policy_buffered)); policy_buffered));
if (rd2 != static_cast<ptrdiff_t>(policy_buffered)) { if (rd2 != static_cast<ptrdiff_t>(policy_buffered)) {
CAF_LOG_ERROR("failed to read buffered data from the policy"); 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); buffered_ += static_cast<size_t>(rd2);
} }
// Try to make progress on the application and return control to the // Read buffered data and then allow other sockets to run.
// multiplexer to allow other sockets to run. handle_buffered_data();
return handle_buffered_data();
} }
stream_transport::read_result stream_transport::handle_buffered_data() { void stream_transport::handle_buffered_data() {
CAF_LOG_TRACE(CAF_ARG(buffered_)); 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. // Loop until we have drained the buffer as much as we can.
CAF_ASSERT(min_read_size_ <= max_read_size_); CAF_ASSERT(min_read_size_ <= max_read_size_);
while (max_read_size_ > 0 && buffered_ >= min_read_size_) { while (max_read_size_ > 0 && buffered_ >= min_read_size_) {
auto old_max_read_size = max_read_size_;
auto n = std::min(buffered_, size_t{max_read_size_}); auto n = std::min(buffered_, size_t{max_read_size_});
auto bytes = make_span(read_buf_.data(), n); auto bytes = make_span(read_buf_.data(), n);
auto delta = bytes.subspan(delta_offset_); auto delta = bytes.subspan(delta_offset_);
...@@ -210,34 +230,21 @@ stream_transport::read_result stream_transport::handle_buffered_data() { ...@@ -210,34 +230,21 @@ stream_transport::read_result stream_transport::handle_buffered_data() {
// Negative values indicate that the application encountered an // Negative values indicate that the application encountered an
// unrecoverable error. // unrecoverable error.
up_->abort(make_error(caf::sec::runtime_error, "consumed < 0")); up_->abort(make_error(caf::sec::runtime_error, "consumed < 0"));
return read_result::abort; parent_->deregister();
} else if (consumed == 0) { return;
// 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;
}
} else if (static_cast<size_t>(consumed) > n) { } else if (static_cast<size_t>(consumed) > n) {
// Must not happen. An application cannot handle more data then we pass // Must not happen. An application cannot handle more data then we pass
// to it. // to it.
up_->abort(make_error(sec::logic_error, "consumed > buffer.size")); up_->abort(make_error(sec::logic_error, "consumed > buffer.size"));
return read_result::abort; parent_->deregister();
return;
} else { } else {
// Shove the unread bytes to the beginning of the buffer and continue // Shove the unread bytes to the beginning of the buffer and continue
// to the next loop iteration. // to the next loop iteration.
auto del = static_cast<size_t>(consumed); auto del = static_cast<size_t>(consumed);
auto prev = buffered_; auto prev = buffered_;
buffered_ -= del; buffered_ -= del;
delta_offset_ = 0; delta_offset_ = static_cast<ptrdiff_t>(n - del);
if (buffered_ > 0) { if (buffered_ > 0) {
auto new_begin = read_buf_.begin() + del; auto new_begin = read_buf_.begin() + del;
auto new_end = read_buf_.begin() + prev; auto new_end = read_buf_.begin() + prev;
...@@ -245,80 +252,82 @@ stream_transport::read_result stream_transport::handle_buffered_data() { ...@@ -245,80 +252,82 @@ stream_transport::read_result stream_transport::handle_buffered_data() {
} }
} }
} }
return max_read_size_ > 0 ? read_result::again : read_result::stop; if (max_read_size_ == 0)
parent_->deregister_reading();
}
void stream_transport::fail(const error& reason) {
CAF_LOG_TRACE(CAF_ARG(reason));
up_->abort(reason);
up_.reset();
parent_->deregister();
parent_->shutdown();
} }
stream_transport::write_result stream_transport::handle_write_event() { void stream_transport::handle_write_event() {
CAF_LOG_TRACE(CAF_ARG2("socket", fd_.id)); 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 // Resume a read operation if the transport waited for the socket to be
// writable from the last call to handle_read_event. // writable from the last call to handle_read_event.
if (flags_.wanted_write_from_read_event) { if (flags_.wanted_write_from_read_event) {
flags_.wanted_write_from_read_event = false; flags_.wanted_write_from_read_event = false;
switch (handle_read_event()) { // The subsequent call to handle_read_event expects a writing manager.
case read_result::want_write: parent_->register_writing();
CAF_ASSERT(flags_.wanted_write_from_read_event); handle_read_event();
return write_result::again; if (!parent_->is_writing()) {
case read_result::handover: // The call to handle_read_event deregisters the manager from writing in
return write_result::handover; // case of an error. So we need to double-check that flag here.
case read_result::again: return;
parent_->register_reading(); }
break;
default:
break;
} }
// Fall though and see if we also have something to write. // 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. // Allow the upper layer to add extra data to the write buffer.
if (!up_->prepare_send()) { up_->prepare_send();
up_->abort(make_error(caf::sec::runtime_error, "prepare_send failed"));
return write_result::abort;
} }
if (write_buf_.empty())
return !up_->done_sending() ? write_result::again : write_result::stop;
auto write_res = policy_->write(fd_, write_buf_); auto write_res = policy_->write(fd_, write_buf_);
if (write_res > 0) { if (write_res > 0) {
write_buf_.erase(write_buf_.begin(), write_buf_.begin() + write_res); write_buf_.erase(write_buf_.begin(), write_buf_.begin() + write_res);
return !write_buf_.empty() || !up_->done_sending() ? write_result::again if (write_buf_.empty() && up_->done_sending()) {
: write_result::stop; if (!flags_.shutting_down) {
parent_->deregister_writing();
} else {
parent_->shutdown();
}
}
return;
} else if (write_res < 0) { } else if (write_res < 0) {
// Try again later on temporary errors such as EWOULDBLOCK and // Try again later on temporary errors such as EWOULDBLOCK and
// stop writing to the socket on hard errors. // stop writing to the socket on hard errors.
switch (policy_->last_error(fd_, write_res)) { switch (policy_->last_error(fd_, write_res)) {
case stream_transport_error::temporary: case stream_transport_error::temporary:
case stream_transport_error::want_write: case stream_transport_error::want_write:
return write_result::again; return;
case stream_transport_error::want_read: case stream_transport_error::want_read:
flags_.wanted_read_from_write_event = true; flags_.wanted_read_from_write_event = true;
return write_result::want_read; parent_->register_reading();
parent_->deregister_writing();
return;
default: default:
return fail(sec::socket_operation_failed); return fail(make_error(sec::socket_operation_failed));
} }
} else { } else {
// write() returns 0 if the connection was closed. // write() returns 0 if the connection was closed.
return fail(sec::socket_disconnected); return fail(make_error(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();
} }
} }
void stream_transport::abort(const error& reason) { void stream_transport::abort(const error& reason) {
up_->abort(reason); up_->abort(reason);
flags_.shutting_down = true;
}
bool stream_transport::finalized() const noexcept {
return write_buf_.empty();
} }
// -- free functions ----------------------------------------------------------- // -- free functions -----------------------------------------------------------
......
...@@ -75,6 +75,7 @@ expected<tcp_accept_socket> new_tcp_acceptor_impl(uint16_t port, ...@@ -75,6 +75,7 @@ expected<tcp_accept_socket> new_tcp_acceptor_impl(uint16_t port,
CAF_NET_SYSCALL("bind", res, !=, 0, CAF_NET_SYSCALL("bind", res, !=, 0,
bind(fd, reinterpret_cast<sockaddr*>(&sa), bind(fd, reinterpret_cast<sockaddr*>(&sa),
static_cast<socket_size_type>(sizeof(sa)))); static_cast<socket_size_type>(sizeof(sa))));
CAF_LOG_DEBUG("bound socket" << fd << "to listen on port" << port);
return sguard.release(); return sguard.release();
} }
...@@ -121,7 +122,16 @@ make_tcp_accept_socket(const uri::authority_type& node, bool reuse_addr) { ...@@ -121,7 +122,16 @@ make_tcp_accept_socket(const uri::authority_type& node, bool reuse_addr) {
to_string(node)); 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) { expected<tcp_stream_socket> accept(tcp_accept_socket x) {
CAF_LOG_TRACE(CAF_ARG(x));
auto sock = ::accept(x.id, nullptr, nullptr); auto sock = ::accept(x.id, nullptr, nullptr);
if (sock == net::invalid_socket_id) { if (sock == net::invalid_socket_id) {
auto err = net::last_socket_error(); auto err = net::last_socket_error();
...@@ -131,6 +141,7 @@ expected<tcp_stream_socket> accept(tcp_accept_socket x) { ...@@ -131,6 +141,7 @@ expected<tcp_stream_socket> accept(tcp_accept_socket x) {
} }
return caf::make_error(sec::socket_operation_failed, "tcp accept failed"); 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}; return tcp_stream_socket{sock};
} }
......
...@@ -38,10 +38,9 @@ std::unique_ptr<client> client::make(handshake_ptr hs, upper_layer_ptr up) { ...@@ -38,10 +38,9 @@ std::unique_ptr<client> client::make(handshake_ptr hs, upper_layer_ptr up) {
// -- implementation of stream_oriented::upper_layer --------------------------- // -- implementation of stream_oriented::upper_layer ---------------------------
error client::init(socket_manager* owner, stream_oriented::lower_layer* down, error client::start(stream_oriented::lower_layer* down, const settings& cfg) {
const settings& cfg) {
CAF_ASSERT(hs_ != nullptr); CAF_ASSERT(hs_ != nullptr);
framing_.init(owner, down); framing_.start(down);
if (!hs_->has_mandatory_fields()) if (!hs_->has_mandatory_fields())
return make_error(sec::runtime_error, return make_error(sec::runtime_error,
"handshake data lacks mandatory fields"); "handshake data lacks mandatory fields");
...@@ -95,13 +94,9 @@ ptrdiff_t client::consume(byte_span buffer, byte_span delta) { ...@@ -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()) if (handshake_completed())
upper_layer().continue_reading(); upper_layer().prepare_send();
}
bool client::prepare_send() {
return handshake_completed() ? upper_layer().prepare_send() : true;
} }
bool client::done_sending() { bool client::done_sending() {
...@@ -115,7 +110,7 @@ bool client::handle_header(std::string_view http) { ...@@ -115,7 +110,7 @@ bool client::handle_header(std::string_view http) {
auto http_ok = hs_->is_valid_http_1_response(http); auto http_ok = hs_->is_valid_http_1_response(http);
hs_.reset(); hs_.reset();
if (http_ok) { 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"); CAF_LOG_DEBUG("failed to initialize WebSocket framing layer");
return false; return false;
} else { } else {
......
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
#include "caf/net/web_socket/default_trait.hpp" #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/net/web_socket/frame.hpp"
#include "caf/sec.hpp"
namespace caf::net::web_socket { namespace caf::net::web_socket {
...@@ -34,4 +37,9 @@ bool default_trait::convert(std::string_view text, input_type& x) { ...@@ -34,4 +37,9 @@ bool default_trait::convert(std::string_view text, input_type& x) {
return true; return true;
} }
error default_trait::last_error() {
CAF_LOG_ERROR("default_trait::last_error called");
return {sec::logic_error};
}
} // namespace caf::net::web_socket } // 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 "caf/net/web_socket/frame.hpp"
#include <cstring> #include <cstring>
......
...@@ -10,7 +10,7 @@ namespace caf::net::web_socket { ...@@ -10,7 +10,7 @@ namespace caf::net::web_socket {
// -- initialization --------------------------------------------------------- // -- initialization ---------------------------------------------------------
void framing::init(socket_manager*, stream_oriented::lower_layer* down) { void framing::start(stream_oriented::lower_layer* down) {
std::random_device rd; std::random_device rd;
rng_.seed(rd()); rng_.seed(rd());
down_ = down; down_ = down;
...@@ -23,15 +23,39 @@ bool framing::can_send_more() const noexcept { ...@@ -23,15 +23,39 @@ bool framing::can_send_more() const noexcept {
} }
void framing::suspend_reading() { void framing::suspend_reading() {
down_->suspend_reading(); down_->configure_read(receive_policy::stop());
} }
bool framing::stopped_reading() const noexcept { bool framing::is_reading() const noexcept {
return down_->stopped_reading(); 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() { void framing::request_messages() {
if (down_->stopped_reading()) if (!down_->is_reading())
down_->configure_read(receive_policy::up_to(2048)); down_->configure_read(receive_policy::up_to(2048));
} }
...@@ -61,14 +85,6 @@ bool framing::end_text_message() { ...@@ -61,14 +85,6 @@ bool framing::end_text_message() {
return true; 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 -------------------------------------------- // -- interface for the lower layer --------------------------------------------
ptrdiff_t framing::consume(byte_span input, byte_span) { ptrdiff_t framing::consume(byte_span input, byte_span) {
...@@ -99,9 +115,6 @@ ptrdiff_t framing::consume(byte_span input, byte_span) { ...@@ -99,9 +115,6 @@ ptrdiff_t framing::consume(byte_span input, byte_span) {
// Wait for more data if necessary. // Wait for more data if necessary.
size_t frame_size = hdr_bytes + hdr.payload_len; size_t frame_size = hdr_bytes + hdr.payload_len;
if (buffer.size() < frame_size) { 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)); down_->configure_read(receive_policy::exactly(frame_size));
return consumed; return consumed;
} }
...@@ -170,7 +183,7 @@ ptrdiff_t framing::consume(byte_span input, byte_span) { ...@@ -170,7 +183,7 @@ ptrdiff_t framing::consume(byte_span input, byte_span) {
buffer = buffer.subspan(frame_size); buffer = buffer.subspan(frame_size);
if (buffer.empty()) { if (buffer.empty()) {
// Ask for more data unless the upper layer called suspend_reading. // Ask for more data unless the upper layer called suspend_reading.
if (!down_->stopped_reading()) if (down_->is_reading())
down_->configure_read(receive_policy::up_to(2048)); down_->configure_read(receive_policy::up_to(2048));
return consumed + static_cast<ptrdiff_t>(frame_size); return consumed + static_cast<ptrdiff_t>(frame_size);
} }
...@@ -215,40 +228,6 @@ void framing::ship_pong(byte_span payload) { ...@@ -215,40 +228,6 @@ void framing::ship_pong(byte_span payload) {
down_->end_output(); 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> template <class T>
void framing::ship_frame(std::vector<T>& buf) { void framing::ship_frame(std::vector<T>& buf) {
uint32_t mask_key = 0; uint32_t mask_key = 0;
......
...@@ -13,8 +13,13 @@ lower_layer::~lower_layer() { ...@@ -13,8 +13,13 @@ lower_layer::~lower_layer() {
// nop // nop
} }
void lower_layer::send_close_message(const error& reason) { void lower_layer::shutdown() {
send_close_message(status::unexpected_condition, to_string(reason)); 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 } // namespace caf::net::web_socket
...@@ -22,9 +22,8 @@ std::unique_ptr<server> server::make(upper_layer_ptr up) { ...@@ -22,9 +22,8 @@ std::unique_ptr<server> server::make(upper_layer_ptr up) {
// -- stream_oriented::upper_layer implementation ------------------------------ // -- stream_oriented::upper_layer implementation ------------------------------
error server::init(socket_manager* owner, stream_oriented::lower_layer* down, error server::start(stream_oriented::lower_layer* down, const settings& cfg) {
const settings& cfg) { framing_.start(down);
framing_.init(owner, down);
cfg_ = cfg; cfg_ = cfg;
lower_layer().configure_read(receive_policy::up_to(handshake::max_http_size)); lower_layer().configure_read(receive_policy::up_to(handshake::max_http_size));
return none; return none;
...@@ -74,13 +73,9 @@ ptrdiff_t server::consume(byte_span input, byte_span delta) { ...@@ -74,13 +73,9 @@ ptrdiff_t server::consume(byte_span input, byte_span delta) {
} }
} }
void server::continue_reading() { void server::prepare_send() {
auto rp = receive_policy::up_to(handshake::max_http_size); if (handshake_complete_)
lower_layer().configure_read(rp); upper_layer().prepare_send();
}
bool server::prepare_send() {
return handshake_complete_ ? upper_layer().prepare_send() : true;
} }
bool server::done_sending() { bool server::done_sending() {
...@@ -131,7 +126,7 @@ bool server::handle_header(std::string_view http) { ...@@ -131,7 +126,7 @@ bool server::handle_header(std::string_view http) {
put(fields, std::string{key}, std::string{val}); put(fields, std::string{key}, std::string{val});
} }
// Try to initialize the upper layer. // Try to initialize the upper layer.
if (auto err = upper_layer().init(owner_, &framing_, cfg_)) { if (auto err = upper_layer().start(&framing_, cfg_)) {
auto descr = to_string(err); auto descr = to_string(err);
CAF_LOG_DEBUG("upper layer rejected a WebSocket connection:" << descr); CAF_LOG_DEBUG("upper layer rejected a WebSocket connection:" << descr);
write_response(http::status::bad_request, descr); write_response(http::status::bad_request, descr);
......
...@@ -19,8 +19,16 @@ bool mock_stream_transport::can_send_more() const noexcept { ...@@ -19,8 +19,16 @@ bool mock_stream_transport::can_send_more() const noexcept {
return true; return true;
} }
void mock_stream_transport::suspend_reading() { bool mock_stream_transport::is_reading() const noexcept {
configure_read(net::receive_policy::stop()); 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) { void mock_stream_transport::configure_read(net::receive_policy policy) {
...@@ -40,10 +48,6 @@ bool mock_stream_transport::end_output() { ...@@ -40,10 +48,6 @@ bool mock_stream_transport::end_output() {
return true; return true;
} }
bool mock_stream_transport::stopped_reading() const noexcept {
return max_read_size == 0;
}
ptrdiff_t mock_stream_transport::handle_input() { ptrdiff_t mock_stream_transport::handle_input() {
ptrdiff_t result = 0; ptrdiff_t result = 0;
while (max_read_size > 0) { while (max_read_size > 0) {
......
...@@ -32,7 +32,11 @@ public: ...@@ -32,7 +32,11 @@ public:
bool can_send_more() const noexcept override; 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; void configure_read(caf::net::receive_policy policy) override;
...@@ -42,17 +46,15 @@ public: ...@@ -42,17 +46,15 @@ public:
bool end_output() override; bool end_output() override;
bool stopped_reading() const noexcept override;
// -- initialization --------------------------------------------------------- // -- initialization ---------------------------------------------------------
caf::error init(const caf::settings& cfg) { caf::error start(const caf::settings& cfg) {
return up->init(nullptr, this, cfg); return up->start(this, cfg);
} }
caf::error init() { caf::error start() {
caf::settings cfg; caf::settings cfg;
return init(cfg); return start(cfg);
} }
// -- buffer management ------------------------------------------------------ // -- buffer management ------------------------------------------------------
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "caf/byte.hpp" #include "caf/byte.hpp"
#include "caf/byte_span.hpp" #include "caf/byte_span.hpp"
#include "caf/callback.hpp" #include "caf/callback.hpp"
#include "caf/net/make_actor_shell.hpp"
#include "caf/net/middleman.hpp" #include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp" #include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp" #include "caf/net/socket_manager.hpp"
...@@ -26,24 +27,20 @@ using svec = std::vector<std::string>; ...@@ -26,24 +27,20 @@ using svec = std::vector<std::string>;
class app_t : public net::stream_oriented::upper_layer { class app_t : public net::stream_oriented::upper_layer {
public: public:
app_t() = default; explicit app_t(actor_system& sys, async::execution_context_ptr loop,
actor hdl = {})
explicit app_t(actor hdl) : worker(std::move(hdl)) { : worker(std::move(hdl)) {
// nop self = net::make_actor_shell(sys, loop);
}
static auto make() {
return std::make_unique<app_t>();
} }
static auto make(actor hdl) { static auto make(actor_system& sys, async::execution_context_ptr loop,
return std::make_unique<app_t>(std::move(hdl)); 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, error start(net::stream_oriented::lower_layer* down,
const settings&) override { const settings&) override {
this->down = down; this->down = down;
self = mgr->make_actor_shell();
self->set_behavior([this](std::string& line) { self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line); CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line)); lines.emplace_back(std::move(line));
...@@ -59,22 +56,16 @@ public: ...@@ -59,22 +56,16 @@ public:
return none; return none;
} }
bool prepare_send() override { void prepare_send() override {
while (self->consume_message()) // nop
; // repeat
return !self->terminated();
}
void continue_reading() override {
CAF_FAIL("continue_reading called");
} }
bool done_sending() override { bool done_sending() override {
return self->try_block_mailbox(); return true;
} }
void abort(const error& reason) override { void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason); MESSAGE("abort: " << reason);
} }
ptrdiff_t consume(byte_span buf, byte_span) override { ptrdiff_t consume(byte_span buf, byte_span) override {
...@@ -148,10 +139,10 @@ public: ...@@ -148,10 +139,10 @@ public:
}; };
struct fixture : test_coordinator_fixture<> { struct fixture : test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) { fixture() : mm(sys), mpx(net::multiplexer::make(&mm)) {
mpx.set_thread_id(); mpx->set_thread_id();
if (auto err = mpx.init()) if (auto err = mpx->init())
CAF_FAIL("mpx.init() failed: " << err); CAF_FAIL("mpx->init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair()); auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first); self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second); testee_socket_guard.reset(sockets.second);
...@@ -166,8 +157,8 @@ struct fixture : test_coordinator_fixture<> { ...@@ -166,8 +157,8 @@ struct fixture : test_coordinator_fixture<> {
if (!predicate()) if (!predicate())
return; return;
for (size_t i = 0; i < 1000; ++i) { for (size_t i = 0; i < 1000; ++i) {
mpx.apply_updates(); mpx->apply_updates();
mpx.poll_once(false); mpx->poll_once(false);
std::byte tmp[1024]; std::byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024)); auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0) if (bytes > 0)
...@@ -186,7 +177,7 @@ struct fixture : test_coordinator_fixture<> { ...@@ -186,7 +177,7 @@ struct fixture : test_coordinator_fixture<> {
} }
net::middleman mm; 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> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard; net::socket_guard<net::stream_socket> testee_socket_guard;
byte_buffer recv_buf; byte_buffer recv_buf;
...@@ -202,11 +193,11 @@ CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture) ...@@ -202,11 +193,11 @@ CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) { CAF_TEST(actor shells expose their mailbox to their owners) {
auto fd = testee_socket_guard.release(); 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 app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr)); auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport)); auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->init(content(cfg))) if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err); CAF_FAIL("mgr->init() failed: " << err);
auto hdl = app->self.as_actor(); auto hdl = app->self.as_actor();
anon_send(hdl, "line 1"); anon_send(hdl, "line 1");
...@@ -214,6 +205,8 @@ CAF_TEST(actor shells expose their mailbox to their owners) { ...@@ -214,6 +205,8 @@ CAF_TEST(actor shells expose their mailbox to their owners) {
anon_send(hdl, "line 3"); anon_send(hdl, "line 3");
run_while([&] { return app->lines.size() != 3; }); run_while([&] { return app->lines.size() != 3; });
CAF_CHECK_EQUAL(app->lines, svec({"line 1", "line 2", "line 3"})); CAF_CHECK_EQUAL(app->lines, svec({"line 1", "line 2", "line 3"}));
self_socket_guard.reset();
run_while([&] { return mpx->num_socket_managers() > 1; });
} }
CAF_TEST(actor shells can send requests and receive responses) { CAF_TEST(actor shells can send requests and receive responses) {
...@@ -223,12 +216,12 @@ CAF_TEST(actor shells can send requests and receive responses) { ...@@ -223,12 +216,12 @@ CAF_TEST(actor shells can send requests and receive responses) {
}; };
}); });
auto fd = testee_socket_guard.release(); 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 app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr)); auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport)); auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->init(content(cfg))) if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err); CAF_FAIL("mgr->start() failed: " << err);
send(input); send(input);
run_while([&] { return app->consumed_bytes != input.size(); }); run_while([&] { return app->consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123)); expect((int32_t), to(worker).with(123));
...@@ -237,6 +230,8 @@ CAF_TEST(actor shells can send requests and receive responses) { ...@@ -237,6 +230,8 @@ CAF_TEST(actor shells can send requests and receive responses) {
std::string_view received_response{reinterpret_cast<char*>(recv_buf.data()), std::string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()}; recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response); CAF_CHECK_EQUAL(received_response, expected_response);
self_socket_guard.reset();
run_while([&] { return mpx->num_socket_managers() > 1; });
} }
CAF_TEST_FIXTURE_SCOPE_END() 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: ...@@ -48,9 +48,9 @@ public:
// -- implementation of http::upper_layer ------------------------------------ // -- implementation of http::upper_layer ------------------------------------
error init(net::socket_manager*, net::http::lower_layer* down_ptr, error start(net::http::lower_layer* down_ptr, const settings&) override {
const settings&) override {
down = down_ptr; down = down_ptr;
down->request_messages();
return none; return none;
} }
...@@ -58,28 +58,23 @@ public: ...@@ -58,28 +58,23 @@ public:
CAF_FAIL("app::abort called: " << reason); CAF_FAIL("app::abort called: " << reason);
} }
bool prepare_send() override { void prepare_send() override {
return true; // nop
} }
bool done_sending() override { bool done_sending() override {
return true; return true;
} }
ptrdiff_t consume(net::http::context ctx, ptrdiff_t consume(const net::http::header& request_hdr,
const net::http::header& request_hdr,
const_byte_span body) override { const_byte_span body) override {
hdr = request_hdr; hdr = request_hdr;
auto content = "Hello world!"sv; 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))); as_bytes(make_span(content)));
payload.assign(body.begin(), body.end()); payload.assign(body.begin(), body.end());
return static_cast<ptrdiff_t>(body.size()); return static_cast<ptrdiff_t>(body.size());
} }
void continue_reading() override {
// nop
}
}; };
} // namespace } // namespace
...@@ -100,7 +95,7 @@ SCENARIO("the server parses HTTP GET requests into header fields") { ...@@ -100,7 +95,7 @@ SCENARIO("the server parses HTTP GET requests into header fields") {
auto app = app_ptr.get(); auto app = app_ptr.get();
auto http_ptr = net::http::server::make(std::move(app_ptr)); auto http_ptr = net::http::server::make(std::move(app_ptr));
auto serv = mock_stream_transport::make(std::move(http_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); serv->push(req);
THEN("the HTTP layer parses the data and calls the application layer") { THEN("the HTTP layer parses the data and calls the application layer") {
CHECK_EQ(serv->handle_input(), static_cast<ptrdiff_t>(req.size())); CHECK_EQ(serv->handle_input(), static_cast<ptrdiff_t>(req.size()));
......
...@@ -17,12 +17,15 @@ ...@@ -17,12 +17,15 @@
#include "caf/byte_buffer.hpp" #include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp" #include "caf/byte_span.hpp"
#include "caf/detail/network_order.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/multiplexer.hpp"
#include "caf/net/socket_guard.hpp" #include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp" #include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp" #include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp" #include "caf/net/stream_transport.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include "caf/span.hpp" #include "caf/span.hpp"
using namespace caf; using namespace caf;
...@@ -35,18 +38,19 @@ using string_list = std::vector<std::string>; ...@@ -35,18 +38,19 @@ using string_list = std::vector<std::string>;
using shared_string_list = std::shared_ptr<string_list>; using shared_string_list = std::shared_ptr<string_list>;
template <bool EnableSuspend> template <bool EnableSuspend>
class app_t : public net::message_oriented::upper_layer { class app_t : public net::binary::upper_layer {
public: public:
static auto make(shared_string_list inputs) { app_t(async::execution_context_ptr loop, shared_string_list ls_ptr)
return std::make_unique<app_t>(std::move(inputs)); : loop(std::move(loop)), inputs(std::move(ls_ptr)) {
// nop
} }
app_t(shared_string_list ls_ptr) : inputs(std::move(ls_ptr)) { static auto make(async::execution_context_ptr loop,
// nop shared_string_list inputs) {
return std::make_unique<app_t>(std::move(loop), std::move(inputs));
} }
caf::error init(net::socket_manager*, caf::error start(net::binary::lower_layer* down_ptr,
net::message_oriented::lower_layer* down_ptr,
const settings&) override { const settings&) override {
// Start reading immediately. // Start reading immediately.
down = down_ptr; down = down_ptr;
...@@ -54,35 +58,35 @@ public: ...@@ -54,35 +58,35 @@ public:
return none; return none;
} }
bool prepare_send() override { void prepare_send() override {
return true; // nop
} }
bool done_sending() override { bool done_sending() override {
return true; return true;
} }
void abort(const error&) override { void abort(const error& err) override {
// nop MESSAGE("abort: " << err);
} }
void continue_reading() override { void continue_reading() {
down->request_messages(); loop->schedule_fn([this] { down->request_messages(); });
} }
ptrdiff_t consume(byte_span buf) override { ptrdiff_t consume(byte_span buf) override {
printf("app_t::consume %d\n", __LINE__);
auto printable = [](std::byte x) { auto printable = [](std::byte x) {
return ::isprint(static_cast<uint8_t>(x)); return ::isprint(static_cast<uint8_t>(x));
}; };
if (CHECK(std::all_of(buf.begin(), buf.end(), printable))) { 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()); auto str_buf = reinterpret_cast<char*>(buf.data());
inputs->emplace_back(std::string{str_buf, buf.size()}); 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 constexpr (EnableSuspend)
if (inputs->back() == "pause") if (inputs->back() == "pause") {
MESSAGE("app: suspend reading");
down->suspend_reading(); down->suspend_reading();
}
std::string response = "ok "; std::string response = "ok ";
response += std::to_string(inputs->size()); response += std::to_string(inputs->size());
auto response_bytes = as_bytes(make_span(response)); auto response_bytes = as_bytes(make_span(response));
...@@ -92,12 +96,13 @@ public: ...@@ -92,12 +96,13 @@ public:
CHECK(down->end_message()); CHECK(down->end_message());
return static_cast<ptrdiff_t>(buf.size()); return static_cast<ptrdiff_t>(buf.size());
} else { } else {
printf("app_t::consume %d\n", __LINE__);
return -1; return -1;
} }
} }
net::message_oriented::lower_layer* down = nullptr; async::execution_context_ptr loop;
net::binary::lower_layer* down = nullptr;
shared_string_list inputs; shared_string_list inputs;
}; };
...@@ -132,16 +137,33 @@ auto decode(byte_buffer& buf) { ...@@ -132,16 +137,33 @@ auto decode(byte_buffer& buf) {
return result; 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 } // namespace
SCENARIO("length-prefix framing reads data with 32-bit size headers") { SCENARIO("length-prefix framing reads data with 32-bit size headers") {
GIVEN("a length_prefix_framing with an app that consumes strings") { GIVEN("a length_prefix_framing with an app that consumes strings") {
WHEN("pushing data into the unit-under-test") { WHEN("pushing data into the unit-under-test") {
auto buf = std::make_shared<string_list>(); 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 framing = net::length_prefix_framing::make(std::move(app));
auto uut = mock_stream_transport::make(std::move(framing)); 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") { THEN("the app receives all strings as individual messages") {
encode(uut->input, "hello"); encode(uut->input, "hello");
encode(uut->input, "world"); encode(uut->input, "world");
...@@ -157,57 +179,97 @@ SCENARIO("length-prefix framing reads data with 32-bit size headers") { ...@@ -157,57 +179,97 @@ 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; using namespace std::literals;
GIVEN("a length_prefix_framing with an app that consumes strings") { GIVEN("a length_prefix_framing with an app that consumes strings") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair()); auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
auto writer = std::thread{[fd1{fd1}] { auto writer = std::thread{run_writer, fd1};
auto guard = make_socket_guard(fd1); auto mpx = net::multiplexer::make(nullptr);
std::vector<std::string_view> inputs{"first", "second", "pause", "third", mpx->set_thread_id();
"fourth"}; if (auto err = mpx->init())
byte_buffer wr_buf; FAIL("mpx->init failed: " << err);
byte_buffer rd_buf; mpx->apply_updates();
rd_buf.resize(512); REQUIRE_EQ(mpx->num_socket_managers(), 1u);
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);
if (auto err = net::nonblocking(fd2, true)) if (auto err = net::nonblocking(fd2, true))
CAF_FAIL("nonblocking returned an error: " << err); CAF_FAIL("nonblocking returned an error: " << err);
auto buf = std::make_shared<string_list>(); 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 framing = net::length_prefix_framing::make(std::move(app));
auto transport = net::stream_transport::make(fd2, std::move(framing)); auto transport = net::stream_transport::make(fd2, std::move(framing));
auto mgr = net::socket_manager::make(&mpx, fd2, std::move(transport)); auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
CHECK_EQ(mgr->init(settings{}), none); CHECK_EQ(mgr->start(settings{}), none);
mpx.apply_updates(); mpx->apply_updates();
REQUIRE_EQ(mpx.num_socket_managers(), 2u); REQUIRE_EQ(mpx->num_socket_managers(), 2u);
CHECK_EQ(mpx.mask_of(mgr), net::operation::read); CHECK_EQ(mpx->mask_of(mgr), net::operation::read);
WHEN("the app calls suspend_reading") { WHEN("the app calls suspend_reading") {
while (mpx.num_socket_managers() > 1u) while (mpx->num_socket_managers() > 1u)
mpx.poll_once(true); mpx->poll_once(true);
CHECK_EQ(mpx.mask_of(mgr), net::operation::none); CHECK_EQ(mpx->mask_of(mgr), net::operation::none);
if (CHECK_EQ(buf->size(), 3u)) { if (CHECK_EQ(buf->size(), 3u)) {
CHECK_EQ(buf->at(0), "first"); CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second"); CHECK_EQ(buf->at(1), "second");
CHECK_EQ(buf->at(2), "pause"); CHECK_EQ(buf->at(2), "pause");
} }
THEN("users can resume it via continue_reading ") { THEN("users can resume it manually") {
mgr->continue_reading(); app_ptr->continue_reading();
mpx.apply_updates(); mpx->apply_updates();
mpx.poll_once(true); mpx->poll_once(true);
CHECK_EQ(mpx.mask_of(mgr), net::operation::read); CHECK_EQ(mpx->mask_of(mgr), net::operation::read);
while (mpx.num_socket_managers() > 1u) while (mpx->num_socket_managers() > 1u)
mpx.poll_once(true); 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();
}
}
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)) { if (CHECK_EQ(buf->size(), 5u)) {
CHECK_EQ(buf->at(0), "first"); CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second"); CHECK_EQ(buf->at(1), "second");
......
...@@ -63,61 +63,47 @@ public: ...@@ -63,61 +63,47 @@ public:
// -- implementation of socket_event_layer ----------------------------------- // -- 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; return none;
} }
read_result handle_read_event() override { net::socket handle() const override {
// if (trigger_handover) { return fd_;
// MESSAGE(name << " triggered a handover"); }
// return read_result::handover;
// } void handle_read_event() override {
if (read_capacity() < 1024) if (read_capacity() < 1024)
rd_buf_.resize(rd_buf_.size() + 2048); rd_buf_.resize(rd_buf_.size() + 2048);
auto num_bytes = read(fd_, auto res = read(fd_, make_span(read_position_begin(), read_capacity()));
make_span(read_position_begin(), read_capacity())); if (res > 0) {
if (num_bytes > 0) { CAF_ASSERT(res > 0);
CAF_ASSERT(num_bytes > 0); rd_buf_pos_ += res;
rd_buf_pos_ += num_bytes; } else if (res == 0 || !net::last_socket_error_is_temporary()) {
return read_result::again; mgr_->deregister();
} else if (num_bytes < 0 && net::last_socket_error_is_temporary()) {
return read_result::again;
} else {
return read_result::stop;
} }
} }
read_result handle_buffered_data() override { void handle_write_event() override {
return read_result::again; if (wr_buf_.size() == 0) {
} mgr_->deregister_writing();
} else if (auto res = write(fd_, wr_buf_); res > 0) {
read_result handle_continue_reading() override { wr_buf_.erase(wr_buf_.begin(), wr_buf_.begin() + res);
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) if (wr_buf_.size() == 0)
return write_result::stop; mgr_->deregister_writing();
auto num_bytes = write(fd_, wr_buf_); } else if (res == 0 || !net::last_socket_error_is_temporary()) {
if (num_bytes > 0) { mgr_->deregister();
wr_buf_.erase(wr_buf_.begin(), wr_buf_.begin() + num_bytes);
return wr_buf_.size() > 0 ? write_result::again : write_result::stop;
} }
return num_bytes < 0 && net::last_socket_error_is_temporary()
? write_result::again
: write_result::stop;
} }
void abort(const error& reason) override { void abort(const error& reason) override {
FAIL("abort called: " << reason); abort_reason = reason;
} }
std::string name; std::string name;
error abort_reason;
private: private:
std::byte* read_position_begin() { std::byte* read_position_begin() {
return rd_buf_.data() + rd_buf_pos_; return rd_buf_.data() + rd_buf_pos_;
...@@ -130,7 +116,6 @@ private: ...@@ -130,7 +116,6 @@ private:
size_t read_capacity() const { size_t read_capacity() const {
return rd_buf_.size() - rd_buf_pos_; return rd_buf_.size() - rd_buf_pos_;
} }
net::stream_socket fd_; net::stream_socket fd_;
shared_count count_; shared_count count_;
...@@ -140,46 +125,50 @@ private: ...@@ -140,46 +125,50 @@ private:
byte_buffer wr_buf_; byte_buffer wr_buf_;
byte_buffer rd_buf_; byte_buffer rd_buf_;
net::socket_manager* mgr_ = nullptr;
}; };
struct fixture { struct fixture {
fixture() : mpx(nullptr) { fixture() : mpx(net::multiplexer::make(nullptr)) {
manager_count = std::make_shared<std::atomic<size_t>>(0); manager_count = std::make_shared<std::atomic<size_t>>(0);
mpx.set_thread_id(); mpx->set_thread_id();
} }
~fixture() { ~fixture() {
mpx.shutdown(); mpx->shutdown();
exhaust(); exhaust();
REQUIRE_EQ(*manager_count, 0u); REQUIRE_EQ(*manager_count, 0u);
} }
void exhaust() { void exhaust() {
mpx.apply_updates(); mpx->apply_updates();
while (mpx.poll_once(false)) while (mpx->poll_once(false))
; // Repeat. ; // Repeat.
} }
void apply_updates() { void apply_updates() {
mpx.apply_updates(); mpx->apply_updates();
} }
std::pair<mock_event_layer*, net::socket_manager_ptr> std::pair<mock_event_layer*, net::socket_manager_ptr>
make_manager(net::stream_socket fd, std::string name) { make_manager(net::stream_socket fd, std::string name) {
auto mock = mock_event_layer::make(fd, std::move(name), manager_count); auto mock = mock_event_layer::make(fd, std::move(name), manager_count);
auto mock_ptr = mock.get(); 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() { void init() {
if (auto err = mpx.init()) if (auto err = mpx->init())
FAIL("mpx.init failed: " << err); FAIL("mpx->init failed: " << err);
exhaust(); exhaust();
} }
shared_count manager_count; shared_count manager_count;
net::multiplexer mpx; net::multiplexer_ptr mpx;
}; };
} // namespace } // namespace
...@@ -190,7 +179,7 @@ SCENARIO("the multiplexer has no socket managers after default construction") { ...@@ -190,7 +179,7 @@ SCENARIO("the multiplexer has no socket managers after default construction") {
GIVEN("a default constructed multiplexer") { GIVEN("a default constructed multiplexer") {
WHEN("querying the number of socket managers") { WHEN("querying the number of socket managers") {
THEN("the result is 0") { 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") { ...@@ -200,10 +189,10 @@ SCENARIO("the multiplexer constructs the pollset updater while initializing") {
GIVEN("an initialized multiplexer") { GIVEN("an initialized multiplexer") {
WHEN("querying the number of socket managers") { WHEN("querying the number of socket managers") {
THEN("the result is 1") { THEN("the result is 1") {
CHECK_EQ(mpx.num_socket_managers(), 0u); CHECK_EQ(mpx->num_socket_managers(), 0u);
CHECK_EQ(mpx.init(), none); CHECK_EQ(mpx->init(), none);
exhaust(); 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") { ...@@ -219,7 +208,7 @@ SCENARIO("socket managers can register for read and write operations") {
alice_mgr->register_reading(); alice_mgr->register_reading();
bob_mgr->register_reading(); bob_mgr->register_reading();
apply_updates(); apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 3u); CHECK_EQ(mpx->num_socket_managers(), 3u);
THEN("the multiplexer runs callbacks on socket activity") { THEN("the multiplexer runs callbacks on socket activity") {
alice->send("Hello Bob!"); alice->send("Hello Bob!");
alice_mgr->register_writing(); alice_mgr->register_writing();
...@@ -235,9 +224,9 @@ SCENARIO("a multiplexer terminates its thread after shutting down") { ...@@ -235,9 +224,9 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
init(); init();
auto go_time = std::make_shared<barrier>(2); auto go_time = std::make_shared<barrier>(2);
auto mpx_thread = std::thread{[this, go_time] { auto mpx_thread = std::thread{[this, go_time] {
mpx.set_thread_id(); mpx->set_thread_id();
go_time->arrive_and_wait(); go_time->arrive_and_wait();
mpx.run(); mpx->run();
}}; }};
go_time->arrive_and_wait(); go_time->arrive_and_wait();
auto [alice_fd, bob_fd] = unbox(net::make_stream_socket_pair()); auto [alice_fd, bob_fd] = unbox(net::make_stream_socket_pair());
...@@ -246,11 +235,11 @@ SCENARIO("a multiplexer terminates its thread after shutting down") { ...@@ -246,11 +235,11 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
alice_mgr->register_reading(); alice_mgr->register_reading();
bob_mgr->register_reading(); bob_mgr->register_reading();
WHEN("calling shutdown on the multiplexer") { WHEN("calling shutdown on the multiplexer") {
mpx.shutdown(); mpx->shutdown();
THEN("the thread terminates and all socket managers get shut down") { THEN("the thread terminates and all socket managers get shut down") {
mpx_thread.join(); mpx_thread.join();
CHECK(alice_mgr->read_closed()); CHECK(alice_mgr->disposed());
CHECK(bob_mgr->read_closed()); CHECK(bob_mgr->disposed());
} }
} }
} }
...@@ -268,7 +257,7 @@ SCENARIO("a multiplexer terminates its thread after shutting down") { ...@@ -268,7 +257,7 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
// alice->register_reading(); // alice->register_reading();
// bob->register_reading(); // bob->register_reading();
// apply_updates(); // 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") { // THEN("the multiplexer swaps out the socket managers for the socket") {
// alice->send("Hello Bob!"); // alice->send("Hello Bob!");
// alice->register_writing(); // 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,7 +59,7 @@ public: ...@@ -59,7 +59,7 @@ public:
*done_ = true; *done_ = true;
} }
error init(socket_manager*, net::stream_oriented::lower_layer* down, error start(net::stream_oriented::lower_layer* down,
const settings&) override { const settings&) override {
MESSAGE("initialize dummy app"); MESSAGE("initialize dummy app");
down_ = down; down_ = down;
...@@ -72,18 +72,14 @@ public: ...@@ -72,18 +72,14 @@ public:
return none; return none;
} }
bool prepare_send() override { void prepare_send() override {
return true; // nop
} }
bool done_sending() override { bool done_sending() override {
return true; return true;
} }
void continue_reading() override {
// nop
}
ptrdiff_t consume(byte_span data, byte_span) override { ptrdiff_t consume(byte_span data, byte_span) override {
MESSAGE("dummy app received " << data.size() << " bytes"); MESSAGE("dummy app received " << data.size() << " bytes");
// Store the received bytes. // Store the received bytes.
...@@ -191,8 +187,9 @@ SCENARIO("ssl::transport::make_client performs the client handshake") { ...@@ -191,8 +187,9 @@ SCENARIO("ssl::transport::make_client performs the client handshake") {
key_1_pem_path}; key_1_pem_path};
WHEN("connecting as a client to an SSL server") { WHEN("connecting as a client to an SSL server") {
THEN("CAF transparently calls SSL_connect") { THEN("CAF transparently calls SSL_connect") {
net::multiplexer mpx{nullptr}; auto mpx = net::multiplexer::make(nullptr);
mpx.set_thread_id(); mpx->set_thread_id();
std::ignore = mpx->init();
auto ctx = unbox(ssl::context::make_client(ssl::tls::any)); auto ctx = unbox(ssl::context::make_client(ssl::tls::any));
auto conn = unbox(ctx.new_connection(client_fd)); auto conn = unbox(ctx.new_connection(client_fd));
auto done = std::make_shared<bool>(false); auto done = std::make_shared<bool>(false);
...@@ -200,12 +197,11 @@ SCENARIO("ssl::transport::make_client performs the client handshake") { ...@@ -200,12 +197,11 @@ SCENARIO("ssl::transport::make_client performs the client handshake") {
auto mock = mock_application::make(done, buf); auto mock = mock_application::make(done, buf);
auto transport = ssl::transport::make_client(std::move(conn), auto transport = ssl::transport::make_client(std::move(conn),
std::move(mock)); std::move(mock));
auto mgr = net::socket_manager::make(&mpx, client_fd, auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
std::move(transport)); mpx->start(mgr);
mpx.init(mgr); mpx->apply_updates();
mpx.apply_updates();
while (!*done) while (!*done)
mpx.poll_once(true); mpx->poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf}; caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
...@@ -228,8 +224,9 @@ SCENARIO("ssl::transport::make_server performs the server handshake") { ...@@ -228,8 +224,9 @@ SCENARIO("ssl::transport::make_server performs the server handshake") {
std::thread client{dummy_tls_client, client_fd}; std::thread client{dummy_tls_client, client_fd};
WHEN("acting as the SSL server") { WHEN("acting as the SSL server") {
THEN("CAF transparently calls SSL_accept") { THEN("CAF transparently calls SSL_accept") {
net::multiplexer mpx{nullptr}; auto mpx = net::multiplexer::make(nullptr);
mpx.set_thread_id(); mpx->set_thread_id();
std::ignore = mpx->init();
auto ctx = unbox(ssl::context::make_server(ssl::tls::any)); auto ctx = unbox(ssl::context::make_server(ssl::tls::any));
REQUIRE(ctx.use_certificate_from_file(cert_1_pem_path, // REQUIRE(ctx.use_certificate_from_file(cert_1_pem_path, //
ssl::format::pem)); ssl::format::pem));
...@@ -241,12 +238,11 @@ SCENARIO("ssl::transport::make_server performs the server handshake") { ...@@ -241,12 +238,11 @@ SCENARIO("ssl::transport::make_server performs the server handshake") {
auto mock = mock_application::make(done, buf); auto mock = mock_application::make(done, buf);
auto transport = ssl::transport::make_server(std::move(conn), auto transport = ssl::transport::make_server(std::move(conn),
std::move(mock)); std::move(mock));
auto mgr = net::socket_manager::make(&mpx, server_fd, auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
std::move(transport)); mpx->start(mgr);
mpx.init(mgr); mpx->apply_updates();
mpx.apply_updates();
while (!*done) while (!*done)
mpx.poll_once(true); mpx->poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf}; caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
......
...@@ -30,15 +30,15 @@ struct fixture : test_coordinator_fixture<> { ...@@ -30,15 +30,15 @@ struct fixture : test_coordinator_fixture<> {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>; using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
fixture() fixture()
: mpx(nullptr), : mpx(net::multiplexer::make(nullptr)),
recv_buf(1024), recv_buf(1024),
shared_recv_buf{std::make_shared<byte_buffer>()}, shared_recv_buf{std::make_shared<byte_buffer>()},
shared_send_buf{std::make_shared<byte_buffer>()} { shared_send_buf{std::make_shared<byte_buffer>()} {
mpx.set_thread_id(); mpx->set_thread_id();
mpx.apply_updates(); mpx->apply_updates();
if (auto err = mpx.init()) if (auto err = mpx->init())
FAIL("mpx.init failed: " << err); FAIL("mpx->init failed: " << err);
REQUIRE_EQ(mpx.num_socket_managers(), 1u); REQUIRE_EQ(mpx->num_socket_managers(), 1u);
auto sockets = unbox(net::make_stream_socket_pair()); auto sockets = unbox(net::make_stream_socket_pair());
send_socket_guard.reset(sockets.first); send_socket_guard.reset(sockets.first);
recv_socket_guard.reset(sockets.second); recv_socket_guard.reset(sockets.second);
...@@ -47,11 +47,11 @@ struct fixture : test_coordinator_fixture<> { ...@@ -47,11 +47,11 @@ struct fixture : test_coordinator_fixture<> {
} }
bool handle_io_event() override { bool handle_io_event() override {
return mpx.poll_once(false); return mpx->poll_once(false);
} }
settings config; settings config;
net::multiplexer mpx; net::multiplexer_ptr mpx;
byte_buffer recv_buf; byte_buffer recv_buf;
net::socket_guard<net::stream_socket> send_socket_guard; net::socket_guard<net::stream_socket> send_socket_guard;
net::socket_guard<net::stream_socket> recv_socket_guard; net::socket_guard<net::stream_socket> recv_socket_guard;
...@@ -75,7 +75,7 @@ public: ...@@ -75,7 +75,7 @@ public:
net::stream_oriented::lower_layer* down; net::stream_oriented::lower_layer* down;
error init(net::socket_manager*, net::stream_oriented::lower_layer* down_ptr, error start(net::stream_oriented::lower_layer* down_ptr,
const settings&) override { const settings&) override {
down = down_ptr; down = down_ptr;
down->configure_read(net::receive_policy::exactly(hello_manager.size())); down->configure_read(net::receive_policy::exactly(hello_manager.size()));
...@@ -93,16 +93,11 @@ public: ...@@ -93,16 +93,11 @@ public:
return static_cast<ptrdiff_t>(recv_buf_->size()); return static_cast<ptrdiff_t>(recv_buf_->size());
} }
void continue_reading() override { void prepare_send() override {
FAIL("continue_reading called");
}
bool prepare_send() override {
MESSAGE("prepare_send called"); MESSAGE("prepare_send called");
auto& buf = down->output_buffer(); auto& buf = down->output_buffer();
auto data = as_bytes(make_span(hello_manager)); auto data = as_bytes(make_span(hello_manager));
buf.insert(buf.end(), data.begin(), data.end()); buf.insert(buf.end(), data.begin(), data.end());
return true;
} }
bool done_sending() override { bool done_sending() override {
...@@ -121,13 +116,12 @@ BEGIN_FIXTURE_SCOPE(fixture) ...@@ -121,13 +116,12 @@ BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(receive) { CAF_TEST(receive) {
auto mock = mock_application::make(shared_recv_buf, shared_send_buf); 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)); std::move(mock));
auto mgr = net::socket_manager::make(&mpx, recv_socket_guard.release(), auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
std::move(transport)); CHECK_EQ(mgr->start(config), none);
CHECK_EQ(mgr->init(config), none); mpx->apply_updates();
mpx.apply_updates(); CHECK_EQ(mpx->num_socket_managers(), 2u);
CHECK_EQ(mpx.num_socket_managers(), 2u);
CHECK_EQ(static_cast<size_t>(write(send_socket_guard.socket(), CHECK_EQ(static_cast<size_t>(write(send_socket_guard.socket(),
as_bytes(make_span(hello_manager)))), as_bytes(make_span(hello_manager)))),
hello_manager.size()); hello_manager.size());
...@@ -140,15 +134,14 @@ CAF_TEST(receive) { ...@@ -140,15 +134,14 @@ CAF_TEST(receive) {
CAF_TEST(send) { CAF_TEST(send) {
auto mock = mock_application::make(shared_recv_buf, shared_send_buf); 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)); std::move(mock));
auto mgr = net::socket_manager::make(&mpx, recv_socket_guard.release(), auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
std::move(transport)); CHECK_EQ(mgr->start(config), none);
CHECK_EQ(mgr->init(config), none); mpx->apply_updates();
mpx.apply_updates(); CHECK_EQ(mpx->num_socket_managers(), 2u);
CHECK_EQ(mpx.num_socket_managers(), 2u);
mgr->register_writing(); mgr->register_writing();
mpx.apply_updates(); mpx->apply_updates();
while (handle_io_event()) while (handle_io_event())
; ;
recv_buf.resize(hello_manager.size()); recv_buf.resize(hello_manager.size());
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "caf/byte.hpp" #include "caf/byte.hpp"
#include "caf/byte_span.hpp" #include "caf/byte_span.hpp"
#include "caf/callback.hpp" #include "caf/callback.hpp"
#include "caf/net/make_actor_shell.hpp"
#include "caf/net/middleman.hpp" #include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp" #include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp" #include "caf/net/socket_manager.hpp"
...@@ -29,24 +30,20 @@ using string_consumer = typed_actor<result<void>(std::string)>; ...@@ -29,24 +30,20 @@ using string_consumer = typed_actor<result<void>(std::string)>;
class app_t : public net::stream_oriented::upper_layer { class app_t : public net::stream_oriented::upper_layer {
public: public:
app_t() = default; explicit app_t(actor_system& sys, async::execution_context_ptr loop,
actor hdl = {})
explicit app_t(actor hdl) : worker(std::move(hdl)) { : worker(std::move(hdl)) {
// nop self = net::make_actor_shell<string_consumer>(sys, loop);
}
static auto make() {
return std::make_unique<app_t>();
} }
static auto make(actor hdl) { static auto make(actor_system& sys, async::execution_context_ptr loop,
return std::make_unique<app_t>(std::move(hdl)); 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, error start(net::stream_oriented::lower_layer* down,
const settings&) override { const settings&) override {
this->down = down; this->down = down;
self = mgr->make_actor_shell<string_consumer>();
self->set_behavior([this](std::string& line) { self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line); CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line)); lines.emplace_back(std::move(line));
...@@ -59,22 +56,16 @@ public: ...@@ -59,22 +56,16 @@ public:
return none; return none;
} }
bool prepare_send() override { void prepare_send() override {
while (self->consume_message()) // nop
; // repeat
return !self->terminated();
}
void continue_reading() override {
CAF_FAIL("continue_reading called");
} }
bool done_sending() override { bool done_sending() override {
return self->try_block_mailbox(); return true;
} }
void abort(const error& reason) override { void abort(const error&) override {
CAF_FAIL("app::abort called: " << reason); // nop
} }
ptrdiff_t consume(byte_span buf, byte_span) override { ptrdiff_t consume(byte_span buf, byte_span) override {
...@@ -138,7 +129,7 @@ public: ...@@ -138,7 +129,7 @@ public:
std::vector<std::string> lines; std::vector<std::string> lines;
// Actor shell representing this app. // 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. // Counts how many bytes we've consumed in total.
size_t consumed_bytes = 0; size_t consumed_bytes = 0;
...@@ -148,10 +139,10 @@ public: ...@@ -148,10 +139,10 @@ public:
}; };
struct fixture : test_coordinator_fixture<> { struct fixture : test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) { fixture() : mm(sys), mpx(net::multiplexer::make(&mm)) {
mpx.set_thread_id(); mpx->set_thread_id();
if (auto err = mpx.init()) if (auto err = mpx->init())
CAF_FAIL("mpx.init() failed: " << err); CAF_FAIL("mpx->init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair()); auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first); self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second); testee_socket_guard.reset(sockets.second);
...@@ -166,8 +157,8 @@ struct fixture : test_coordinator_fixture<> { ...@@ -166,8 +157,8 @@ struct fixture : test_coordinator_fixture<> {
if (!predicate()) if (!predicate())
return; return;
for (size_t i = 0; i < 1000; ++i) { for (size_t i = 0; i < 1000; ++i) {
mpx.apply_updates(); mpx->apply_updates();
mpx.poll_once(false); mpx->poll_once(false);
std::byte tmp[1024]; std::byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024)); auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0) if (bytes > 0)
...@@ -186,7 +177,7 @@ struct fixture : test_coordinator_fixture<> { ...@@ -186,7 +177,7 @@ struct fixture : test_coordinator_fixture<> {
} }
net::middleman mm; 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> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard; net::socket_guard<net::stream_socket> testee_socket_guard;
byte_buffer recv_buf; byte_buffer recv_buf;
...@@ -202,18 +193,20 @@ CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture) ...@@ -202,18 +193,20 @@ CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) { CAF_TEST(actor shells expose their mailbox to their owners) {
auto fd = testee_socket_guard.release(); 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 app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr)); auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport)); auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->init(content(cfg))) if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err); CAF_FAIL("mgr->start() failed: " << err);
auto hdl = app->self.as_actor(); auto hdl = app->self.as_actor();
anon_send(hdl, "line 1"); anon_send(hdl, "line 1");
anon_send(hdl, "line 2"); anon_send(hdl, "line 2");
anon_send(hdl, "line 3"); anon_send(hdl, "line 3");
run_while([&] { return app->lines.size() != 3; }); run_while([&] { return app->lines.size() != 3; });
CAF_CHECK_EQUAL(app->lines, svec({"line 1", "line 2", "line 3"})); CAF_CHECK_EQUAL(app->lines, svec({"line 1", "line 2", "line 3"}));
self_socket_guard.reset();
run_while([&] { return mpx->num_socket_managers() > 1; });
} }
CAF_TEST(actor shells can send requests and receive responses) { CAF_TEST(actor shells can send requests and receive responses) {
...@@ -223,12 +216,12 @@ CAF_TEST(actor shells can send requests and receive responses) { ...@@ -223,12 +216,12 @@ CAF_TEST(actor shells can send requests and receive responses) {
}; };
}); });
auto fd = testee_socket_guard.release(); 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 app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr)); auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport)); auto mgr = net::socket_manager::make(mpx.get(), std::move(transport));
if (auto err = mgr->init(content(cfg))) if (auto err = mgr->start(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err); CAF_FAIL("mgr->start() failed: " << err);
send(input); send(input);
run_while([&] { return app->consumed_bytes != input.size(); }); run_while([&] { return app->consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123)); expect((int32_t), to(worker).with(123));
...@@ -237,6 +230,8 @@ CAF_TEST(actor shells can send requests and receive responses) { ...@@ -237,6 +230,8 @@ CAF_TEST(actor shells can send requests and receive responses) {
std::string_view received_response{reinterpret_cast<char*>(recv_buf.data()), std::string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()}; recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response); CAF_CHECK_EQUAL(received_response, expected_response);
self_socket_guard.reset();
run_while([&] { return mpx->num_socket_managers() > 1; });
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
...@@ -26,24 +26,19 @@ public: ...@@ -26,24 +26,19 @@ public:
settings cfg; settings cfg;
error init(net::socket_manager*, net::web_socket::lower_layer*, error start(net::web_socket::lower_layer*, const settings& args) override {
const settings& init_cfg) override { cfg = args;
cfg = init_cfg;
return none; return none;
} }
bool prepare_send() override { void prepare_send() override {
return true; // nop
} }
bool done_sending() override { bool done_sending() override {
return true; return true;
} }
void continue_reading() override {
// nop
}
void abort(const error& reason) override { void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason); CAF_FAIL("app::abort called: " << reason);
} }
...@@ -106,7 +101,7 @@ SCENARIO("the client performs the WebSocket handshake on startup") { ...@@ -106,7 +101,7 @@ SCENARIO("the client performs the WebSocket handshake on startup") {
auto& ws_state = *ws; auto& ws_state = *ws;
auto uut = mock_stream_transport::make(std::move(ws)); auto uut = mock_stream_transport::make(std::move(ws));
THEN("the client sends its HTTP request when initializing it") { 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); CHECK_EQ(uut->output_as_str(), http_request);
} }
AND("the client waits for the server handshake and validates it") { AND("the client waits for the server handshake and validates it") {
......
...@@ -33,24 +33,19 @@ public: ...@@ -33,24 +33,19 @@ public:
return std::make_unique<app_t>(); return std::make_unique<app_t>();
} }
error init(net::socket_manager*, net::web_socket::lower_layer*, error start(net::web_socket::lower_layer*, const settings& args) override {
const settings& init_cfg) override { cfg = args;
cfg = init_cfg;
return none; return none;
} }
bool prepare_send() override { void prepare_send() override {
return true; // nop
} }
bool done_sending() override { bool done_sending() override {
return true; return true;
} }
void continue_reading() override {
// nop
}
void abort(const error& reason) override { void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason); CAF_FAIL("app::abort called: " << reason);
} }
...@@ -74,7 +69,7 @@ struct fixture { ...@@ -74,7 +69,7 @@ struct fixture {
auto ws_ptr = net::web_socket::server::make(std::move(app_ptr)); auto ws_ptr = net::web_socket::server::make(std::move(app_ptr));
ws = ws_ptr.get(); ws = ws_ptr.get();
transport = mock_stream_transport::make(std::move(ws_ptr)); 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); CAF_FAIL("failed to initialize mock transport: " << err);
rng.seed(0xD3ADC0D3); 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