Commit 4c68deb8 authored by Dominik Charousset's avatar Dominik Charousset

Add infrastructure for a new flow API

parent b10ee098
...@@ -42,7 +42,7 @@ configure_file("${PROJECT_SOURCE_DIR}/cmake/build_config.hpp.in" ...@@ -42,7 +42,7 @@ configure_file("${PROJECT_SOURCE_DIR}/cmake/build_config.hpp.in"
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/caf/detail/build_config.hpp" install(FILES "${CMAKE_CURRENT_BINARY_DIR}/caf/detail/build_config.hpp"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/caf/detail/") DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/caf/detail/")
# -- add targets --------------------------------------------------------------- #-- add targets ----------------------------------------------------------------
caf_add_component( caf_add_component(
core core
...@@ -81,6 +81,9 @@ caf_add_component( ...@@ -81,6 +81,9 @@ caf_add_component(
src/actor_registry.cpp src/actor_registry.cpp
src/actor_system.cpp src/actor_system.cpp
src/actor_system_config.cpp src/actor_system_config.cpp
src/async/batch.cpp
src/async/consumer.cpp
src/async/producer.cpp
src/attachable.cpp src/attachable.cpp
src/behavior.cpp src/behavior.cpp
src/binary_deserializer.cpp src/binary_deserializer.cpp
...@@ -141,6 +144,9 @@ caf_add_component( ...@@ -141,6 +144,9 @@ caf_add_component(
src/error.cpp src/error.cpp
src/event_based_actor.cpp src/event_based_actor.cpp
src/execution_unit.cpp src/execution_unit.cpp
src/flow/coordinator.cpp
src/flow/scoped_coordinator.cpp
src/flow/subscription.cpp
src/forwarding_actor_proxy.cpp src/forwarding_actor_proxy.cpp
src/group.cpp src/group.cpp
src/group_manager.cpp src/group_manager.cpp
...@@ -221,6 +227,7 @@ caf_add_component( ...@@ -221,6 +227,7 @@ caf_add_component(
actor_system_config actor_system_config
actor_termination actor_termination
aout aout
async.bounded_buffer
behavior behavior
binary_deserializer binary_deserializer
binary_serializer binary_serializer
...@@ -273,6 +280,13 @@ caf_add_component( ...@@ -273,6 +280,13 @@ caf_add_component(
dynamic_spawn dynamic_spawn
error error
expected expected
flow.concat
flow.concat_map
flow.flat_map
flow.for_each
flow.merge
flow.observe_on
flow.single
function_view function_view
fused_downstream_manager fused_downstream_manager
handles handles
...@@ -315,6 +329,7 @@ caf_add_component( ...@@ -315,6 +329,7 @@ caf_add_component(
policy.select_all policy.select_all
policy.select_any policy.select_any
request_timeout request_timeout
response_handle
response_promise response_promise
result result
save_inspector save_inspector
......
...@@ -587,6 +587,61 @@ public: ...@@ -587,6 +587,61 @@ public:
return res; return res;
} }
/// Creates a new, cooperatively scheduled `flow::coordinator`. The returned
/// coordinator is constructed but has not been added to the scheduler yet to
/// allow the caller to set up flows.
/// @returns A pointer to the new coordinator and a function object that the
/// caller must invoke to launch the coordinator. After the
/// coordinator started running, the caller *must not* access the
/// pointer again.
template <class Impl, spawn_options = no_spawn_options, class... Ts>
auto make_flow_coordinator(Ts&&... xs) {
static_assert(std::is_base_of_v<scheduled_actor, Impl>,
"make_flow_coordinator only supports scheduled actors ATM");
CAF_SET_LOGGER_SYS(this);
actor_config cfg{dummy_execution_unit(), nullptr};
auto res = make_actor<Impl>(next_actor_id(), node(), this, cfg,
std::forward<Ts>(xs)...);
auto ptr = static_cast<Impl*>(actor_cast<abstract_actor*>(res));
#ifdef CAF_ENABLE_ACTOR_PROFILER
profiler_add_actor(*ptr, cfg.parent);
#endif
auto launch = [res, host{cfg.host}] {
// Note: we pass `res` to this lambda instead of `ptr` to keep a strong
// reference to the actor.
static_cast<Impl*>(actor_cast<abstract_actor*>(res))
->launch(host, false, false);
};
return std::make_tuple(ptr, launch);
}
/// Creates a new, cooperatively scheduled actor. The returned actor is
/// constructed but has not been added to the scheduler yet to allow the
/// caller to set up any additional logic on the actor before it starts.
/// @returns A pointer to the new actor and a function object that the caller
/// must invoke to launch the actor. After the actor started running,
/// the caller *must not* access the pointer again.
template <class Impl, spawn_options = no_spawn_options, class... Ts>
auto spawn_inactive(Ts&&... xs) {
static_assert(std::is_base_of_v<scheduled_actor, Impl>,
"only scheduled actors may get spawned inactively");
CAF_SET_LOGGER_SYS(this);
actor_config cfg{dummy_execution_unit(), nullptr};
auto res = make_actor<Impl>(next_actor_id(), node(), this, cfg,
std::forward<Ts>(xs)...);
auto ptr = static_cast<Impl*>(actor_cast<abstract_actor*>(res));
#ifdef CAF_ENABLE_ACTOR_PROFILER
profiler_add_actor(*ptr, cfg.parent);
#endif
auto launch = [res, host{cfg.host}] {
// Note: we pass `res` to this lambda instead of `ptr` to keep a strong
// reference to the actor.
static_cast<Impl*>(actor_cast<abstract_actor*>(res))
->launch(host, false, false);
};
return std::make_tuple(ptr, launch);
}
void profiler_add_actor(const local_actor& self, const local_actor* parent) { void profiler_add_actor(const local_actor& self, const local_actor* parent) {
if (profiler_) if (profiler_)
profiler_->add_actor(self, parent); profiler_->add_actor(self, parent);
......
// 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 <atomic>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include "caf/async/fwd.hpp"
#include "caf/byte.hpp"
#include "caf/config.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/raise_error.hpp"
#include "caf/span.hpp"
#include "caf/type_id.hpp"
#ifdef CAF_CLANG
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wc99-extensions"
#elif defined(CAF_GCC)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wpedantic"
#elif defined(CAF_MSVC)
# pragma warning(push)
# pragma warning(disable : 4200)
#endif
namespace caf::async {
/// A reference-counted container for transferring items from publishers to
/// subscribers.
class CAF_CORE_EXPORT batch {
public:
template <class T>
friend batch make_batch(span<const T> items);
using item_destructor = void (*)(type_id_t, uint16_t, size_t, byte*);
batch() = default;
batch(batch&&) = default;
batch(const batch&) = default;
batch& operator=(batch&&) = default;
batch& operator=(const batch&) = default;
size_t size() const noexcept {
return data_ ? data_->size() : 0;
}
bool empty() const noexcept {
return data_ ? data_->size() == 0 : true;
}
template <class T>
span<T> items() noexcept {
return data_ ? data_->items<T>() : span<T>{};
}
template <class T>
span<const T> items() const noexcept {
return data_ ? data_->items<T>() : span<const T>{};
}
bool save(serializer& f) const;
bool save(binary_serializer& f) const;
bool load(deserializer& f);
bool load(binary_deserializer& f);
void swap(batch& other) {
data_.swap(other.data_);
}
private:
template <class Inspector>
bool save_impl(Inspector& f) const;
template <class Inspector>
bool load_impl(Inspector& f);
class data {
public:
data() = delete;
data(const data&) = delete;
data& operator=(const data&) = delete;
data(item_destructor destroy_items, type_id_t item_type, uint16_t item_size,
size_t size)
: rc_(1),
destroy_items_(destroy_items),
item_type_(item_type),
item_size_(item_size),
size_(size) {
// nop
}
~data() {
if (size_ > 0)
destroy_items_(item_type_, item_size_, size_, storage_);
}
// -- reference counting ---------------------------------------------------
bool unique() const noexcept {
return rc_ == 1;
}
void ref() const noexcept {
rc_.fetch_add(1, std::memory_order_relaxed);
}
void deref() noexcept {
if (unique() || rc_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
this->~data();
free(this);
}
}
friend void intrusive_ptr_add_ref(const data* ptr) {
ptr->ref();
}
friend void intrusive_ptr_release(data* ptr) {
ptr->deref();
}
// -- properties -----------------------------------------------------------
type_id_t item_type() {
return item_type_;
}
size_t size() noexcept {
return size_;
}
byte* storage() noexcept {
return storage_;
}
const byte* storage() const noexcept {
return storage_;
}
template <class T>
span<T> items() noexcept {
return {reinterpret_cast<T*>(storage_), size_};
}
template <class T>
span<const T> items() const noexcept {
return {reinterpret_cast<const T*>(storage_), size_};
}
// -- serialization --------------------------------------------------------
/// @pre `size() > 0`
template <class Inspector>
bool save(Inspector& sink) const;
template <class Inspector>
bool load(Inspector& source);
private:
mutable std::atomic<size_t> rc_;
item_destructor destroy_items_;
type_id_t item_type_;
uint16_t item_size_;
size_t size_;
byte storage_[];
};
explicit batch(intrusive_ptr<data> ptr) : data_(std::move(ptr)) {
// nop
}
intrusive_ptr<data> data_;
};
template <class Inspector>
auto inspect(Inspector& f, batch& x)
-> std::enable_if_t<!Inspector::is_loading, decltype(x.save(f))> {
return x.save(f);
}
template <class Inspector>
auto inspect(Inspector& f, batch& x)
-> std::enable_if_t<Inspector::is_loading, decltype(x.load(f))> {
return x.load(f);
}
template <class T>
batch make_batch(span<const T> items) {
static_assert(sizeof(T) < 0xFFFF);
auto total_size = sizeof(batch::data) + (items.size() * sizeof(T));
auto vptr = malloc(total_size);
if (vptr == nullptr)
CAF_RAISE_ERROR(std::bad_alloc, "make_batch");
auto destroy_items = [](type_id_t, uint16_t, size_t size, byte* storage) {
auto ptr = reinterpret_cast<T*>(storage);
std::destroy(ptr, ptr + size);
};
intrusive_ptr<batch::data> ptr{
new (vptr) batch::data(destroy_items, type_id_or_invalid<T>(),
static_cast<uint16_t>(sizeof(T)), items.size()),
false};
std::uninitialized_copy(items.begin(), items.end(),
reinterpret_cast<T*>(ptr->storage()));
return batch{std::move(ptr)};
}
} // namespace caf::async
#ifdef CAF_CLANG
# pragma clang diagnostic pop
#elif defined(CAF_GCC)
# pragma GCC diagnostic pop
#elif defined(CAF_MSVC)
# pragma warning(pop)
#endif
// 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 <condition_variable>
#include <type_traits>
#include "caf/async/observer_buffer.hpp"
namespace caf::async {
/// Consumes all elements from a publisher and blocks the current thread until
/// completion.
template <class T>
class blocking_observer : public observer_buffer<T> {
public:
using super = observer_buffer<T>;
using super::super;
template <class OnNext, class OnError, class OnComplete>
auto run(OnNext fun, OnError err, OnComplete fin) {
static_assert(std::is_invocable_v<OnNext, T>,
"OnNext handlers must have signature 'void(T)' or 'bool(T)'");
static_assert(std::is_invocable_v<OnError, error>,
"OnError handlers must have signature 'void(const error&)'");
static_assert(std::is_invocable_v<OnComplete>,
"OnComplete handlers must have signature 'void()'");
auto wait_fn = [this](std::unique_lock<std::mutex>& guard) {
cv_.wait(guard);
};
for (;;) {
auto [val, done, what] = super::wait_with(wait_fn);
if (val) {
using fun_res = decltype(fun(*val));
if constexpr (std::is_same_v<bool, fun_res>) {
if (!fun(*val)) {
std::unique_lock guard{super::mtx_};
if (super::sub_)
super::sub_->cancel();
return;
}
} else {
static_assert(
std::is_same_v<void, fun_res>,
"OnNext handlers must have signature 'void(T)' or 'bool(T)'");
fun(*val);
}
} else if (done) {
if (!what)
fin();
else
err(*what);
return;
}
}
}
private:
std::condition_variable cv_;
void wakeup(std::unique_lock<std::mutex>&) override {
cv_.notify_all();
}
};
} // namespace caf::async
// 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 <cstdlib>
#include <mutex>
#include "caf/async/consumer.hpp"
#include "caf/async/producer.hpp"
#include "caf/config.hpp"
#include "caf/defaults.hpp"
#include "caf/error.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/make_counted.hpp"
#include "caf/raise_error.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/unit.hpp"
namespace caf::async {
/// Policy type for having `consume` call `on_error` immediately after the
/// producer has aborted even if the buffer still contains events.
struct prioritize_errors_t {
static constexpr bool calls_on_error = true;
};
/// @relates prioritize_errors_t
constexpr auto prioritize_errors = prioritize_errors_t{};
/// Policy type for having `consume` call `on_error` only after processing all
/// events from the buffer.
struct delay_errors_t {
static constexpr bool calls_on_error = true;
};
/// @relates delay_errors_t
constexpr auto delay_errors = delay_errors_t{};
/// Policy type for having `consume` treat errors as ordinary shutdowns.
struct ignore_errors_t {
static constexpr bool calls_on_error = false;
};
/// @relates ignore_errors_t
constexpr auto ignore_errors = ignore_errors_t{};
/// A bounded buffer for transmitting events from one producer to one consumer.
template <class T>
class bounded_buffer : public ref_counted {
public:
using value_type = T;
bounded_buffer(uint32_t max_in_flight, uint32_t min_pull_size)
: max_in_flight_(max_in_flight), min_pull_size_(min_pull_size) {
buf_ = reinterpret_cast<T*>(malloc(sizeof(T) * max_in_flight * 2));
}
~bounded_buffer() {
auto first = buf_ + rd_pos_;
auto last = buf_ + wr_pos_;
std::destroy(first, last);
free(buf_);
}
/// Appends to the buffer and calls `on_producer_wakeup` on the consumer if
/// the buffer becomes non-empty.
size_t push(span<const T> items) {
std::unique_lock guard{mtx_};
CAF_ASSERT(producer_ != nullptr);
CAF_ASSERT(!closed_);
std::uninitialized_copy(items.begin(), items.end(), buf_ + wr_pos_);
wr_pos_ += items.size();
if (size() == items.size() && consumer_)
consumer_->on_producer_wakeup();
return capacity() - size();
}
size_t push(const T& item) {
return push(make_span(&item, 1));
}
/// Consumes up to `demand` items from the buffer with `on_next`, ignoring any
/// errors set by the producer.
/// @tparam Policy Either `instant_error_t` or `delay_error_t`. Instant
/// error propagation requires also passing `on_error` handler.
/// Delaying errors without passing an `on_error` handler
/// effectively suppresses errors.
/// @returns `true` if no more elements are available, `false` otherwise.
template <class Policy, class OnNext, class OnError = unit_t>
bool
consume(Policy, size_t demand, OnNext on_next, OnError on_error = OnError{}) {
static constexpr size_t local_buf_size = 16;
if constexpr (Policy::calls_on_error)
static_assert(!std::is_same_v<OnError, unit_t>,
"Policy requires an on_error handler");
else
static_assert(std::is_same_v<OnError, unit_t>,
"Policy prohibits an on_error handler");
T local_buf[local_buf_size];
std::unique_lock guard{mtx_};
CAF_ASSERT(demand > 0);
CAF_ASSERT(consumer_ != nullptr);
if constexpr (std::is_same_v<Policy, prioritize_errors_t>) {
if (err_) {
on_error(err_);
consumer_ = nullptr;
return true;
}
}
auto next_n = [this, &demand] {
return std::min({local_buf_size, demand, size()});
};
for (auto n = next_n(); n > 0; n = next_n()) {
auto first = buf_ + rd_pos_;
std::move(first, first + n, local_buf);
std::destroy(first, first + n);
rd_pos_ += n;
shift_elements();
signal_demand(n);
guard.unlock();
on_next(make_span(local_buf, n));
demand -= n;
guard.lock();
}
if (!empty() || !closed_) {
return false;
} else {
if constexpr (std::is_same_v<Policy, delay_errors_t>) {
if (err_)
on_error(err_);
}
consumer_ = nullptr;
return true;
}
}
/// Checks whether there is any pending data in the buffer.
bool has_data() const noexcept {
std::unique_lock guard{mtx_};
return !empty();
}
/// Closes the buffer by request of the producer.
void close() {
std::unique_lock guard{mtx_};
CAF_ASSERT(producer_ != nullptr);
closed_ = true;
producer_ = nullptr;
if (empty() && consumer_)
consumer_->on_producer_wakeup();
}
/// Closes the buffer and signals an error by request of the producer.
void abort(error reason) {
std::unique_lock guard{mtx_};
closed_ = true;
err_ = std::move(reason);
producer_ = nullptr;
if (empty() && consumer_) {
consumer_->on_producer_wakeup();
consumer_ = nullptr;
}
}
/// Closes the buffer by request of the consumer.
void cancel() {
std::unique_lock guard{mtx_};
if (producer_)
producer_->on_consumer_cancel();
consumer_ = nullptr;
}
void set_consumer(consumer_ptr consumer) {
CAF_ASSERT(consumer != nullptr);
std::unique_lock guard{mtx_};
if (consumer_)
CAF_RAISE_ERROR("producer-consumer queue already has a consumer");
consumer_ = std::move(consumer);
if (producer_)
ready();
}
void set_producer(producer_ptr producer) {
CAF_ASSERT(producer != nullptr);
std::unique_lock guard{mtx_};
if (producer_)
CAF_RAISE_ERROR("producer-consumer queue already has a producer");
producer_ = std::move(producer);
if (consumer_)
ready();
}
size_t capacity() const noexcept {
return max_in_flight_;
}
private:
void ready() {
producer_->on_consumer_ready();
consumer_->on_producer_ready();
if (!empty())
consumer_->on_producer_wakeup();
}
size_t empty() const noexcept {
CAF_ASSERT(wr_pos_ >= rd_pos_);
return wr_pos_ == rd_pos_;
}
size_t size() const noexcept {
CAF_ASSERT(wr_pos_ >= rd_pos_);
return wr_pos_ - rd_pos_;
}
void shift_elements() {
if (rd_pos_ >= max_in_flight_) {
if (empty()) {
rd_pos_ = 0;
wr_pos_ = 0;
} else {
// No need to check for overlap: the first half of the buffer is
// empty.
auto first = buf_ + rd_pos_;
auto last = buf_ + wr_pos_;
std::uninitialized_move(first, last, buf_);
std::destroy(first, last);
wr_pos_ -= rd_pos_;
rd_pos_ = 0;
}
}
}
void signal_demand(uint32_t new_demand) {
demand_ += new_demand;
if (demand_ >= min_pull_size_ && producer_) {
producer_->on_consumer_demand(demand_);
demand_ = 0;
}
}
/// Guards access to all other member variables.
mutable std::mutex mtx_;
/// Allocated to max_in_flight_ * 2, but at most holds max_in_flight_
/// elements at any point in time. We dynamically shift elements into the
/// first half of the buffer whenever rd_pos_ crosses the midpoint.
T* buf_;
/// Stores how many items the buffer may hold at any time.
uint32_t max_in_flight_;
/// Configures the minimum amount of free buffer slots that we signal to the
/// producer.
uint32_t min_pull_size_;
/// Stores the read position of the consumer.
uint32_t rd_pos_ = 0;
/// Stores the write position of the producer.
uint32_t wr_pos_ = 0;
/// Demand that has not yet been signaled back to the producer.
uint32_t demand_ = 0;
/// Stores whether `close` has been called.
bool closed_ = false;
/// Stores the abort reason.
error err_;
/// Callback handle to the consumer.
consumer_ptr consumer_;
/// Callback handle to the producer.
producer_ptr producer_;
};
/// @relates bounded_buffer
template <class T>
using bounded_buffer_ptr = intrusive_ptr<bounded_buffer<T>>;
/// @relates bounded_buffer
template <class T, bool IsProducer>
struct resource_ctrl : ref_counted {
using buffer_ptr = bounded_buffer_ptr<T>;
explicit resource_ctrl(buffer_ptr ptr) : buf(std::move(ptr)) {
// nop
}
~resource_ctrl() {
if (buf) {
if constexpr (IsProducer) {
auto err = make_error(sec::invalid_upstream,
"producer_resource destroyed without opening it");
buf->abort(err);
} else {
buf->cancel();
}
}
}
buffer_ptr try_open() {
std::unique_lock guard{mtx};
if (buf) {
auto res = buffer_ptr{};
res.swap(buf);
return res;
}
return nullptr;
}
mutable std::mutex mtx;
buffer_ptr buf;
};
/// Grants read access to the first consumer that calls `open` on the resource.
/// Cancels consumption of items on the buffer if the resources gets destroyed
/// before opening it.
/// @relates bounded_buffer
template <class T>
class consumer_resource {
public:
using value_type = T;
using buffer_type = bounded_buffer<T>;
using buffer_ptr = bounded_buffer_ptr<T>;
explicit consumer_resource(buffer_ptr buf) {
ctrl_.emplace(std::move(buf));
}
consumer_resource() = default;
consumer_resource(consumer_resource&&) = default;
consumer_resource(const consumer_resource&) = default;
consumer_resource& operator=(consumer_resource&&) = default;
consumer_resource& operator=(const consumer_resource&) = default;
/// Tries to open the resource for reading from the buffer. The first `open`
/// wins on concurrent access.
/// @returns a pointer to the buffer on success, `nullptr` otherwise.
buffer_ptr try_open() {
if (ctrl_) {
auto res = ctrl_->try_open();
ctrl_ = nullptr;
return res;
} else {
return nullptr;
}
}
private:
intrusive_ptr<resource_ctrl<T, false>> ctrl_;
};
/// Grants access to a buffer to the first producer that calls `open`. Aborts
/// writes on the buffer if the resources gets destroyed before opening it.
/// @relates bounded_buffer
template <class T>
class producer_resource {
public:
using value_type = T;
using buffer_type = bounded_buffer<T>;
using buffer_ptr = bounded_buffer_ptr<T>;
explicit producer_resource(buffer_ptr buf) {
ctrl_.emplace(std::move(buf));
}
producer_resource() = default;
producer_resource(producer_resource&&) = default;
producer_resource(const producer_resource&) = default;
producer_resource& operator=(producer_resource&&) = default;
producer_resource& operator=(const producer_resource&) = default;
/// Tries to open the resource for writing to the buffer. The first `open`
/// wins on concurrent access.
/// @returns a pointer to the buffer on success, `nullptr` otherwise.
buffer_ptr try_open() {
if (ctrl_) {
auto res = ctrl_->try_open();
ctrl_ = nullptr;
return res;
} else {
return nullptr;
}
}
private:
intrusive_ptr<resource_ctrl<T, true>> ctrl_;
};
/// Creates bounded buffer and returns two resources connected by that buffer.
template <class T>
std::pair<consumer_resource<T>, producer_resource<T>>
make_bounded_buffer_resource(size_t buffer_size, size_t min_request_size) {
using buffer_type = bounded_buffer<T>;
auto buf = make_counted<buffer_type>(buffer_size, min_request_size);
return {async::consumer_resource<T>{buf}, async::producer_resource<T>{buf}};
}
/// Creates bounded buffer and returns two resources connected by that buffer.
template <class T>
std::pair<consumer_resource<T>, producer_resource<T>>
make_bounded_buffer_resource() {
return make_bounded_buffer_resource<T>(defaults::flow::buffer_size,
defaults::flow::min_demand);
}
} // namespace caf::async
// 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/core_export.hpp"
#include "caf/intrusive_ptr.hpp"
namespace caf::async {
/// Base type for asynchronous consumers of an event source.
class CAF_CORE_EXPORT consumer {
public:
virtual ~consumer();
/// Called to signal that the producer started emitting items.
virtual void on_producer_ready() = 0;
/// Called to signal to the consumer that the producer added an item to a
/// previously empty source or completed the flow of events.
virtual void on_producer_wakeup() = 0;
/// Increases the reference count of the consumer.
virtual void ref_consumer() const noexcept = 0;
/// Decreases the reference count of the consumer and destroys the object if
/// necessary.
virtual void deref_consumer() const noexcept = 0;
};
/// @relates consumer
inline void intrusive_ptr_add_ref(const consumer* p) noexcept {
p->ref_consumer();
}
/// @relates consumer
inline void intrusive_ptr_release(const consumer* p) noexcept {
p->deref_consumer();
}
/// @relates consumer
using consumer_ptr = intrusive_ptr<consumer>;
} // namespace caf::async
// 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>
namespace caf::async {
// -- classes ------------------------------------------------------------------
class batch;
class consumer;
class producer;
// -- template classes ---------------------------------------------------------
template <class T>
class bounded_buffer;
template <class T>
class consumer_resource;
template <class T>
class producer_resource;
// -- free function templates --------------------------------------------------
template <class T>
batch make_batch(span<const T> items);
} // namespace caf::async
// 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 <list>
#include <mutex>
#include <tuple>
#include "caf/async/batch.hpp"
#include "caf/defaults.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/subscription.hpp"
namespace caf::async {
/// Enables buffered consumption of published items.
template <class T>
class observer_buffer : public flow::observer_impl<T> {
public:
observer_buffer() {
// nop
}
void on_complete() override {
std::unique_lock guard{mtx_};
if (!done_) {
sub_ = nullptr;
done_ = true;
deinit(guard);
}
}
void on_error(const error& what) override {
std::unique_lock guard{mtx_};
if (!done_) {
sub_ = nullptr;
done_ = true;
err_ = what;
deinit(guard);
}
}
void on_next(span<const T> items) override {
on_batch(make_batch(items));
}
void on_batch(const batch& buf) override {
std::list<batch> ls;
ls.emplace_back(buf);
std::unique_lock guard{mtx_};
batches_.splice(batches_.end(), std::move(ls));
if (batches_.size() == 1)
wakeup(guard);
}
void on_attach(flow::subscription sub) override {
CAF_ASSERT(sub.valid());
std::unique_lock guard{mtx_};
sub_ = std::move(sub);
init(guard);
}
bool has_data() {
if (local.pos_ != local.end_) {
return true;
} else {
std::unique_lock guard{mtx_};
return !batches_.empty();
}
}
/// Tries to fetch the next value. If no value exists, the first element in
/// the tuple is `nullptr`. The second value indicates whether the stream was
/// closed. If the stream was closed, the third value is `nullptr` if
/// `on_complete` was called and a pointer to the error if `on_error` was
/// called.
std::tuple<const T*, bool, const error*> poll() {
if (local.pos_ != local.end_) {
auto res = local.pos_;
if (++local.pos_ == local.end_) {
std::unique_lock guard{mtx_};
if (sub_)
sub_.request(local.cache_.size());
}
return {res, false, nullptr};
} else if (std::unique_lock guard{mtx_}; !batches_.empty()) {
batches_.front().swap(local.cache_);
batches_.pop_front();
guard.unlock();
auto items = local.cache_.template items<T>();
CAF_ASSERT(!items.empty());
local.pos_ = items.begin();
local.end_ = items.end();
return {local.pos_++, false, nullptr};
} else if (!err_) {
return {nullptr, done_, nullptr};
} else {
return {nullptr, true, &err_};
}
}
void dispose() override {
on_complete();
}
bool disposed() const noexcept override {
std::unique_lock guard{mtx_};
return done_;
}
protected:
template <class WaitFn>
std::tuple<const T*, bool, const error*> wait_with(WaitFn wait_fn) {
if (local.pos_ != local.end_) {
auto res = local.pos_;
if (++local.pos_ == local.end_) {
std::unique_lock guard{mtx_};
if (sub_)
sub_.request(local.cache_.size());
}
return {res, false, nullptr};
} else {
std::unique_lock guard{mtx_};
while (batches_.empty() && !done_)
wait_fn(guard);
if (!batches_.empty()) {
batches_.front().swap(local.cache_);
batches_.pop_front();
guard.unlock();
auto items = local.cache_.template items<T>();
local.pos_ = items.begin();
local.end_ = items.end();
return {local.pos_++, false, nullptr};
} else if (!err_) {
return {nullptr, done_, nullptr};
} else {
return {nullptr, true, &err_};
}
}
}
/// Wraps fields that we only access from the consumer's thread.
struct local_t {
const T* pos_ = nullptr;
const T* end_ = nullptr;
batch cache_;
} local;
static_assert(sizeof(local_t) < CAF_CACHE_LINE_SIZE);
/// Avoids false sharing.
char pad_[CAF_CACHE_LINE_SIZE - sizeof(local_t)];
// -- state for consumer and publisher ---------------------------------------
/// Protects fields that we access with both the consumer and the producer.
mutable std::mutex mtx_;
flow::subscription sub_;
bool done_ = false;
error err_;
std::list<batch> batches_;
private:
virtual void init(std::unique_lock<std::mutex>&) {
sub_.request(defaults::flow::buffer_size);
}
virtual void deinit(std::unique_lock<std::mutex>& guard) {
wakeup(guard);
}
virtual void wakeup(std::unique_lock<std::mutex>&) {
// Customization point.
}
};
} // namespace caf::async
// 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/core_export.hpp"
#include "caf/intrusive_ptr.hpp"
namespace caf::async {
/// Base type for asynchronous producers of events.
class CAF_CORE_EXPORT producer {
public:
virtual ~producer();
/// Called to signal that the consumer started handling events.
virtual void on_consumer_ready() = 0;
/// Called to signal that the consumer stopped handling events.
virtual void on_consumer_cancel() = 0;
/// Called to signal that the consumer requests more events.
virtual void on_consumer_demand(size_t demand) = 0;
/// Increases the reference count of the producer.
virtual void ref_producer() const noexcept = 0;
/// Decreases the reference count of the producer and destroys the object if
/// necessary.
virtual void deref_producer() const noexcept = 0;
};
/// @relates producer
inline void intrusive_ptr_add_ref(const producer* p) noexcept {
p->ref_producer();
}
/// @relates producer
inline void intrusive_ptr_release(const producer* p) noexcept {
p->deref_producer();
}
/// @relates producer
using producer_ptr = intrusive_ptr<producer>;
} // namespace caf::async
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#pragma once #pragma once
#include "caf/allowed_unsafe_message_type.hpp"
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
...@@ -91,4 +92,10 @@ auto make_shared_type_erased_callback(F fun) { ...@@ -91,4 +92,10 @@ auto make_shared_type_erased_callback(F fun) {
return shared_callback_ptr<signature>{std::move(res)}; return shared_callback_ptr<signature>{std::move(res)};
} }
/// Convenience type alias for an action with shared ownership.
/// @relates callback
using shared_action_ptr = shared_callback_ptr<void()>;
} // namespace caf } // namespace caf
CAF_ALLOW_UNSAFE_MESSAGE_TYPE(caf::shared_action_ptr)
...@@ -115,3 +115,11 @@ constexpr auto cached_udp_buffers = size_t{10}; ...@@ -115,3 +115,11 @@ constexpr auto cached_udp_buffers = size_t{10};
constexpr auto max_pending_msgs = size_t{10}; constexpr auto max_pending_msgs = size_t{10};
} // namespace caf::defaults::middleman } // namespace caf::defaults::middleman
namespace caf::defaults::flow {
constexpr auto min_demand = size_t{8};
constexpr auto batch_size = size_t{32};
constexpr auto buffer_size = size_t{128};
} // namespace caf::defaults::flow
...@@ -15,9 +15,13 @@ namespace caf { ...@@ -15,9 +15,13 @@ namespace caf {
/// Represents a disposable resource. /// Represents a disposable resource.
class CAF_CORE_EXPORT disposable { class CAF_CORE_EXPORT disposable {
public: public:
// -- member types -----------------------------------------------------------
/// Internal implementation class of a `disposable`. /// Internal implementation class of a `disposable`.
class impl { class impl {
public: public:
CAF_INTRUSIVE_PTR_FRIENDS_SFX(impl, _disposable)
virtual ~impl(); virtual ~impl();
virtual void dispose() = 0; virtual void dispose() = 0;
...@@ -29,14 +33,6 @@ public: ...@@ -29,14 +33,6 @@ public:
virtual void ref_disposable() const noexcept = 0; virtual void ref_disposable() const noexcept = 0;
virtual void deref_disposable() const noexcept = 0; virtual void deref_disposable() const noexcept = 0;
friend void intrusive_ptr_add_ref(const impl* ptr) noexcept {
ptr->ref_disposable();
}
friend void intrusive_ptr_release(const impl* ptr) noexcept {
ptr->deref_disposable();
}
}; };
explicit disposable(intrusive_ptr<impl> pimpl) noexcept explicit disposable(intrusive_ptr<impl> pimpl) noexcept
...@@ -44,17 +40,27 @@ public: ...@@ -44,17 +40,27 @@ public:
// nop // nop
} }
// -- constructors, destructors, and assignment operators --------------------
disposable() noexcept = default; disposable() noexcept = default;
disposable(disposable&&) noexcept = default; disposable(disposable&&) noexcept = default;
disposable(const disposable&) noexcept = default; disposable(const disposable&) noexcept = default;
disposable& operator=(disposable&&) noexcept = default; disposable& operator=(disposable&&) noexcept = default;
disposable& operator=(const disposable&) noexcept = default; disposable& operator=(const disposable&) noexcept = default;
// -- factories --------------------------------------------------------------
/// Combines multiple disposables into a single disposable. The new disposable /// Combines multiple disposables into a single disposable. The new disposable
/// is disposed if all of its elements are disposed. Disposing the composite /// is disposed if all of its elements are disposed. Disposing the composite
/// disposes all elements individually. /// disposes all elements individually.
static disposable make_composite(std::vector<disposable> entries); static disposable make_composite(std::vector<disposable> entries);
// -- mutators ---------------------------------------------------------------
/// Disposes the resource. Calling `dispose()` on a disposed resource is a /// Disposes the resource. Calling `dispose()` on a disposed resource is a
/// no-op. /// no-op.
void dispose() { void dispose() {
...@@ -64,6 +70,13 @@ public: ...@@ -64,6 +70,13 @@ public:
} }
} }
/// Exchanges the content of this handle with `other`.
void swap(disposable& other) {
pimpl_.swap(other.pimpl_);
}
// -- properties -------------------------------------------------------------
/// Returns whether the resource has been disposed. /// Returns whether the resource has been disposed.
[[nodiscard]] bool disposed() const noexcept { [[nodiscard]] bool disposed() const noexcept {
return pimpl_ ? pimpl_->disposed() : true; return pimpl_ ? pimpl_->disposed() : true;
...@@ -89,6 +102,8 @@ public: ...@@ -89,6 +102,8 @@ public:
return pimpl_.get(); return pimpl_.get();
} }
// -- conversions ------------------------------------------------------------
/// Returns a smart pointer to the implementation. /// Returns a smart pointer to the implementation.
[[nodiscard]] intrusive_ptr<impl>&& as_intrusive_ptr() && noexcept { [[nodiscard]] intrusive_ptr<impl>&& as_intrusive_ptr() && noexcept {
return std::move(pimpl_); return std::move(pimpl_);
...@@ -99,11 +114,6 @@ public: ...@@ -99,11 +114,6 @@ public:
return pimpl_; return pimpl_;
} }
/// Exchanges the content of this handle with `other`.
void swap(disposable& other) {
pimpl_.swap(other.pimpl_);
}
private: private:
intrusive_ptr<impl> pimpl_; intrusive_ptr<impl> pimpl_;
}; };
......
...@@ -48,6 +48,8 @@ public: ...@@ -48,6 +48,8 @@ public:
/// Required by `spawn` for type deduction. /// Required by `spawn` for type deduction.
using behavior_type = behavior; using behavior_type = behavior;
using handle_type = actor;
// -- constructors, destructors ---------------------------------------------- // -- constructors, destructors ----------------------------------------------
explicit event_based_actor(actor_config& cfg); explicit event_based_actor(actor_config& 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/flow/observable.hpp"
#include "caf/flow/observer.hpp"
namespace caf::flow {
/// Combines the items emitted from `pub` and `pubs...` to appear as a single
/// stream of items.
template <class Observable, class... Observables>
observable<typename Observable::output_type>
concat(Observable x, Observables... xs) {
using output_type = output_type_t<Observable>;
static_assert(
(std::is_same_v<output_type, output_type_t<Observables>> && ...));
auto hdl = std::move(x).as_observable();
auto ptr = make_counted<merger_impl<output_type>>(hdl.ptr()->ctx());
ptr->concat_mode(true);
ptr->add(std::move(hdl));
(ptr->add(std::move(xs).as_observable()), ...);
return observable<output_type>{std::move(ptr)};
}
} // namespace caf::flow
// 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 <tuple>
#include "caf/action.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ref_counted.hpp"
namespace caf::flow {
/// Coordinates any number of co-located publishers and observers. The
/// co-located objects never need to synchronize calls to other co-located
/// objects since the coordinator guarantees synchronous execution.
class CAF_CORE_EXPORT coordinator {
public:
// class CAF_CORE_EXPORT subscription_impl : public subscription::impl {
// public:
// friend class coordinator;
// subscription_impl(coordinator* ctx, observable_base_ptr src,
// observer_base_ptr snk)
// : ctx_(ctx), src_(std::move(src)), snk_(std::move(snk)) {
// // nop
// }
// void request(size_t n) final;
// void cancel() final;
// bool disposed() const noexcept final;
// auto* ctx() const noexcept {
// return ctx_;
// }
// private:
// coordinator* ctx_;
// observable_base_ptr src_;
// observer_base_ptr snk_;
// };
// using subscription_impl_ptr = intrusive_ptr<subscription_impl>;
friend class subscription_impl;
virtual ~coordinator();
[[nodiscard]] observable_builder make_observable();
/// Increases the reference count of the coordinator.
virtual void ref_coordinator() const noexcept = 0;
/// Decreases the reference count of the coordinator and destroys the object
/// if necessary.
virtual void deref_coordinator() const noexcept = 0;
/// Schedules an action for execution on this coordinator. This member
/// function may get called from external sources or threads.
/// @thread-safe
virtual void schedule(action what) = 0;
///@copydoc schedule
template <class F>
void schedule_fn(F&& what) {
return schedule(make_action(std::forward<F>(what)));
}
/// Schedules an action for execution from within the coordinator. May call
/// `schedule` for coordinators that use a single work queue.
virtual void post_internally(action what) = 0;
///@copydoc schedule
template <class F>
void post_internally_fn(F&& what) {
return post_internally(make_action(std::forward<F>(what)));
}
/// Asks the coordinator to keep its event loop running until `obj` becomes
/// disposed since it depends on external events or produces events that are
/// visible to outside observers.
virtual void watch(disposable what) = 0;
friend void intrusive_ptr_add_ref(const coordinator* ptr) noexcept {
ptr->ref_coordinator();
}
friend void intrusive_ptr_release(const coordinator* ptr) noexcept {
ptr->deref_coordinator();
}
};
/// @relates coordinator
using coordinator_ptr = intrusive_ptr<coordinator>;
} // namespace caf::flow
// 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 <type_traits>
namespace caf::detail {
template <class Left, class Right>
struct left_oracle {
using type = Left;
};
/// Useful to force dependent evaluation of Left. For example in template
/// functions where only Right is actually a template parameter.
template <class Left, class Right>
using left_t = typename left_oracle<Left, Right>::type;
} // namespace caf::detail
namespace caf::flow {
class coordinator;
class subscription;
template <class T>
class single;
template <class T>
class observer;
template <class T>
class observable;
template <class In, class Out>
class processor;
template <class Generator, class... Steps>
class generation;
template <class Step, class... Steps>
class transformation;
template <class T>
struct is_observable {
static constexpr bool value = false;
};
template <class T>
struct is_observable<observable<T>> {
static constexpr bool value = true;
};
template <class Step, class... Steps>
struct is_observable<transformation<Step, Steps...>> {
static constexpr bool value = true;
};
template <class Generator, class... Steps>
struct is_observable<generation<Generator, Steps...>> {
static constexpr bool value = true;
};
template <class In, class Out>
struct is_observable<processor<In, Out>> {
static constexpr bool value = true;
};
template <class T>
struct is_observable<single<T>> {
static constexpr bool value = true;
};
template <class T>
constexpr bool is_observable_v = is_observable<T>::value;
template <class T>
struct is_observer {
static constexpr bool value = false;
};
template <class T>
struct is_observer<observer<T>> {
static constexpr bool value = true;
};
template <class Step, class... Steps>
struct is_observer<transformation<Step, Steps...>> {
static constexpr bool value = true;
};
template <class In, class Out>
struct is_observer<processor<In, Out>> {
static constexpr bool value = true;
};
template <class T>
constexpr bool is_observer_v = is_observer<T>::value;
class observable_builder;
template <class T>
struct input_type_oracle {
using type = typename T::input_type;
};
template <class T>
using input_type_t = typename input_type_oracle<T>::type;
template <class T>
struct output_type_oracle {
using type = typename T::output_type;
};
template <class T>
using output_type_t = typename output_type_oracle<T>::type;
template <class T>
class merger_impl;
template <class>
struct has_impl_include {
static constexpr bool value = false;
};
template <class T>
constexpr bool has_impl_include_v = has_impl_include<T>::value;
template <class T>
struct assert_scheduled_actor_hdr {
static constexpr bool value
= has_impl_include_v<detail::left_t<scheduled_actor, T>>;
static_assert(value,
"include 'caf/scheduled_actor/flow.hpp' for this method");
};
template <class T>
using assert_scheduled_actor_hdr_t
= std::enable_if_t<assert_scheduled_actor_hdr<T>::value, T>;
} // namespace caf::flow
// 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/flow/observable.hpp"
#include "caf/flow/observer.hpp"
namespace caf::flow {
/// Combines the items emitted from `pub` and `pubs...` to appear as a single
/// stream of items.
template <class Observable, class... Observables>
observable<typename Observable::output_type>
merge(Observable x, Observables... xs) {
using output_type = output_type_t<Observable>;
static_assert(
(std::is_same_v<output_type, output_type_t<Observables>> && ...));
auto hdl = std::move(x).as_observable();
auto ptr = make_counted<merger_impl<output_type>>(hdl.ptr()->ctx());
ptr->add(std::move(hdl));
(ptr->add(std::move(xs).as_observable()), ...);
return observable<output_type>{std::move(ptr)};
}
} // namespace caf::flow
// 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 <cstddef>
#include <numeric>
#include <type_traits>
#include <vector>
#include "caf/async/bounded_buffer.hpp"
#include "caf/async/consumer.hpp"
#include "caf/async/producer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/step.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
namespace caf::flow {
/// Represents a potentially unbound sequence of values.
template <class T>
class observable {
public:
using output_type = T;
/// Internal interface of an `observable`.
class impl : public disposable::impl {
public:
// -- member types ---------------------------------------------------------
using output_type = T;
// -- properties -----------------------------------------------------------
virtual coordinator* ctx() const noexcept = 0;
// -- flow processing ------------------------------------------------------
/// Subscribes a new observer.
virtual disposable subscribe(observer<T> what) = 0;
virtual void on_request(observer_impl<T>* sink, size_t n) = 0;
virtual void on_cancel(observer_impl<T>* sink) = 0;
protected:
disposable do_subscribe(observer_impl<T>* snk);
};
class sub_impl final : public ref_counted, public subscription::impl {
public:
using src_type = typename observable<T>::impl;
using snk_type = typename observer<T>::impl;
CAF_INTRUSIVE_PTR_FRIENDS(sub_impl)
sub_impl(coordinator* ctx, src_type* src, snk_type* snk)
: ctx_(ctx), src_(src), snk_(snk) {
// nop
}
bool disposed() const noexcept override {
return src_ == nullptr;
}
void ref_disposable() const noexcept override {
this->ref();
}
void deref_disposable() const noexcept override {
this->deref();
}
void request(size_t n) override {
if (src_)
ctx()->post_internally_fn([src = src_, snk = snk_, n] { //
src->on_request(snk.get(), n);
});
}
void cancel() override {
if (src_) {
ctx()->post_internally_fn([src = src_, snk = snk_] { //
src->on_cancel(snk.get());
});
src_.reset();
snk_.reset();
}
}
auto* ctx() const noexcept {
return ctx_;
}
private:
coordinator* ctx_;
intrusive_ptr<src_type> src_;
intrusive_ptr<snk_type> snk_;
};
explicit observable(intrusive_ptr<impl> pimpl) noexcept
: pimpl_(std::move(pimpl)) {
// nop
}
observable& operator=(std::nullptr_t) noexcept {
pimpl_.reset();
return *this;
}
observable() noexcept = default;
observable(observable&&) noexcept = default;
observable(const observable&) noexcept = default;
observable& operator=(observable&&) noexcept = default;
observable& operator=(const observable&) noexcept = default;
disposable as_disposable() const& noexcept {
return disposable{pimpl_};
}
disposable as_disposable() && noexcept {
return disposable{std::move(pimpl_)};
}
/// @copydoc impl::subscribe
disposable subscribe(observer<T> what) {
if (pimpl_) {
return pimpl_->subscribe(std::move(what));
} else {
what.on_error(make_error(sec::invalid_observable));
return disposable{};
}
}
/// Creates a new observer that pushes all observed items to the resource.
disposable subscribe(async::producer_resource<T> resource);
/// Returns a transformation that applies a step function to each input.
template <class Step>
transformation<Step> transform(Step step);
/// Returns a transformation that selects only the first `n` items.
transformation<limit_step<T>> take(size_t n);
/// Returns a transformation that selects only items that satisfy `predicate`.
template <class Predicate>
transformation<filter_step<Predicate>> filter(Predicate prediate);
/// Returns a transformation that applies `f` to each input and emits the
/// result of the function application.
template <class F>
transformation<map_step<F>> map(F f);
/// Calls `on_next` for each item emitted by this observable.
template <class OnNext>
disposable for_each(OnNext on_next);
/// Calls `on_next` for each item and `on_error` for each error emitted by
/// this observable.
template <class OnNext, class OnError>
disposable for_each(OnNext on_next, OnError on_error);
/// Calls `on_next` for each item, `on_error` for each error and `on_complete`
/// for each completion signal emitted by this observable.
template <class OnNext, class OnError, class OnComplete>
disposable for_each(OnNext on_next, OnError on_error, OnComplete on_complete);
/// Returns a transformation that emits items by merging the outputs of all
/// observables returned by `f`.
template <class F>
auto flat_map(F f);
/// Returns a transformation that emits items by concatenating the outputs of
/// all observables returned by `f`.
template <class F>
auto concat_map(F f);
/// Creates an asynchronous resource that makes emitted items available in a
/// bounded buffer.
async::consumer_resource<T> to_resource(size_t buffer_size,
size_t min_request_size);
async::consumer_resource<T> to_resource() {
return to_resource(defaults::flow::buffer_size, defaults::flow::min_demand);
}
observable observe_on(coordinator* other, size_t buffer_size,
size_t min_request_size);
observable observe_on(coordinator* other) {
return observe_on(other, defaults::flow::buffer_size,
defaults::flow::min_demand);
}
const observable& as_observable() const& noexcept {
return std::move(*this);
}
observable&& as_observable() && noexcept {
return std::move(*this);
}
bool valid() const noexcept {
return pimpl_ != nullptr;
}
explicit operator bool() const noexcept {
return valid();
}
bool operator!() const noexcept {
return !valid();
}
impl* ptr() {
return pimpl_.get();
}
const impl* ptr() const {
return pimpl_.get();
}
const intrusive_ptr<impl>& as_intrusive_ptr() const& noexcept {
return pimpl_;
}
intrusive_ptr<impl>&& as_intrusive_ptr() && noexcept {
return std::move(pimpl_);
}
void swap(observable& other) {
pimpl_.swap(other.pimpl_);
}
private:
intrusive_ptr<impl> pimpl_;
};
template <class T>
disposable observable<T>::impl::do_subscribe(observer_impl<T>* snk) {
auto ptr = make_counted<sub_impl>(ctx(), this, snk);
snk->on_subscribe(subscription{ptr});
return disposable{std::move(ptr)};
}
template <class T>
using observable_impl = typename observable<T>::impl;
/// Base type for classes that represent a definition of an `observable` which
/// has not yet been converted to an actual `observable`.
template <class T>
class observable_def {
public:
virtual ~observable_def() = default;
template <class OnNext>
auto for_each(OnNext on_next) && {
return lift().for_each(std::move(on_next));
}
template <class OnNext, class OnError>
auto for_each(OnNext on_next, OnError on_error) && {
return lift().for_each(std::move(on_next), std::move(on_error));
}
template <class OnNext, class OnError, class OnComplete>
auto for_each(OnNext on_next, OnError on_error, OnComplete on_complete) && {
return lift().for_each(std::move(on_next), std::move(on_error),
std::move(on_complete));
}
template <class F>
auto flat_map(F f) && {
return lift().flat_map(std::move(f));
}
template <class F>
auto concat_map(F f) && {
return lift().concat_map(std::move(f));
}
disposable subscribe(observer<T> what) && {
return lift().subscribe(std::move(what));
}
disposable subscribe(async::producer_resource<T> resource) && {
return lift().subscribe(std::move(resource));
}
async::consumer_resource<T> to_resource() && {
return lift().to_resource();
}
async::consumer_resource<T> to_resource(size_t buffer_size,
size_t min_request_size) && {
return lift().to_resource(buffer_size, min_request_size);
}
observable<T> observe_on(coordinator* other) && {
return lift().observe_on(other);
}
observable<T> observe_on(coordinator* other, size_t buffer_size,
size_t min_request_size) && {
return lift().observe_on(other, buffer_size, min_request_size);
}
virtual observable<T> as_observable() && = 0;
private:
decltype(auto) lift() {
return std::move(*this).as_observable();
}
};
template <class In, class Out>
class processor {
public:
class impl : public observer_impl<In>, public observable_impl<Out> {
public:
observer_impl<In>* as_observer_ptr() noexcept {
return this;
}
observer<In> as_observer() noexcept {
return observer<In>{as_observer_ptr()};
}
observable_impl<In>* as_observable_ptr() noexcept {
return this;
}
observable<In> as_observable() noexcept {
return observable<In>{as_observable_ptr()};
}
};
explicit processor(intrusive_ptr<impl> pimpl) noexcept
: pimpl_(std::move(pimpl)) {
// nop
}
processor& operator=(std::nullptr_t) noexcept {
pimpl_.reset();
return *this;
}
using input_type = In;
using output_type = Out;
processor() noexcept = default;
processor(processor&&) noexcept = default;
processor(const processor&) noexcept = default;
processor& operator=(processor&&) noexcept = default;
processor& operator=(const processor&) noexcept = default;
// -- conversion -------------------------------------------------------------
disposable as_disposable() const& noexcept {
return as_observable().as_disposable();
}
disposable as_disposable() && noexcept {
return std::move(*this).as_observable().as_disposable();
}
observer<In> as_observer() const& noexcept {
return pimpl_->as_observer();
}
observer<In> as_observer() && noexcept {
auto raw = pimpl_.release()->as_observer_ptr();
auto ptr = intrusive_ptr<observer_impl<In>>{raw, false};
return observer<In>{std::move(ptr)};
}
observable<Out> as_observable() const& noexcept {
return pimpl_->as_observable();
}
observable<Out> as_observable() && noexcept {
auto raw = pimpl_.release()->as_observable_ptr();
auto ptr = intrusive_ptr<observable_impl<Out>>{raw, false};
return observable<Out>{std::move(ptr)};
}
private:
intrusive_ptr<impl> pimpl_;
};
template <class In, class Out>
using processor_impl = typename processor<In, Out>::impl;
// -- representing an error as an observable -----------------------------------
template <class T>
class observable_error_impl : public ref_counted, public observable_impl<T> {
public:
// -- member types -----------------------------------------------------------
using output_type = T;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS(observable_error_impl)
// -- constructors, destructors, and assignment operators --------------------
observable_error_impl(coordinator* ctx, error what)
: ctx_(ctx), what_(std::move(what)) {
// nop
}
// -- implementation of disposable::impl -------------------------------------
void dispose() override {
// nop
}
bool disposed() const noexcept override {
return true;
}
void ref_disposable() const noexcept override {
this->ref();
}
void deref_disposable() const noexcept override {
this->deref();
}
// -- implementation of observable<T>::impl ----------------------------------
coordinator* ctx() const noexcept override {
return ctx_;
}
void on_request(observer_impl<T>*, size_t) override {
CAF_RAISE_ERROR("observable_error_impl::on_request called");
}
void on_cancel(observer_impl<T>*) override {
CAF_RAISE_ERROR("observable_error_impl::on_cancel called");
}
disposable subscribe(observer<T> what) override {
what.on_error(what_);
return {};
}
private:
coordinator* ctx_;
error what_;
};
// -- broadcasting -------------------------------------------------------------
/// Base type for processors with a buffer that broadcasts output to all
/// observers.
template <class T>
class buffered_observable_impl : public ref_counted, public observable_impl<T> {
public:
// -- member types -----------------------------------------------------------
using super = observable_impl<T>;
using handle_type = observable<T>;
struct output_t {
size_t demand;
observer<T> sink;
};
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS(buffered_observable_impl)
// -- constructors, destructors, and assignment operators --------------------
explicit buffered_observable_impl(coordinator* ctx)
: ctx_(ctx), desired_capacity_(defaults::flow::buffer_size) {
buf_.reserve(desired_capacity_);
}
buffered_observable_impl(coordinator* ctx, size_t desired_capacity)
: ctx_(ctx), desired_capacity_(desired_capacity) {
buf_.reserve(desired_capacity_);
}
// -- implementation of disposable::impl -------------------------------------
void dispose() override {
if (!completed_) {
completed_ = true;
buf_.clear();
for (auto& out : outputs_)
out.sink.on_complete();
outputs_.clear();
do_on_complete();
}
}
bool disposed() const noexcept override {
return done() && outputs_.empty();
}
void ref_disposable() const noexcept override {
this->ref();
}
void deref_disposable() const noexcept override {
this->deref();
}
// -- implementation of observable<T>::impl ----------------------------------
coordinator* ctx() const noexcept override {
return ctx_;
}
void on_request(observer_impl<T>* sink, size_t n) override {
if (auto i = find(sink); i != outputs_.end()) {
i->demand += n;
update_max_demand();
try_push();
}
}
void on_cancel(observer_impl<T>* sink) override {
if (auto i = find(sink); i != outputs_.end()) {
outputs_.erase(i);
if (outputs_.empty()) {
shutdown();
} else {
update_max_demand();
try_push();
}
}
}
disposable subscribe(observer<T> sink) override {
if (done()) {
sink.on_complete();
return disposable{};
} else {
max_demand_ = 0;
outputs_.emplace_back(output_t{0u, sink});
return super::do_subscribe(sink.ptr());
}
}
// -- properties -------------------------------------------------------------
size_t has_observers() const noexcept {
return !outputs_.empty();
}
virtual bool done() const noexcept {
return completed_ && buf_.empty();
}
// -- buffer handling --------------------------------------------------------
template <class Iterator, class Sentinel>
void append_to_buf(Iterator first, Sentinel last) {
buf_.insert(buf_.end(), first, last);
}
template <class Val>
void append_to_buf(Val&& val) {
buf_.emplace_back(std::forward<Val>(val));
}
/// Stops the source, but allows observers to still consume buffered data.
virtual void shutdown() {
if (!completed_) {
completed_ = true;
if (done()) {
for (auto& out : outputs_)
out.sink.on_complete();
outputs_.clear();
do_on_complete();
}
}
}
/// Stops the source and drops any remaining data.
virtual void abort(const error& reason) {
if (!completed_) {
completed_ = true;
for (auto& out : outputs_)
out.sink.on_error(reason);
outputs_.clear();
do_on_error(reason);
}
}
/// Tries to push data from the buffer downstream.
void try_push() {
if (!batch_.empty()) {
// Can only be true if a sink calls try_push in on_next.
return;
}
size_t batch_size = std::min(desired_capacity_, defaults::flow::batch_size);
while (max_demand_ > 0) {
// Try to ship full batches.
if (batch_size > buf_.size())
pull(batch_size - buf_.size());
auto n = std::min(max_demand_, buf_.size());
if (n == 0)
break;
batch_.assign(std::make_move_iterator(buf_.begin()),
std::make_move_iterator(buf_.begin() + n));
buf_.erase(buf_.begin(), buf_.begin() + n);
auto items = span<const T>{batch_};
for (auto& out : outputs_) {
out.demand -= n;
out.sink.on_next(items);
}
max_demand_ -= n;
batch_.clear();
if (done()) {
for (auto& out : outputs_)
out.sink.on_complete();
outputs_.clear();
break;
}
}
}
auto find(observer_impl<T>* sink) {
auto pred = [sink](auto& out) { return out.sink.ptr() == sink; };
return std::find_if(outputs_.begin(), outputs_.end(), pred);
}
protected:
void update_max_demand() {
if (outputs_.empty()) {
max_demand_ = 0;
} else {
auto i = outputs_.begin();
auto e = outputs_.end();
auto init = (*i++).demand;
auto f = [](size_t x, auto& out) { return std::min(x, out.demand); };
max_demand_ = std::accumulate(i, e, init, f);
}
}
coordinator* ctx_;
size_t desired_capacity_;
std::vector<T> buf_;
bool completed_ = false;
size_t max_demand_ = 0;
std::vector<output_t> outputs_;
/// Stores items right before calling on_next on the sinks.
std::vector<T> batch_;
private:
/// Customization point for generating more data.
virtual void pull(size_t) {
// nop
}
/// Customization point for adding cleanup logic.
virtual void do_on_complete() {
// nop
}
/// Customization point for adding error handling logic.
virtual void do_on_error(const error&) {
// nop
}
};
template <class T>
using buffered_observable_impl_ptr = intrusive_ptr<buffered_observable_impl<T>>;
template <class T>
struct term_step {
buffered_observable_impl<T>* pimpl;
using output_type = T;
bool on_next(const T& item) {
pimpl->append_to_buf(item);
return true;
}
void on_complete() {
pimpl->shutdown();
}
void on_error(const error& what) {
pimpl->abort(what);
}
};
/// Base type for processors with a buffer that broadcasts output to all
/// observers.
template <class In, class Out>
class buffered_processor_impl : public buffered_observable_impl<Out>,
public processor_impl<In, Out> {
public:
// -- member types -----------------------------------------------------------
using super = buffered_observable_impl<Out>;
using handle_type = processor<In, Out>;
// -- constructors, destructors, and assignment operators --------------------
explicit buffered_processor_impl(coordinator* ctx)
: super(ctx, defaults::flow::buffer_size) {
// nop
}
buffered_processor_impl(coordinator* ctx, size_t max_buffer_size)
: super(ctx, max_buffer_size) {
// nop
}
// -- implementation of disposable::impl -------------------------------------
coordinator* ctx() const noexcept override {
return super::ctx();
}
void dispose() override {
if (!this->completed_) {
if (sub_) {
sub_.cancel();
sub_ = nullptr;
}
super::dispose();
}
}
bool disposed() const noexcept override {
return super::disposed();
}
void ref_disposable() const noexcept override {
return super::ref_disposable();
}
void deref_disposable() const noexcept override {
return super::ref_disposable();
}
// -- implementation of observable<T>::impl ----------------------------------
disposable subscribe(observer<Out> sink) override {
return super::subscribe(std::move(sink));
}
void on_request(observer_impl<Out>* sink, size_t n) final {
super::on_request(sink, n);
try_fetch_more();
}
void on_cancel(observer_impl<Out>* sink) final {
super::on_cancel(sink);
try_fetch_more();
}
// -- implementation of observer<T>::impl ------------------------------------
void on_subscribe(subscription sub) override {
if (sub_) {
sub.cancel();
} else {
sub_ = std::move(sub);
in_flight_ = this->desired_capacity_;
sub_.request(in_flight_);
}
}
void on_next(span<const In> items) final {
CAF_ASSERT(in_flight_ >= items.size());
in_flight_ -= items.size();
do_on_next(items);
this->try_push();
try_fetch_more();
}
void on_complete() override {
sub_ = nullptr;
this->shutdown();
}
void on_error(const error& what) override {
sub_ = nullptr;
this->abort(what);
}
// -- overrides for buffered_observable_impl ---------------------------------
void shutdown() override {
super::shutdown();
cancel_subscription();
}
void abort(const error& reason) override {
super::abort(reason);
cancel_subscription();
}
protected:
subscription sub_;
size_t in_flight_ = 0;
private:
void cancel_subscription() {
if (sub_) {
sub_.cancel();
sub_ = nullptr;
}
}
void try_fetch_more() {
if (sub_) {
auto abs = in_flight_ + this->buf_.size();
if (this->desired_capacity_ > abs) {
auto new_demand = this->desired_capacity_ - abs;
in_flight_ += new_demand;
sub_.request(new_demand);
}
}
}
/// Transforms input items to outputs.
virtual void do_on_next(span<const In> items) = 0;
};
/// Broadcasts its input to all observers without modifying it.
template <class T>
class broadcaster_impl : public buffered_processor_impl<T, T> {
public:
using super = buffered_processor_impl<T, T>;
using super::super;
private:
void do_on_next(span<const T> items) override {
this->append_to_buf(items.begin(), items.end());
}
};
template <class T>
using broadcaster_impl_ptr = intrusive_ptr<broadcaster_impl<T>>;
// -- transformation -----------------------------------------------------------
template <class... Steps>
struct transform_processor_oracle;
template <class Step>
struct transform_processor_oracle<Step> {
using type = typename Step::output_type;
};
template <class Step1, class Step2, class... Steps>
struct transform_processor_oracle<Step1, Step2, Steps...>
: transform_processor_oracle<Step2, Steps...> {};
template <class... Steps>
using transform_processor_output_type_t =
typename transform_processor_oracle<Steps...>::type;
/// A special type of observer that applies a series of transformation steps to
/// its input before broadcasting the result as output.
template <class Step, class... Steps>
class transformation final
: public observable_def<transform_processor_output_type_t<Step, Steps...>> {
public:
using input_type = typename Step::input_type;
using output_type = transform_processor_output_type_t<Step, Steps...>;
class impl : public buffered_processor_impl<input_type, output_type> {
public:
using super = buffered_processor_impl<input_type, output_type>;
template <class... Ts>
explicit impl(coordinator* ctx, Ts&&... steps)
: super(ctx), steps(std::forward<Ts>(steps)...) {
// nop
}
std::tuple<Step, Steps...> steps;
private:
void do_on_next(span<const input_type> items) override {
auto f = [this, items](auto& step, auto&... steps) {
term_step<output_type> term{this};
for (auto&& item : items)
if (!step.on_next(item, steps..., term))
return;
};
std::apply(f, steps);
}
void do_on_complete() override {
auto f = [this](auto& step, auto&... steps) {
term_step<output_type> term{this};
step.on_complete(steps..., term);
};
std::apply(f, steps);
}
void do_on_error(const error& what) override {
auto f = [this, &what](auto& step, auto&... steps) {
term_step<output_type> term{this};
step.on_error(what, steps..., term);
};
std::apply(f, steps);
}
};
template <class Tuple>
transformation(observable<input_type> source, Tuple&& steps)
: source_(std::move(source)), steps_(std::move(steps)) {
// nop
}
transformation() = delete;
transformation(const transformation&) = delete;
transformation& operator=(const transformation&) = delete;
transformation(transformation&&) = default;
transformation& operator=(transformation&&) = default;
/// @copydoc observable::transform
template <class NewStep>
transformation<Step, Steps..., NewStep> transform(NewStep step) && {
return {std::move(source_),
std::tuple_cat(std::move(steps_),
std::make_tuple(std::move(step)))};
}
auto take(size_t n) && {
return std::move(*this).transform(limit_step<output_type>{n});
}
template <class Predicate>
auto filter(Predicate predicate) && {
return std::move(*this).transform(
filter_step<Predicate>{std::move(predicate)});
}
template <class F>
auto map(F f) && {
return std::move(*this).transform(map_step<F>{std::move(f)});
}
template <class F>
auto do_on_complete(F f) {
return std::move(*this) //
.transform(on_complete_step<output_type, F>{std::move(f)});
}
template <class F>
auto do_on_error(F f) {
return std::move(*this) //
.transform(on_error_step<output_type, F>{std::move(f)});
}
template <class F>
auto do_finally(F f) {
return std::move(*this) //
.transform(finally_step<output_type, F>{std::move(f)});
}
observable<output_type> as_observable() && override {
auto pimpl = make_counted<impl>(source_.ptr()->ctx(), std::move(steps_));
auto res = pimpl->as_observable();
source_.subscribe(observer<input_type>{std::move(pimpl)});
return res;
}
private:
observable<input_type> source_;
std::tuple<Step, Steps...> steps_;
};
// -- observable::transform ----------------------------------------------------
template <class T>
template <class Step>
transformation<Step> observable<T>::transform(Step step) {
static_assert(std::is_same_v<typename Step::input_type, T>,
"step object does not match the output type");
return {*this, std::forward_as_tuple(std::move(step))};
}
// -- observable::take ---------------------------------------------------------
template <class T>
transformation<limit_step<T>> observable<T>::take(size_t n) {
return {*this, std::forward_as_tuple(limit_step<T>{n})};
}
// -- observable::filter -------------------------------------------------------
template <class T>
template <class Predicate>
transformation<filter_step<Predicate>>
observable<T>::filter(Predicate predicate) {
using step_type = filter_step<Predicate>;
static_assert(std::is_same_v<typename step_type::input_type, T>,
"predicate does not match the output type");
return {*this, std::forward_as_tuple(step_type{std::move(predicate)})};
}
// -- observable::map ----------------------------------------------------------
template <class T>
template <class F>
transformation<map_step<F>> observable<T>::map(F f) {
using step_type = map_step<F>;
static_assert(std::is_same_v<typename step_type::input_type, T>,
"map function does not match the output type");
return {*this, std::forward_as_tuple(step_type{std::move(f)})};
}
// -- observable::for_each -----------------------------------------------------
template <class T>
template <class OnNext>
disposable observable<T>::for_each(OnNext on_next) {
auto obs = make_observer(std::move(on_next));
subscribe(obs);
return std::move(obs).as_disposable();
}
template <class T>
template <class OnNext, class OnError>
disposable observable<T>::for_each(OnNext on_next, OnError on_error) {
auto obs = make_observer(std::move(on_next), std::move(on_error));
subscribe(obs);
return std::move(obs).as_disposable();
}
template <class T>
template <class OnNext, class OnError, class OnComplete>
disposable observable<T>::for_each(OnNext on_next, OnError on_error,
OnComplete on_complete) {
auto obs = make_observer(std::move(on_next), std::move(on_error),
std::move(on_complete));
subscribe(obs);
return std::move(obs).as_disposable();
}
// -- observable::flat_map -----------------------------------------------------
/// Combines items from any number of observables.
template <class T>
class merger_impl : public buffered_observable_impl<T> {
public:
using super = buffered_observable_impl<T>;
using super::super;
void concat_mode(bool new_value) {
flags_.concat_mode = new_value;
}
class forwarder;
friend class forwarder;
class forwarder : public ref_counted, public observer_impl<T> {
public:
CAF_INTRUSIVE_PTR_FRIENDS(forwarder)
explicit forwarder(intrusive_ptr<merger_impl> parent)
: parent(std::move(parent)) {
// nop
}
void on_complete() override {
if (sub) {
sub = nullptr;
parent->forwarder_completed(this);
parent = nullptr;
}
}
void on_error(const error& what) override {
if (sub) {
sub = nullptr;
parent->forwarder_failed(this, what);
parent = nullptr;
}
}
void on_subscribe(subscription new_sub) override {
if (!sub) {
sub = std::move(new_sub);
parent->forwarder_subscribed(this, sub);
} else {
new_sub.cancel();
}
}
void on_next(span<const T> items) override {
if (parent)
parent->on_batch(async::make_batch(items), this);
}
void dispose() override {
on_complete();
}
bool disposed() const noexcept override {
return !parent;
}
void ref_disposable() const noexcept final {
this->ref();
}
void deref_disposable() const noexcept final {
this->deref();
}
intrusive_ptr<merger_impl> parent;
subscription sub;
};
explicit merger_impl(coordinator* ctx)
: super(ctx, defaults::flow::batch_size) {
// nop
}
disposable add(observable<T> source, intrusive_ptr<forwarder> fwd) {
forwarders_.emplace_back(fwd);
return source.subscribe(observer<T>{std::move(fwd)});
}
template <class Observable>
disposable add(Observable source) {
return add(std::move(source).as_observable(),
make_counted<forwarder>(this));
}
bool done() const noexcept override {
return super::done() && inputs_.empty() && forwarders_.empty();
}
void dispose() override {
inputs_.clear();
std::vector<fwd_ptr> fwds;
fwds.swap(forwarders_);
for (auto& fwd : fwds)
fwd->dispose();
super::dispose();
}
void cancel_inputs() {
if (!this->completed_) {
std::vector<fwd_ptr> fwds;
fwds.swap(forwarders_);
for (auto& fwd : fwds) {
if (auto& sub = fwd->sub) {
sub.cancel();
sub = nullptr;
}
}
this->shutdown();
}
}
bool disposed() const noexcept override {
return forwarders_.empty() && super::disposed();
}
void delay_error(bool value) {
flags_.delay_error = value;
}
void shutdown_on_last_complete(bool value) {
flags_.shutdown_on_last_complete = value;
if (value && done())
this->shutdown();
}
void on_error(const error& reason) {
if (!flags_.delay_error) {
abort(reason);
return;
}
if (!delayed_error_)
delayed_error_ = reason;
}
protected:
void abort(const error& reason) override {
super::abort(reason);
inputs_.clear();
forwarders_.clear();
}
private:
using fwd_ptr = intrusive_ptr<forwarder>;
void pull(size_t n) override {
while (n > 0 && !inputs_.empty()) {
auto& input = inputs_[0];
auto m = std::min(input.buf.size() - input.offset, n);
CAF_ASSERT(m > 0);
auto items = input.buf.template items<T>().subspan(input.offset, m);
this->append_to_buf(items.begin(), items.end());
if (m + input.offset == input.buf.size()) {
if (auto& sub = input.src->sub)
sub.request(input.buf.size());
inputs_.erase(inputs_.begin());
} else {
input.offset += m;
}
n -= m;
}
}
void on_batch(async::batch buf, fwd_ptr src) {
inputs_.emplace_back(buf, src);
this->try_push();
}
void forwarder_subscribed(forwarder* ptr, subscription& sub) {
if (!flags_.concat_mode || (!forwarders_.empty() && forwarders_[0] == ptr))
sub.request(defaults::flow::buffer_size);
}
void forwarder_failed(forwarder* ptr, const error& reason) {
if (!flags_.delay_error) {
abort(reason);
return;
}
if (!delayed_error_)
delayed_error_ = reason;
forwarder_completed(ptr);
}
void forwarder_completed(forwarder* ptr) {
auto is_ptr = [ptr](auto& x) { return x == ptr; };
auto i = std::find_if(forwarders_.begin(), forwarders_.end(), is_ptr);
if (i != forwarders_.end()) {
forwarders_.erase(i);
if (forwarders_.empty()) {
if (flags_.shutdown_on_last_complete) {
if (delayed_error_)
this->abort(delayed_error_);
else
this->shutdown();
}
} else if (flags_.concat_mode) {
if (auto& sub = forwarders_.front()->sub)
sub.request(defaults::flow::buffer_size);
// else: not subscribed yet, so forwarder_subscribed calls sub.request
}
}
}
struct input_t {
size_t offset = 0;
async::batch buf;
fwd_ptr src;
input_t(async::batch content, fwd_ptr source)
: buf(std::move(content)), src(std::move(source)) {
// nop
}
};
struct flags_t {
bool delay_error : 1;
bool shutdown_on_last_complete : 1;
bool concat_mode : 1;
flags_t()
: delay_error(false),
shutdown_on_last_complete(true),
concat_mode(false) {
// nop
}
};
std::vector<input_t> inputs_;
std::vector<fwd_ptr> forwarders_;
flags_t flags_;
error delayed_error_;
};
template <class T>
using merger_impl_ptr = intrusive_ptr<merger_impl<T>>;
template <class T, class F>
class flat_map_observer_impl : public ref_counted, public observer_impl<T> {
public:
using mapped_type = decltype((std::declval<F&>())(std::declval<const T&>()));
using inner_type = typename mapped_type::output_type;
CAF_INTRUSIVE_PTR_FRIENDS(flat_map_observer_impl)
flat_map_observer_impl(coordinator* ctx, F f) : map_(std::move(f)) {
merger_.emplace(ctx);
merger_->shutdown_on_last_complete(false);
}
void dispose() override {
if (sub_) {
sub_.cancel();
sub_ = nullptr;
merger_->shutdown_on_last_complete(true);
merger_ = nullptr;
}
}
bool disposed() const noexcept override {
return merger_ != nullptr;
}
void ref_disposable() const noexcept final {
this->ref();
}
void deref_disposable() const noexcept final {
this->deref();
}
void on_complete() override {
if (sub_) {
sub_ = nullptr;
merger_->shutdown_on_last_complete(true);
merger_ = nullptr;
}
}
void on_error(const error& what) override {
if (sub_) {
sub_ = nullptr;
merger_->shutdown_on_last_complete(true);
merger_->on_error(what);
merger_ = nullptr;
}
}
void on_subscribe(subscription sub) override {
if (!sub_ && merger_) {
sub_ = std::move(sub);
sub_.request(10);
} else {
sub.cancel();
}
}
void on_next(span<const T> observables) override {
if (sub_) {
for (const auto& x : observables)
merger_->add(map_(x).as_observable());
sub_.request(observables.size());
}
}
observable<inner_type> merger() {
return observable<inner_type>{merger_};
}
auto& merger_ptr() {
return merger_;
}
private:
subscription sub_;
F map_;
intrusive_ptr<merger_impl<inner_type>> merger_;
};
template <class T>
template <class F>
auto observable<T>::flat_map(F f) {
using f_res = decltype(f(std::declval<const T&>()));
static_assert(is_observable_v<f_res>,
"mapping functions must return an observable");
using impl_t = flat_map_observer_impl<T, F>;
auto obs = make_counted<impl_t>(pimpl_->ctx(), std::move(f));
pimpl_->subscribe(obs->as_observer());
return obs->merger();
}
// -- observable::concat_map ---------------------------------------------------
template <class T>
template <class F>
auto observable<T>::concat_map(F f) {
using f_res = decltype(f(std::declval<const T&>()));
static_assert(is_observable_v<f_res>,
"mapping functions must return an observable");
using impl_t = flat_map_observer_impl<T, F>;
auto obs = make_counted<impl_t>(pimpl_->ctx(), std::move(f));
obs->merger_ptr()->concat_mode(true);
pimpl_->subscribe(obs->as_observer());
return obs->merger();
}
// -- observable::to_resource --------------------------------------------------
/// Reads from an observable buffer and emits the consumed items.
/// @note Only supports a single observer.
template <class Buffer>
class observable_buffer_impl
: public ref_counted,
public observable_impl<typename Buffer::value_type>,
public async::consumer {
public:
// -- member types -----------------------------------------------------------
using value_type = typename Buffer::value_type;
using buffer_ptr = intrusive_ptr<Buffer>;
using super = observable_impl<value_type>;
// -- friends ----------------------------------------------------------------
CAF_INTRUSIVE_PTR_FRIENDS(observable_buffer_impl)
// -- constructors, destructors, and assignment operators --------------------
observable_buffer_impl(coordinator* ctx, buffer_ptr buf)
: ctx_(ctx), buf_(buf) {
// Unlike regular observables, we need a strong reference to the context.
// Otherwise, the buffer might call schedule_fn on a destroyed object.
this->ctx()->ref_coordinator();
}
~observable_buffer_impl() {
this->ctx()->deref_coordinator();
}
// -- implementation of disposable::impl -------------------------------------
coordinator* ctx() const noexcept final {
return ctx_;
}
void dispose() override {
CAF_LOG_TRACE("");
if (buf_) {
buf_->cancel();
buf_ = nullptr;
if (dst_) {
dst_.on_complete();
dst_ = nullptr;
}
}
}
bool disposed() const noexcept override {
return buf_ == nullptr;
}
void ref_disposable() const noexcept override {
this->ref();
}
void deref_disposable() const noexcept override {
this->deref();
}
// -- implementation of observable<T>::impl ----------------------------------
void on_request(observer_impl<value_type>*, size_t n) override {
CAF_LOG_TRACE(CAF_ARG(n));
demand_ += n;
if (demand_ == n)
pull();
}
void on_cancel(observer_impl<value_type>*) override {
CAF_LOG_TRACE("");
dst_ = nullptr;
dispose();
}
disposable subscribe(observer<value_type> what) override {
CAF_LOG_TRACE("");
if (buf_ && !dst_) {
CAF_LOG_DEBUG("add destination");
dst_ = std::move(what);
return super::do_subscribe(dst_.ptr());
} else {
CAF_LOG_DEBUG("already have a destination");
auto err = make_error(sec::cannot_add_upstream,
"observable buffers support only one observer");
what.on_error(err);
return disposable{};
}
}
// -- implementation of consumer ---------------------------------------------
void on_producer_ready() override {
// nop
}
void on_producer_wakeup() override {
CAF_LOG_TRACE("");
this->ctx()->schedule_fn([ptr{strong_ptr()}] {
CAF_LOG_TRACE("");
ptr->pull();
});
}
void ref_consumer() const noexcept override {
this->ref();
}
void deref_consumer() const noexcept override {
this->deref();
}
protected:
coordinator* ctx_;
private:
void pull() {
CAF_LOG_TRACE("");
if (!buf_ || pulling_ || !dst_ || demand_ == 0)
return;
pulling_ = true;
auto fin = buf_->consume(
async::prioritize_errors, demand_,
[this](span<const value_type> items) {
CAF_LOG_TRACE(CAF_ARG(items));
CAF_ASSERT(!items.empty());
CAF_ASSERT(demand_ >= items.empty());
demand_ -= items.size();
dst_.on_next(items);
},
[this](const error& what) {
if (dst_) {
dst_.on_error(what);
dst_ = nullptr;
}
buf_ = nullptr;
});
pulling_ = false;
if (fin && buf_) {
buf_ = nullptr;
if (dst_) {
dst_.on_complete();
dst_ = nullptr;
}
}
}
intrusive_ptr<observable_buffer_impl> strong_ptr() {
return {this};
}
buffer_ptr buf_;
/// Stores a pointer to the target observer running on `remote_ctx_`.
observer<value_type> dst_;
bool pulling_ = false;
size_t demand_ = 0;
};
template <class T>
async::consumer_resource<T>
observable<T>::to_resource(size_t buffer_size, size_t min_request_size) {
using buffer_type = async::bounded_buffer<T>;
auto buf = make_counted<buffer_type>(buffer_size, min_request_size);
auto up = make_counted<buffer_writer_impl<buffer_type>>(pimpl_->ctx(), buf);
buf->set_producer(up);
subscribe(up->as_observer());
return async::consumer_resource<T>{std::move(buf)};
}
// -- observable::observe_on ---------------------------------------------------
template <class T>
observable<T> observable<T>::observe_on(coordinator* other, size_t buffer_size,
size_t min_request_size) {
using buffer_type = async::bounded_buffer<T>;
auto buf = make_counted<buffer_type>(buffer_size, min_request_size);
auto up = make_counted<buffer_writer_impl<buffer_type>>(pimpl_->ctx(), buf);
auto down = make_counted<observable_buffer_impl<buffer_type>>(other, buf);
buf->set_producer(up);
buf->set_consumer(down);
subscribe(up->as_observer());
auto hdl = observable<T>{std::move(down)};
pimpl_->ctx()->watch(hdl.as_disposable());
return hdl;
}
// -- observable::subscribe ----------------------------------------------------
template <class T>
disposable observable<T>::subscribe(async::producer_resource<T> resource) {
using buffer_type = typename async::consumer_resource<T>::buffer_type;
using adapter_type = buffer_writer_impl<buffer_type>;
if (auto buf = resource.try_open()) {
CAF_LOG_DEBUG("subscribe producer resource to flow");
auto adapter = make_counted<adapter_type>(pimpl_->ctx(), buf);
buf->set_producer(adapter);
auto obs = adapter->as_observer();
pimpl_->ctx()->watch(obs.as_disposable());
return subscribe(std::move(obs));
} else {
CAF_LOG_DEBUG("failed to open producer resource");
return {};
}
}
} // namespace caf::flow
// 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/bounded_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
namespace caf::flow {
template <class Container>
class container_source {
public:
using output_type = typename Container::value_type;
explicit container_source(Container&& values) : values_(std::move(values)) {
pos_ = values_.begin();
}
container_source(container_source&&) = default;
container_source& operator=(container_source&&) = default;
container_source() = delete;
container_source(const container_source&) = delete;
container_source& operator=(const container_source&) = delete;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
while (pos_ != values_.end() && n > 0) {
if (!step.on_next(*pos_++, steps...))
return;
--n;
}
if (pos_ == values_.end())
step.on_complete(steps...);
}
private:
Container values_;
typename Container::const_iterator pos_;
};
template <class T>
class repeater_source {
public:
using output_type = T;
explicit repeater_source(T value) : value_(std::move(value)) {
// nop
}
repeater_source(repeater_source&&) = default;
repeater_source(const repeater_source&) = default;
repeater_source& operator=(repeater_source&&) = default;
repeater_source& operator=(const repeater_source&) = default;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
for (size_t i = 0; i < n; ++i)
if (!step.on_next(value_, steps...))
return;
}
private:
T value_;
};
template <class F>
class callable_source {
public:
using output_type = std::decay_t<decltype(std::declval<F&>()())>;
explicit callable_source(F fn) : fn_(std::move(fn)) {
// nop
}
callable_source(callable_source&&) = default;
callable_source& operator=(callable_source&&) = default;
callable_source(const callable_source&) = delete;
callable_source& operator=(const callable_source&) = delete;
template <class Step, class... Steps>
void pull(size_t n, Step& step, Steps&... steps) {
for (size_t i = 0; i < n; ++i)
if (!step.on_next(fn_(), steps...))
return;
}
private:
F fn_;
};
class observable_builder {
public:
friend class coordinator;
observable_builder(const observable_builder&) noexcept = default;
observable_builder& operator=(const observable_builder&) noexcept = default;
template <class T>
[[nodiscard]] generation<repeater_source<T>> repeat(T value) const;
template <class Container>
[[nodiscard]] generation<container_source<Container>>
from_container(Container values) const;
template <class T>
[[nodiscard]] auto just(T value) const;
template <class F>
[[nodiscard]] generation<callable_source<F>> from_callable(F fn) const;
/// Opens an asynchronous, buffered resource and emits all inputs from the
/// buffer.
template <class T>
[[nodiscard]] observable<T>
from_resource(async::consumer_resource<T> hdl) const;
template <class Pullable>
[[nodiscard]] generation<Pullable> lift(Pullable pullable) const;
private:
explicit observable_builder(coordinator* ctx) : ctx_(ctx) {
// nop
}
coordinator* ctx_;
};
// -- generation ---------------------------------------------------------------
template <class Generator, class... Steps>
class generation final
: public observable_def<
transform_processor_output_type_t<Generator, Steps...>> {
public:
using output_type = transform_processor_output_type_t<Generator, Steps...>;
class impl : public buffered_observable_impl<output_type> {
public:
using super = buffered_observable_impl<output_type>;
template <class... Ts>
impl(coordinator* ctx, Generator gen, Ts&&... steps)
: super(ctx), gen_(std::move(gen)), steps_(std::forward<Ts>(steps)...) {
// nop
}
private:
virtual void pull(size_t n) {
auto fn = [this, n](auto&... steps) {
term_step<output_type> term{this};
gen_.pull(n, steps..., term);
};
std::apply(fn, steps_);
}
Generator gen_;
std::tuple<Steps...> steps_;
};
template <class... Ts>
generation(coordinator* ctx, Generator gen, Ts&&... steps)
: ctx_(ctx), gen_(std::move(gen)), steps_(std::forward<Ts>(steps)...) {
// nop
}
generation() = delete;
generation(const generation&) = delete;
generation& operator=(const generation&) = delete;
generation(generation&&) = default;
generation& operator=(generation&&) = default;
/// @copydoc observable::transform
template <class NewStep>
generation<Generator, Steps..., NewStep> transform(NewStep step) && {
static_assert(std::is_same_v<typename NewStep::input_type, output_type>,
"step object does not match the output type");
return {ctx_, std::move(gen_),
std::tuple_cat(std::move(steps_),
std::make_tuple(std::move(step)))};
}
auto take(size_t n) && {
return std::move(*this).transform(limit_step<output_type>{n});
}
template <class Predicate>
auto filter(Predicate predicate) && {
return std::move(*this).transform(
filter_step<Predicate>{std::move(predicate)});
}
template <class Fn>
auto map(Fn fn) && {
return std::move(*this).transform(map_step<Fn>{std::move(fn)});
}
observable<output_type> as_observable() && override {
auto pimpl = make_counted<impl>(ctx_, std::move(gen_), std::move(steps_));
return observable<output_type>{std::move(pimpl)};
}
private:
coordinator* ctx_;
Generator gen_;
std::tuple<Steps...> steps_;
};
// -- observable_builder::repeat -----------------------------------------------
template <class T>
generation<repeater_source<T>> observable_builder::repeat(T value) const {
return {ctx_, repeater_source<T>{std::move(value)}};
}
// -- observable_builder::from_container ---------------------------------------
template <class Container>
generation<container_source<Container>>
observable_builder::from_container(Container values) const {
return {ctx_, container_source<Container>{std::move(values)}};
}
// -- observable_builder::just -------------------------------------------------
template <class T>
auto observable_builder::just(T value) const {
return repeat(std::move(value)).take(1);
}
// -- observable_builder::from_callable ----------------------------------------
template <class F>
generation<callable_source<F>> observable_builder::from_callable(F fn) const {
return {ctx_, callable_source<F>{std::move(fn)}};
}
// -- observable_builder::from_resource ----------------------------------------
template <class T>
observable<T>
observable_builder::from_resource(async::consumer_resource<T> hdl) const {
using buffer_type = typename async::consumer_resource<T>::buffer_type;
using res_t = observable<T>;
if (auto buf = hdl.try_open()) {
auto adapter = make_counted<observable_buffer_impl<buffer_type>>(ctx_, buf);
buf->set_consumer(adapter);
ctx_->watch(adapter->as_disposable());
return res_t{std::move(adapter)};
} else {
auto err = make_error(sec::invalid_observable,
"from_resource: failed to open the resource");
return res_t{make_counted<observable_error_impl<T>>(ctx_, std::move(err))};
}
}
// -- observable_builder::lift -------------------------------------------------
template <class Pullable>
generation<Pullable> observable_builder::lift(Pullable pullable) const {
return {ctx_, std::move(pullable)};
}
} // namespace caf::flow
// 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/batch.hpp"
#include "caf/async/producer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include "caf/span.hpp"
#include "caf/unit.hpp"
namespace caf::flow {
/// Handle to a consumer of items.
template <class T>
class observer {
public:
/// Internal interface of an `observer`.
class impl : public disposable::impl {
public:
using input_type = T;
virtual void on_subscribe(subscription sub) = 0;
virtual void on_next(span<const T> items) = 0;
virtual void on_complete() = 0;
virtual void on_error(const error& what) = 0;
observer as_observer() {
return observer{intrusive_ptr<impl>(this)};
}
};
using input_type = T;
explicit observer(intrusive_ptr<impl> pimpl) noexcept
: pimpl_(std::move(pimpl)) {
// nop
}
observer& operator=(std::nullptr_t) noexcept {
pimpl_.reset();
return *this;
}
observer() noexcept = default;
observer(observer&&) noexcept = default;
observer(const observer&) noexcept = default;
observer& operator=(observer&&) noexcept = default;
observer& operator=(const observer&) noexcept = default;
disposable as_disposable() const& noexcept {
return disposable{pimpl_};
}
disposable as_disposable() && noexcept {
return disposable{std::move(pimpl_)};
}
/// @pre `valid()`
void on_complete() {
pimpl_->on_complete();
}
/// @pre `valid()`
void on_error(const error& what) {
pimpl_->on_error(what);
}
/// @pre `valid()`
void on_subscribe(subscription sub) {
pimpl_->on_subscribe(std::move(sub));
}
/// @pre `valid()`
void on_batch(const async::batch& buf) {
pimpl_->on_batch(buf);
}
/// @pre `valid()`
void on_next(span<const T> items) {
pimpl_->on_next(items);
}
/// Creates a new observer from `Impl`.
template <class Impl, class... Ts>
[[nodiscard]] static observer make(Ts&&... xs) {
static_assert(std::is_base_of_v<impl, Impl>);
return observer{make_counted<Impl>(std::forward<Ts>(xs)...)};
}
bool valid() const noexcept {
return pimpl_ != nullptr;
}
explicit operator bool() const noexcept {
return valid();
}
bool operator!() const noexcept {
return !valid();
}
impl* ptr() {
return pimpl_.get();
}
const impl* ptr() const {
return pimpl_.get();
}
const intrusive_ptr<impl>& as_intrusive_ptr() const& noexcept {
return pimpl_;
}
intrusive_ptr<impl>&& as_intrusive_ptr() && noexcept {
return std::move(pimpl_);
}
void swap(observer& other) {
pimpl_.swap(other.pimpl_);
}
private:
intrusive_ptr<impl> pimpl_;
};
template <class T>
using observer_impl = typename observer<T>::impl;
// -- writing observed values to a buffer --------------------------------------
/// Writes observed values to a bounded buffer.
template <class Buffer>
class buffer_writer_impl : public ref_counted,
public observer_impl<typename Buffer::value_type>,
public async::producer {
public:
using buffer_ptr = intrusive_ptr<Buffer>;
using value_type = typename Buffer::value_type;
CAF_INTRUSIVE_PTR_FRIENDS(buffer_writer_impl)
buffer_writer_impl(coordinator* ctx, buffer_ptr buf)
: ctx_(ctx), buf_(std::move(buf)) {
CAF_ASSERT(ctx_ != nullptr);
CAF_ASSERT(buf_ != nullptr);
}
void on_consumer_ready() override {
// nop
}
void on_consumer_cancel() override {
CAF_LOG_TRACE("");
ctx_->schedule_fn([ptr{strong_ptr()}] {
CAF_LOG_TRACE("");
ptr->on_cancel();
});
}
void on_consumer_demand(size_t demand) override {
CAF_LOG_TRACE(CAF_ARG(demand));
ctx_->schedule_fn([ptr{strong_ptr()}, demand] { //
CAF_LOG_TRACE(CAF_ARG(demand));
ptr->on_demand(demand);
});
}
void ref_disposable() const noexcept final {
this->ref();
}
void deref_disposable() const noexcept final {
this->deref();
}
void ref_producer() const noexcept final {
this->ref();
}
void deref_producer() const noexcept final {
this->deref();
}
void on_next(span<const value_type> items) override {
CAF_LOG_TRACE(CAF_ARG(items));
if (buf_)
buf_->push(items);
}
void on_complete() override {
CAF_LOG_TRACE("");
if (buf_) {
buf_->close();
buf_ = nullptr;
sub_ = nullptr;
}
}
void on_error(const error& what) override {
CAF_LOG_TRACE(CAF_ARG(what));
if (buf_) {
buf_->abort(what);
buf_ = nullptr;
sub_ = nullptr;
}
}
void on_subscribe(subscription sub) override {
CAF_LOG_TRACE("");
if (buf_ && !sub_) {
CAF_LOG_DEBUG("add subscription");
sub_ = std::move(sub);
sub_.request(buf_->capacity());
} else {
CAF_LOG_DEBUG("already have a subscription");
sub.cancel();
}
}
void dispose() override {
CAF_LOG_TRACE("");
on_complete();
}
bool disposed() const noexcept override {
return buf_ == nullptr;
}
private:
void on_demand(size_t n) {
CAF_LOG_TRACE(CAF_ARG(n));
if (sub_)
sub_.request(n);
}
void on_cancel() {
CAF_LOG_TRACE("");
if (sub_) {
sub_.cancel();
sub_ = nullptr;
}
buf_ = nullptr;
}
intrusive_ptr<buffer_writer_impl> strong_ptr() {
return {this};
}
coordinator_ptr ctx_;
buffer_ptr buf_;
subscription sub_;
};
} // namespace caf::flow
namespace caf::detail {
template <class OnNextSignature>
struct on_next_trait;
template <class T>
struct on_next_trait<void(T)> {
using value_type = T;
template <class F>
static void apply(F& f, span<const T> items) {
for (auto&& item : items)
f(item);
}
};
template <class T>
struct on_next_trait<void(const T&)> {
using value_type = T;
template <class F>
static void apply(F& f, span<const T> items) {
for (auto&& item : items)
f(item);
}
};
template <class T>
struct on_next_trait<void(span<const T>)> {
using value_type = T;
template <class F>
static void apply(F& f, span<const T> items) {
f(items);
}
};
template <class F>
using on_next_trait_t
= on_next_trait<typename get_callable_trait_t<F>::fun_sig>;
template <class F>
using on_next_value_type = typename on_next_trait_t<F>::value_type;
template <class OnNext, class OnError = unit_t, class OnComplete = unit_t>
class default_observer_impl
: public ref_counted,
public flow::observer_impl<on_next_value_type<OnNext>> {
public:
static_assert(std::is_invocable_v<OnError, const error&>);
static_assert(std::is_invocable_v<OnComplete>);
using input_type = on_next_value_type<OnNext>;
CAF_INTRUSIVE_PTR_FRIENDS(default_observer_impl)
explicit default_observer_impl(OnNext&& on_next_fn)
: on_next_(std::move(on_next_fn)) {
// nop
}
default_observer_impl(OnNext&& on_next_fn, OnError&& on_error_fn)
: on_next_(std::move(on_next_fn)), on_error_(std::move(on_error_fn)) {
// nop
}
default_observer_impl(OnNext&& on_next_fn, OnError&& on_error_fn,
OnComplete&& on_complete_fn)
: on_next_(std::move(on_next_fn)),
on_error_(std::move(on_error_fn)),
on_complete_(std::move(on_complete_fn)) {
// nop
}
void ref_disposable() const noexcept final {
this->ref();
}
void deref_disposable() const noexcept final {
this->deref();
}
void on_next(span<const input_type> items) override {
if (!completed_) {
on_next_trait_t<OnNext>::apply(on_next_, items);
sub_.request(items.size());
}
}
void on_error(const error& what) override {
if (!completed_) {
on_error_(what);
sub_ = nullptr;
completed_ = true;
}
}
void on_complete() override {
if (!completed_) {
on_complete_();
sub_ = nullptr;
completed_ = true;
}
}
void on_subscribe(flow::subscription sub) override {
if (!completed_ && !sub_) {
sub_ = std::move(sub);
sub_.request(defaults::flow::buffer_size);
} else {
sub.cancel();
}
}
void dispose() override {
if (!completed_) {
on_complete_();
if (sub_) {
sub_.cancel();
sub_ = nullptr;
}
completed_ = true;
}
}
bool disposed() const noexcept override {
return completed_;
}
private:
bool completed_ = false;
OnNext on_next_;
OnError on_error_;
OnComplete on_complete_;
flow::subscription sub_;
};
} // namespace caf::detail
namespace caf::flow {
template <class OnNext, class OnError, class OnComplete>
auto make_observer(OnNext on_next, OnError on_error, OnComplete on_complete) {
using impl_type = detail::default_observer_impl<OnNext, OnError, OnComplete>;
using input_type = typename impl_type::input_type;
auto ptr = make_counted<impl_type>(std::move(on_next), std::move(on_error),
std::move(on_complete));
return observer<input_type>{std::move(ptr)};
}
template <class OnNext, class OnError>
auto make_observer(OnNext on_next, OnError on_error) {
using impl_type = detail::default_observer_impl<OnNext, OnError>;
using input_type = typename impl_type::input_type;
auto ptr = make_counted<impl_type>(std::move(on_next), std::move(on_error));
return observer<input_type>{std::move(ptr)};
}
template <class OnNext>
auto make_observer(OnNext on_next) {
using impl_type = detail::default_observer_impl<OnNext>;
using input_type = typename impl_type::input_type;
auto ptr = make_counted<impl_type>(std::move(on_next));
return observer<input_type>{std::move(ptr)};
}
} // namespace caf::flow
// 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 <condition_variable>
#include <mutex>
#include "caf/flow/coordinator.hpp"
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
namespace caf::flow {
class CAF_CORE_EXPORT scoped_coordinator final : public ref_counted,
public coordinator {
public:
void run();
void ref_coordinator() const noexcept override;
void deref_coordinator() const noexcept override;
void schedule(action what) override;
void post_internally(action what) override;
void watch(disposable what) override;
friend void intrusive_ptr_add_ref(const scoped_coordinator* ptr) {
ptr->ref();
}
friend void intrusive_ptr_release(scoped_coordinator* ptr) {
ptr->deref();
}
static intrusive_ptr<scoped_coordinator> make();
private:
scoped_coordinator() = default;
void drop_disposed_flows();
std::vector<disposable> watched_disposables_;
mutable std::mutex mtx_;
std::condition_variable cv_;
std::vector<action> actions_;
};
using scoped_coordinator_ptr = intrusive_ptr<scoped_coordinator>;
inline scoped_coordinator_ptr make_scoped_coordinator() {
return scoped_coordinator::make();
}
} // namespace caf::flow
// 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 <algorithm>
#include <utility>
#include <variant>
#include "caf/detail/overload.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/flow/observable.hpp"
#include "caf/none.hpp"
#include "caf/ref_counted.hpp"
namespace caf::flow {
/// Similar to an `observable`, but always emits either a single value or an
/// error.
template <class T>
class single {
public:
using output_type = T;
/// Internal interface of a `single`.
class impl : public ref_counted, public observable_impl<T> {
public:
using super = observable_impl<T>;
CAF_INTRUSIVE_PTR_FRIENDS(impl)
explicit impl(coordinator* ctx) : ctx_(ctx) {
// nop
}
coordinator* ctx() const noexcept override {
return ctx_;
}
disposable subscribe(observer<T> what) override {
if (!std::holds_alternative<error>(value_)) {
auto res = super::do_subscribe(what.ptr());
observers_.emplace_back(std::move(what), 0u);
return res;
} else {
what.on_error(std::get<error>(value_));
return disposable{};
}
}
void on_request(observer_impl<T>* sink, size_t n) override {
auto pred = [sink](auto& entry) { return entry.first.ptr() == sink; };
if (auto i = std::find_if(observers_.begin(), observers_.end(), pred);
i != observers_.end()) {
auto f = detail::make_overload( //
[i, n](none_t) { i->second += n; },
[this, i](const T& val) {
i->first.on_next(make_span(&val, 1));
i->first.on_complete();
observers_.erase(i);
},
[this, i](const error& err) {
i->first.on_error(err);
observers_.erase(i);
});
std::visit(f, value_);
}
}
void on_cancel(observer_impl<T>* sink) override {
auto pred = [sink](auto& entry) { return entry.first.ptr() == sink; };
if (auto i = std::find_if(observers_.begin(), observers_.end(), pred);
i != observers_.end())
observers_.erase(i);
}
void dispose() override {
if (!std::holds_alternative<error>(value_))
set_error(make_error(sec::discarded));
}
bool disposed() const noexcept override {
return observers_.empty() && !std::holds_alternative<none_t>(value_);
}
void ref_disposable() const noexcept final {
this->ref();
}
void deref_disposable() const noexcept final {
this->deref();
}
void set_value(T val) {
if (std::holds_alternative<none_t>(value_)) {
value_ = std::move(val);
auto& ref = std::get<T>(value_);
auto pred = [](auto& entry) { return entry.second == 0; };
if (auto first = std::partition(observers_.begin(), observers_.end(),
pred);
first != observers_.end()) {
for (auto i = first; i != observers_.end(); ++i) {
i->first.on_next(make_span(&ref, 1));
i->first.on_complete();
}
observers_.erase(first, observers_.end());
}
}
}
void set_error(error err) {
value_ = std::move(err);
auto& ref = std::get<error>(value_);
for (auto& entry : observers_)
entry.first.on_error(ref);
observers_.clear();
}
private:
coordinator* ctx_;
std::variant<none_t, T, error> value_;
std::vector<std::pair<observer<T>, size_t>> observers_;
};
explicit single(intrusive_ptr<impl> pimpl) noexcept
: pimpl_(std::move(pimpl)) {
// nop
}
single& operator=(std::nullptr_t) noexcept {
pimpl_.reset();
return *this;
}
single() noexcept = default;
single(single&&) noexcept = default;
single(const single&) noexcept = default;
single& operator=(single&&) noexcept = default;
single& operator=(const single&) noexcept = default;
disposable as_disposable() && {
return disposable{std::move(pimpl_)};
}
disposable as_disposable() const& {
return disposable{pimpl_};
}
observable<T> as_observable() && {
return observable<T>{std::move(pimpl_)};
}
observable<T> as_observable() const& {
return observable<T>{pimpl_};
}
void subscribe(observer<T> what) {
if (pimpl_)
pimpl_->subscribe(std::move(what));
else
what.on_error(make_error(sec::invalid_observable));
}
template <class OnSuccess, class OnError>
void subscribe(OnSuccess on_success, OnError on_error) {
static_assert(std::is_invocable_v<OnSuccess, const T&>);
as_observable().for_each(
[f{std::move(on_success)}](span<const T> items) mutable {
CAF_ASSERT(items.size() == 1);
f(items[0]);
},
std::move(on_error));
}
void set_value(T val) {
if (pimpl_)
pimpl_->set_value(std::move(val));
}
void set_error(error err) {
if (pimpl_)
pimpl_->set_error(std::move(err));
}
bool valid() const noexcept {
return pimpl_ != nullptr;
}
explicit operator bool() const noexcept {
return valid();
}
bool operator!() const noexcept {
return !valid();
}
impl* ptr() {
return pimpl_.get();
}
const impl* ptr() const {
return pimpl_.get();
}
const intrusive_ptr<impl>& as_intrusive_ptr() const& noexcept {
return pimpl_;
}
intrusive_ptr<impl>&& as_intrusive_ptr() && noexcept {
return std::move(pimpl_);
}
void swap(single& other) {
pimpl_.swap(other.pimpl_);
}
private:
intrusive_ptr<impl> pimpl_;
};
template <class T>
using single_impl = typename single<T>::impl;
template <class T>
single<T> make_single(coordinator* ctx) {
return single<T>{make_counted<single_impl<T>>(ctx)};
}
} // namespace caf::flow
// 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 <type_traits>
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
namespace caf::flow {
template <class T>
struct limit_step {
size_t remaining;
using input_type = T;
using output_type = T;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (remaining > 0) {
if (next.on_next(item, steps...)) {
if (--remaining > 0) {
return true;
} else {
next.on_complete(steps...);
return false;
}
}
}
return false;
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
};
template <class Predicate>
struct filter_step {
using trait = detail::get_callable_trait_t<Predicate>;
static_assert(std::is_convertible_v<typename trait::result_type, bool>,
"predicates must return a boolean value");
static_assert(trait::num_args == 1,
"predicates must take exactly one argument");
using input_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
using output_type = input_type;
Predicate predicate;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
if (predicate(item))
return next.on_next(item, steps...);
else
return true;
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
};
template <class Fn>
struct map_step {
using trait = detail::get_callable_trait_t<Fn>;
static_assert(!std::is_same_v<typename trait::result_type, void>,
"map functions may not return void");
static_assert(trait::num_args == 1,
"map functions must take exactly one argument");
using input_type = std::decay_t<detail::tl_head_t<typename trait::arg_types>>;
using output_type = std::decay_t<typename trait::result_type>;
Fn fn;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
return next.on_next(fn(item), steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
};
template <class T, class Fn>
struct on_complete_step {
using input_type = T;
using output_type = T;
Fn fn;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
fn();
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
next.on_error(what, steps...);
}
};
template <class T, class Fn>
struct on_error_step {
using input_type = T;
using output_type = T;
Fn fn;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
fn();
next.on_error(what, steps...);
}
};
template <class T, class Fn>
struct finally_step {
using input_type = T;
using output_type = T;
Fn fn;
template <class Next, class... Steps>
bool on_next(const input_type& item, Next& next, Steps&... steps) {
return next.on_next(item, steps...);
}
template <class Next, class... Steps>
void on_complete(Next& next, Steps&... steps) {
fn();
next.on_complete(steps...);
}
template <class Next, class... Steps>
void on_error(const error& what, Next& next, Steps&... steps) {
fn();
next.on_error(what, steps...);
}
};
} // namespace caf::flow
// 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 <cstddef>
#include "caf/detail/core_export.hpp"
#include "caf/disposable.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ref_counted.hpp"
namespace caf::flow {
/// Controls the flow of items from publishers to subscribers.
class CAF_CORE_EXPORT subscription {
public:
// -- nested types -----------------------------------------------------------
/// Internal impl of a `disposable`.
class impl : public disposable::impl {
public:
~impl() override;
/// Causes the publisher to stop producing items for the subscriber. Any
/// in-flight items may still get dispatched.
virtual void cancel() = 0;
/// Signals demand for `n` more items.
virtual void request(size_t n) = 0;
void dispose() final;
};
// -- constructors, destructors, and assignment operators --------------------
explicit subscription(intrusive_ptr<impl> pimpl) noexcept
: pimpl_(std::move(pimpl)) {
// nop
}
subscription& operator=(std::nullptr_t) noexcept {
pimpl_.reset();
return *this;
}
subscription() noexcept = default;
subscription(subscription&&) noexcept = default;
subscription(const subscription&) noexcept = default;
subscription& operator=(subscription&&) noexcept = default;
subscription& operator=(const subscription&) noexcept = default;
// -- demand signaling -------------------------------------------------------
/// @copydoc impl::cancel
void cancel() {
if (pimpl_) {
pimpl_->cancel();
pimpl_ = nullptr;
}
}
/// @copydoc impl::request
/// @pre `valid()`
void request(size_t n) {
pimpl_->request(n);
}
// -- properties -------------------------------------------------------------
bool valid() const noexcept {
return pimpl_ != nullptr;
}
explicit operator bool() const noexcept {
return valid();
}
bool operator!() const noexcept {
return !valid();
}
impl* ptr() noexcept {
return pimpl_.get();
}
const impl* ptr() const noexcept {
return pimpl_.get();
}
intrusive_ptr<impl> as_intrusive_ptr() const& noexcept {
return pimpl_;
}
intrusive_ptr<impl>&& as_intrusive_ptr() && noexcept {
return std::move(pimpl_);
}
disposable as_disposable() const& noexcept {
return disposable{pimpl_};
}
disposable as_disposable() && noexcept {
return disposable{std::move(pimpl_)};
}
// -- swapping ---------------------------------------------------------------
void swap(subscription& other) noexcept {
pimpl_.swap(other.pimpl_);
}
private:
intrusive_ptr<impl> pimpl_;
};
} // namespace caf::flow
...@@ -381,4 +381,8 @@ using strong_actor_ptr = intrusive_ptr<actor_control_block>; ...@@ -381,4 +381,8 @@ using strong_actor_ptr = intrusive_ptr<actor_control_block>;
using mailbox_element_ptr = std::unique_ptr<mailbox_element>; using mailbox_element_ptr = std::unique_ptr<mailbox_element>;
using tracing_data_ptr = std::unique_ptr<tracing_data>; using tracing_data_ptr = std::unique_ptr<tracing_data>;
// -- shared pointer aliases ---------------------------------------------------
using shared_action_ptr = std::shared_ptr<callback<void()>>;
} // namespace caf } // namespace caf
...@@ -271,3 +271,22 @@ std::string to_string(const intrusive_ptr<T>& x) { ...@@ -271,3 +271,22 @@ std::string to_string(const intrusive_ptr<T>& x) {
} // namespace caf } // namespace caf
/// Convenience macro for adding `intrusive_ptr_add_ref` and
/// `intrusive_ptr_release` as free friend functions.
#define CAF_INTRUSIVE_PTR_FRIENDS(class_name) \
friend void intrusive_ptr_add_ref(const class_name* ptr) noexcept { \
ptr->ref(); \
} \
friend void intrusive_ptr_release(const class_name* ptr) noexcept { \
ptr->deref(); \
}
/// Convenience macro for adding `intrusive_ptr_add_ref` and
/// `intrusive_ptr_release` as free friend functions with a custom suffix.
#define CAF_INTRUSIVE_PTR_FRIENDS_SFX(class_name, suffix) \
friend void intrusive_ptr_add_ref(const class_name* ptr) noexcept { \
ptr->ref##suffix(); \
} \
friend void intrusive_ptr_release(const class_name* ptr) noexcept { \
ptr->deref##suffix(); \
}
...@@ -41,18 +41,16 @@ public: ...@@ -41,18 +41,16 @@ public:
return rc_; return rc_;
} }
friend void intrusive_ptr_add_ref(const ref_counted* p) noexcept {
p->ref();
}
friend void intrusive_ptr_release(const ref_counted* p) noexcept {
p->deref();
}
protected: protected:
mutable std::atomic<size_t> rc_; mutable std::atomic<size_t> rc_;
}; };
/// @relates ref_counted
inline void intrusive_ptr_add_ref(const ref_counted* p) {
p->ref();
}
/// @relates ref_counted
inline void intrusive_ptr_release(const ref_counted* p) {
p->deref();
}
} // namespace caf } // namespace caf
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "caf/actor_traits.hpp" #include "caf/actor_traits.hpp"
#include "caf/catch_all.hpp" #include "caf/catch_all.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/message_id.hpp" #include "caf/message_id.hpp"
#include "caf/none.hpp" #include "caf/none.hpp"
#include "caf/sec.hpp" #include "caf/sec.hpp"
...@@ -104,6 +105,18 @@ public: ...@@ -104,6 +105,18 @@ public:
then(std::move(f), [self](error& err) { self->call_error_handler(err); }); then(std::move(f), [self](error& err) { self->call_error_handler(err); });
} }
template <class T>
flow::assert_scheduled_actor_hdr_t<flow::single<T>> as_single() && {
static_assert(std::is_same_v<response_type, message>);
return self_->template single_from_response<T>(policy_);
}
template <class T>
flow::assert_scheduled_actor_hdr_t<flow::observable<T>> as_observable() && {
static_assert(std::is_same_v<response_type, message>);
return self_->template single_from_response<T>(policy_).as_observable();
}
// -- blocking API ----------------------------------------------------------- // -- blocking API -----------------------------------------------------------
template <class T = traits, class F = none_t, class OnError = none_t, template <class T = traits, class F = none_t, class OnError = none_t,
......
...@@ -20,8 +20,11 @@ ...@@ -20,8 +20,11 @@
#include "caf/detail/behavior_stack.hpp" #include "caf/detail/behavior_stack.hpp"
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/detail/unordered_flat_map.hpp" #include "caf/detail/unordered_flat_map.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/extend.hpp" #include "caf/extend.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/fwd.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/inbound_path.hpp" #include "caf/inbound_path.hpp"
#include "caf/intrusive/drr_cached_queue.hpp" #include "caf/intrusive/drr_cached_queue.hpp"
...@@ -72,8 +75,14 @@ CAF_CORE_EXPORT skippable_result drop(scheduled_actor*, message&); ...@@ -72,8 +75,14 @@ CAF_CORE_EXPORT skippable_result drop(scheduled_actor*, message&);
/// A cooperatively scheduled, event-based actor implementation. /// A cooperatively scheduled, event-based actor implementation.
class CAF_CORE_EXPORT scheduled_actor : public local_actor, class CAF_CORE_EXPORT scheduled_actor : public local_actor,
public resumable, public resumable,
public non_blocking_actor_base { public non_blocking_actor_base,
public flow::coordinator {
public: public:
// -- friends ----------------------------------------------------------------
template <class, class>
friend class response_handle;
// -- nested enums ----------------------------------------------------------- // -- nested enums -----------------------------------------------------------
/// Categorizes incoming messages. /// Categorizes incoming messages.
...@@ -472,6 +481,18 @@ public: ...@@ -472,6 +481,18 @@ public:
/// Returns the queue of the mailbox that stores `downstream_msg` messages. /// Returns the queue of the mailbox that stores `downstream_msg` messages.
downstream_queue& get_downstream_queue(); downstream_queue& get_downstream_queue();
// -- caf::flow API ----------------------------------------------------------
void ref_coordinator() const noexcept override;
void deref_coordinator() const noexcept override;
void schedule(action what) override;
void post_internally(action what) override;
void watch(disposable what) override;
// -- inbound_path management ------------------------------------------------ // -- inbound_path management ------------------------------------------------
/// Creates a new path for incoming stream traffic from `sender`. /// Creates a new path for incoming stream traffic from `sender`.
...@@ -660,7 +681,8 @@ public: ...@@ -660,7 +681,8 @@ public:
bool alive() const noexcept { bool alive() const noexcept {
return !bhvr_stack_.empty() || !awaited_responses_.empty() return !bhvr_stack_.empty() || !awaited_responses_.empty()
|| !multiplexed_responses_.empty() || !stream_managers_.empty() || !multiplexed_responses_.empty() || !stream_managers_.empty()
|| !pending_stream_managers_.empty(); || !pending_stream_managers_.empty()
|| !watched_disposables_.empty();
} }
auto max_batch_delay() const noexcept { auto max_batch_delay() const noexcept {
...@@ -671,6 +693,13 @@ public: ...@@ -671,6 +693,13 @@ public:
std::vector<stream_manager*> active_stream_managers(); std::vector<stream_manager*> active_stream_managers();
/// Runs all pending actions.
void run_actions();
std::vector<disposable> watched_disposables() const {
return watched_disposables_;
}
/// @endcond /// @endcond
protected: protected:
...@@ -735,6 +764,8 @@ protected: ...@@ -735,6 +764,8 @@ protected:
#endif // CAF_ENABLE_EXCEPTIONS #endif // CAF_ENABLE_EXCEPTIONS
private: private:
// -- utilities for instrumenting actors -------------------------------------
template <class F> template <class F>
intrusive::task_result run_with_metrics(mailbox_element& x, F body) { intrusive::task_result run_with_metrics(mailbox_element& x, F body) {
if (metrics_.mailbox_time) { if (metrics_.mailbox_time) {
...@@ -752,9 +783,31 @@ private: ...@@ -752,9 +783,31 @@ private:
} }
} }
// -- timeout management -----------------------------------------------------
disposable run_scheduled(timestamp when, action what); disposable run_scheduled(timestamp when, action what);
disposable run_scheduled(actor_clock::time_point when, action what); disposable run_scheduled(actor_clock::time_point when, action what);
disposable run_delayed(timespan delay, action what); disposable run_delayed(timespan delay, action what);
// -- caf::flow bindings -----------------------------------------------------
template <class T, class Policy>
flow::single<T> single_from_response(Policy& policy) {
return single_from_response_impl<T>(policy);
}
template <class T, class Policy>
flow::single<T> single_from_response_impl(Policy& policy);
/// Removes any watched object that became disposed since the last update.
void update_watched_disposables();
/// Stores actions that the actor executes after processing the current
/// message.
std::vector<action> actions_;
/// Stores resources that block the actor from terminating.
std::vector<disposable> watched_disposables_;
}; };
} // namespace caf } // namespace caf
// 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.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/single.hpp"
#include "caf/scheduled_actor.hpp"
namespace caf::flow {
template <>
struct has_impl_include<scheduled_actor> {
static constexpr bool value = true;
};
} // namespace caf::flow
namespace caf {
template <class T, class Policy>
flow::single<T> scheduled_actor::single_from_response_impl(Policy& policy) {
using output_type = T;
using impl_type = typename flow::single<output_type>::impl;
auto ptr = make_counted<impl_type>(this);
policy.then(
this,
[this, ptr](T& val) {
ptr->set_value(std::move(val));
run_actions();
},
[this, ptr](error& err) {
ptr->set_error(std::move(err));
run_actions();
});
return flow::single<output_type>{std::move(ptr)};
}
} // namespace caf
...@@ -166,6 +166,11 @@ enum class sec : uint8_t { ...@@ -166,6 +166,11 @@ enum class sec : uint8_t {
/// Signals that an actor fell behind a periodic action trigger. After raising /// Signals that an actor fell behind a periodic action trigger. After raising
/// this error, an @ref actor_clock stops scheduling the action. /// this error, an @ref actor_clock stops scheduling the action.
action_reschedule_failed, action_reschedule_failed,
/// Attaching to an observable failed because the target is invalid.
invalid_observable,
/// Signals to a component that is has been discarded by its parent or the
/// consumer/producer that was attached to the component.
discarded = 70,
}; };
// --(rst-sec-end)-- // --(rst-sec-end)--
......
...@@ -406,6 +406,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0) ...@@ -406,6 +406,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::open_stream_msg)) CAF_ADD_TYPE_ID(core_module, (caf::open_stream_msg))
CAF_ADD_TYPE_ID(core_module, (caf::pec)) CAF_ADD_TYPE_ID(core_module, (caf::pec))
CAF_ADD_TYPE_ID(core_module, (caf::sec)) CAF_ADD_TYPE_ID(core_module, (caf::sec))
CAF_ADD_TYPE_ID(core_module, (caf::shared_action_ptr))
CAF_ADD_TYPE_ID(core_module, (caf::stream_slots)) CAF_ADD_TYPE_ID(core_module, (caf::stream_slots))
CAF_ADD_TYPE_ID(core_module, (caf::strong_actor_ptr)) CAF_ADD_TYPE_ID(core_module, (caf::strong_actor_ptr))
CAF_ADD_TYPE_ID(core_module, (caf::timespan)) CAF_ADD_TYPE_ID(core_module, (caf::timespan))
......
// 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/async/batch.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/deserializer.hpp"
#include "caf/detail/meta_object.hpp"
#include "caf/serializer.hpp"
namespace caf::async {
// -- batch::data --------------------------------------------------------------
namespace {
// void dynamic_item_destructor(type_id_t item_type, uint16_t item_size,
// size_t array_size, byte* data_ptr) {
// auto meta = detail::global_meta_object(item_type);
// do {
// meta->destroy(data_ptr);
// data_ptr += item_size;
// --array_size;
// } while (array_size > 0);
// }
} // namespace
template <class Inspector>
bool batch::data::save(Inspector& sink) const {
CAF_ASSERT(size_ > 0);
if (item_type_ == invalid_type_id) {
sink.emplace_error(sec::unsafe_type);
return false;
}
auto meta = detail::global_meta_object(item_type_);
auto ptr = storage_;
if (!sink.begin_sequence(size_))
return false;
auto len = size_;
do {
if constexpr (std::is_same_v<Inspector, binary_serializer>) {
if (!meta->save_binary(sink, ptr))
return false;
} else {
if (!meta->save(sink, ptr))
return false;
}
ptr += item_size_;
--len;
} while (len > 0);
return sink.end_sequence();
}
// -- batch --------------------------------------------------------------------
template <class Inspector>
bool batch::save_impl(Inspector& f) const {
if (data_)
return data_->save(f);
else
return f.begin_sequence(0) && f.end_sequence();
}
bool batch::save(serializer& f) const {
return save_impl(f);
}
bool batch::save(binary_serializer& f) const {
return save_impl(f);
}
template <class Inspector>
bool batch::load_impl(Inspector&) {
// TODO: implement me
return false;
/*
auto len = size_t{0};
if (!f.begin_sequence(len))
return false;
if (len == 0) {
data_.reset();
return f.end_sequence();
}
*/
}
bool batch::load(deserializer& f) {
return load_impl(f);
}
bool batch::load(binary_deserializer& f) {
return load_impl(f);
}
} // namespace caf::async
// 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/async/consumer.hpp"
namespace caf::async {
consumer::~consumer() {
// nop
}
} // namespace caf::async
// 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/async/producer.hpp"
namespace caf::async {
producer::~producer() {
// nop
}
} // namespace caf::async
// 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/flow/coordinator.hpp"
#include "caf/config.hpp"
#include "caf/flow/observable_builder.hpp"
namespace caf::flow {
coordinator::~coordinator() {
// nop
}
observable_builder coordinator::make_observable() {
return observable_builder{this};
}
// void coordinator::subscription_impl::request(size_t n) {
// CAF_ASSERT(n != 0);
// if (src_) {
// ctx_->dispatch_request(src_.get(), snk_.get(), n);
// }
// }
//
// void coordinator::subscription_impl::cancel() {
// if (src_) {
// ctx_->dispatch_cancel(src_.get(), snk_.get());
// src_.reset();
// snk_.reset();
// }
// }
//
// bool coordinator::subscription_impl::disposed() const noexcept {
// return src_ == nullptr;
// }
} // namespace caf::flow
// 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/flow/scoped_coordinator.hpp"
namespace caf::flow {
void scoped_coordinator::run() {
auto next = [this](bool blocking) {
std::unique_lock guard{mtx_};
if (blocking) {
while (actions_.empty())
cv_.wait(guard);
} else if (actions_.empty()) {
return action{};
}
auto res = std::move(actions_[0]);
actions_.erase(actions_.begin());
return res;
};
for (;;) {
auto hdl = next(!watched_disposables_.empty());
if (hdl.ptr() != nullptr)
hdl.run();
else
return;
}
}
void scoped_coordinator::ref_coordinator() const noexcept {
ref();
}
void scoped_coordinator::deref_coordinator() const noexcept {
deref();
}
void scoped_coordinator::schedule(action what) {
std::unique_lock guard{mtx_};
actions_.emplace_back(std::move(what));
if (actions_.size() == 1)
cv_.notify_all();
}
void scoped_coordinator::post_internally(action what) {
schedule(std::move(what));
}
void scoped_coordinator::watch(disposable what) {
watched_disposables_.emplace_back(std::move(what));
}
intrusive_ptr<scoped_coordinator> scoped_coordinator::make() {
return {new scoped_coordinator, false};
}
void scoped_coordinator::drop_disposed_flows() {
auto disposed = [](auto& hdl) { return hdl.disposed(); };
auto& xs = watched_disposables_;
if (auto e = std::remove_if(xs.begin(), xs.end(), disposed); e != xs.end())
xs.erase(e, xs.end());
}
} // namespace caf::flow
// 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/flow/subscription.hpp"
namespace caf::flow {
subscription::impl::~impl() {
// nop
}
void subscription::impl::dispose() {
cancel();
}
} // namespace caf::flow
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "caf/actor_control_block.hpp" #include "caf/actor_control_block.hpp"
#include "caf/actor_system.hpp" #include "caf/actor_system.hpp"
#include "caf/byte_buffer.hpp" #include "caf/byte_buffer.hpp"
#include "caf/callback.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/downstream_msg.hpp" #include "caf/downstream_msg.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
......
...@@ -231,6 +231,13 @@ bool scheduled_actor::cleanup(error&& fail_state, execution_unit* host) { ...@@ -231,6 +231,13 @@ bool scheduled_actor::cleanup(error&& fail_state, execution_unit* host) {
stream_managers_.clear(); stream_managers_.clear();
pending_stream_managers_.clear(); pending_stream_managers_.clear();
get_downstream_queue().cleanup(); get_downstream_queue().cleanup();
// Cancel any active flow.
while (!watched_disposables_.empty()) {
for (auto& ptr : watched_disposables_)
ptr.dispose();
watched_disposables_.clear();
run_actions();
}
// Clear mailbox. // Clear mailbox.
if (!mailbox_.closed()) { if (!mailbox_.closed()) {
mailbox_.close(); mailbox_.close();
...@@ -452,8 +459,10 @@ proxy_registry* scheduled_actor::proxy_registry_ptr() { ...@@ -452,8 +459,10 @@ proxy_registry* scheduled_actor::proxy_registry_ptr() {
void scheduled_actor::quit(error x) { void scheduled_actor::quit(error x) {
CAF_LOG_TRACE(CAF_ARG(x)); CAF_LOG_TRACE(CAF_ARG(x));
// Make sure repeated calls to quit don't do anything. // Make sure repeated calls to quit don't do anything.
if (getf(is_shutting_down_flag)) if (getf(is_shutting_down_flag)) {
CAF_LOG_DEBUG("already shutting down");
return; return;
}
// Mark this actor as about-to-die. // Mark this actor as about-to-die.
setf(is_shutting_down_flag); setf(is_shutting_down_flag);
// Store shutdown reason. // Store shutdown reason.
...@@ -468,6 +477,13 @@ void scheduled_actor::quit(error x) { ...@@ -468,6 +477,13 @@ void scheduled_actor::quit(error x) {
set_error_handler(silently_ignore<error>); set_error_handler(silently_ignore<error>);
// Drop future messages and produce sec::request_receiver_down for requests. // Drop future messages and produce sec::request_receiver_down for requests.
set_default_handler(drop_after_quit); set_default_handler(drop_after_quit);
// Cancel any active flow.
while (!watched_disposables_.empty()) {
for (auto& ptr : watched_disposables_)
ptr.dispose();
watched_disposables_.clear();
run_actions();
}
// Tell all streams to shut down. // Tell all streams to shut down.
std::vector<stream_manager_ptr> managers; std::vector<stream_manager_ptr> managers;
for (auto& smm : {stream_managers_, pending_stream_managers_}) for (auto& smm : {stream_managers_, pending_stream_managers_})
...@@ -569,6 +585,24 @@ void scheduled_actor::set_stream_timeout(actor_clock::time_point x) { ...@@ -569,6 +585,24 @@ void scheduled_actor::set_stream_timeout(actor_clock::time_point x) {
}); });
} }
// -- caf::flow API ------------------------------------------------------------
void scheduled_actor::ref_coordinator() const noexcept {
intrusive_ptr_add_ref(ctrl());
}
void scheduled_actor::deref_coordinator() const noexcept {
intrusive_ptr_release(ctrl());
}
void scheduled_actor::schedule(action what) {
enqueue(nullptr, make_message_id(), make_message(std::move(what)), nullptr);
}
void scheduled_actor::post_internally(action what) {
actions_.emplace_back(std::move(what));
}
// -- message processing ------------------------------------------------------- // -- message processing -------------------------------------------------------
void scheduled_actor::add_awaited_response_handler(message_id response_id, void scheduled_actor::add_awaited_response_handler(message_id response_id,
...@@ -868,8 +902,9 @@ bool scheduled_actor::finalize() { ...@@ -868,8 +902,9 @@ bool scheduled_actor::finalize() {
++i; ++i;
} }
} }
// An actor is considered alive as long as it has a behavior and didn't set // An actor is considered alive as long as it has a behavior, didn't set
// the terminated flag. // the terminated flag and has no watched flows remaining.
run_actions();
if (alive()) if (alive())
return false; return false;
CAF_LOG_DEBUG("actor has no behavior and is ready for cleanup"); CAF_LOG_DEBUG("actor has no behavior and is ready for cleanup");
...@@ -1187,4 +1222,33 @@ std::vector<stream_manager*> scheduled_actor::active_stream_managers() { ...@@ -1187,4 +1222,33 @@ std::vector<stream_manager*> scheduled_actor::active_stream_managers() {
return result; return result;
} }
// -- scheduling of caf::flow events -------------------------------------------
void scheduled_actor::watch(disposable obj) {
CAF_ASSERT(obj.valid());
watched_disposables_.emplace_back(std::move(obj));
CAF_LOG_DEBUG("now watching" << watched_disposables_.size() << "disposables");
}
void scheduled_actor::run_actions() {
if (!actions_.empty()) {
// Note: can't use iterators here since actions may add to the vector.
for (auto index = size_t{0}; index < actions_.size(); ++index) {
auto f = std::move(actions_[index]);
f.run();
}
actions_.clear();
}
update_watched_disposables();
}
void scheduled_actor::update_watched_disposables() {
auto disposed = [](auto& hdl) { return hdl.disposed(); };
auto& xs = watched_disposables_;
if (auto e = std::remove_if(xs.begin(), xs.end(), disposed); e != xs.end()) {
xs.erase(e, xs.end());
CAF_LOG_DEBUG("now watching" << xs.size() << "disposables");
}
}
} // namespace caf } // namespace caf
// clang-format off
// DO NOT EDIT: this file is auto-generated by caf-generate-enum-strings.
// Run the target update-enum-strings if this file is out of sync.
#include "caf/config.hpp"
#include "caf/string_view.hpp"
CAF_PUSH_DEPRECATED_WARNING
#include "caf/sec.hpp"
#include <string>
namespace caf {
std::string to_string(sec x) {
switch(x) {
default:
return "???";
case sec::none:
return "caf::sec::none";
case sec::unexpected_message:
return "caf::sec::unexpected_message";
case sec::unexpected_response:
return "caf::sec::unexpected_response";
case sec::request_receiver_down:
return "caf::sec::request_receiver_down";
case sec::request_timeout:
return "caf::sec::request_timeout";
case sec::no_such_group_module:
return "caf::sec::no_such_group_module";
case sec::no_actor_published_at_port:
return "caf::sec::no_actor_published_at_port";
case sec::unexpected_actor_messaging_interface:
return "caf::sec::unexpected_actor_messaging_interface";
case sec::state_not_serializable:
return "caf::sec::state_not_serializable";
case sec::unsupported_sys_key:
return "caf::sec::unsupported_sys_key";
case sec::unsupported_sys_message:
return "caf::sec::unsupported_sys_message";
case sec::disconnect_during_handshake:
return "caf::sec::disconnect_during_handshake";
case sec::cannot_forward_to_invalid_actor:
return "caf::sec::cannot_forward_to_invalid_actor";
case sec::no_route_to_receiving_node:
return "caf::sec::no_route_to_receiving_node";
case sec::failed_to_assign_scribe_from_handle:
return "caf::sec::failed_to_assign_scribe_from_handle";
case sec::failed_to_assign_doorman_from_handle:
return "caf::sec::failed_to_assign_doorman_from_handle";
case sec::cannot_close_invalid_port:
return "caf::sec::cannot_close_invalid_port";
case sec::cannot_connect_to_node:
return "caf::sec::cannot_connect_to_node";
case sec::cannot_open_port:
return "caf::sec::cannot_open_port";
case sec::network_syscall_failed:
return "caf::sec::network_syscall_failed";
case sec::invalid_argument:
return "caf::sec::invalid_argument";
case sec::invalid_protocol_family:
return "caf::sec::invalid_protocol_family";
case sec::cannot_publish_invalid_actor:
return "caf::sec::cannot_publish_invalid_actor";
case sec::cannot_spawn_actor_from_arguments:
return "caf::sec::cannot_spawn_actor_from_arguments";
case sec::end_of_stream:
return "caf::sec::end_of_stream";
case sec::no_context:
return "caf::sec::no_context";
case sec::unknown_type:
return "caf::sec::unknown_type";
case sec::no_proxy_registry:
return "caf::sec::no_proxy_registry";
case sec::runtime_error:
return "caf::sec::runtime_error";
case sec::remote_linking_failed:
return "caf::sec::remote_linking_failed";
case sec::cannot_add_upstream:
return "caf::sec::cannot_add_upstream";
case sec::upstream_already_exists:
return "caf::sec::upstream_already_exists";
case sec::invalid_upstream:
return "caf::sec::invalid_upstream";
case sec::cannot_add_downstream:
return "caf::sec::cannot_add_downstream";
case sec::downstream_already_exists:
return "caf::sec::downstream_already_exists";
case sec::invalid_downstream:
return "caf::sec::invalid_downstream";
case sec::no_downstream_stages_defined:
return "caf::sec::no_downstream_stages_defined";
case sec::stream_init_failed:
return "caf::sec::stream_init_failed";
case sec::invalid_stream_state:
return "caf::sec::invalid_stream_state";
case sec::unhandled_stream_error:
return "caf::sec::unhandled_stream_error";
case sec::bad_function_call:
return "caf::sec::bad_function_call";
case sec::feature_disabled:
return "caf::sec::feature_disabled";
case sec::cannot_open_file:
return "caf::sec::cannot_open_file";
case sec::socket_invalid:
return "caf::sec::socket_invalid";
case sec::socket_disconnected:
return "caf::sec::socket_disconnected";
case sec::socket_operation_failed:
return "caf::sec::socket_operation_failed";
case sec::unavailable_or_would_block:
return "caf::sec::unavailable_or_would_block";
case sec::incompatible_versions:
return "caf::sec::incompatible_versions";
case sec::incompatible_application_ids:
return "caf::sec::incompatible_application_ids";
case sec::malformed_basp_message:
return "caf::sec::malformed_basp_message";
case sec::serializing_basp_payload_failed:
return "caf::sec::serializing_basp_payload_failed";
case sec::redundant_connection:
return "caf::sec::redundant_connection";
case sec::remote_lookup_failed:
return "caf::sec::remote_lookup_failed";
case sec::no_tracing_context:
return "caf::sec::no_tracing_context";
case sec::all_requests_failed:
return "caf::sec::all_requests_failed";
case sec::field_invariant_check_failed:
return "caf::sec::field_invariant_check_failed";
case sec::field_value_synchronization_failed:
return "caf::sec::field_value_synchronization_failed";
case sec::invalid_field_type:
return "caf::sec::invalid_field_type";
case sec::unsafe_type:
return "caf::sec::unsafe_type";
case sec::save_callback_failed:
return "caf::sec::save_callback_failed";
case sec::load_callback_failed:
return "caf::sec::load_callback_failed";
case sec::conversion_failed:
return "caf::sec::conversion_failed";
case sec::connection_closed:
return "caf::sec::connection_closed";
case sec::type_clash:
return "caf::sec::type_clash";
case sec::unsupported_operation:
return "caf::sec::unsupported_operation";
case sec::no_such_key:
return "caf::sec::no_such_key";
case sec::broken_promise:
return "caf::sec::broken_promise";
case sec::connection_timeout:
return "caf::sec::connection_timeout";
case sec::invalid_observable:
return "caf::sec::invalid_observable";
case sec::discarded:
return "caf::sec::discarded";
};
}
bool from_string(string_view in, sec& out) {
if (in == "caf::sec::none") {
out = sec::none;
return true;
} else if (in == "caf::sec::unexpected_message") {
out = sec::unexpected_message;
return true;
} else if (in == "caf::sec::unexpected_response") {
out = sec::unexpected_response;
return true;
} else if (in == "caf::sec::request_receiver_down") {
out = sec::request_receiver_down;
return true;
} else if (in == "caf::sec::request_timeout") {
out = sec::request_timeout;
return true;
} else if (in == "caf::sec::no_such_group_module") {
out = sec::no_such_group_module;
return true;
} else if (in == "caf::sec::no_actor_published_at_port") {
out = sec::no_actor_published_at_port;
return true;
} else if (in == "caf::sec::unexpected_actor_messaging_interface") {
out = sec::unexpected_actor_messaging_interface;
return true;
} else if (in == "caf::sec::state_not_serializable") {
out = sec::state_not_serializable;
return true;
} else if (in == "caf::sec::unsupported_sys_key") {
out = sec::unsupported_sys_key;
return true;
} else if (in == "caf::sec::unsupported_sys_message") {
out = sec::unsupported_sys_message;
return true;
} else if (in == "caf::sec::disconnect_during_handshake") {
out = sec::disconnect_during_handshake;
return true;
} else if (in == "caf::sec::cannot_forward_to_invalid_actor") {
out = sec::cannot_forward_to_invalid_actor;
return true;
} else if (in == "caf::sec::no_route_to_receiving_node") {
out = sec::no_route_to_receiving_node;
return true;
} else if (in == "caf::sec::failed_to_assign_scribe_from_handle") {
out = sec::failed_to_assign_scribe_from_handle;
return true;
} else if (in == "caf::sec::failed_to_assign_doorman_from_handle") {
out = sec::failed_to_assign_doorman_from_handle;
return true;
} else if (in == "caf::sec::cannot_close_invalid_port") {
out = sec::cannot_close_invalid_port;
return true;
} else if (in == "caf::sec::cannot_connect_to_node") {
out = sec::cannot_connect_to_node;
return true;
} else if (in == "caf::sec::cannot_open_port") {
out = sec::cannot_open_port;
return true;
} else if (in == "caf::sec::network_syscall_failed") {
out = sec::network_syscall_failed;
return true;
} else if (in == "caf::sec::invalid_argument") {
out = sec::invalid_argument;
return true;
} else if (in == "caf::sec::invalid_protocol_family") {
out = sec::invalid_protocol_family;
return true;
} else if (in == "caf::sec::cannot_publish_invalid_actor") {
out = sec::cannot_publish_invalid_actor;
return true;
} else if (in == "caf::sec::cannot_spawn_actor_from_arguments") {
out = sec::cannot_spawn_actor_from_arguments;
return true;
} else if (in == "caf::sec::end_of_stream") {
out = sec::end_of_stream;
return true;
} else if (in == "caf::sec::no_context") {
out = sec::no_context;
return true;
} else if (in == "caf::sec::unknown_type") {
out = sec::unknown_type;
return true;
} else if (in == "caf::sec::no_proxy_registry") {
out = sec::no_proxy_registry;
return true;
} else if (in == "caf::sec::runtime_error") {
out = sec::runtime_error;
return true;
} else if (in == "caf::sec::remote_linking_failed") {
out = sec::remote_linking_failed;
return true;
} else if (in == "caf::sec::cannot_add_upstream") {
out = sec::cannot_add_upstream;
return true;
} else if (in == "caf::sec::upstream_already_exists") {
out = sec::upstream_already_exists;
return true;
} else if (in == "caf::sec::invalid_upstream") {
out = sec::invalid_upstream;
return true;
} else if (in == "caf::sec::cannot_add_downstream") {
out = sec::cannot_add_downstream;
return true;
} else if (in == "caf::sec::downstream_already_exists") {
out = sec::downstream_already_exists;
return true;
} else if (in == "caf::sec::invalid_downstream") {
out = sec::invalid_downstream;
return true;
} else if (in == "caf::sec::no_downstream_stages_defined") {
out = sec::no_downstream_stages_defined;
return true;
} else if (in == "caf::sec::stream_init_failed") {
out = sec::stream_init_failed;
return true;
} else if (in == "caf::sec::invalid_stream_state") {
out = sec::invalid_stream_state;
return true;
} else if (in == "caf::sec::unhandled_stream_error") {
out = sec::unhandled_stream_error;
return true;
} else if (in == "caf::sec::bad_function_call") {
out = sec::bad_function_call;
return true;
} else if (in == "caf::sec::feature_disabled") {
out = sec::feature_disabled;
return true;
} else if (in == "caf::sec::cannot_open_file") {
out = sec::cannot_open_file;
return true;
} else if (in == "caf::sec::socket_invalid") {
out = sec::socket_invalid;
return true;
} else if (in == "caf::sec::socket_disconnected") {
out = sec::socket_disconnected;
return true;
} else if (in == "caf::sec::socket_operation_failed") {
out = sec::socket_operation_failed;
return true;
} else if (in == "caf::sec::unavailable_or_would_block") {
out = sec::unavailable_or_would_block;
return true;
} else if (in == "caf::sec::incompatible_versions") {
out = sec::incompatible_versions;
return true;
} else if (in == "caf::sec::incompatible_application_ids") {
out = sec::incompatible_application_ids;
return true;
} else if (in == "caf::sec::malformed_basp_message") {
out = sec::malformed_basp_message;
return true;
} else if (in == "caf::sec::serializing_basp_payload_failed") {
out = sec::serializing_basp_payload_failed;
return true;
} else if (in == "caf::sec::redundant_connection") {
out = sec::redundant_connection;
return true;
} else if (in == "caf::sec::remote_lookup_failed") {
out = sec::remote_lookup_failed;
return true;
} else if (in == "caf::sec::no_tracing_context") {
out = sec::no_tracing_context;
return true;
} else if (in == "caf::sec::all_requests_failed") {
out = sec::all_requests_failed;
return true;
} else if (in == "caf::sec::field_invariant_check_failed") {
out = sec::field_invariant_check_failed;
return true;
} else if (in == "caf::sec::field_value_synchronization_failed") {
out = sec::field_value_synchronization_failed;
return true;
} else if (in == "caf::sec::invalid_field_type") {
out = sec::invalid_field_type;
return true;
} else if (in == "caf::sec::unsafe_type") {
out = sec::unsafe_type;
return true;
} else if (in == "caf::sec::save_callback_failed") {
out = sec::save_callback_failed;
return true;
} else if (in == "caf::sec::load_callback_failed") {
out = sec::load_callback_failed;
return true;
} else if (in == "caf::sec::conversion_failed") {
out = sec::conversion_failed;
return true;
} else if (in == "caf::sec::connection_closed") {
out = sec::connection_closed;
return true;
} else if (in == "caf::sec::type_clash") {
out = sec::type_clash;
return true;
} else if (in == "caf::sec::unsupported_operation") {
out = sec::unsupported_operation;
return true;
} else if (in == "caf::sec::no_such_key") {
out = sec::no_such_key;
return true;
} else if (in == "caf::sec::broken_promise") {
out = sec::broken_promise;
return true;
} else if (in == "caf::sec::connection_timeout") {
out = sec::connection_timeout;
return true;
} else if (in == "caf::sec::invalid_observable") {
out = sec::invalid_observable;
return true;
} else if (in == "caf::sec::discarded") {
out = sec::discarded;
return true;
} else {
return false;
}
}
bool from_integer(std::underlying_type_t<sec> in,
sec& out) {
auto result = static_cast<sec>(in);
switch(result) {
default:
return false;
case sec::none:
case sec::unexpected_message:
case sec::unexpected_response:
case sec::request_receiver_down:
case sec::request_timeout:
case sec::no_such_group_module:
case sec::no_actor_published_at_port:
case sec::unexpected_actor_messaging_interface:
case sec::state_not_serializable:
case sec::unsupported_sys_key:
case sec::unsupported_sys_message:
case sec::disconnect_during_handshake:
case sec::cannot_forward_to_invalid_actor:
case sec::no_route_to_receiving_node:
case sec::failed_to_assign_scribe_from_handle:
case sec::failed_to_assign_doorman_from_handle:
case sec::cannot_close_invalid_port:
case sec::cannot_connect_to_node:
case sec::cannot_open_port:
case sec::network_syscall_failed:
case sec::invalid_argument:
case sec::invalid_protocol_family:
case sec::cannot_publish_invalid_actor:
case sec::cannot_spawn_actor_from_arguments:
case sec::end_of_stream:
case sec::no_context:
case sec::unknown_type:
case sec::no_proxy_registry:
case sec::runtime_error:
case sec::remote_linking_failed:
case sec::cannot_add_upstream:
case sec::upstream_already_exists:
case sec::invalid_upstream:
case sec::cannot_add_downstream:
case sec::downstream_already_exists:
case sec::invalid_downstream:
case sec::no_downstream_stages_defined:
case sec::stream_init_failed:
case sec::invalid_stream_state:
case sec::unhandled_stream_error:
case sec::bad_function_call:
case sec::feature_disabled:
case sec::cannot_open_file:
case sec::socket_invalid:
case sec::socket_disconnected:
case sec::socket_operation_failed:
case sec::unavailable_or_would_block:
case sec::incompatible_versions:
case sec::incompatible_application_ids:
case sec::malformed_basp_message:
case sec::serializing_basp_payload_failed:
case sec::redundant_connection:
case sec::remote_lookup_failed:
case sec::no_tracing_context:
case sec::all_requests_failed:
case sec::field_invariant_check_failed:
case sec::field_value_synchronization_failed:
case sec::invalid_field_type:
case sec::unsafe_type:
case sec::save_callback_failed:
case sec::load_callback_failed:
case sec::conversion_failed:
case sec::connection_closed:
case sec::type_clash:
case sec::unsupported_operation:
case sec::no_such_key:
case sec::broken_promise:
case sec::connection_timeout:
case sec::invalid_observable:
case sec::discarded:
out = result;
return true;
};
}
} // namespace caf
CAF_POP_WARNINGS
// 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_for_each
#include "caf/async/publisher.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/observable.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.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
}
};
using ctx_impl = event_based_actor;
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("blocking_for_each iterates all values in a stream") {
GIVEN("an asynchronous source") {
WHEN("subscribing to its output via blocking_for_each") {
THEN("the observer blocks until it has received all values") {
auto inputs = std::vector<int>(2539, 42);
auto outputs = std::vector<int>{};
async::publisher_from<ctx_impl>(sys, [](auto* self) {
return self->make_observable().repeat(42).take(2539);
}).blocking_for_each([&outputs](int x) { outputs.emplace_back(x); });
CHECK_EQ(inputs, outputs);
}
}
}
}
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.bounded_buffer
#include "caf/async/bounded_buffer.hpp"
#include "core-test.hpp"
#include <memory>
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
BEGIN_FIXTURE_SCOPE(test_coordinator_fixture<>)
SCENARIO("bounded buffers moves data between actors") {
GIVEN("a bounded buffer resource") {
WHEN("opening the resource from two actors") {
THEN("data travels through the bounded buffer") {
using actor_t = event_based_actor;
auto [rd, wr] = async::make_bounded_buffer_resource<int>(6, 2);
auto inputs = std::vector<int>{1, 2, 4, 8, 16, 32, 64, 128};
auto outputs = std::vector<int>{};
sys.spawn([wr{wr}, &inputs](actor_t* src) {
src->make_observable()
.from_container(inputs)
.filter([](int) { return true; })
.subscribe(wr);
});
sys.spawn([rd{rd}, &outputs](actor_t* snk) {
snk
->make_observable() //
.from_resource(rd)
.for_each([&outputs](int x) { outputs.emplace_back(x); });
});
run();
CHECK_EQ(inputs, outputs);
}
}
}
}
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()
...@@ -164,8 +164,7 @@ SCENARIO("tunnels dispatch published messages") { ...@@ -164,8 +164,7 @@ SCENARIO("tunnels dispatch published messages") {
} }
WHEN("an actors sends to the tunnel") { WHEN("an actors sends to the tunnel") {
self->send(proxy, put_atom_v, 42); self->send(proxy, put_atom_v, 42);
THEN("the message travels to the origin") THEN("the message travels to the origin and eventually to subscribers") {
AND("tunnel subscribers get the forwarded message eventually") {
expect((sys_atom, forward_atom, message), from(self).to(worker)); expect((sys_atom, forward_atom, message), from(self).to(worker));
expect((forward_atom, message), from(self).to(intermediary)); expect((forward_atom, message), from(self).to(intermediary));
expect((put_atom, int), from(self).to(t1).with(_, 42)); expect((put_atom, int), from(self).to(t1).with(_, 42));
......
// 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 flow.concat
#include "caf/flow/concat.hpp"
#include "core-test.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using namespace caf;
namespace {
struct fixture : test_coordinator_fixture<> {
flow::scoped_coordinator_ptr ctx = flow::make_scoped_coordinator();
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("concat operators combine inputs") {
GIVEN("two observables") {
WHEN("merging them to a single publisher via concat") {
THEN("the observer receives the output of both sources in order") {
auto outputs = std::vector<int>{};
auto r1 = ctx->make_observable().repeat(11).take(113);
auto r2 = ctx->make_observable().repeat(22).take(223);
flow::concat(std::move(r1), std::move(r2)).for_each([&outputs](int x) {
outputs.emplace_back(x);
});
ctx->run();
if (CHECK_EQ(outputs.size(), 336u)) {
CHECK(std::all_of(outputs.begin(), outputs.begin() + 113,
[](int x) { return x == 11; }));
CHECK(std::all_of(outputs.begin() + 113, outputs.end(),
[](int x) { return x == 22; }));
}
}
}
}
}
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 flow.concat_map
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
struct adder_state {
static inline const char* name = "adder";
explicit adder_state(int32_t x) : x(x) {
// nop
}
caf::behavior make_behavior() {
return {
[this](int32_t y) { return x + y; },
};
}
int32_t x;
};
using adder_actor = stateful_actor<adder_state>;
struct fixture : test_coordinator_fixture<> {
flow::scoped_coordinator_ptr ctx = flow::make_scoped_coordinator();
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("concat_map merges multiple observables") {
using i32_list = std::vector<int32_t>;
GIVEN("a generation that emits lists") {
WHEN("lifting each list to an observable with concat_map") {
THEN("the observer receives values from all observables one by one") {
auto outputs = i32_list{};
auto inputs = std::vector<i32_list>{
i32_list{1},
i32_list{2, 2},
i32_list{3, 3, 3},
};
ctx->make_observable()
.from_container(inputs)
.concat_map([this](const i32_list& x) {
return ctx->make_observable().from_container(x);
})
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
ctx->run();
auto expected_outputs = i32_list{1, 2, 2, 3, 3, 3};
CHECK_EQ(outputs, expected_outputs);
}
}
}
GIVEN("a generation that emits 10 integers") {
WHEN("sending a request for each each integer") {
THEN("concat_map merges the responses one by one") {
auto outputs = i32_list{};
auto adder = sys.spawn<adder_actor>(1);
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
auto inputs = i32_list(10);
std::iota(inputs.begin(), inputs.end(), 0);
self->make_observable()
.from_container(inputs)
.concat_map([self{self}, add1{adder}](int32_t x) {
return self->request(add1, infinite, x).as_observable<int32_t>();
})
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
launch();
run();
auto expected_outputs = i32_list{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
CHECK_EQ(outputs, expected_outputs);
}
}
}
}
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 flow.flat_map
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
struct adder_state {
static inline const char* name = "adder";
explicit adder_state(int32_t x) : x(x) {
// nop
}
caf::behavior make_behavior() {
return {
[this](int32_t y) { return x + y; },
};
}
int32_t x;
};
using adder_actor = stateful_actor<adder_state>;
struct fixture : test_coordinator_fixture<> {
flow::scoped_coordinator_ptr ctx = flow::make_scoped_coordinator();
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("flat_map merges multiple observables") {
using i32_list = std::vector<int32_t>;
GIVEN("a generation that emits lists") {
WHEN("lifting each list to an observable with flat_map") {
THEN("the observer receives values from all observables") {
auto outputs = i32_list{};
auto inputs = std::vector<i32_list>{
i32_list{1},
i32_list{2, 2},
i32_list{3, 3, 3},
};
ctx->make_observable()
.from_container(inputs)
.flat_map([this](const i32_list& x) {
return ctx->make_observable().from_container(x);
})
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
ctx->run();
std::sort(outputs.begin(), outputs.end());
auto expected_outputs = i32_list{1, 2, 2, 3, 3, 3};
CHECK_EQ(outputs, expected_outputs);
}
}
}
GIVEN("a generation that emits 10 integers") {
WHEN("sending a request for each each integer") {
THEN("flat_map merges the responses") {
auto outputs = i32_list{};
auto inputs = i32_list(10);
std::iota(inputs.begin(), inputs.end(), 0);
auto adder = sys.spawn<adder_actor>(1);
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->make_observable()
.from_container(inputs)
.flat_map([self{self}, adder](int32_t x) {
return self->request(adder, infinite, x).as_observable<int32_t>();
})
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
launch();
run();
std::sort(outputs.begin(), outputs.end());
auto expected_outputs = i32_list{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
CHECK_EQ(outputs, expected_outputs);
}
}
}
}
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 flow.for_each
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using namespace caf;
namespace {
struct fixture : test_coordinator_fixture<> {
flow::scoped_coordinator_ptr ctx = flow::make_scoped_coordinator();
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("for_each iterates all values in a stream") {
GIVEN("a generation") {
WHEN("subscribing to its output via for_each") {
THEN("the observer receives all values") {
/* subtest */ {
auto inputs = std::vector<int>{1, 2, 4, 8, 16, 32, 64, 128};
auto outputs = std::vector<int>{};
ctx->make_observable()
.from_container(inputs) //
.filter([](int) { return true; })
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(inputs, outputs);
}
/* subtest */ {
auto inputs = std::vector<int>{21, 21, 21, 21, 21, 21, 21};
auto outputs = std::vector<int>{};
ctx->make_observable()
.repeat(7) //
.take(7)
.map([](int x) { return x * 3; })
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(inputs, outputs);
}
}
}
}
GIVEN("a transformation") {
WHEN("subscribing to its output via for_each") {
THEN("the observer receives all values") {
/* subtest */ {
auto inputs = std::vector<int>{1, 2, 4, 8, 16, 32, 64, 128};
auto outputs = std::vector<int>{};
ctx->make_observable()
.from_container(inputs) //
.as_observable()
.filter([](int) { return true; })
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(inputs, outputs);
}
/* subtest */ {
auto inputs = std::vector<int>{21, 21, 21, 21, 21, 21, 21};
auto outputs = std::vector<int>{};
ctx->make_observable()
.repeat(7) //
.as_observable()
.take(7)
.map([](int x) { return x * 3; })
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(inputs, outputs);
}
}
}
}
GIVEN("an observable") {
WHEN("subscribing to its output via for_each") {
THEN("the observer receives all values") {
/* subtest */ {
auto inputs = std::vector<int>{1, 2, 4, 8, 16, 32, 64, 128};
auto outputs = std::vector<int>{};
ctx->make_observable()
.from_container(inputs) //
.filter([](int) { return true; })
.as_observable()
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(inputs, outputs);
}
/* subtest */ {
auto inputs = std::vector<int>{21, 21, 21, 21, 21, 21, 21};
auto outputs = std::vector<int>{};
ctx->make_observable()
.repeat(7) //
.take(7)
.map([](int x) { return x * 3; })
.as_observable()
.for_each([&outputs](int x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(inputs, outputs);
}
}
}
}
}
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 flow.merge
#include "caf/flow/merge.hpp"
#include "core-test.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using namespace caf;
namespace {
struct fixture : test_coordinator_fixture<> {
flow::scoped_coordinator_ptr ctx = flow::make_scoped_coordinator();
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("merge operators combine inputs") {
GIVEN("two observables") {
WHEN("merging them to a single publisher") {
THEN("the observer receives the output of both sources") {
auto outputs = std::vector<int>{};
auto r1 = ctx->make_observable().repeat(11).take(113);
auto r2 = ctx->make_observable().repeat(22).take(223);
flow::merge(std::move(r1), std::move(r2)).for_each([&outputs](int x) {
outputs.emplace_back(x);
});
ctx->run();
if (CHECK_EQ(outputs.size(), 336u)) {
std::sort(outputs.begin(), outputs.end());
CHECK(std::all_of(outputs.begin(), outputs.begin() + 113,
[](int x) { return x == 11; }));
CHECK(std::all_of(outputs.begin() + 113, outputs.end(),
[](int x) { return x == 22; }));
}
}
}
}
}
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 flow.observe_on
#include "caf/flow/observable.hpp"
#include "core-test.hpp"
#include <memory>
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observer.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
BEGIN_FIXTURE_SCOPE(test_coordinator_fixture<>)
SCENARIO("observe_on moves data between actors") {
GIVEN("a generation") {
WHEN("calling observe_on") {
THEN("the target actor observes all values") {
using actor_t = event_based_actor;
auto inputs = std::vector<int>{1, 2, 4, 8, 16, 32, 64, 128};
auto outputs = std::vector<int>{};
auto [src, launch_src] = sys.spawn_inactive<actor_t>();
auto [snk, launch_snk] = sys.spawn_inactive<actor_t>();
src->make_observable()
.from_container(inputs)
.filter([](int) { return true; })
.observe_on(snk)
.for_each([&outputs](int x) { outputs.emplace_back(x); });
launch_src();
launch_snk();
run();
CHECK_EQ(inputs, outputs);
}
}
}
}
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 flow.single
#include "caf/flow/single.hpp"
#include "core-test.hpp"
#include "caf/flow/scoped_coordinator.hpp"
using namespace caf;
namespace {
struct fixture : test_coordinator_fixture<> {
flow::scoped_coordinator_ptr ctx = flow::make_scoped_coordinator();
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(single_tests, fixture)
SCENARIO("singles emit at most one value") {
using i32_list = std::vector<int32_t>;
GIVEN("a single<int32>") {
WHEN("an observer subscribes before the values has been set") {
THEN("the observer receives the value when calling set_value") {
auto outputs = i32_list{};
auto single_int = flow::make_single<int32_t>(ctx.get());
single_int //
.as_observable()
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(outputs, i32_list());
single_int.set_value(42);
CHECK_EQ(outputs, i32_list({42}));
}
}
WHEN("an observer subscribes after the values has been set") {
THEN("the observer receives the value immediately") {
auto outputs = i32_list{};
auto single_int = flow::make_single<int32_t>(ctx.get());
single_int.set_value(42);
single_int //
.as_observable()
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
ctx->run();
CHECK_EQ(outputs, i32_list({42}));
}
}
}
}
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 response_handle
#include "caf/response_handle.hpp"
#include "core-test.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
struct dummy_state {
static inline const char* name = "dummy";
caf::behavior make_behavior() {
return {
[](int32_t x) -> result<int32_t> {
if (x % 2 == 0)
return x + x;
else
return make_error(sec::invalid_argument);
},
};
}
};
using dummy_actor = stateful_actor<dummy_state>;
struct fixture : test_coordinator_fixture<> {
actor dummy;
actor aut;
fixture() {
dummy = sys.spawn<dummy_actor>();
sched.run();
}
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("response handles are convertible to observables and singles") {
GIVEN("a response handle that produces a valid result") {
WHEN("calling as_single") {
THEN("observers see the result") {
using result_t = std::variant<none_t, int32_t, caf::error>;
result_t result;
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->request(dummy, infinite, int32_t{42})
.as_single<int32_t>()
.subscribe([&result](int32_t val) { result = val; },
[&result](const error& what) { result = what; });
launch();
expect((int32_t), from(aut).to(dummy).with(42));
expect((int32_t), from(dummy).to(aut).with(84));
CHECK(!sched.has_job());
CHECK_EQ(result, result_t{84});
}
}
WHEN("calling as_observable") {
THEN("observers see the result") {
using result_t = std::variant<none_t, int32_t, caf::error>;
size_t on_next_calls = 0;
bool completed = false;
result_t result;
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->request(dummy, infinite, int32_t{42})
.as_observable<int32_t>()
.for_each(
[&](int32_t val) {
result = val;
++on_next_calls;
},
[&](const error& what) { result = what; },
[&] { completed = true; });
launch();
expect((int32_t), from(aut).to(dummy).with(42));
expect((int32_t), from(dummy).to(aut).with(84));
CHECK(!sched.has_job());
CHECK_EQ(result, result_t{84});
CHECK_EQ(on_next_calls, 1u);
CHECK(completed);
}
}
}
GIVEN("a response handle that produces an error") {
WHEN("calling as_single") {
THEN("observers see an error") {
using result_t = std::variant<none_t, int32_t, caf::error>;
result_t result;
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->request(dummy, infinite, int32_t{13})
.as_single<int32_t>()
.subscribe([&result](int32_t val) { result = val; },
[&result](const error& what) { result = what; });
launch();
expect((int32_t), from(aut).to(dummy).with(13));
expect((error), from(dummy).to(aut));
CHECK(!sched.has_job());
CHECK_EQ(result, result_t{make_error(sec::invalid_argument)});
}
}
WHEN("calling as_observable") {
THEN("observers see an error") {
using result_t = std::variant<none_t, int32_t, caf::error>;
size_t on_next_calls = 0;
bool completed = false;
result_t result;
auto [self, launch] = sys.spawn_inactive<event_based_actor>();
self->request(dummy, infinite, int32_t{13})
.as_observable<int32_t>()
.for_each(
[&](int32_t val) {
result = val;
++on_next_calls;
},
[&](const error& what) { result = what; },
[&] { completed = true; });
launch();
expect((int32_t), from(aut).to(dummy).with(13));
expect((error), from(dummy).to(aut));
CHECK(!sched.has_job());
CHECK_EQ(result, result_t{make_error(sec::invalid_argument)});
CHECK_EQ(on_next_calls, 0u);
CHECK(!completed);
}
}
}
}
END_FIXTURE_SCOPE()
...@@ -20,10 +20,18 @@ ...@@ -20,10 +20,18 @@
CAF_MESSAGE("WHEN " description); \ CAF_MESSAGE("WHEN " description); \
if (true) if (true)
#define AND_WHEN(description) \
CAF_MESSAGE("AND WHEN " description); \
if (true)
#define THEN(description) \ #define THEN(description) \
CAF_MESSAGE("THEN " description); \ CAF_MESSAGE("THEN " description); \
if (true) if (true)
#define AND_THEN(description) \
CAF_MESSAGE("AND THEN " description); \
if (true)
#define AND(description) \ #define AND(description) \
{} \ {} \
CAF_MESSAGE("AND " description); \ CAF_MESSAGE("AND " description); \
......
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