Commit 120926cd authored by Andrew Smith's avatar Andrew Smith Committed by Facebook GitHub Bot

Channels framework

Summary: A channel is a sender and receiver pair that allows one component to send values to another. A sender and receiver pair is similar to an AsyncPipe and AsyncGenerator pair. However, unlike AsyncPipe/AsyncGenerator, senders and receivers can be used by higher level transformation abstractions that are much more memory-efficient than using AsyncGenerator directly. These higher level abstractions do not require long-lived coroutine frames to wait on incoming values.

Reviewed By: aary

Differential Revision: D29158424

fbshipit-source-id: 88c51d0f9d73677a04906197f4c44fe84ac01cdb
parent af0a489d
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/experimental/channels/detail/ChannelBridge.h>
namespace folly {
namespace channels {
template <typename TValue>
class Receiver;
template <typename TValue>
class Sender;
namespace detail {
template <typename TValue>
ChannelBridgePtr<TValue>& senderGetBridge(Sender<TValue>& sender);
template <typename TValue>
bool receiverWait(
Receiver<TValue>& receiver, detail::IChannelCallback* receiverCallback);
template <typename TValue>
std::optional<folly::Try<TValue>> receiverGetValue(Receiver<TValue>& receiver);
template <typename TValue>
std::pair<detail::ChannelBridgePtr<TValue>, detail::ReceiverQueue<TValue>>
receiverUnbuffer(Receiver<TValue>&& receiver);
} // namespace detail
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/CancellationToken.h>
#include <folly/Synchronized.h>
#include <folly/experimental/channels/detail/ChannelBridge.h>
#include <folly/experimental/coro/Coroutine.h>
namespace folly {
namespace channels {
namespace detail {
template <typename TValue>
ChannelBridgePtr<TValue>& senderGetBridge(Sender<TValue>& sender) {
return sender.bridge_;
}
template <typename TValue>
bool receiverWait(
Receiver<TValue>& receiver, detail::IChannelCallback* callback) {
if (!receiver.buffer_.empty()) {
return false;
}
return receiver.bridge_->receiverWait(callback);
}
template <typename TValue>
std::optional<folly::Try<TValue>> receiverGetValue(Receiver<TValue>& receiver) {
if (receiver.buffer_.empty()) {
receiver.buffer_ = receiver.bridge_->receiverGetValues();
if (receiver.buffer_.empty()) {
return std::nullopt;
}
}
auto result = std::move(receiver.buffer_.front());
receiver.buffer_.pop();
return result;
}
template <typename TValue>
std::pair<detail::ChannelBridgePtr<TValue>, detail::ReceiverQueue<TValue>>
receiverUnbuffer(Receiver<TValue>&& receiver) {
return std::make_pair(
std::move(receiver.bridge_), std::move(receiver.buffer_));
}
} // namespace detail
template <typename TValue>
class Receiver<TValue>::Waiter : public detail::IChannelCallback {
public:
Waiter(Receiver<TValue>* receiver, folly::CancellationToken cancelToken)
: state_(State{.receiver = receiver}),
cancelCallback_(makeCancellationCallback(std::move(cancelToken))) {}
bool await_ready() const noexcept {
// We are ready immediately if the receiver is either cancelled or closed.
return state_.withRLock(
[&](const State& state) { return state.cancelled || !state.receiver; });
}
bool await_suspend(folly::coro::coroutine_handle<> awaitingCoroutine) {
return state_.withWLock([&](State& state) {
if (state.cancelled || !state.receiver ||
!receiverWait(*state.receiver, this)) {
// We will not suspend at all if the receiver is either cancelled or
// closed.
return false;
}
state.awaitingCoroutine = awaitingCoroutine;
return true;
});
}
std::optional<TValue> await_resume() {
auto result = getResult();
if (!result.hasValue() && !result.hasException()) {
return std::nullopt;
}
return std::move(result.value());
}
folly::Try<TValue> await_resume_try() { return getResult(); }
protected:
struct State {
Receiver<TValue>* receiver;
folly::coro::coroutine_handle<> awaitingCoroutine;
bool cancelled{false};
};
std::unique_ptr<folly::CancellationCallback> makeCancellationCallback(
folly::CancellationToken cancelToken) {
if (!cancelToken.canBeCancelled()) {
return nullptr;
}
return std::make_unique<folly::CancellationCallback>(
std::move(cancelToken), [this] {
auto receiver = state_.withWLock([&](State& state) {
state.cancelled = true;
return std::exchange(state.receiver, nullptr);
});
if (receiver) {
std::move(*receiver).cancel();
}
});
}
void consume(detail::ChannelBridgeBase*) override { resume(); }
void canceled(detail::ChannelBridgeBase*) override { resume(); }
void resume() {
auto awaitingCoroutine = state_.withWLock([&](State& state) {
return std::exchange(state.awaitingCoroutine, nullptr);
});
awaitingCoroutine.resume();
}
folly::Try<TValue> getResult() {
cancelCallback_.reset();
return state_.withWLock([&](State& state) {
if (state.cancelled) {
return folly::Try<TValue>(
folly::make_exception_wrapper<folly::OperationCancelled>());
}
if (!state.receiver) {
return folly::Try<TValue>();
}
auto result =
std::move(detail::receiverGetValue(*state.receiver).value());
if (!result.hasValue()) {
std::move(*state.receiver).cancel();
state.receiver = nullptr;
}
return result;
});
}
folly::Synchronized<State> state_;
std::unique_ptr<folly::CancellationCallback> cancelCallback_;
};
template <typename TValue>
struct Receiver<TValue>::NextSemiAwaitable {
public:
explicit NextSemiAwaitable(
Receiver<TValue>* receiver,
std::optional<folly::CancellationToken> cancelToken = std::nullopt)
: receiver_(receiver), cancelToken_(std::move(cancelToken)) {}
[[nodiscard]] Waiter operator co_await() {
return Waiter(receiver_, cancelToken_.value_or(folly::CancellationToken()));
}
friend NextSemiAwaitable co_withCancellation(
folly::CancellationToken cancelToken, NextSemiAwaitable&& awaitable) {
if (awaitable.cancelToken_.has_value()) {
return std::move(awaitable);
}
return NextSemiAwaitable(awaitable.receiver_, std::move(cancelToken));
}
private:
Receiver<TValue>* receiver_;
std::optional<folly::CancellationToken> cancelToken_;
};
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/experimental/channels/Channel-fwd.h>
#include <folly/experimental/channels/detail/ChannelBridge.h>
namespace folly {
namespace channels {
/*
* A channel is a sender and receiver pair that allows one component to send
* values to another. A sender and receiver pair is similar to an AsyncPipe and
* AsyncGenerator pair. However, unlike AsyncPipe/AsyncGenerator, senders and
* receivers can be used by memory-efficient higher level transformation
* abstractions.
*
* Typical usage:
* auto [receiver, sender] = Channel<T>::create();
* sender.write(val1);
* auto val2 = co_await receiver.next();
*/
template <typename TValue>
class Channel {
public:
/**
* Creates a new channel with a sender/receiver pair. The channel will be
* closed if the sender is destroyed, and will be cancelled if the receiver is
* destroyed.
*/
static std::pair<Receiver<TValue>, Sender<TValue>> create() {
auto senderBridge = detail::ChannelBridge<TValue>::create();
auto receiverBridge = senderBridge->copy();
return std::make_pair(
Receiver<TValue>(std::move(receiverBridge)),
Sender<TValue>(std::move(senderBridge)));
}
};
/**
* A sender sends values to be consumed by a receiver.
*/
template <typename TValue>
class Sender {
public:
friend Channel<TValue>;
using ValueType = TValue;
Sender(Sender&& other) noexcept : bridge_(std::move(other.bridge_)) {}
Sender& operator=(Sender&& other) noexcept {
if (this == &other) {
return *this;
}
if (bridge_) {
std::move(*this).close();
}
bridge_ = std::move(other.bridge_);
return *this;
}
~Sender() {
if (bridge_) {
std::move(*this).close();
}
}
/**
* Returns whether or not this sender instance is valid. This will return
* false if the sender was closed or moved away.
*/
explicit operator bool() const { return bridge_ != nullptr; }
/**
* Writes a value into the pipe.
*/
template <typename U = TValue>
void write(U&& element) {
if (!bridge_->isSenderClosed()) {
bridge_->senderPush(std::forward<U>(element));
}
}
/**
* Closes the pipe without an exception.
*/
void close() && {
if (!bridge_->isSenderClosed()) {
bridge_->senderClose();
}
bridge_ = nullptr;
}
/**
* Closes the pipe with an exception.
*/
void close(folly::exception_wrapper exception) && {
if (!bridge_->isSenderClosed()) {
bridge_->senderClose(std::move(exception));
}
bridge_ = nullptr;
}
/**
* Returns whether or not the corresponding receiver has been cancelled or
* destroyed.
*/
bool isReceiverCancelled() {
if (bridge_->isSenderClosed()) {
return true;
}
auto values = bridge_->senderGetValues();
if (!values.empty()) {
bridge_->senderClose();
return true;
}
return false;
}
private:
friend detail::ChannelBridgePtr<TValue>& detail::senderGetBridge<>(
Sender<TValue>&);
explicit Sender(detail::ChannelBridgePtr<TValue> bridge)
: bridge_(std::move(bridge)) {}
detail::ChannelBridgePtr<TValue> bridge_;
};
/**
* A receiver that receives values sent by a sender. There are two ways that a
* receiver can be consumed directly:
*
* 1. Call co_await receiver.next() to get the next value. See the docstring of
* next() for more details.
*
* 2. Call consumeChannelWithCallback to get a callback when each value comes
* in. See ConsumeChannel.h for more details. This uses less memory than
* #1, as it only needs to allocate coroutine frames when processing values
* (rather than always having such frames allocated when waiting for values).
*
* A receiver may also be passed to framework primitives that consume the
* receiver (such as transform or merge). Like #2, these primitives do not
* require coroutine frames to be allocated when waiting for values to come in.
*/
template <typename TValue>
class Receiver {
class Waiter;
struct NextSemiAwaitable;
public:
friend Channel<TValue>;
using ValueType = TValue;
Receiver() {}
Receiver(Receiver&& other) noexcept
: bridge_(std::move(other.bridge_)), buffer_(std::move(other.buffer_)) {}
Receiver& operator=(Receiver&& other) noexcept {
if (this == &other) {
return *this;
}
if (bridge_ != nullptr) {
std::move(*this).cancel();
}
bridge_ = std::move(other.bridge_);
buffer_ = std::move(other.buffer_);
return *this;
}
~Receiver() {
if (bridge_ != nullptr) {
std::move(*this).cancel();
}
}
/**
* Returns whether or not this receiver instance is valid. This will return
* false if the receiver was cancelled or moved away.
*/
explicit operator bool() const { return bridge_ != nullptr; }
/**
* Returns the next value sent by a sender. The behavior is the same as the
* behavior of next() on folly::coro::AsyncGenerator<TValue>.
*
* If consumed directly with co_await, next() will return an std::optional:
*
* std::optional<TValue> value = co_await receiver.next();
*
* - If a value is sent, the std::optional will contain the value.
* - If the channel is closed by the sender with no exception, the optional
* will be empty.
* - If the channel is closed by the sender with an exception, next() will
* throw the exception.
* - If the next() call was cancelled, next() will throw an exception of
* type folly::OperationCancelled.
*
* If consumed with folly::coro::co_awaitTry, this will return a folly::Try:
*
* folly::Try<TValue> value = co_await folly::coro::co_awaitTry(
* receiver.next());
*
* - If a value is sent, the folly::Try will contain the value.
* - If the channel is closed by the sender with no exception, the try will
* be empty (with no value or exception).
* - If the channel is closed by the sender with an exception, the try will
* contain the exception.
* - If the next() call was cancelled, the try will contain an exception of
* type folly::OperationCancelled.
*/
NextSemiAwaitable next() { return NextSemiAwaitable(*this ? this : nullptr); }
/**
* Cancels this receiver. If the receiver is currently being consumed, the
* consumer will receive a folly::OperationCancelled exception.
*/
void cancel() && {
bridge_->receiverCancel();
bridge_ = nullptr;
buffer_.clear();
}
private:
explicit Receiver(detail::ChannelBridgePtr<TValue> bridge)
: bridge_(std::move(bridge)) {}
friend bool detail::receiverWait<>(
Receiver<TValue>&, detail::IChannelCallback*);
friend std::optional<folly::Try<TValue>> detail::receiverGetValue<>(
Receiver<TValue>&);
friend std::
pair<detail::ChannelBridgePtr<TValue>, detail::ReceiverQueue<TValue>>
detail::receiverUnbuffer<>(Receiver<TValue>&& receiver);
detail::ChannelBridgePtr<TValue> bridge_;
detail::ReceiverQueue<TValue> buffer_;
};
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/Channel-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/IntrusiveList.h>
#include <folly/ScopeGuard.h>
#include <folly/experimental/channels/Channel.h>
namespace folly {
namespace channels {
namespace detail {
class ChannelCallbackProcessor : public IChannelCallback {
public:
virtual void onHandleDestroyed() = 0;
};
} // namespace detail
/**
* A callback handle for a consumption operation on a channel. The consumption
* operation will be cancelled when this handle is destroyed.
*/
class ChannelCallbackHandle {
public:
ChannelCallbackHandle() : processor_(nullptr) {}
explicit ChannelCallbackHandle(detail::ChannelCallbackProcessor* processor)
: processor_(processor) {}
~ChannelCallbackHandle() {
if (processor_) {
processor_->onHandleDestroyed();
}
}
ChannelCallbackHandle(ChannelCallbackHandle&& other) noexcept
: processor_(std::exchange(other.processor_, nullptr)) {}
ChannelCallbackHandle& operator=(ChannelCallbackHandle&& other) {
if (&other == this) {
return *this;
}
reset();
processor_ = std::exchange(other.processor_, nullptr);
return *this;
}
void reset() {
if (processor_) {
processor_->onHandleDestroyed();
processor_ = nullptr;
}
}
private:
detail::ChannelCallbackProcessor* processor_;
};
namespace detail {
/**
* A wrapper around a ChannelCallbackHandle that belongs to an intrusive linked
* list. When the holder is destroyed, the object will automatically be unlinked
* from the linked list that it is in (if any).
*/
struct ChannelCallbackHandleHolder {
explicit ChannelCallbackHandleHolder(ChannelCallbackHandle _handle)
: handle(std::move(_handle)) {}
ChannelCallbackHandleHolder(ChannelCallbackHandleHolder&& other) noexcept
: handle(std::move(other.handle)) {
hook.swap_nodes(other.hook);
}
ChannelCallbackHandleHolder& operator=(
ChannelCallbackHandleHolder&& other) noexcept {
if (&other == this) {
return *this;
}
handle = std::move(other.handle);
hook.unlink();
hook.swap_nodes(other.hook);
return *this;
}
void requestCancellation() { handle.reset(); }
ChannelCallbackHandle handle;
folly::IntrusiveListHook hook;
};
template <typename TValue, typename OnNextFunc>
class ChannelCallbackProcessorImplWithList;
} // namespace detail
/**
* A list of channel callback handles. When consumeChannelWithCallback is
* invoked with a list, a cancellation handle is automatically added to the list
* for the consumption operation. Similarly, when a consumption operation is
* completed, the handle is automatically removed from the lists.
*
* If the list still has any cancellation handles remaining when the list is
* destroyed, cancellation is triggered for each handle in the list.
*
* This list is not thread safe.
*/
class ChannelCallbackHandleList {
public:
ChannelCallbackHandleList() {}
ChannelCallbackHandleList(ChannelCallbackHandleList&& other) noexcept {
holders_.swap(other.holders_);
}
ChannelCallbackHandleList& operator=(
ChannelCallbackHandleList&& other) noexcept {
if (&other == this) {
return *this;
}
holders_.swap(other.holders_);
return *this;
}
~ChannelCallbackHandleList() { clear(); }
void clear() {
for (auto& holder : holders_) {
holder.requestCancellation();
}
holders_.clear();
}
private:
template <typename TValue, typename OnNextFunc>
friend class detail::ChannelCallbackProcessorImplWithList;
void add(detail::ChannelCallbackHandleHolder& holder) {
holders_.push_back(holder);
}
using ChannelCallbackHandleListImpl = folly::IntrusiveList<
detail::ChannelCallbackHandleHolder,
&detail::ChannelCallbackHandleHolder::hook>;
ChannelCallbackHandleListImpl holders_;
};
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/Executor.h>
#include <folly/Format.h>
#include <folly/IntrusiveList.h>
#include <folly/ScopeGuard.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/ChannelCallbackHandle.h>
#include <folly/experimental/channels/detail/Utility.h>
#include <folly/experimental/coro/Task.h>
namespace folly {
namespace channels {
namespace detail {
template <typename TValue, typename OnNextFunc>
class ChannelCallbackProcessorImpl : public ChannelCallbackProcessor {
public:
ChannelCallbackProcessorImpl(
ChannelBridgePtr<TValue> receiver,
folly::Executor::KeepAlive<> executor,
OnNextFunc onNext)
: receiver_(std::move(receiver)),
executor_(std::move(executor)),
onNext_(std::move(onNext)),
cancelSource_(folly::CancellationSource::invalid()) {}
void start(std::optional<detail::ReceiverQueue<TValue>> buffer) {
runCoroutineWithCancellation(processAllAvailableValues(std::move(buffer)))
.scheduleOn(executor_)
.start();
}
protected:
virtual void onFinishedConsumption() {}
private:
/**
* Called when the handle is destroyed.
*/
void onHandleDestroyed() override {
executor_->add([=]() { processHandleDestroyed(); });
}
/**
* Called when the channel we are listening to has an update.
*/
void consume(ChannelBridgeBase*) override {
runCoroutineWithCancellation(processAllAvailableValues())
.scheduleOn(executor_)
.start();
}
/**
* Called after we cancelled the input channel (which happens after the handle
* is destroyed).
*/
void canceled(ChannelBridgeBase*) override {
runCoroutineWithCancellation(
processReceiverCancelled(true /* fromHandleDestruction */))
.scheduleOn(executor_)
.start();
}
/**
* Processes all available values from the input receiver (starting from the
* provided buffer, if present).
*
* If a value was received indicating that the input channel has been closed,
* we will process cancellation for the input receiver.
*/
folly::coro::Task<void> processAllAvailableValues(
std::optional<ReceiverQueue<TValue>> buffer = std::nullopt) {
bool closed = buffer.has_value()
? !co_await processValues(std::move(buffer.value()))
: false;
while (!closed) {
if (receiver_->receiverWait(this)) {
// There are no more values available right now, but more values may
// come in the future. We will stop processing for now, until we
// re-start processing when the consume() callback is fired.
break;
}
auto values = receiver_->receiverGetValues();
CHECK(!values.empty());
closed = !co_await processValues(std::move(values));
}
if (closed) {
// The input receiver was closed.
receiver_->receiverCancel();
co_await processReceiverCancelled(false /* fromHandleDestruction */);
}
}
/**
* Processes values from the channel. Returns false if the channel has been
* closed, so the caller can stop processing values from it.
*/
folly::coro::Task<bool> processValues(ReceiverQueue<TValue> values) {
auto cancelToken = co_await folly::coro::co_current_cancellation_token;
while (!values.empty()) {
if (cancelToken.isCancellationRequested()) {
co_return true;
}
auto result = std::move(values.front());
values.pop();
bool closed = !result.hasValue();
if (!co_await callCallback(std::move(result))) {
closed = true;
}
if (closed) {
co_return false;
}
co_await folly::coro::co_reschedule_on_current_executor;
}
co_return true;
}
/**
* Process cancellation of the input receiver.
*
* @param fromHandleDestruction: Whether the cancellation was prompted by the
* handle being destroyed. If true, we will call the user's callback with
* a folly::OperationCancelled exception. This will be false if the
* cancellation was prompted by the closure of the channel.
*/
folly::coro::Task<void> processReceiverCancelled(bool fromHandleDestruction) {
CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered);
receiver_ = nullptr;
if (fromHandleDestruction) {
co_await callCallback(folly::Try<TValue>(
folly::make_exception_wrapper<folly::OperationCancelled>()));
}
maybeDelete();
}
/**
* Processes the destruction of the handle.
*/
void processHandleDestroyed() {
CHECK(!handleDestroyed_);
handleDestroyed_ = true;
cancelSource_.requestCancellation();
if (getReceiverState() == ChannelState::Active) {
receiver_->receiverCancel();
}
maybeDelete();
}
/**
* Deletes this object if we have already processed cancellation for the
* receiver and the handle.
*/
void maybeDelete() {
if (getReceiverState() == ChannelState::CancellationProcessed &&
handleDestroyed_) {
delete this;
}
}
/**
* Calls the user's callback with the given result.
*/
folly::coro::Task<bool> callCallback(folly::Try<TValue> result) {
auto retVal = co_await folly::coro::co_awaitTry(onNext_(std::move(result)));
if (retVal.template hasException<folly::OperationCancelled>()) {
co_return false;
} else if (retVal.hasException()) {
LOG(FATAL) << folly::sformat(
"Encountered exception from callback when consuming channel of "
"type {}: {}",
typeid(TValue).name(),
retVal.exception().what());
}
co_return retVal.value();
}
/**
* Runs the given coroutine while listening for cancellation triggered by the
* handle's destruction.
*/
folly::coro::Task<void> runCoroutineWithCancellation(
folly::coro::Task<void> task) {
cancelSource_ = folly::CancellationSource();
if (handleDestroyed_) {
// The handle was already destroyed before we even started the coroutine.
// Request cancellation so that the user's callback knows to stop quickly.
cancelSource_.requestCancellation();
}
auto token = cancelSource_.getToken();
auto retVal = co_await folly::coro::co_awaitTry(
folly::coro::co_withCancellation(token, std::move(task)));
CHECK(!retVal.hasException()) << fmt::format(
"Unexpected exception when running coroutine: {}",
retVal.exception().what());
if (!token.isCancellationRequested()) {
cancelSource_ = folly::CancellationSource::invalid();
}
}
ChannelState getReceiverState() {
return detail::getReceiverState(receiver_.get());
}
ChannelBridgePtr<TValue> receiver_;
folly::Executor::KeepAlive<> executor_;
OnNextFunc onNext_;
folly::CancellationSource cancelSource_;
bool handleDestroyed_{false};
};
} // namespace detail
namespace detail {
template <typename TValue, typename OnNextFunc>
class ChannelCallbackProcessorImplWithList
: public ChannelCallbackProcessorImpl<TValue, OnNextFunc> {
public:
ChannelCallbackProcessorImplWithList(
ChannelBridgePtr<TValue> receiver,
folly::Executor::KeepAlive<> executor,
OnNextFunc onNext,
ChannelCallbackHandleList& holders)
: ChannelCallbackProcessorImpl<TValue, OnNextFunc>(
std::move(receiver), std::move(executor), std::move(onNext)),
holder_(ChannelCallbackHandle(this)) {
holders.add(holder_);
}
private:
void onFinishedConsumption() override {
// In this subclass, we will remove ourselves from the list of handles
// when consumption is complete (triggering cancellation).
std::ignore = std::move(holder_);
}
ChannelCallbackHandleHolder holder_;
};
} // namespace detail
template <
typename TReceiver,
typename OnNextFunc,
typename TValue,
std::enable_if_t<
std::is_constructible_v<
folly::Function<folly::coro::Task<bool>(folly::Try<TValue>)>,
OnNextFunc>,
int>>
ChannelCallbackHandle consumeChannelWithCallback(
TReceiver receiver,
folly::Executor::KeepAlive<> executor,
OnNextFunc onNext) {
detail::ChannelCallbackProcessorImpl<TValue, OnNextFunc>* processor = nullptr;
auto [unbufferedReceiver, buffer] =
detail::receiverUnbuffer(std::move(receiver));
processor = new detail::ChannelCallbackProcessorImpl<TValue, OnNextFunc>(
std::move(unbufferedReceiver), std::move(executor), std::move(onNext));
processor->start(std::move(buffer));
return ChannelCallbackHandle(processor);
}
template <
typename TReceiver,
typename OnNextFunc,
typename TValue,
std::enable_if_t<
std::is_constructible_v<
folly::Function<folly::coro::Task<bool>(folly::Try<TValue>)>,
OnNextFunc>,
int>>
void consumeChannelWithCallback(
TReceiver receiver,
folly::Executor::KeepAlive<> executor,
OnNextFunc onNext,
ChannelCallbackHandleList& callbackHandles) {
auto [unbufferedReceiver, buffer] =
detail::receiverUnbuffer(std::move(receiver));
auto* processor =
new detail::ChannelCallbackProcessorImplWithList<TValue, OnNextFunc>(
std::move(unbufferedReceiver),
std::move(executor),
std::move(onNext),
callbackHandles);
processor->start(std::move(buffer));
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/Executor.h>
#include <folly/IntrusiveList.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/ChannelCallbackHandle.h>
#include <folly/experimental/coro/Task.h>
namespace folly {
namespace channels {
/**
* This function takes a Receiver, and consumes updates from that receiver with
* a callback.
*
* This function returns a ChannelCallbackHandle. On destruction of this handle,
* the callback will receive a try containing an exception of type
* folly::OperationCancelled. If an active callback is running at the time the
* cancellation request is received, cancellation will be requested on the
* ambient cancellation token of the callback.
*
* The callback is run for each received value on the given executor. A try
* is passed to the callback with the result:
*
* - If a value is sent, the folly::Try will contain the value.
* - If the channel is closed by the sender with no exception, the try will
* be empty (with no value or exception).
* - If the channel is closed by the sender with an exception, the try will
* contain the exception.
* - If the channel was cancelled (by the destruction of the returned
* handle), the try will contain an exception of type
* folly::OperationCancelled.
*
* If the callback returns false or throws a folly::OperationCancelled
* exception, the channel will be cancelled and no further values will be
* received.
*/
template <
typename TReceiver,
typename OnNextFunc,
typename TValue = typename TReceiver::ValueType,
std::enable_if_t<
std::is_constructible_v<
folly::Function<folly::coro::Task<bool>(folly::Try<TValue>)>,
OnNextFunc>,
int> = 0>
ChannelCallbackHandle consumeChannelWithCallback(
TReceiver receiver,
folly::Executor::KeepAlive<> executor,
OnNextFunc onNext);
/**
* This overload is similar to the previous overload. However, unlike the
* previous overload (which returns a handle that allows cancellation of that
* specific consumption operation), this overload accepts a list of handles and
* returns void. This overload will immediately add a handle to the list, and
* will remove itself from the list if the channel is closed or cancelled.
*
* When the passed-in handle list is destroyed by the caller, all handles
* remaining in the list will trigger cancellation on their corresponding
* consumption operations.
*
* Note that ChannelCallbackHandleList is not thread safe. This means that all
* operations using a particular list (including start and cancellation) need to
* run on the same serial executor passed to this function.
*/
template <
typename TReceiver,
typename OnNextFunc,
typename TValue = typename TReceiver::ValueType,
std::enable_if_t<
std::is_constructible_v<
folly::Function<folly::coro::Task<bool>(folly::Try<TValue>)>,
OnNextFunc>,
int> = 0>
void consumeChannelWithCallback(
TReceiver receiver,
folly::Executor::KeepAlive<> executor,
OnNextFunc onNext,
ChannelCallbackHandleList& callbackHandles);
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/ConsumeChannel-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/container/F14Set.h>
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/detail/Utility.h>
namespace folly {
namespace channels {
template <typename TValue>
FanoutChannel<TValue>::FanoutChannel(TProcessor* processor)
: processor_(processor) {}
template <typename TValue>
FanoutChannel<TValue>::FanoutChannel(FanoutChannel&& other) noexcept
: processor_(std::exchange(other.processor_, nullptr)) {}
template <typename TValue>
FanoutChannel<TValue>& FanoutChannel<TValue>::operator=(
FanoutChannel&& other) noexcept {
if (&other == this) {
return *this;
}
if (processor_) {
std::move(*this).close();
}
processor_ = std::exchange(other.processor_, nullptr);
return *this;
}
template <typename TValue>
FanoutChannel<TValue>::~FanoutChannel() {
if (processor_ != nullptr) {
std::move(*this).close(std::nullopt /* ex */);
}
}
template <typename TValue>
FanoutChannel<TValue>::operator bool() const {
return processor_ != nullptr;
}
template <typename TValue>
Receiver<TValue> FanoutChannel<TValue>::getNewReceiver(
folly::Function<std::vector<TValue>()> getInitialValues) {
return processor_->newReceiver(std::move(getInitialValues));
}
template <typename TValue>
bool FanoutChannel<TValue>::anyReceivers() {
return processor_->anySenders();
}
template <typename TValue>
void FanoutChannel<TValue>::close(
std::optional<folly::exception_wrapper> ex) && {
processor_->destroyHandle(
ex.has_value() ? detail::CloseResult(std::move(ex.value()))
: detail::CloseResult());
processor_ = nullptr;
}
namespace detail {
template <typename TValue>
class IFanoutChannelProcessor : public IChannelCallback {
public:
virtual Receiver<TValue> newReceiver(
folly::Function<std::vector<TValue>()> getInitialValues) = 0;
virtual bool anySenders() = 0;
virtual void destroyHandle(CloseResult closeResult) = 0;
};
/**
* This object fans out values from the input receiver to all output receivers.
* The lifetime of this object is described by the following state machine.
*
* The input receiver and all active senders can be in one of three conceptual
* states: Active, CancellationTriggered, or CancellationProcessed (removed).
* When the input receiver and all senders reach the CancellationProcessed state
* AND the user's FanoutChannel object is deleted, this object is deleted.
*
* When a sender receives a value indicating that the channel has been closed,
* the state of that sender transitions from Active directly to
* CancellationProcessed and the sender is removed.
*
* When an input receiver receives a value indicating that the channel has
* been closed, the state of the input receiver transitions from Active directly
* to CancellationProcessed, and the state of all remaining senders transitions
* from Active to CancellationTriggered. Once we receive callbacks for all
* senders indicating that the cancellation signal has been received, each such
* sender is transitioned to the CancellationProcessed state (and this object
* will be deleted once the user destroys their FanoutChannel object).
*
* When the user destroys their FanoutChannel object, the state of the input
* receiver and all senders transitions from Active to CancellationTriggered.
* This object will then be deleted once the input receiver and each remaining
* input receiver transitions to the CancellationProcessed state.
*/
template <typename TValue>
class FanoutChannelProcessor : public IFanoutChannelProcessor<TValue> {
public:
explicit FanoutChannelProcessor(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
: executor_(std::move(executor)), numSendersPlusHandle_(1) {}
/**
* Starts fanning out values from the input receiver to all output receivers.
*
* @param inputReceiver: The input receiver to fan out values from.
*/
void start(Receiver<TValue> inputReceiver) {
executor_->add([=, inputReceiver = std::move(inputReceiver)]() mutable {
auto [unbufferedInputReceiver, buffer] =
detail::receiverUnbuffer(std::move(inputReceiver));
receiver_ = std::move(unbufferedInputReceiver);
// Start processing new values that come in from the input receiver.
processAllAvailableValues(std::move(buffer));
});
}
/**
* Returns a new output receiver that will receive all values from the input
* receiver. If a getInitialValues parameter is provided, it will be executed
* to determine the set of initial values that will (only) go to the new input
* receiver.
*/
Receiver<TValue> newReceiver(
folly::Function<std::vector<TValue>()> getInitialValues) override {
numSendersPlusHandle_++;
auto [receiver, sender] = Channel<TValue>::create();
executor_->add([=,
sender = std::move(senderGetBridge(sender)),
getInitialValues = std::move(getInitialValues)]() mutable {
if (getInitialValues) {
auto initialValues = getInitialValues();
for (auto& value : initialValues) {
sender->senderPush(std::move(value));
}
}
if (getReceiverState() != ChannelState::Active ||
!sender->senderWait(this)) {
sender->senderClose();
numSendersPlusHandle_--;
} else {
senders_.insert(std::move(sender));
}
});
return std::move(receiver);
}
/**
* This is called when the user's FanoutChannel object has been destroyed.
*/
void destroyHandle(CloseResult closeResult) {
executor_->add([=, closeResult = std::move(closeResult)]() mutable {
processHandleDestroyed(std::move(closeResult));
});
}
/**
* Returns whether this fanout channel has any output receivers.
*/
bool anySenders() override { return numSendersPlusHandle_ > 1; }
ChannelState getReceiverState() {
return detail::getReceiverState(receiver_.get());
}
ChannelState getSenderState(ChannelBridge<TValue>* sender) {
return detail::getSenderState(sender);
}
private:
bool isClosed() {
if (!receiver_) {
CHECK(!anySenders());
return true;
} else {
return false;
}
}
/**
* Called when one of the channels we are listening to has an update (either
* a value from the input receiver or a cancellation from an output receiver).
*/
void consume(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (receiver_.get() == bridge) {
// One or more values are now available from the input receiver.
CHECK_NE(getReceiverState(), ChannelState::CancellationProcessed);
processAllAvailableValues();
} else {
// The consumer of an output receiver has stopped consuming.
auto* sender = static_cast<ChannelBridge<TValue>*>(bridge);
CHECK_NE(getSenderState(sender), ChannelState::CancellationProcessed);
sender->senderClose();
processSenderCancelled(sender);
}
});
}
void canceled(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (receiver_.get() == bridge) {
// We previously cancelled this input receiver, either because the
// consumer of the output receiver stopped consuming or because of the
// destruction of the user's FanoutChannel object. Process the
// cancellation for this input receiver.
processReceiverCancelled(CloseResult());
} else {
// We previously cancelled the sender due to the closure of the input
// receiver or the destruction of the user's FanoutChannel object.
// Process the cancellation for the sender.
auto* sender = static_cast<ChannelBridge<TValue>*>(bridge);
CHECK_EQ(getSenderState(sender), ChannelState::CancellationTriggered);
processSenderCancelled(sender);
}
});
}
/**
* Processes all available values from the input receiver (starting from the
* provided buffer, if present).
*
* If an value was received indicating that the input channel has been closed
* (or if the transform function indicated that channel should be closed), we
* will process cancellation for the input receiver.
*/
void processAllAvailableValues(
std::optional<ReceiverQueue<TValue>> buffer = std::nullopt) {
auto closeResult = receiver_->isReceiverCancelled()
? CloseResult()
: (buffer.has_value() ? processValues(std::move(buffer.value()))
: std::nullopt);
while (!closeResult.has_value()) {
if (receiver_->receiverWait(this)) {
// There are no more values available right now. We will stop processing
// until the channel fires the consume() callback (indicating that more
// values are available).
break;
}
auto values = receiver_->receiverGetValues();
CHECK(!values.empty());
closeResult = processValues(std::move(values));
}
if (closeResult.has_value()) {
// The receiver received a value indicating channel closure.
receiver_->receiverCancel();
processReceiverCancelled(std::move(closeResult.value()));
}
}
/**
* Processes the given set of values for the input receiver. Returns a
* CloseResult if channel was closed, so the caller can stop attempting to
* process values from it.
*/
std::optional<CloseResult> processValues(ReceiverQueue<TValue> values) {
while (!values.empty()) {
auto inputResult = std::move(values.front());
values.pop();
if (inputResult.hasValue()) {
// We have received a normal value from the input receiver. Write it to
// all output senders.
for (auto& sender : senders_) {
sender->senderPush(inputResult.value());
}
} else {
// The input receiver was closed.
return inputResult.hasException()
? CloseResult(std::move(inputResult.exception()))
: CloseResult();
}
}
return std::nullopt;
}
/**
* Processes the cancellation of the input receiver. We will close all senders
* with the exception received from the input receiver (if any).
*/
void processReceiverCancelled(CloseResult closeResult) {
CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered);
receiver_ = nullptr;
for (auto& sender : senders_) {
if (closeResult.exception.has_value()) {
sender->senderClose(closeResult.exception.value());
} else {
sender->senderClose();
}
}
maybeDelete();
}
/**
* Processes the cancellation of a sender (indicating that the consumer of
* the corresponding output receiver has stopped consuming).
*/
void processSenderCancelled(ChannelBridge<TValue>* sender) {
CHECK_EQ(getSenderState(sender), ChannelState::CancellationTriggered);
senders_.erase(sender);
numSendersPlusHandle_--;
maybeDelete();
}
/**
* Processes the destruction of the user's FanoutChannel object. We will
* cancel the receiver and trigger cancellation for all senders not already
* cancelled.
*/
void processHandleDestroyed(CloseResult closeResult) {
CHECK_GT(numSendersPlusHandle_, 0);
numSendersPlusHandle_--;
if (getReceiverState() == ChannelState::Active) {
receiver_->receiverCancel();
}
for (auto& sender : senders_) {
if (closeResult.exception.has_value()) {
sender->senderClose(closeResult.exception.value());
} else {
sender->senderClose();
}
}
maybeDelete();
}
/**
* Deletes this object if we have already processed cancellation for the
* receiver and all senders, and if the user's FanoutChannel object was
* destroyed.
*/
void maybeDelete() {
if (getReceiverState() == ChannelState::CancellationProcessed &&
numSendersPlusHandle_ == 0) {
delete this;
}
}
ChannelBridgePtr<TValue> receiver_;
folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
folly::F14FastSet<
ChannelBridgePtr<TValue>,
ChannelBridgeHash<TValue>,
ChannelBridgeEqual<TValue>>
senders_;
std::atomic<size_t> numSendersPlusHandle_;
};
} // namespace detail
template <typename TReceiver, typename TValue>
FanoutChannel<TValue> createFanoutChannel(
TReceiver inputReceiver,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor) {
auto* processor =
new detail::FanoutChannelProcessor<TValue>(std::move(executor));
processor->start(std::move(inputReceiver));
return FanoutChannel<TValue>(processor);
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
namespace folly {
namespace channels {
namespace detail {
template <typename TValue>
class IFanoutChannelProcessor;
}
/**
* A fanout channel allows one to fan out updates from a single input receiver
* to multiple output receivers.
*
* When a new output receiver is added, an optional function will be run that
* computes a set of initial values. These initial values will only be sent to
* the new receiver.
*
* Example:
*
* // Function that returns a receiver:
* Receiver<int> getInputReceiver();
*
* // Function that returns an executor
* folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
*
* auto fanoutChannel = createFanoutChannel(getReceiver(), getExecutor());
* auto receiver1 = fanoutChannel.newReceiver();
* auto receiver2 = fanoutChannel.newReceiver();
* auto receiver3 = fanoutChannel.newReceiver([]{ return {1, 2, 3}; });
* std::move(fanoutChannel).close();
*/
template <typename TValue>
class FanoutChannel {
using TProcessor = detail::IFanoutChannelProcessor<TValue>;
public:
explicit FanoutChannel(TProcessor* processor);
FanoutChannel(FanoutChannel&& other) noexcept;
FanoutChannel& operator=(FanoutChannel&& other) noexcept;
~FanoutChannel();
/**
* Returns whether this FanoutChannel is a valid object.
*/
explicit operator bool() const;
/**
* Returns a new output receiver that will receive all values from the input
* receiver. If a getInitialValues parameter is provided, it will be executed
* to determine the set of initial values that will (only) go to the new input
* receiver.
*/
Receiver<TValue> getNewReceiver(
folly::Function<std::vector<TValue>()> getInitialValues = {});
/**
* Returns whether this fanout channel has any output receivers.
*/
bool anyReceivers();
/**
* Closes the fanout channel.
*/
void close(std::optional<folly::exception_wrapper> ex = std::nullopt) &&;
private:
TProcessor* processor_;
};
/**
* Creates a new fanout channel that fans out updates from an input receiver.
*/
template <typename TReceiver, typename TValue = typename TReceiver::ValueType>
FanoutChannel<TValue> createFanoutChannel(
TReceiver inputReceiver,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/FanoutChannel-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/container/F14Set.h>
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/detail/Utility.h>
namespace folly {
namespace channels {
namespace detail {
/**
* This object does the merging of values from the input receiver to the output
* receiver. It is not an object that the user is aware of or holds a pointer
* to. The lifetime of this object is described by the following state machine.
*
* The sender and all receivers can be in one of three conceptual states:
* Active, CancellationTriggered, or CancellationProcessed. When the sender and
* all receivers reach the CancellationProcessed state, this object is deleted.
*
* When an input receiver receives a value indicating that the channel has been
* closed, the state of that receiver transitions from Active directly to
* CancellationProcessed.
*
* If this is the last receiver to be closed, or if the receiver closed with an
* exception, the state of the sender and all other receivers transitions from
* Active to CancellationTriggered. In that case, once we receive callbacks
* indicating the cancellation signal has been received for all other receivers
* and the sender, the state of the sender and all other receivers transitions
* to CancellationProcessed (and this object is deleted).
*
* When the sender receives notification that the consumer of the output
* receiver has stopped consuming, the state of the sender transitions from
* Active directly to CancellationProcessed, and the state of all remaining
* input receivers transitions from Active to CancellationTriggered. This
* object will then be deleted once each remaining input receiver transitions to
* the CancellationProcessed state (after we receive each cancelled callback).
*/
template <typename TValue>
class MergeProcessor : public IChannelCallback {
public:
MergeProcessor(
Sender<TValue> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
: sender_(std::move(detail::senderGetBridge(sender))),
executor_(std::move(executor)) {}
/**
* Starts merging inputs from all input receivers into the output receiver.
*
* @param inputReceivers: The collection of input receivers to merge.
*/
void start(std::vector<Receiver<TValue>> inputReceivers) {
executor_->add([=, inputReceivers = std::move(inputReceivers)]() mutable {
if (!sender_->senderWait(this)) {
processSenderCancelled();
return;
}
auto buffers =
folly::F14FastMap<ChannelBridge<TValue>*, ReceiverQueue<TValue>>();
receivers_.reserve(inputReceivers.size());
buffers.reserve(inputReceivers.size());
for (auto& inputReceiver : inputReceivers) {
auto [unbufferedInputReceiver, buffer] =
detail::receiverUnbuffer(std::move(inputReceiver));
CHECK(buffers
.insert(std::make_pair(
unbufferedInputReceiver.get(), std::move(buffer)))
.second);
receivers_.insert(std::move(unbufferedInputReceiver));
}
for (auto& receiver : receivers_) {
processAllAvailableValues(
receiver.get(),
!buffers.empty()
? std::make_optional(std::move(buffers.at(receiver.get())))
: std::nullopt);
}
});
}
/**
* Called when one of the channels we are listening to has an update (either
* a value from an input receiver or a cancellation from the output receiver).
*/
void consume(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (bridge == sender_.get()) {
// The consumer of the output receiver has stopped consuming.
CHECK_NE(getSenderState(), ChannelState::CancellationProcessed);
sender_->senderClose();
processSenderCancelled();
} else {
// One or more values are now available from an input receiver.
auto* receiver = static_cast<ChannelBridge<TValue>*>(bridge);
CHECK_NE(
getReceiverState(receiver), ChannelState::CancellationProcessed);
processAllAvailableValues(receiver);
}
});
}
/**
* Called after we cancelled one of the channels we were listening to (either
* the sender or an input receiver).
*/
void canceled(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (bridge == sender_.get()) {
// We previously cancelled the sender due to an input receiver closure
// with an exception (or the closure of all input receivers without an
// exception). Process the cancellation for the sender.
CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
processSenderCancelled();
} else {
// We previously cancelled this input receiver, either because the
// consumer of the output receiver stopped consuming or because another
// input receiver received an exception. Process the cancellation for
// this input receiver.
auto* receiver = static_cast<ChannelBridge<TValue>*>(bridge);
CHECK_EQ(
getReceiverState(receiver), ChannelState::CancellationTriggered);
processReceiverCancelled(receiver, CloseResult());
}
});
}
private:
/**
* Processes all available values from the input receiver (starting from the
* provided buffer, if present).
*
* If an value was received indicating that the input channel has been closed
* (or if the transform function indicated that channel should be closed), we
* will process cancellation for the input receiver.
*/
void processAllAvailableValues(
ChannelBridge<TValue>* receiver,
std::optional<ReceiverQueue<TValue>> buffer = std::nullopt) {
auto closeResult =
getReceiverState(receiver) == ChannelState::CancellationTriggered
? CloseResult()
: buffer.has_value() ? processValues(std::move(buffer.value()))
: std::nullopt;
while (!closeResult.has_value()) {
if (receiver->receiverWait(this)) {
// There are no more values available right now. We will stop processing
// until the channel fires the consume() callback (indicating that more
// values are available).
break;
}
auto values = receiver->receiverGetValues();
CHECK(!values.empty());
closeResult = processValues(std::move(values));
}
if (closeResult.has_value()) {
// The receiver received a value indicating channel closure.
receiver->receiverCancel();
processReceiverCancelled(receiver, std::move(closeResult.value()));
}
}
/**
* Processes the given set of values for an input receiver. Returns a
* CloseResult if the given channel was closed, so the caller can stop
* attempting to process values from it.
*/
std::optional<CloseResult> processValues(ReceiverQueue<TValue> values) {
while (!values.empty()) {
auto inputResult = std::move(values.front());
values.pop();
if (inputResult.hasValue()) {
// We have received a normal value from an input receiver. Write it to
// the output receiver.
sender_->senderPush(std::move(inputResult.value()));
} else {
// The input receiver was closed.
return inputResult.hasException()
? CloseResult(std::move(inputResult.exception()))
: CloseResult();
}
}
return std::nullopt;
}
/**
* Processes the cancellation of an input receiver. If the cancellation was
* due to receipt of an exception (or the cancellation was the last input
* receiver to be closed), we will also trigger cancellation for the sender
* (and all other input receivers).
*/
void processReceiverCancelled(
ChannelBridge<TValue>* receiver, CloseResult closeResult) {
CHECK_EQ(getReceiverState(receiver), ChannelState::CancellationTriggered);
receivers_.erase(receiver);
if (closeResult.exception.has_value()) {
// We received an exception. We need to close the sender and all other
// receivers.
if (getSenderState() == ChannelState::Active) {
sender_->senderClose(std::move(closeResult.exception.value()));
}
for (auto& otherReceiver : receivers_) {
if (getReceiverState(otherReceiver.get()) == ChannelState::Active) {
otherReceiver->receiverCancel();
}
}
} else if (receivers_.empty()) {
// We just closed the last receiver. Close the sender.
if (getSenderState() == ChannelState::Active) {
sender_->senderClose();
}
}
maybeDelete();
}
/**
* Processes the cancellation of the sender (indicating that the consumer of
* the output receiver has stopped consuming). We will trigger cancellation
* for all input receivers not already cancelled.
*/
void processSenderCancelled() {
CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
sender_ = nullptr;
for (auto& receiver : receivers_) {
if (getReceiverState(receiver.get()) == ChannelState::Active) {
receiver->receiverCancel();
}
}
maybeDelete();
}
/**
* Deletes this object if we have already processed cancellation for the
* sender and all input receivers.
*/
void maybeDelete() {
if (getSenderState() == ChannelState::CancellationProcessed &&
receivers_.empty()) {
delete this;
}
}
ChannelState getReceiverState(ChannelBridge<TValue>* receiver) {
return detail::getReceiverState(receiver);
}
ChannelState getSenderState() {
return detail::getSenderState(sender_.get());
}
folly::F14FastSet<
ChannelBridgePtr<TValue>,
ChannelBridgeHash<TValue>,
ChannelBridgeEqual<TValue>>
receivers_;
ChannelBridgePtr<TValue> sender_;
folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
};
} // namespace detail
template <typename TReceiver, typename TValue>
Receiver<TValue> merge(
std::vector<TReceiver> inputReceivers,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor) {
auto [outputReceiver, outputSender] = Channel<TValue>::create();
auto* processor = new detail::MergeProcessor<TValue>(
std::move(outputSender), std::move(executor));
processor->start(std::move(inputReceivers));
return std::move(outputReceiver);
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
namespace folly {
namespace channels {
/**
* Merge takes a list of receivers, and returns a new receiver that receives
* all updates from all input receivers. If any input receiver closes with
* an exception, the exception is forwarded and the channel is closed. If any
* input receiver closes without an exception, the channel continues to merge
* values from the other input receivers until all input receivers are closed.
*
* @param inputReceivers: The collection of input receivers to merge.
*
* @param executor: A SequencedExecutor used to merge input values.
*
* Example:
*
* // Example function that returns a list of receivers
* std::vector<Receiver<int>> getReceivers();
*
* // Example function that returns an executor
* folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
*
* Receiver<int> mergedReceiver = merge(getReceivers(), getExecutor());
*/
template <typename TReceiver, typename TValue = typename TReceiver::ValueType>
Receiver<TValue> merge(
std::vector<TReceiver> inputReceivers,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/Merge-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/container/F14Map.h>
#include <folly/container/F14Set.h>
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/detail/Utility.h>
namespace folly {
namespace channels {
template <typename TValue, typename TSubscriptionId>
MergeChannel<TValue, TSubscriptionId>::MergeChannel(TProcessor* processor)
: processor_(processor) {}
template <typename TValue, typename TSubscriptionId>
MergeChannel<TValue, TSubscriptionId>::MergeChannel(
MergeChannel&& other) noexcept
: processor_(std::exchange(other.processor_, nullptr)) {}
template <typename TValue, typename TSubscriptionId>
MergeChannel<TValue, TSubscriptionId>&
MergeChannel<TValue, TSubscriptionId>::operator=(
MergeChannel&& other) noexcept {
if (&other == this) {
return *this;
}
if (processor_) {
std::move(*this).close();
}
processor_ = std::exchange(other.processor_, nullptr);
return *this;
}
template <typename TValue, typename TSubscriptionId>
MergeChannel<TValue, TSubscriptionId>::~MergeChannel() {
if (processor_) {
std::move(*this).close(std::nullopt /* ex */);
}
}
template <typename TValue, typename TSubscriptionId>
MergeChannel<TValue, TSubscriptionId>::operator bool() const {
return processor_;
}
template <typename TValue, typename TSubscriptionId>
template <typename TReceiver>
void MergeChannel<TValue, TSubscriptionId>::addNewReceiver(
TSubscriptionId subscriptionId, TReceiver receiver) {
processor_->addNewReceiver(subscriptionId, std::move(receiver));
}
template <typename TValue, typename TSubscriptionId>
void MergeChannel<TValue, TSubscriptionId>::removeReceiver(
TSubscriptionId subscriptionId) {
processor_->removeReceiver(subscriptionId);
}
template <typename TValue, typename TSubscriptionId>
void MergeChannel<TValue, TSubscriptionId>::close(
std::optional<folly::exception_wrapper> ex) && {
processor_->destroyHandle(
ex.has_value() ? detail::CloseResult(std::move(ex.value()))
: detail::CloseResult());
processor_ = nullptr;
}
namespace detail {
template <typename TValue, typename TSubscriptionId>
class IMergeChannelProcessor : public IChannelCallback {
public:
virtual void addNewReceiver(
TSubscriptionId subscriptionId, Receiver<TValue> receiver) = 0;
virtual void removeReceiver(TSubscriptionId subscriptionId) = 0;
virtual void destroyHandle(CloseResult closeResult) = 0;
};
/**
* This object does the merging of values from the input receivers to the output
* receiver. The lifetime of this object is described by the following state
* machine.
*
* The sender and all active receivers can be in one of three conceptual states:
* Active, CancellationTriggered, or CancellationProcessed (removed). When the
* sender and all receivers reach the CancellationProcessed state AND the user's
* MergeChannel object is deleted, this object is deleted.
*
* When an input receiver receives a value indicating that the channel has
* been closed, the state of that receiver transitions from Active directly to
* CancellationProcessed and the receiver is removed.
*
* If the receiver closed with an exception, the state of the sender and all
* other receivers transitions from Active to CancellationTriggered. In that
* case, once we receive callbacks indicating the cancellation signal has been
* received for all other receivers and the sender, the state of the sender and
* all other receivers transitions to CancellationProcessed (and this object
* will be deleted once the user destroys their MergeChannel object).
*
* When the sender receives notification that the consumer of the output
* receiver has stopped consuming, the state of the sender transitions from
* Active directly to CancellationProcessed, and the state of all remaining
* input receivers transitions from Active to CancellationTriggered. Once we
* receive callbacks for all input receivers indicating that the cancellation
* signal has been received, each such receiver is transitioned to the
* CancellationProcessed state (and this object will be deleted once the user
* destroys their MergeChannel object).
*
* When the user destroys their MergeChannel object, the state of the sender and
* all remaining receivers transition from Active to CancellationTriggered. This
* object will then be deleted once the sender and each remaining input receiver
* transitions to the CancellationProcessed state (after we receive each
* cancelled callback).
*/
template <typename TValue, typename TSubscriptionId>
class MergeChannelProcessor
: public IMergeChannelProcessor<TValue, TSubscriptionId> {
public:
MergeChannelProcessor(
Sender<TValue> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
: sender_(std::move(detail::senderGetBridge(sender))),
executor_(std::move(executor)) {
CHECK(sender_->senderWait(this));
}
/**
* Adds a new receiver to be merged, along with a subscription ID to allow for
* later removal.
*/
void addNewReceiver(
TSubscriptionId subscriptionId, Receiver<TValue> receiver) {
executor_->add([=,
subscriptionId = std::move(subscriptionId),
receiver = std::move(receiver)]() mutable {
if (getSenderState() != ChannelState::Active) {
return;
}
auto [unbufferedReceiver, buffer] =
detail::receiverUnbuffer(std::move(receiver));
auto existingReceiverIt = receiversBySubscriptionId_.find(subscriptionId);
if (existingReceiverIt != receiversBySubscriptionId_.end() &&
receivers_.contains(existingReceiverIt->second)) {
// We already have a receiver with the given subscription ID. Trigger
// cancellation on that previous receiver.
if (!existingReceiverIt->second->isReceiverCancelled()) {
existingReceiverIt->second->receiverCancel();
}
receiversBySubscriptionId_.erase(existingReceiverIt);
}
receiversBySubscriptionId_.insert(
std::make_pair(subscriptionId, unbufferedReceiver.get()));
auto* receiverPtr = unbufferedReceiver.get();
receivers_.insert(std::move(unbufferedReceiver));
processAllAvailableValues(receiverPtr, std::move(buffer));
});
}
/**
* Removes the receiver with the given subscription ID.
*/
void removeReceiver(TSubscriptionId subscriptionId) {
executor_->add([=]() {
if (getSenderState() != ChannelState::Active) {
return;
}
auto receiverIt = receiversBySubscriptionId_.find(subscriptionId);
if (receiverIt == receiversBySubscriptionId_.end()) {
return;
}
if (!receiverIt->second->isReceiverCancelled()) {
receiverIt->second->receiverCancel();
}
receiversBySubscriptionId_.erase(receiverIt);
});
}
/**
* Called when the user's MergeChannel object is destroyed.
*/
void destroyHandle(CloseResult closeResult) {
executor_->add([=, closeResult = std::move(closeResult)]() mutable {
processHandleDestroyed(std::move(closeResult));
});
}
/**
* Called when one of the channels we are listening to has an update (either
* a value from an input receiver or a cancellation from the output receiver).
*/
void consume(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (bridge == sender_.get()) {
// The consumer of the output receiver has stopped consuming.
CHECK(getSenderState() != ChannelState::CancellationProcessed);
sender_->senderClose();
processSenderCancelled();
} else {
// One or more values are now available from an input receiver.
auto* receiver = static_cast<ChannelBridge<TValue>*>(bridge);
CHECK(
getReceiverState(receiver) != ChannelState::CancellationProcessed);
processAllAvailableValues(receiver);
}
});
}
/**
* Called after we cancelled one of the channels we were listening to (either
* the sender or an input receiver).
*/
void canceled(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (bridge == sender_.get()) {
// We previously cancelled the sender due to an input receiver closure
// with an exception (or the closure of all input receivers without an
// exception). Process the cancellation for the sender.
CHECK(getSenderState() == ChannelState::CancellationTriggered);
processSenderCancelled();
} else {
// We previously cancelled this input receiver, either because the
// consumer of the output receiver stopped consuming or because another
// input receiver received an exception. Process the cancellation for
// this input receiver.
auto* receiver = static_cast<ChannelBridge<TValue>*>(bridge);
processReceiverCancelled(receiver, CloseResult());
}
});
}
protected:
/**
* Processes all available values from the given input receiver channel
* (starting from the provided buffer, if present).
*
* If an value was received indicating that the input channel has been closed
* (or if the transform function indicated that channel should be closed), we
* will process cancellation for the input receiver.
*/
void processAllAvailableValues(
ChannelBridge<TValue>* receiver,
std::optional<ReceiverQueue<TValue>> buffer = std::nullopt) {
auto closeResult = receiver->isReceiverCancelled()
? CloseResult()
: (buffer.has_value() ? processValues(std::move(buffer.value()))
: std::nullopt);
while (!closeResult.has_value()) {
if (receiver->receiverWait(this)) {
// There are no more values available right now. We will stop processing
// until the channel fires the consume() callback (indicating that more
// values are available).
break;
}
auto values = receiver->receiverGetValues();
CHECK(!values.empty());
closeResult = processValues(std::move(values));
}
if (closeResult.has_value()) {
// The receiver received a value indicating channel closure.
receiver->receiverCancel();
processReceiverCancelled(receiver, std::move(closeResult.value()));
}
}
/**
* Processes the given set of values for an input receiver. Returns a
* CloseResult if the given channel was closed, so the caller can stop
* attempting to process values from it.
*/
std::optional<CloseResult> processValues(ReceiverQueue<TValue> values) {
while (!values.empty()) {
auto inputResult = std::move(values.front());
values.pop();
if (inputResult.hasValue()) {
// We have received a normal value from an input receiver. Write it to
// the output receiver.
sender_->senderPush(std::move(inputResult.value()));
} else {
// The input receiver was closed.
return inputResult.hasException()
? CloseResult(std::move(inputResult.exception()))
: CloseResult();
}
}
return std::nullopt;
}
/**
* Processes the cancellation of an input receiver. If the cancellation was
* due to receipt of an exception, we will also trigger cancellation for the
* sender (and all other input receivers).
*/
void processReceiverCancelled(
ChannelBridge<TValue>* receiver, CloseResult closeResult) {
CHECK(getReceiverState(receiver) == ChannelState::CancellationTriggered);
receivers_.erase(receiver);
if (closeResult.exception.has_value()) {
// We received an exception. We need to close the sender and all
// receivers.
if (getSenderState() == ChannelState::Active) {
sender_->senderClose(std::move(closeResult.exception.value()));
}
for (auto& otherReceiver : receivers_) {
if (getReceiverState(otherReceiver.get()) == ChannelState::Active) {
otherReceiver->receiverCancel();
}
}
}
maybeDelete();
}
/**
* Processes the cancellation of the sender (indicating that the consumer of
* the output receiver has stopped consuming). We will trigger cancellation
* for all input receivers not already cancelled.
*/
void processSenderCancelled() {
CHECK(getSenderState() == ChannelState::CancellationTriggered);
sender_ = nullptr;
for (auto& receiver : receivers_) {
if (getReceiverState(receiver.get()) == ChannelState::Active) {
receiver->receiverCancel();
}
}
maybeDelete();
}
/**
* Processes the destruction of the user's MergeChannel object. We will
* close the sender and trigger cancellation for all input receivers not
* already cancelled.
*/
void processHandleDestroyed(CloseResult closeResult) {
CHECK(!handleDestroyed_);
handleDestroyed_ = true;
if (getSenderState() == ChannelState::Active) {
if (closeResult.exception.has_value()) {
sender_->senderClose(std::move(closeResult.exception.value()));
} else {
sender_->senderClose();
}
}
for (auto& receiver : receivers_) {
if (getReceiverState(receiver.get()) == ChannelState::Active) {
receiver->receiverCancel();
}
}
maybeDelete();
}
/**
* Deletes this object if we have already processed cancellation for the
* sender and all input receivers, and if the user's MergeChannel object was
* destroyed.
*/
void maybeDelete() {
if (getSenderState() == ChannelState::CancellationProcessed &&
receivers_.empty() && handleDestroyed_) {
delete this;
}
}
ChannelState getReceiverState(ChannelBridge<TValue>* receiver) {
return detail::getReceiverState(receiver);
}
ChannelState getSenderState() {
return detail::getSenderState(sender_.get());
}
ChannelBridgePtr<TValue> sender_;
folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
folly::F14FastSet<
ChannelBridgePtr<TValue>,
ChannelBridgeHash<TValue>,
ChannelBridgeEqual<TValue>>
receivers_;
folly::F14FastMap<TSubscriptionId, ChannelBridge<TValue>*>
receiversBySubscriptionId_;
bool handleDestroyed_{false};
};
} // namespace detail
template <typename TValue, typename TSubscriptionId>
std::pair<Receiver<TValue>, MergeChannel<TValue, TSubscriptionId>>
createMergeChannel(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor) {
auto [receiver, sender] = Channel<TValue>::create();
auto* processor = new detail::MergeChannelProcessor<TValue, TSubscriptionId>(
std::move(sender), std::move(executor));
return std::make_pair(
std::move(receiver), MergeChannel<TValue, TSubscriptionId>(processor));
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
namespace folly {
namespace channels {
namespace detail {
template <typename TValue, typename TSubscriptionId>
class IMergeChannelProcessor;
}
/**
* A merge channel allows one to merge multiple receivers into a single output
* receiver. The set of receivers being merged can be changed at runtime. Each
* receiver is added with a subscription ID, that can be used to remove the
* receiver at a later point.
*
* Example:
*
* // Example function that returns a receiver for a given entity:
* Receiver<int> subscribe(std::string entity);
*
* // Example function that returns an executor
* folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
*
* auto [outputReceiver, mergeChannel]
* = createMergeChannel<int, std::string>(getExecutor());
* mergeChannel.addNewReceiver("abc", subscribe("abc"));
* mergeChannel.addNewReceiver("def", subscribe("def"));
* mergeChannel.removeReceiver("abc");
* std::move(mergeChannel).close();
*/
template <typename TValue, typename TSubscriptionId>
class MergeChannel {
using TProcessor = detail::IMergeChannelProcessor<TValue, TSubscriptionId>;
public:
explicit MergeChannel(
detail::IMergeChannelProcessor<TValue, TSubscriptionId>* processor);
MergeChannel(MergeChannel&& other) noexcept;
MergeChannel& operator=(MergeChannel&& other) noexcept;
~MergeChannel();
/**
* Returns whether this MergeChannel is a valid object.
*/
explicit operator bool() const;
/**
* Adds a new receiver to be merged, along with a given subscription ID. If
* the subscription ID matches the ID of an existing receiver, that existing
* receiver is replaced with the new one (and changes to the old receiver will
* no longer be merged). An added receiver can later be removed by passing the
* subscription ID to removeReceiver.
*/
template <typename TReceiver>
void addNewReceiver(TSubscriptionId subscriptionId, TReceiver receiver);
/**
* Removes the receiver added with the given subscription ID. The receiver
* will be asynchronously removed, so the consumer may still receive some
* values from this receiver after this call.
*/
void removeReceiver(TSubscriptionId subscriptionId);
/**
* Closes the merge channel.
*/
void close(std::optional<folly::exception_wrapper> ex = std::nullopt) &&;
private:
TProcessor* processor_;
};
/**
* Creates a new merge channel.
*
* @param executor: The SequencedExecutor to use for merging values.
*/
template <typename TValue, typename TSubscriptionId>
std::pair<Receiver<TValue>, MergeChannel<TValue, TSubscriptionId>>
createMergeChannel(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/MergeChannel-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <fmt/format.h>
#include <folly/CancellationToken.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/ConsumeChannel.h>
#include <folly/experimental/channels/Producer.h>
#include <folly/experimental/coro/Task.h>
namespace folly {
namespace channels {
template <typename TValue>
Producer<TValue>::KeepAlive::KeepAlive(Producer<TValue>* ptr) : ptr_(ptr) {}
template <typename TValue>
Producer<TValue>::KeepAlive::~KeepAlive() {
if (ptr_ && --ptr_->refCount_ == 0) {
auto deleteTask =
folly::coro::co_invoke([ptr = ptr_]() -> folly::coro::Task<void> {
delete ptr;
co_return;
});
std::move(deleteTask).scheduleOn(ptr_->getExecutor()).start();
}
}
template <typename TValue>
Producer<TValue>::KeepAlive::KeepAlive(
Producer<TValue>::KeepAlive&& other) noexcept
: ptr_(std::exchange(other.ptr_, nullptr)) {}
template <typename TValue>
typename Producer<TValue>::KeepAlive& Producer<TValue>::KeepAlive::operator=(
Producer<TValue>::KeepAlive&& other) noexcept {
if (&other == this) {
return *this;
}
ptr_ = std::exchange(other.ptr_, nullptr);
return *this;
}
template <typename TValue>
Producer<TValue>::Producer(
Sender<TValue> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
: sender_(std::move(detail::senderGetBridge(sender))),
executor_(std::move(executor)) {
CHECK(sender_->senderWait(this));
}
template <typename TValue>
void Producer<TValue>::write(TValue value) {
executor_->add([this, value = std::move(value)]() mutable {
sender_->senderPush(std::move(value));
});
}
template <typename TValue>
void Producer<TValue>::close(std::optional<folly::exception_wrapper> ex) {
executor_->add([this, ex = std::move(ex)]() mutable {
if (ex.has_value()) {
sender_->senderClose(std::move(ex.value()));
} else {
sender_->senderClose();
}
});
}
template <typename TValue>
bool Producer<TValue>::isClosed() {
return sender_->isSenderClosed();
}
template <typename TValue>
folly::Executor::KeepAlive<folly::SequencedExecutor>
Producer<TValue>::getExecutor() {
return executor_;
}
template <typename TValue>
typename Producer<TValue>::KeepAlive Producer<TValue>::getKeepAlive() {
refCount_.fetch_add(1, std::memory_order_relaxed);
return KeepAlive(this);
}
template <typename TValue>
void Producer<TValue>::consume(detail::ChannelBridgeBase*) {
onClosed().scheduleOn(getExecutor()).start([=](auto) {
// Decrement ref count
KeepAlive(this);
});
}
template <typename TValue>
void Producer<TValue>::canceled(detail::ChannelBridgeBase* bridge) {
consume(bridge);
}
namespace detail {
template <typename TProducer>
class ProducerImpl : public TProducer {
template <typename ProducerType, typename... Args>
friend Receiver<typename ProducerType::ValueType> makeProducer(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
Args&&... args);
public:
using TProducer::TProducer;
private:
void ensureMakeProducerUsedForCreation() override {}
};
} // namespace detail
template <typename TProducer, typename... Args>
Receiver<typename TProducer::ValueType> makeProducer(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
Args&&... args) {
using TValue = typename TProducer::ValueType;
auto [receiver, sender] = Channel<TValue>::create();
new detail::ProducerImpl<TProducer>(
std::move(sender), std::move(executor), std::forward<Args>(args)...);
return std::move(receiver);
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/ChannelCallbackHandle.h>
#include <folly/experimental/coro/Task.h>
namespace folly {
namespace channels {
/**
* A Producer is a base class for an object that produces a channel. The
* subclass can call write to write a new value to the channel, and close to
* close the channel. It is a useful way to generate output values for a
* receiver, without having to keep alive an extraneous object that produces
* those values.
*
* When the consumer of the channel stops consuming, the onClosed function will
* be called. The subclass should cancel any ongoing work in this function.
* After onCancelled is called, the object will be deleted once the last
* outstanding KeepAlive is destroyed.
*
* Example:
* // Function that returns an executor
* folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
*
* // Function that returns output values
* std::vector<int> getLatestOutputValues();
*
* // Example producer implementation
* class PollingProducer : public Producer<int> {
* public:
* PollingProducer(
* Sender<int> sender,
* folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
* : Producer<int>(std::move(sender), std::move(executor)) {
* // Start polling for values.
* folly::coro::co_withCancellation(
* cancelSource_.getToken(),
* [=, keepAlive = getKeepAlive()]() {
* return pollForOutputValues();
* })
* .scheduleOn(getExecutor())
* .start();
* }
*
* folly::coro::Task<void> onClosed() override {
* // The consumer has stopped consuming our values. Stop polling.
* cancelSource_.requestCancellation();
* }
*
* private:
* folly::coro::Task<void> pollForOutputValues() {
* auto cancelToken = co_await folly::coro::co_current_cancellation_token;
* while (!cancelToken.isCancellationRequested()) {
* auto outputValues = getLatestOutputValues();
* for (auto& outputValue : outputValues) {
* write(std::move(outputValue));
* }
* }
* co_await folly::coro::sleep(std::chrono::seconds(1));
* }
*
* folly::CancellationSource cancelSource_;
* };
*
* // Producer usage
* Receiver<int> receiver = makeProducer<PollingProducer>(getExecutor());
*/
template <typename TValue>
class Producer : public detail::IChannelCallback {
public:
using ValueType = TValue;
protected:
/**
* This object will ensure that the corresponding Producer that created it
* will not be destroyed.
*/
class KeepAlive {
public:
~KeepAlive();
KeepAlive(KeepAlive&&) noexcept;
KeepAlive& operator=(KeepAlive&&) noexcept;
private:
friend class Producer<TValue>;
explicit KeepAlive(Producer<TValue>* ptr);
Producer<TValue>* ptr_;
};
Producer(
Sender<TValue> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor);
virtual ~Producer() override = default;
/**
* Writes a value into the channel.
*/
void write(TValue value);
/**
* Closes the channel.
*/
void close(std::optional<folly::exception_wrapper> ex = std::nullopt);
/**
* Returns whether or not this producer is closed or cancelled.
*/
bool isClosed();
/**
* Returns the executor used for this producer.
*/
folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
/**
* Returns a KeepAlive object. This object will not be destroyed before all
* KeepAlive objects are destroyed.
*/
KeepAlive getKeepAlive();
/**
* Called when the corresponding receiver is cancelled, or the sender is
* closed.
*/
virtual folly::coro::Task<void> onClosed() { co_return; }
/**
* If you get an error that this function is not implemented, do not
* implement it. Instead, create your object with makeProducer
* below.
*/
virtual void ensureMakeProducerUsedForCreation() = 0;
private:
template <typename TProducer, typename... Args>
friend Receiver<typename TProducer::ValueType> makeProducer(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
Args&&... args);
void consume(detail::ChannelBridgeBase* bridge) override;
void canceled(detail::ChannelBridgeBase* bridge) override;
detail::ChannelBridgePtr<TValue> sender_;
folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
std::atomic<int> refCount_{1};
};
/**
* Creates a new object that extends the Producer class, and returns a receiver.
* The receiver will receive any values produced by the producer. See the
* description of the Producer class for information on how to implement a
* producer.
*/
template <typename TProducer, typename... Args>
Receiver<typename TProducer::ValueType> makeProducer(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
Args&&... args);
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/Producer-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/detail/Utility.h>
#include <folly/experimental/coro/Task.h>
namespace folly {
namespace channels {
namespace detail {
/**
* This object transforms values from the input receiver to the output receiver.
* It is not an object that the user is aware of or holds a pointer to. The
* lifetime of this object is described by the following state machine.
*
* Both the sender and receiver can be in one of three states: Active,
* CancellationTriggered, or CancellationProcessed. When both the sender and
* receiver reach the CancellationProcessed state, this object is deleted.
*
* When the input receiver receives a value indicating that the channel has been
* closed, the state of the receiver transitions from Active directly to
* CancellationProcessed and the state of the sender transitions from Active to
* CancellationTriggered. Once we receive a callback indicating the sender's
* cancellation signal has been received, the sender's state is transitioned
* from CancellationTriggered to CancellationProcessed (and the object is
* deleted).
*
* When the sender receives notification that the consumer of the output
* receiver has stopped consuming, the state of the sender transitions from
* Active directly to CancellationProcessed, and the state of the input receiver
* transitions from Active to CancellationTriggered. Once we receive a callback
* indicating that the input receiver's cancellation signal has been received,
* the input receiver's state is transitioned from CancellationTriggered to
* to CancellationProcessed (and the object is deleted).
*/
template <
typename TInputValue,
typename TOutputValue,
typename TransformValueFunc>
class TransformProcessorBase : public IChannelCallback {
public:
TransformProcessorBase(
Sender<TOutputValue> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
TransformValueFunc transformValue)
: sender_(std::move(senderGetBridge(sender))),
executor_(std::move(executor)),
transformValue_(std::move(transformValue)) {}
template <typename TReceiver>
void startTransform(TReceiver receiver) {
executor_->add([=, receiver = std::move(receiver)]() mutable {
runOperationWithSenderCancellation(
this->executor_,
this->sender_,
false /* alreadyStartedWaiting */,
this /* channelCallbackToRestore */,
startTransformImpl(std::move(receiver)));
});
}
protected:
/**
* Starts transforming values from the input receiver and sending the
* resulting transformed values to the output receiver.
*
* @param inputReceiver: The input receiver to transform values from.
*/
folly::coro::Task<void> startTransformImpl(Receiver<TInputValue> receiver) {
auto [unbufferedInputReceiver, buffer] =
detail::receiverUnbuffer(std::move(receiver));
receiver_ = std::move(unbufferedInputReceiver);
co_await processAllAvailableValues(std::move(buffer));
}
/**
* This is called when one of the channels we are listening to has an update
* (either a value from the input receiver or a cancellation signal from the
* sender).
*/
void consume(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (bridge == receiver_.get()) {
// We have received new values from the input receiver.
CHECK_NE(getReceiverState(), ChannelState::CancellationProcessed);
runOperationWithSenderCancellation(
this->executor_,
this->sender_,
true /* alreadyStartedWaiting */,
this /* channelCallbackToRestore */,
processAllAvailableValues());
} else {
CHECK_NE(getSenderState(), ChannelState::CancellationProcessed);
// The consumer of the output receiver has stopped consuming.
if (getSenderState() == ChannelState::Active) {
sender_->senderClose();
}
processSenderCancelled();
}
});
}
/**
* This is called after we explicitly cancel one of the channels we are
* listening to.
*/
void canceled(ChannelBridgeBase* bridge) override {
executor_->add([=]() {
if (bridge == receiver_.get()) {
// We previously cancelled the input receiver (because the consumer of
// the output receiver stopped consuming). Process the cancellation for
// the input receiver.
CHECK_EQ(getReceiverState(), ChannelState::CancellationTriggered);
processReceiverCancelled(CloseResult()).scheduleOn(executor_).start();
} else {
// We previously cancelled the sender due to the closure of the input
// receiver. Process the cancellation for the sender.
CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
processSenderCancelled();
}
});
}
/**
* Processes all available values from the input receiver (starting from the
* provided buffer, if present).
*
* If a value was received indicating that the input channel has been closed
* (or if the transform function indicated that channel should be closed), we
* will process cancellation for the input receiver.
*/
folly::coro::Task<void> processAllAvailableValues(
std::optional<ReceiverQueue<TInputValue>> buffer = std::nullopt) {
auto closeResult = buffer.has_value()
? co_await processValues(std::move(buffer.value()))
: std::nullopt;
while (!closeResult.has_value()) {
if (receiver_->receiverWait(this)) {
// There are no more values available right now, but more values may
// come in the future. We will stop processing for now, until we
// re-start processing when the consume() callback is fired.
break;
}
auto values = receiver_->receiverGetValues();
CHECK(!values.empty());
closeResult = co_await processValues(std::move(values));
}
if (closeResult.has_value()) {
// The output receiver should be closed (either because the input receiver
// was closed or the transform function desired the closure of the output
// receiver).
receiver_->receiverCancel();
co_await processReceiverCancelled(std::move(closeResult.value()));
}
}
/**
* Processes the given set of values for the input receiver. If the output
* receiver should be closed (either because the input receiver was closed or
* the transform function desired the closure of the output receiver), a
* CloseResult is returned containing the exception (if any) that should be
* used to close the output receiver.
*/
folly::coro::Task<std::optional<CloseResult>> processValues(
ReceiverQueue<TInputValue> values) {
auto cancelToken = co_await folly::coro::co_current_cancellation_token;
while (!values.empty()) {
auto inputResult = std::move(values.front());
values.pop();
bool inputClosed = !inputResult.hasValue();
if (!inputResult.hasValue() && !inputResult.hasException()) {
inputResult = folly::Try<TInputValue>(OnClosedException());
}
auto outputGen = folly::makeTryWith(
[&]() { return transformValue_(std::move(inputResult)); });
if (!outputGen.hasValue()) {
// The transform function threw an exception and was not a coroutine.
// We will close the output receiver.
co_return outputGen.template hasException<OnClosedException>()
? CloseResult()
: CloseResult(std::move(outputGen.exception()));
}
while (true) {
auto outputResult =
co_await folly::coro::co_awaitTry(outputGen->next());
if (!outputResult.hasValue() && !outputResult.hasException()) {
break;
}
if (cancelToken.isCancellationRequested()) {
co_return CloseResult();
}
if (outputResult.hasValue()) {
sender_->senderPush(std::move(outputResult.value()));
} else {
// The transform coroutine threw an exception. We will close the
// output receiver.
co_return outputResult.template hasException<OnClosedException>()
? CloseResult()
: CloseResult(std::move(outputResult.exception()));
}
}
if (inputClosed) {
// The input receiver was closed, and the transform function did not
// explicitly close the output receiver. We will therefore close it
// anyway, as it does not make sense to keep it open when no future
// values will arrive.
co_return CloseResult();
}
}
co_return std::nullopt;
}
/**
* Process cancellation for the input receiver.
*/
virtual folly::coro::Task<void> processReceiverCancelled(
CloseResult closeResult, bool noRetriesAllowed = false) = 0;
/**
* Process cancellation for the sender.
*/
void processSenderCancelled() {
CHECK_EQ(getSenderState(), ChannelState::CancellationTriggered);
sender_ = nullptr;
if (getReceiverState() == ChannelState::Active) {
receiver_->receiverCancel();
}
maybeDelete();
}
/**
* Deletes this object if we have already processed cancellation for the
* receiver and the sender.
*/
void maybeDelete() {
if (getReceiverState() == ChannelState::CancellationProcessed &&
getSenderState() == ChannelState::CancellationProcessed) {
delete this;
}
}
ChannelState getReceiverState() {
return detail::getReceiverState(receiver_.get());
}
ChannelState getSenderState() {
return detail::getSenderState(sender_.get());
}
ChannelBridgePtr<TInputValue> receiver_;
ChannelBridgePtr<TOutputValue> sender_;
folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
TransformValueFunc transformValue_;
};
/**
* This subclass is used for simple transformations triggered by a call to the
* transform function (i.e. with a single input receiver and no initialization
* function).
*/
template <
typename TInputValue,
typename TOutputValue,
typename TransformValueFunc>
class TransformProcessor : public TransformProcessorBase<
TInputValue,
TOutputValue,
TransformValueFunc> {
public:
using Base =
TransformProcessorBase<TInputValue, TOutputValue, TransformValueFunc>;
using Base::Base;
private:
/**
* Process cancellation for the input receiver.
*/
folly::coro::Task<void> processReceiverCancelled(
CloseResult closeResult, bool /* noRetriesAllowed */) override {
CHECK_EQ(this->getReceiverState(), ChannelState::CancellationTriggered);
this->receiver_ = nullptr;
if (this->getSenderState() == ChannelState::Active) {
if (closeResult.exception.has_value()) {
this->sender_->senderClose(std::move(closeResult.exception.value()));
} else {
this->sender_->senderClose();
}
}
this->maybeDelete();
co_return;
}
};
/**
* This subclass is used for resumable transformations triggered by a call to
* the resumableTransform function.
*/
template <
typename TInputValue,
typename TOutputValue,
typename InitializeTransformFunc,
typename TransformValueFunc>
class ResumableTransformProcessor : public TransformProcessorBase<
TInputValue,
TOutputValue,
TransformValueFunc> {
public:
using Base =
TransformProcessorBase<TInputValue, TOutputValue, TransformValueFunc>;
ResumableTransformProcessor(
Sender<TOutputValue> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
InitializeTransformFunc initializeTransform,
TransformValueFunc transformValue)
: Base(std::move(sender), std::move(executor), std::move(transformValue)),
initializeTransform_(std::move(initializeTransform)) {}
void initialize() {
this->executor_->add([=]() {
runOperationWithSenderCancellation(
this->executor_,
this->sender_,
false /* currentlyWaiting */,
this /* channelCallbackToRestore */,
initializeImpl());
});
}
private:
/**
* Runs the user-provided initialization function to get a set of initial
* values and a receiver to continue transforming. This is called when the
* resumableTransform is created, and again whenever the previous input
* receiver closed without an exception.
*/
folly::coro::Task<void> initializeImpl() {
auto cancelToken = co_await folly::coro::co_current_cancellation_token;
auto initializeResult =
co_await folly::coro::co_awaitTry(initializeTransform_());
if (initializeResult.hasException()) {
co_await processReceiverCancelled(
initializeResult.template hasException<OnClosedException>()
? CloseResult()
: CloseResult(std::move(initializeResult.exception())),
true /* noRetriesAllowed */);
co_return;
}
auto [initialValues, inputReceiver] = std::move(initializeResult.value());
CHECK(inputReceiver)
<< "The initialize function of a resumableTransform returned an "
"invalid receiver.";
if (cancelToken.isCancellationRequested()) {
// The sender was closed before we finished running the initialization
// function. We will ignore the results from that function and proceed
// to process cancellation for the receiver.
co_await processReceiverCancelled(
CloseResult(), true /* noRetriesAllowed */);
co_return;
}
for (auto& initialValue : initialValues) {
this->sender_->senderPush(std::move(initialValue));
}
co_await this->startTransformImpl(std::move(inputReceiver));
}
/**
* Process cancellation for the input receiver.
*/
folly::coro::Task<void> processReceiverCancelled(
CloseResult closeResult, bool noRetriesAllowed) override {
if (this->receiver_) {
CHECK_EQ(this->getReceiverState(), ChannelState::CancellationTriggered);
this->receiver_ = nullptr;
}
auto cancelToken = co_await folly::coro::co_current_cancellation_token;
if (this->getSenderState() == ChannelState::Active &&
!cancelToken.isCancellationRequested()) {
if (closeResult.exception.has_value()) {
// We were closed with an exception. We will close the sender with that
// exception.
this->sender_->senderClose(std::move(closeResult.exception.value()));
} else if (noRetriesAllowed) {
// We were closed without an exception, but no retries are allowed. This
// means that the user-provided initialization function threw an
// OnClosedException.
this->sender_->senderClose();
} else {
// We were not closed with an exception. We will re-run the user's
// initialization function and resume the resumableTransform.
co_await initializeImpl();
co_return;
}
}
this->maybeDelete();
}
InitializeTransformFunc initializeTransform_;
};
} // namespace detail
template <
typename TReceiver,
typename TransformValueFunc,
typename TInputValue,
typename TOutputValue>
Receiver<TOutputValue> transform(
TReceiver inputReceiver,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
TransformValueFunc transformValue) {
auto [outputReceiver, outputSender] = Channel<TOutputValue>::create();
using TProcessor =
detail::TransformProcessor<TInputValue, TOutputValue, TransformValueFunc>;
auto* processor = new TProcessor(
std::move(outputSender), std::move(executor), std::move(transformValue));
processor->startTransform(std::move(inputReceiver));
return std::move(outputReceiver);
}
template <
typename InitializeTransformFunc,
typename TransformValueFunc,
typename TReceiver,
typename TInputValue,
typename TOutputValue>
Receiver<TOutputValue> resumableTransform(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
InitializeTransformFunc initializeTransformFunc,
TransformValueFunc transformValue) {
auto [outputReceiver, outputSender] = Channel<TOutputValue>::create();
using TProcessor = detail::ResumableTransformProcessor<
TInputValue,
TOutputValue,
InitializeTransformFunc,
TransformValueFunc>;
auto* processor = new TProcessor(
std::move(outputSender),
executor,
std::move(initializeTransformFunc),
std::move(transformValue));
processor->initialize();
return std::move(outputReceiver);
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
namespace folly {
namespace channels {
/**
* Returns an output receiver that applies a given transformation function to
* each value from an input receiver.
*
* The TransformValue function takes a Try<TInputValue>, and returns a
* folly::coro::AsyncGenerator<TOutputValue>.
*
* - If the TransformValue function yields one or more output values, those
* output values are sent to the output receiver.
*
* - If the TransformValue function throws an OnClosedException, the output
* receiver is closed (without an exception).
*
* - If the TransformValue function throws any other type of exception, the
* output receiver is closed with that exception.
*
* If the input receiver was closed, the TransformValue function is called with
* a Try containing an exception (either OnClosedException if the input receiver
* was closed without an exception, or the closure exception if the input
* receiver was closed with an exception). In this case, regardless of what the
* TransformValue function returns, the output receiver will be closed
* (potentially after receiving the last output values the TransformValue
* function returned, if any).
*
* @param inputReceiver: The input receiver.
*
* @param executor: A folly::SequencedExecutor used to transform the values.
*
* @param transformValue: A function as described above.
*
* Example:
*
* // Function that returns a receiver
* Receiver<int> getInputReceiver();
*
* // Function that returns an executor
* folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
*
* Receiver<std::string> outputReceiver = transform(
* getInputReceiver(),
* getExecutor(),
* [](folly::Try<int> try) -> folly::coro::AsyncGenerator<std::string&&> {
* co_yield folly::to<std::string>(try.value());
* });
*/
template <
typename TReceiver,
typename TransformValueFunc,
typename TInputValue = typename TReceiver::ValueType,
typename TOutputValue = typename folly::invoke_result_t<
TransformValueFunc,
folly::Try<TInputValue>>::value_type>
Receiver<TOutputValue> transform(
TReceiver inputReceiver,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
TransformValueFunc transformValue);
/**
* This function is similar to the above transform function. However, instead of
* taking a single input receiver, it takes an initialization function that
* returns a std::pair<std::vector<TOutputValue>, Receiver<TInputValue>>.
*
* - If the InitializeTransform function returns successfully, the vector's
* output values will be immediately sent to the output receiver. The input
* receiver is then processed as described in the transform function's
* documentation, until it is closed (without an exception). At that point,
* the InitializationTransform is re-run, and the transform begins anew.
*
* - If the InitializeTransform function throws an OnClosedException, the
* output receiver is closed (with no exception).
*
* - If the InitializeTransform function throws any other type of exception,
* the output receiver is closed with that exception.
*
* - If the TransformValue function throws any exception other than
* OnClosedException, the output receiver is closed with that exception.
*
* @param executor: A folly::SequencedExecutor used to transform the values.
*
* @param initializeTransform: The InitializeTransform function as described
* above.
*
* @param transformValue: A function as described above.
*
* Example:
*
* // Function that returns a receiver
* Receiver<int> getInputReceiver();
*
* // Function that returns an executor
* folly::Executor::KeepAlive<folly::SequencedExecutor> getExecutor();
*
* Receiver<std::string> outputReceiver = transform(
* getExecutor(),
* []() -> folly::coro::Task<
* std::pair<std::vector<std::string>, Receiver<int>> {
* co_return std::make_pair(
* std::vector<std::string>({"Initialized"}),
* getInputReceiver());
* },
* [](folly::Try<int> try) -> folly::coro::AsyncGenerator<std::string&&> {
* co_yield folly::to<std::string>(try.value());
* });
*
*/
template <
typename InitializeTransformFunc,
typename TransformValueFunc,
typename TReceiver = typename folly::invoke_result_t<
InitializeTransformFunc>::StorageType::second_type,
typename TInputValue = typename TReceiver::ValueType,
typename TOutputValue = typename folly::invoke_result_t<
TransformValueFunc,
folly::Try<TInputValue>>::value_type>
Receiver<TOutputValue> resumableTransform(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
InitializeTransformFunc initializeTransform,
TransformValueFunc transformValue);
/**
* An OnClosedException passed to a transform callback indicates that the input
* channel was closed. An OnClosedException can also be thrown by a transform
* callback, which will close the output channel.
*/
struct OnClosedException : public std::exception {
const char* what() const noexcept override {
return "A transform has closed the channel.";
}
};
} // namespace channels
} // namespace folly
#include <folly/experimental/channels/Transform-inl.h>
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/Try.h>
#include <folly/experimental/channels/detail/AtomicQueue.h>
namespace folly {
namespace channels {
namespace detail {
class ChannelBridgeBase {};
class IChannelCallback {
public:
virtual ~IChannelCallback() = default;
virtual void consume(ChannelBridgeBase* bridge) = 0;
virtual void canceled(ChannelBridgeBase* bridge) = 0;
};
using SenderQueue = typename folly::channels::detail::Queue<folly::Unit>;
template <typename TValue>
using ReceiverQueue =
typename folly::channels::detail::Queue<folly::Try<TValue>>;
template <typename TValue>
class ChannelBridge : public ChannelBridgeBase {
public:
struct Deleter {
void operator()(ChannelBridge<TValue>* ptr) { ptr->decref(); }
};
using Ptr = std::unique_ptr<ChannelBridge<TValue>, Deleter>;
static Ptr create() { return Ptr(new ChannelBridge<TValue>()); }
Ptr copy() {
auto refCount = refCount_.fetch_add(1, std::memory_order_relaxed);
DCHECK(refCount > 0);
return Ptr(this);
}
// These should only be called from the sender thread
template <typename U = TValue>
void senderPush(U&& value) {
receiverQueue_.push(
folly::Try<TValue>(std::forward<U>(value)),
static_cast<ChannelBridgeBase*>(this));
}
bool senderWait(IChannelCallback* callback) {
return senderQueue_.wait(callback, static_cast<ChannelBridgeBase*>(this));
}
IChannelCallback* cancelSenderWait() { return senderQueue_.cancelCallback(); }
void senderClose() {
if (!isSenderClosed()) {
receiverQueue_.push(
folly::Try<TValue>(), static_cast<ChannelBridgeBase*>(this));
senderQueue_.close(static_cast<ChannelBridgeBase*>(this));
}
}
void senderClose(folly::exception_wrapper ex) {
if (!isSenderClosed()) {
receiverQueue_.push(
folly::Try<TValue>(std::move(ex)),
static_cast<ChannelBridgeBase*>(this));
senderQueue_.close(static_cast<ChannelBridgeBase*>(this));
}
}
bool isSenderClosed() { return senderQueue_.isClosed(); }
SenderQueue senderGetValues() {
return senderQueue_.getMessages(static_cast<ChannelBridgeBase*>(this));
}
// These should only be called from the receiver thread
void receiverCancel() {
if (!isReceiverCancelled()) {
senderQueue_.push(folly::Unit(), static_cast<ChannelBridgeBase*>(this));
receiverQueue_.close(static_cast<ChannelBridgeBase*>(this));
}
}
bool isReceiverCancelled() { return receiverQueue_.isClosed(); }
bool receiverWait(IChannelCallback* callback) {
return receiverQueue_.wait(callback, static_cast<ChannelBridgeBase*>(this));
}
IChannelCallback* cancelReceiverWait() {
return receiverQueue_.cancelCallback();
}
ReceiverQueue<TValue> receiverGetValues() {
return receiverQueue_.getMessages(static_cast<ChannelBridgeBase*>(this));
}
private:
using ReceiverAtomicQueue = typename folly::channels::detail::
AtomicQueue<IChannelCallback, folly::Try<TValue>>;
using SenderAtomicQueue = typename folly::channels::detail::
AtomicQueue<IChannelCallback, folly::Unit>;
void decref() {
if (refCount_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete this;
}
}
ReceiverAtomicQueue receiverQueue_;
SenderAtomicQueue senderQueue_;
std::atomic<int8_t> refCount_{1};
};
template <typename TValue>
using ChannelBridgePtr = typename ChannelBridge<TValue>::Ptr;
template <typename TValue>
struct ChannelBridgeHash {
using is_transparent = std::true_type;
bool operator()(ChannelBridgePtr<TValue> const& arg) const {
return std::hash<ChannelBridgePtr<TValue>>()(arg);
}
bool operator()(ChannelBridge<TValue>* const& arg) const {
return std::hash<ChannelBridge<TValue>*>()(arg);
}
};
template <typename TValue>
struct ChannelBridgeEqual {
using is_transparent = std::true_type;
bool operator()(
ChannelBridgePtr<TValue> const& lhs,
ChannelBridgePtr<TValue> const& rhs) const {
return lhs.get() == rhs.get();
}
bool operator()(
ChannelBridge<TValue>* const& lhs,
ChannelBridgePtr<TValue> const& rhs) const {
return lhs == rhs.get();
}
bool operator()(
ChannelBridgePtr<TValue> const& lhs,
ChannelBridge<TValue>* const& rhs) const {
return lhs.get() == rhs;
}
};
} // namespace detail
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <optional>
#include <folly/ExceptionWrapper.h>
#include <folly/Function.h>
#include <folly/ScopeGuard.h>
#include <folly/executors/SequencedExecutor.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/coro/Task.h>
namespace folly {
namespace channels {
namespace detail {
struct CloseResult {
CloseResult() {}
explicit CloseResult(folly::exception_wrapper _exception)
: exception(std::move(_exception)) {}
std::optional<folly::exception_wrapper> exception;
};
enum class ChannelState {
Active,
CancellationTriggered,
CancellationProcessed
};
template <typename TSender>
ChannelState getSenderState(TSender* sender) {
if (sender == nullptr) {
return ChannelState::CancellationProcessed;
} else if (sender->isSenderClosed()) {
return ChannelState::CancellationTriggered;
} else {
return ChannelState::Active;
}
}
template <typename TReceiver>
ChannelState getReceiverState(TReceiver* receiver) {
if (receiver == nullptr) {
return ChannelState::CancellationProcessed;
} else if (receiver->isReceiverCancelled()) {
return ChannelState::CancellationTriggered;
} else {
return ChannelState::Active;
}
}
inline std::ostream& operator<<(std::ostream& os, ChannelState state) {
switch (state) {
case ChannelState::Active:
return os << "Active";
case ChannelState::CancellationTriggered:
return os << "CancellationTriggered";
case ChannelState::CancellationProcessed:
return os << "CancellationProcessed";
default:
return os << "Should never be hit";
}
}
/**
* A cancellation callback that wraps an existing channel callback. When the
* callback is fired, this object will trigger cancellation on its cancellation
* source (in addition to firing the wrapped callback).
*/
template <typename TSender>
class SenderCancellationCallback : public IChannelCallback {
public:
explicit SenderCancellationCallback(
TSender& sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
IChannelCallback* channelCallback)
: sender_(sender),
executor_(std::move(executor)),
channelCallback_(channelCallback) {
if (channelCallback_ == nullptr) {
// The sender was already canceled runOperationWithSenderCancellation was
// even called. This means the cancelled callback already was fired, so
// we will not set the callback to fire here.
cancelSource_.requestCancellation();
return;
}
CHECK(sender_);
if (!sender_->senderWait(this)) {
// The sender was cancelled after runOperationWithSenderCancellation was
// called, but before we had a chance to start the operation. This means
// that the cancelled callback was never called. We will therefore set it
// to fire here, when the operation is complete.
cancelSource_.requestCancellation();
callbackToFire_.setValue(CallbackToFire::Consume);
}
}
folly::coro::Task<void> onTaskCompleted() {
if (!channelCallback_) {
co_return;
}
auto callbackToFire = std::optional<CallbackToFire>();
if (callbackToFire_.isFulfilled()) {
// The callback was fired.
callbackToFire = co_await callbackToFire_.getSemiFuture();
} else {
// The callback has not yet been fired.
if (!sender_->cancelSenderWait()) {
// The sender has been cancelled, but the callback has not been called
// yet. Wait for the callback to be called.
callbackToFire = co_await callbackToFire_.getSemiFuture();
} else if (!sender_->senderWait(channelCallback_)) {
// The sender was cancelled between the call to cancelSenderWait and
// the call to senderWait. This means that the cancelled callback was
// never called. We will therefore set it to fire here.
callbackToFire = CallbackToFire::Consume;
}
}
if (callbackToFire.has_value()) {
switch (callbackToFire.value()) {
case CallbackToFire::Consume:
channelCallback_->consume(sender_.get());
co_return;
case CallbackToFire::Canceled:
channelCallback_->canceled(sender_.get());
co_return;
}
}
// The sender has not yet been cancelled, and we are now back in the state
// where the sender is waiting on the user-provided callback. We are done.
}
/**
* Returns a cancellation token that will trigger when the sender
*/
folly::CancellationToken getCancellationToken() {
return cancelSource_.getToken();
}
/**
* Requests cancellation, and triggers the consume function on the callback
* if the callback was not previously triggered.
*/
void consume(ChannelBridgeBase*) override {
executor_->add([=]() {
cancelSource_.requestCancellation();
CHECK(!callbackToFire_.isFulfilled());
callbackToFire_.setValue(CallbackToFire::Consume);
});
}
/**
* Requests cancellation, and triggers the canceled function on the callback
* if the callback was not previously triggered.
*/
void canceled(ChannelBridgeBase*) override {
executor_->add([=]() {
cancelSource_.requestCancellation();
CHECK(!callbackToFire_.isFulfilled());
callbackToFire_.setValue(CallbackToFire::Canceled);
});
}
private:
enum class CallbackToFire { Consume, Canceled };
TSender& sender_;
folly::Executor::KeepAlive<folly::SequencedExecutor> executor_;
IChannelCallback* channelCallback_;
folly::CancellationSource cancelSource_;
folly::Promise<CallbackToFire> callbackToFire_;
};
/**
* Any object that produces an output receiver (transform, merge,
* MergeChannel, etc) will listen for a cancellation signal from that output
* receiver. Once the consumer of the output receiver stops consuming, a
* callback will be called that triggers these objects to start cleaning
* themselves up (and eventually destroy themselves).
*
* However, when one of these objects decides to run a user coroutine, they
* would like that user coroutine to be able to get notified when that
* cancellation signal is received. That allows the coroutine to stop any
* long-running operations quickly, rather than running a long time when the
* consumer of the output receiver no longer cares about the result.
*
* This function enables that behavior. It will run the provided operation
* coroutine. While that coroutine is running, it will listen to cancellation
* events from the output receiver (through its sender). If it receives a
* cancellation signal from the sender, it will trigger cancellation of the
* operation coroutine.
*
* Once the coroutine finishes, it will then call the given channel callback
* to notify it of the cancellation event (the same way that callback would
* have been notified if no coroutine had been started). It will also resume
* waiting on the channel callback.
*
* @param executor: The executor to run the coroutine on.
*
* @param sender: The sender to use to listen for cancellation. If this is
* null, we will assume that cancellation already occurred.
*
* @param alreadyStartedWaiting: Whether or not the caller already started
* listening for a cancellation signal from the output receiver. If so, this
* function will temporarily stop waiting with that callback (so it can listen
* for the cancellation signal to stop the coroutine).
*
* @param channelCallbackToRestore: The channel callback to restore once the
* coroutine operation is complete.
*
* @param operation: The operation to run.
*/
template <typename TSender>
void runOperationWithSenderCancellation(
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
TSender& sender,
bool alreadyStartedWaiting,
IChannelCallback* channelCallbackToRestore,
folly::coro::Task<void> operation) noexcept {
if (alreadyStartedWaiting && (!sender || !sender->cancelSenderWait())) {
// The output receiver was cancelled before starting this operation
// (indicating that the channel callback already ran).
channelCallbackToRestore = nullptr;
}
folly::coro::co_invoke(
[&sender,
executor,
channelCallbackToRestore,
operation = std::move(operation)]() mutable -> folly::coro::Task<void> {
auto senderCancellationCallback = SenderCancellationCallback(
sender, executor, channelCallbackToRestore);
auto result =
co_await folly::coro::co_awaitTry(folly::coro::co_withCancellation(
senderCancellationCallback.getCancellationToken(),
std::move(operation)));
if (result.hasException()) {
LOG(FATAL) << fmt::format(
"Unexpected exception when running coroutine operation with "
"sender cancellation: {}",
result.exception().what());
}
co_await senderCancellationCallback.onTaskCompleted();
})
.scheduleOn(executor)
.start();
}
} // namespace detail
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/executors/ManualExecutor.h>
#include <folly/experimental/channels/Channel.h>
#include <folly/experimental/channels/test/ChannelTestUtil.h>
#include <folly/portability/GTest.h>
namespace folly {
namespace channels {
using namespace testing;
class ChannelFixture : public Test,
public WithParamInterface<ConsumptionMode>,
public ChannelConsumerBase<int> {
protected:
ChannelFixture() : ChannelConsumerBase(GetParam()) {}
~ChannelFixture() override {
cancellationSource_.requestCancellation();
if (!continueConsuming_.isFulfilled()) {
continueConsuming_.setValue(false);
}
executor_.drain();
}
folly::Executor::KeepAlive<> getExecutor() override { return &executor_; }
void onNext(folly::Try<int> result) override { onNext_(std::move(result)); }
folly::ManualExecutor executor_;
StrictMock<MockNextCallback<int>> onNext_;
};
TEST_P(ChannelFixture, SingleWriteBeforeNext_ThenCancelled) {
auto [receiver, sender] = Channel<int>::create();
sender.write(1);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onCancelled());
startConsuming(std::move(receiver));
executor_.drain();
EXPECT_FALSE(sender.isReceiverCancelled());
cancellationSource_.requestCancellation();
executor_.drain();
EXPECT_TRUE(sender.isReceiverCancelled());
}
TEST_P(ChannelFixture, SingleWriteAfterNext_ThenCancelled) {
auto [receiver, sender] = Channel<int>::create();
startConsuming(std::move(receiver));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onCancelled());
sender.write(1);
executor_.drain();
EXPECT_FALSE(sender.isReceiverCancelled());
cancellationSource_.requestCancellation();
executor_.drain();
EXPECT_TRUE(sender.isReceiverCancelled());
}
TEST_P(ChannelFixture, MultipleWrites_ThenStopConsumingByReturningFalse) {
auto [receiver, sender] = Channel<int>::create();
startConsuming(std::move(receiver));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
sender.write(1);
sender.write(2);
executor_.drain();
sender.write(3);
sender.write(4);
continueConsuming_ = folly::SharedPromise<bool>();
continueConsuming_.setValue(false);
executor_.drain();
}
TEST_P(
ChannelFixture,
MultipleWrites_ThenStopConsumingByThrowingOperationCancelled) {
auto [receiver, sender] = Channel<int>::create();
startConsuming(std::move(receiver));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
sender.write(1);
sender.write(2);
executor_.drain();
sender.write(3);
sender.write(4);
continueConsuming_ = folly::SharedPromise<bool>();
continueConsuming_.setException(folly::OperationCancelled());
executor_.drain();
}
TEST_P(ChannelFixture, Close_NoException_BeforeSubscribe) {
auto [receiver, sender] = Channel<int>::create();
std::move(sender).close();
EXPECT_CALL(onNext_, onClosed());
startConsuming(std::move(receiver));
executor_.drain();
}
TEST_P(ChannelFixture, Close_NoException_AfterSubscribeAndWrite) {
auto [receiver, sender] = Channel<int>::create();
sender.write(1);
std::move(sender).close();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onClosed());
startConsuming(std::move(receiver));
executor_.drain();
}
TEST_P(ChannelFixture, Close_DueToDestruction_BeforeSubscribe) {
auto [receiver, sender] = Channel<int>::create();
{ auto toDestroy = std::move(sender); }
EXPECT_CALL(onNext_, onClosed());
startConsuming(std::move(receiver));
executor_.drain();
}
TEST_P(ChannelFixture, Close_DueToDestruction_AfterSubscribeAndWrite) {
auto [receiver, sender] = Channel<int>::create();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onClosed());
startConsuming(std::move(receiver));
sender.write(1);
{ auto toDestroy = std::move(sender); }
executor_.drain();
}
TEST_P(ChannelFixture, Close_Exception_BeforeSubscribe) {
auto [receiver, sender] = Channel<int>::create();
std::move(sender).close(std::runtime_error("Error"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
startConsuming(std::move(receiver));
executor_.drain();
}
TEST_P(ChannelFixture, Close_Exception_AfterSubscribeAndWrite) {
auto [receiver, sender] = Channel<int>::create();
sender.write(1);
std::move(sender).close(std::runtime_error("Error"));
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
startConsuming(std::move(receiver));
executor_.drain();
}
TEST_P(ChannelFixture, CancellationRespected) {
auto [receiver, sender] = Channel<int>::create();
EXPECT_CALL(onNext_, onValue(1));
continueConsuming_ = folly::SharedPromise<bool>();
sender.write(1);
startConsuming(std::move(receiver));
executor_.drain();
EXPECT_FALSE(sender.isReceiverCancelled());
cancellationSource_.requestCancellation();
executor_.drain();
EXPECT_TRUE(sender.isReceiverCancelled());
}
INSTANTIATE_TEST_CASE_P(
Channel_Coro_WithTry,
ChannelFixture,
testing::Values(ConsumptionMode::CoroWithTry));
INSTANTIATE_TEST_CASE_P(
Channel_Coro_WithoutTry,
ChannelFixture,
testing::Values(ConsumptionMode::CoroWithoutTry));
INSTANTIATE_TEST_CASE_P(
Channel_Callback_WithHandle,
ChannelFixture,
testing::Values(ConsumptionMode::CallbackWithHandle));
INSTANTIATE_TEST_CASE_P(
Channel_Callback_WithHandleList,
ChannelFixture,
testing::Values(ConsumptionMode::CallbackWithHandleList));
class ChannelFixtureStress : public Test,
public WithParamInterface<ConsumptionMode> {
protected:
ChannelFixtureStress()
: producer_(std::make_unique<StressTestProducer<int>>(
[value = 0]() mutable { return value++; })),
consumer_(std::make_unique<StressTestConsumer<int>>(
GetParam(), [lastReceived = -1](int value) mutable {
EXPECT_EQ(value, ++lastReceived);
})) {}
static constexpr std::chrono::milliseconds kTestTimeout =
std::chrono::milliseconds{5000};
std::unique_ptr<StressTestProducer<int>> producer_;
std::unique_ptr<StressTestConsumer<int>> consumer_;
};
TEST_P(ChannelFixtureStress, Close_NoException) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
consumer_->startConsuming(std::move(receiver));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
producer_->stopProducing();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::NoException);
}
TEST_P(ChannelFixtureStress, Close_Exception) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::runtime_error("Error"));
consumer_->startConsuming(std::move(receiver));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
producer_->stopProducing();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Exception);
}
TEST_P(ChannelFixtureStress, Cancelled) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
consumer_->startConsuming(std::move(receiver));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
consumer_->cancel();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Cancelled);
producer_->stopProducing();
}
TEST_P(ChannelFixtureStress, Close_NoException_ThenCancelledImmediately) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
consumer_->startConsuming(std::move(receiver));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
producer_->stopProducing();
consumer_->cancel();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
TEST_P(ChannelFixtureStress, Cancelled_ThenClosedImmediately_NoException) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
consumer_->startConsuming(std::move(receiver));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
consumer_->cancel();
producer_->stopProducing();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
INSTANTIATE_TEST_CASE_P(
Channel_Coro_WithTry,
ChannelFixtureStress,
testing::Values(ConsumptionMode::CoroWithTry));
INSTANTIATE_TEST_CASE_P(
Channel_Coro_WithoutTry,
ChannelFixtureStress,
testing::Values(ConsumptionMode::CoroWithoutTry));
INSTANTIATE_TEST_CASE_P(
Channel_Callback_WithHandle,
ChannelFixtureStress,
testing::Values(ConsumptionMode::CallbackWithHandle));
INSTANTIATE_TEST_CASE_P(
Channel_Callback_WithHandleList,
ChannelFixtureStress,
testing::Values(ConsumptionMode::CallbackWithHandleList));
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/executors/CPUThreadPoolExecutor.h>
#include <folly/experimental/channels/ConsumeChannel.h>
#include <folly/experimental/coro/DetachOnCancel.h>
#include <folly/experimental/coro/Sleep.h>
#include <folly/futures/SharedPromise.h>
#include <folly/portability/GMock.h>
namespace folly {
namespace channels {
template <typename T, typename... Others>
std::vector<T> toVector(T firstItem, Others... items) {
std::vector<T> itemsVector;
itemsVector.push_back(std::move(firstItem));
[[maybe_unused]] int dummy[] = {
(itemsVector.push_back(std::move(items)), 0)...};
return itemsVector;
}
template <typename TValue>
class MockNextCallback {
public:
void operator()(folly::Try<TValue> result) {
if (result.hasValue()) {
onValue(result.value());
} else if (result.template hasException<folly::OperationCancelled>()) {
onCancelled();
} else if (result.template hasException<std::runtime_error>()) {
onRuntimeError(result.exception().what().toStdString());
} else if (result.hasException()) {
LOG(FATAL) << "Unexpected exception: " << result.exception().what();
} else {
onClosed();
}
}
MOCK_METHOD1_T(onValue, void(TValue));
MOCK_METHOD0(onClosed, void());
MOCK_METHOD0(onCancelled, void());
MOCK_METHOD1(onRuntimeError, void(std::string));
};
enum class ConsumptionMode {
CoroWithTry,
CoroWithoutTry,
CallbackWithHandle,
CallbackWithHandleList
};
template <typename TValue>
class ChannelConsumerBase {
public:
explicit ChannelConsumerBase(ConsumptionMode mode) : mode_(mode) {
continueConsuming_.setValue(true);
}
ChannelConsumerBase(ChannelConsumerBase&&) = default;
ChannelConsumerBase& operator=(ChannelConsumerBase&&) = default;
virtual ~ChannelConsumerBase() = default;
virtual folly::Executor::KeepAlive<> getExecutor() = 0;
virtual void onNext(folly::Try<TValue> result) = 0;
void startConsuming(Receiver<TValue> receiver) {
folly::coro::co_withCancellation(
cancellationSource_.getToken(), processValuesCoro(std::move(receiver)))
.scheduleOn(getExecutor())
.start();
}
folly::coro::Task<void> processValuesCoro(Receiver<TValue> receiver) {
auto executor = co_await folly::coro::co_current_executor;
if (mode_ == ConsumptionMode::CoroWithTry ||
mode_ == ConsumptionMode::CoroWithoutTry) {
do {
folly::Try<TValue> resultTry;
if (mode_ == ConsumptionMode::CoroWithTry) {
resultTry = co_await folly::coro::co_awaitTry(receiver.next());
} else if (mode_ == ConsumptionMode::CoroWithoutTry) {
try {
auto result = co_await receiver.next();
if (result.has_value()) {
resultTry = folly::Try<TValue>(result.value());
} else {
resultTry = folly::Try<TValue>();
}
} catch (const std::exception& ex) {
resultTry = folly::Try<TValue>(
folly::exception_wrapper(std::current_exception(), ex));
} catch (...) {
resultTry = folly::Try<TValue>(
folly::exception_wrapper(std::current_exception()));
}
} else {
LOG(FATAL) << "Unknown consumption mode";
}
bool hasValue = resultTry.hasValue();
onNext(std::move(resultTry));
if (!hasValue) {
co_return;
}
} while (co_await folly::coro::detachOnCancel(
continueConsuming_.getSemiFuture()));
} else if (mode_ == ConsumptionMode::CallbackWithHandle) {
auto callbackHandle = consumeChannelWithCallback(
std::move(receiver),
getExecutor(),
[=](folly::Try<TValue> resultTry) -> folly::coro::Task<bool> {
onNext(std::move(resultTry));
co_return co_await folly::coro::detachOnCancel(
continueConsuming_.getSemiFuture());
});
cancelCallback_ = std::make_unique<folly::CancellationCallback>(
co_await folly::coro::co_current_cancellation_token,
[=, handle = std::move(callbackHandle)]() mutable {
handle.reset();
});
} else if (mode_ == ConsumptionMode::CallbackWithHandleList) {
auto callbackHandleList = ChannelCallbackHandleList();
consumeChannelWithCallback(
std::move(receiver),
getExecutor(),
[=](folly::Try<TValue> resultTry) -> folly::coro::Task<bool> {
onNext(std::move(resultTry));
co_return co_await folly::coro::detachOnCancel(
continueConsuming_.getSemiFuture());
},
callbackHandleList);
cancelCallback_ = std::make_unique<folly::CancellationCallback>(
co_await folly::coro::co_current_cancellation_token,
[=, handleList = std::move(callbackHandleList)]() mutable {
executor->add([handleList = std::move(handleList)]() mutable {
handleList.clear();
});
});
} else {
LOG(FATAL) << "Unknown consumption mode";
}
}
protected:
ConsumptionMode mode_;
folly::CancellationSource cancellationSource_;
folly::SharedPromise<bool> continueConsuming_;
std::unique_ptr<folly::CancellationCallback> cancelCallback_;
};
enum class CloseType { NoException, Exception, Cancelled };
template <typename TValue>
class StressTestConsumer : public ChannelConsumerBase<TValue> {
public:
StressTestConsumer(
ConsumptionMode mode, folly::Function<void(TValue)> onValue)
: ChannelConsumerBase<TValue>(mode),
executor_(std::make_unique<folly::CPUThreadPoolExecutor>(1)),
onValue_(std::move(onValue)) {}
StressTestConsumer(StressTestConsumer&&) = delete;
StressTestConsumer&& operator=(StressTestConsumer&&) = delete;
~StressTestConsumer() override {
this->cancellationSource_.requestCancellation();
if (!this->continueConsuming_.isFulfilled()) {
this->continueConsuming_.setValue(false);
}
executor_.reset();
}
folly::Executor::KeepAlive<> getExecutor() override {
return executor_.get();
}
void onNext(folly::Try<TValue> result) override {
if (result.hasValue()) {
onValue_(std::move(result.value()));
} else if (result.template hasException<folly::OperationCancelled>()) {
closedType_.setValue(CloseType::Cancelled);
} else if (result.hasException()) {
EXPECT_TRUE(result.template hasException<std::runtime_error>());
closedType_.setValue(CloseType::Exception);
} else {
closedType_.setValue(CloseType::NoException);
}
}
void cancel() { this->cancellationSource_.requestCancellation(); }
folly::SemiFuture<CloseType> waitForClose() {
return closedType_.getSemiFuture();
}
private:
std::unique_ptr<folly::CPUThreadPoolExecutor> executor_;
folly::Function<void(TValue)> onValue_;
folly::Promise<CloseType> closedType_;
};
template <typename TValue>
class StressTestProducer {
public:
explicit StressTestProducer(folly::Function<TValue()> getNextValue)
: executor_(std::make_unique<folly::CPUThreadPoolExecutor>(1)),
getNextValue_(std::move(getNextValue)) {}
StressTestProducer(StressTestProducer&&) = delete;
StressTestProducer&& operator=(StressTestProducer&&) = delete;
~StressTestProducer() {
if (executor_) {
stopProducing();
executor_.reset();
}
}
void startProducing(
Sender<TValue> sender,
std::optional<folly::exception_wrapper> closeException) {
auto produceTask = folly::coro::co_invoke(
[=,
sender = std::move(sender),
ex = std::move(closeException)]() mutable -> folly::coro::Task<void> {
for (int i = 1; !stopped_.load(std::memory_order_relaxed); i++) {
if (i % 1000 == 0) {
co_await folly::coro::sleep(std::chrono::milliseconds(100));
}
sender.write(getNextValue_());
}
if (ex.has_value()) {
std::move(sender).close(std::move(ex.value()));
} else {
std::move(sender).close();
}
co_return;
});
std::move(produceTask).scheduleOn(executor_.get()).start();
}
void stopProducing() { stopped_.store(true); }
private:
std::unique_ptr<folly::CPUThreadPoolExecutor> executor_;
folly::Function<TValue()> getNextValue_;
std::atomic<bool> stopped_{false};
};
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/executors/ManualExecutor.h>
#include <folly/executors/SerialExecutor.h>
#include <folly/experimental/channels/ConsumeChannel.h>
#include <folly/experimental/channels/FanoutChannel.h>
#include <folly/experimental/channels/test/ChannelTestUtil.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
namespace folly {
namespace channels {
using namespace testing;
class FanoutChannelFixture : public Test {
protected:
FanoutChannelFixture() {}
~FanoutChannelFixture() { executor_.drain(); }
using TCallback = StrictMock<MockNextCallback<int>>;
std::pair<ChannelCallbackHandle, TCallback*> processValues(
Receiver<int> receiver) {
auto callback = std::make_unique<TCallback>();
auto callbackPtr = callback.get();
auto handle = consumeChannelWithCallback(
std::move(receiver),
&executor_,
[cbk = std::move(callback)](
folly::Try<int> resultTry) mutable -> folly::coro::Task<bool> {
(*cbk)(std::move(resultTry));
co_return true;
});
return std::make_pair(std::move(handle), callbackPtr);
}
StrictMock<MockNextCallback<std::string>> createCallback() {
return StrictMock<MockNextCallback<std::string>>();
}
folly::ManualExecutor executor_;
};
TEST_F(FanoutChannelFixture, ReceiveValue_FanoutBroadcastsValues) {
auto [inputReceiver, sender] = Channel<int>::create();
auto fanoutChannel =
createFanoutChannel(std::move(inputReceiver), &executor_);
auto [handle1, callback1] = processValues(fanoutChannel.getNewReceiver(
[] { return toVector(100); } /* getInitialValues */));
auto [handle2, callback2] = processValues(fanoutChannel.getNewReceiver(
[] { return toVector(200); } /* getInitialValues */));
EXPECT_CALL(*callback1, onValue(100));
EXPECT_CALL(*callback2, onValue(200));
EXPECT_CALL(*callback1, onValue(1));
EXPECT_CALL(*callback2, onValue(1));
EXPECT_CALL(*callback1, onValue(2));
EXPECT_CALL(*callback2, onValue(2));
EXPECT_CALL(*callback1, onClosed());
EXPECT_CALL(*callback2, onClosed());
executor_.drain();
EXPECT_TRUE(fanoutChannel.anyReceivers());
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close();
executor_.drain();
EXPECT_FALSE(fanoutChannel.anyReceivers());
}
TEST_F(FanoutChannelFixture, InputClosed_AllOutputReceiversClose) {
auto [inputReceiver, sender] = Channel<int>::create();
auto fanoutChannel =
createFanoutChannel(std::move(inputReceiver), &executor_);
auto [handle1, callback1] = processValues(fanoutChannel.getNewReceiver());
auto [handle2, callback2] = processValues(fanoutChannel.getNewReceiver());
EXPECT_CALL(*callback1, onValue(1));
EXPECT_CALL(*callback2, onValue(1));
EXPECT_CALL(*callback1, onClosed());
EXPECT_CALL(*callback2, onClosed());
executor_.drain();
EXPECT_TRUE(fanoutChannel.anyReceivers());
sender.write(1);
executor_.drain();
std::move(sender).close();
executor_.drain();
EXPECT_FALSE(fanoutChannel.anyReceivers());
}
TEST_F(FanoutChannelFixture, InputThrows_AllOutputReceiversGetException) {
auto [inputReceiver, sender] = Channel<int>::create();
auto fanoutChannel =
createFanoutChannel(std::move(inputReceiver), &executor_);
auto [handle1, callback1] = processValues(fanoutChannel.getNewReceiver());
auto [handle2, callback2] = processValues(fanoutChannel.getNewReceiver());
EXPECT_CALL(*callback1, onValue(1));
EXPECT_CALL(*callback2, onValue(1));
EXPECT_CALL(*callback1, onRuntimeError("std::runtime_error: Error"));
EXPECT_CALL(*callback2, onRuntimeError("std::runtime_error: Error"));
executor_.drain();
EXPECT_TRUE(fanoutChannel.anyReceivers());
sender.write(1);
executor_.drain();
std::move(sender).close(std::runtime_error("Error"));
executor_.drain();
EXPECT_FALSE(fanoutChannel.anyReceivers());
}
TEST_F(FanoutChannelFixture, ReceiversCancelled) {
auto [inputReceiver, sender] = Channel<int>::create();
auto fanoutChannel =
createFanoutChannel(std::move(inputReceiver), &executor_);
auto [handle1, callback1] = processValues(fanoutChannel.getNewReceiver());
auto [handle2, callback2] = processValues(fanoutChannel.getNewReceiver());
EXPECT_CALL(*callback1, onValue(1));
EXPECT_CALL(*callback2, onValue(1));
EXPECT_CALL(*callback1, onCancelled());
EXPECT_CALL(*callback2, onValue(2));
EXPECT_CALL(*callback2, onCancelled());
executor_.drain();
EXPECT_TRUE(fanoutChannel.anyReceivers());
sender.write(1);
executor_.drain();
EXPECT_TRUE(fanoutChannel.anyReceivers());
handle1.reset();
sender.write(2);
executor_.drain();
EXPECT_TRUE(fanoutChannel.anyReceivers());
handle2.reset();
sender.write(3);
executor_.drain();
EXPECT_FALSE(fanoutChannel.anyReceivers());
std::move(sender).close();
executor_.drain();
EXPECT_FALSE(fanoutChannel.anyReceivers());
}
class FanoutChannelFixtureStress : public Test {
protected:
FanoutChannelFixtureStress()
: producer_(makeProducer()),
consumers_(toVector(makeConsumer(), makeConsumer(), makeConsumer())) {}
static std::unique_ptr<StressTestProducer<int>> makeProducer() {
return std::make_unique<StressTestProducer<int>>(
[value = 0]() mutable { return value++; });
}
static std::unique_ptr<StressTestConsumer<int>> makeConsumer() {
return std::make_unique<StressTestConsumer<int>>(
ConsumptionMode::CallbackWithHandle,
[lastReceived = -1](int value) mutable {
if (lastReceived == -1) {
lastReceived = value;
} else {
EXPECT_EQ(value, ++lastReceived);
}
});
}
static void sleepFor(std::chrono::milliseconds duration) {
/* sleep override */
std::this_thread::sleep_for(duration);
}
static constexpr std::chrono::milliseconds kTestTimeout =
std::chrono::milliseconds{10};
std::unique_ptr<StressTestProducer<int>> producer_;
std::vector<std::unique_ptr<StressTestConsumer<int>>> consumers_;
};
TEST_F(FanoutChannelFixtureStress, HandleClosed) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
folly::CPUThreadPoolExecutor fanoutChannelExecutor(1);
auto fanoutChannel = createFanoutChannel(
std::move(receiver),
folly::SerialExecutor::create(&fanoutChannelExecutor));
consumers_.at(0)->startConsuming(fanoutChannel.getNewReceiver());
consumers_.at(1)->startConsuming(fanoutChannel.getNewReceiver());
sleepFor(kTestTimeout / 3);
consumers_.at(2)->startConsuming(fanoutChannel.getNewReceiver());
sleepFor(kTestTimeout / 3);
consumers_.at(0)->cancel();
EXPECT_EQ(consumers_.at(0)->waitForClose().get(), CloseType::Cancelled);
sleepFor(kTestTimeout / 3);
std::move(fanoutChannel).close();
EXPECT_EQ(consumers_.at(1)->waitForClose().get(), CloseType::NoException);
EXPECT_EQ(consumers_.at(2)->waitForClose().get(), CloseType::NoException);
}
TEST_F(FanoutChannelFixtureStress, InputChannelClosed) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
folly::CPUThreadPoolExecutor fanoutChannelExecutor(1);
auto fanoutChannel = createFanoutChannel(
std::move(receiver),
folly::SerialExecutor::create(&fanoutChannelExecutor));
consumers_.at(0)->startConsuming(fanoutChannel.getNewReceiver());
consumers_.at(1)->startConsuming(fanoutChannel.getNewReceiver());
sleepFor(kTestTimeout / 3);
consumers_.at(2)->startConsuming(fanoutChannel.getNewReceiver());
sleepFor(kTestTimeout / 3);
consumers_.at(0)->cancel();
EXPECT_EQ(consumers_.at(0)->waitForClose().get(), CloseType::Cancelled);
sleepFor(kTestTimeout / 3);
producer_->stopProducing();
EXPECT_EQ(consumers_.at(1)->waitForClose().get(), CloseType::NoException);
EXPECT_EQ(consumers_.at(2)->waitForClose().get(), CloseType::NoException);
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/executors/ManualExecutor.h>
#include <folly/executors/SerialExecutor.h>
#include <folly/experimental/channels/ConsumeChannel.h>
#include <folly/experimental/channels/MergeChannel.h>
#include <folly/experimental/channels/test/ChannelTestUtil.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
namespace folly {
namespace channels {
using namespace testing;
class MergeChannelFixture : public Test {
protected:
MergeChannelFixture() {}
~MergeChannelFixture() { executor_.drain(); }
using TCallback = StrictMock<MockNextCallback<int>>;
ChannelCallbackHandle processValues(Receiver<int> receiver) {
return consumeChannelWithCallback(
std::move(receiver),
&executor_,
[=](folly::Try<int> resultTry) -> folly::coro::Task<bool> {
onNext_(std::move(resultTry));
co_return true;
});
}
folly::ManualExecutor executor_;
StrictMock<MockNextCallback<int>> onNext_;
};
TEST_F(MergeChannelFixture, ReceiveValues_ReturnMergedValues) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto [mergedReceiver, mergeChannel] =
createMergeChannel<int, std::string>(&executor_);
mergeChannel.addNewReceiver("sub1", std::move(receiver1));
mergeChannel.addNewReceiver("sub2", std::move(receiver2));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onValue(5));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2.write(2);
executor_.drain();
mergeChannel.addNewReceiver("sub3", std::move(receiver3));
mergeChannel.removeReceiver("sub2");
sender1.write(3);
sender2.write(4);
sender3.write(5);
executor_.drain();
std::move(mergeChannel).close();
executor_.drain();
}
TEST_F(
MergeChannelFixture,
ReceiveValues_NewInputReceiver_WithSameSubscriptionId) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2a, sender2a] = Channel<int>::create();
auto [receiver2b, sender2b] = Channel<int>::create();
auto [mergedReceiver, mergeChannel] =
createMergeChannel<int, std::string>(&executor_);
mergeChannel.addNewReceiver("sub1", std::move(receiver1));
mergeChannel.addNewReceiver("sub2", std::move(receiver2a));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onValue(5));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2a.write(2);
executor_.drain();
mergeChannel.addNewReceiver("sub2", std::move(receiver2b));
sender1.write(3);
sender2a.write(4);
sender2b.write(5);
executor_.drain();
std::move(mergeChannel).close();
executor_.drain();
}
TEST_F(
MergeChannelFixture,
ReceiveValues_NewInputReceiver_WithSameSubscriptionId_AfterClose) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2a, sender2a] = Channel<int>::create();
auto [receiver2b, sender2b] = Channel<int>::create();
auto [mergedReceiver, mergeChannel] =
createMergeChannel<int, std::string>(&executor_);
mergeChannel.addNewReceiver("sub1", std::move(receiver1));
mergeChannel.addNewReceiver("sub2", std::move(receiver2a));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onValue(5));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2a.write(2);
executor_.drain();
std::move(sender2a).close();
executor_.drain();
mergeChannel.addNewReceiver("sub2", std::move(receiver2b));
executor_.drain();
sender1.write(3);
sender2b.write(5);
executor_.drain();
std::move(mergeChannel).close();
executor_.drain();
}
TEST_F(MergeChannelFixture, OneInputClosed_ContinuesMerging) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto [mergedReceiver, mergeChannel] =
createMergeChannel<int, std::string>(&executor_);
mergeChannel.addNewReceiver("sub1", std::move(receiver1));
mergeChannel.addNewReceiver("sub2", std::move(receiver2));
mergeChannel.addNewReceiver("sub3", std::move(receiver3));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onValue(4));
EXPECT_CALL(onNext_, onValue(5));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2.write(2);
sender3.write(3);
std::move(sender3).close();
executor_.drain();
sender1.write(4);
sender2.write(5);
executor_.drain();
std::move(sender1).close();
std::move(sender2).close();
executor_.drain();
std::move(mergeChannel).close();
executor_.drain();
}
TEST_F(MergeChannelFixture, OneInputThrows_OutputClosedWithException) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto [mergedReceiver, mergeChannel] =
createMergeChannel<int, std::string>(&executor_);
mergeChannel.addNewReceiver("sub1", std::move(receiver1));
mergeChannel.addNewReceiver("sub2", std::move(receiver2));
mergeChannel.addNewReceiver("sub3", std::move(receiver3));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2.write(2);
sender3.write(3);
std::move(sender3).close(std::runtime_error("Error"));
executor_.drain();
sender1.write(4);
sender2.write(5);
std::move(sender1).close();
std::move(sender2).close();
executor_.drain();
}
TEST_F(MergeChannelFixture, Cancelled) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto [mergedReceiver, mergeChannel] =
createMergeChannel<int, std::string>(&executor_);
mergeChannel.addNewReceiver("sub1", std::move(receiver1));
mergeChannel.addNewReceiver("sub2", std::move(receiver2));
mergeChannel.addNewReceiver("sub3", std::move(receiver3));
executor_.drain();
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2.write(2);
sender3.write(3);
executor_.drain();
callbackHandle.reset();
executor_.drain();
sender1.write(4);
sender2.write(5);
sender3.write(6);
executor_.drain();
std::move(sender1).close();
std::move(sender2).close();
std::move(sender3).close();
executor_.drain();
}
struct ProducedValue {
int producerIndex;
int value;
};
class MergeChannelFixtureStress : public Test {
protected:
MergeChannelFixtureStress()
: producers_(toVector(makeProducer(0), makeProducer(1))),
consumer_(makeConsumer()) {}
static std::unique_ptr<StressTestProducer<ProducedValue>> makeProducer(
int index) {
return std::make_unique<StressTestProducer<ProducedValue>>(
[index, value = 0]() mutable {
return ProducedValue{index, value++};
});
}
static std::unique_ptr<StressTestConsumer<ProducedValue>> makeConsumer() {
return std::make_unique<StressTestConsumer<ProducedValue>>(
ConsumptionMode::CallbackWithHandle,
[lastReceived = toVector(-1, -1, -1)](ProducedValue value) mutable {
EXPECT_EQ(value.value, ++lastReceived[value.producerIndex]);
});
}
static void sleepFor(std::chrono::milliseconds duration) {
/* sleep override */
std::this_thread::sleep_for(duration);
}
static constexpr std::chrono::milliseconds kTestTimeout =
std::chrono::milliseconds{5000};
std::vector<std::unique_ptr<StressTestProducer<ProducedValue>>> producers_;
std::unique_ptr<StressTestConsumer<ProducedValue>> consumer_;
};
TEST_F(MergeChannelFixtureStress, HandleClosed) {
folly::CPUThreadPoolExecutor mergeChannelExecutor(1);
auto [mergeReceiver, mergeChannel] = createMergeChannel<ProducedValue, int>(
folly::SerialExecutor::create(&mergeChannelExecutor));
consumer_->startConsuming(std::move(mergeReceiver));
auto [receiver0, sender0] = Channel<ProducedValue>::create();
producers_.at(0)->startProducing(std::move(sender0), std::nullopt);
mergeChannel.addNewReceiver(0 /* subscriptionId */, std::move(receiver0));
sleepFor(kTestTimeout / 3);
auto [receiver1, sender1] = Channel<ProducedValue>::create();
producers_.at(1)->startProducing(std::move(sender1), std::nullopt);
mergeChannel.addNewReceiver(1 /* subscriptionId */, std::move(receiver1));
sleepFor(kTestTimeout / 3);
mergeChannel.removeReceiver(0 /* subscriptionId */);
sleepFor(kTestTimeout / 3);
std::move(mergeChannel).close();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::NoException);
}
TEST_F(MergeChannelFixtureStress, InputChannelReceivesException) {
folly::CPUThreadPoolExecutor mergeChannelExecutor(1);
auto [mergeReceiver, mergeChannel] = createMergeChannel<ProducedValue, int>(
folly::SerialExecutor::create(&mergeChannelExecutor));
consumer_->startConsuming(std::move(mergeReceiver));
auto [receiver0, sender0] = Channel<ProducedValue>::create();
producers_.at(0)->startProducing(
std::move(sender0), std::runtime_error("Error"));
mergeChannel.addNewReceiver(0 /* subscriptionId */, std::move(receiver0));
sleepFor(kTestTimeout / 4);
auto [receiver1, sender1] = Channel<ProducedValue>::create();
producers_.at(1)->startProducing(
std::move(sender1), std::runtime_error("Error"));
mergeChannel.addNewReceiver(1 /* subscriptionId */, std::move(receiver1));
sleepFor(kTestTimeout / 4);
mergeChannel.removeReceiver(0 /* subscriptionId */);
sleepFor(kTestTimeout / 4);
producers_.at(0)->stopProducing();
sleepFor(kTestTimeout / 4);
auto closeFuture = consumer_->waitForClose();
EXPECT_FALSE(closeFuture.isReady());
producers_.at(1)->stopProducing();
EXPECT_EQ(std::move(closeFuture).get(), CloseType::Exception);
}
TEST_F(MergeChannelFixtureStress, Cancelled) {
folly::CPUThreadPoolExecutor mergeChannelExecutor(1);
auto [mergeReceiver, mergeChannel] = createMergeChannel<ProducedValue, int>(
folly::SerialExecutor::create(&mergeChannelExecutor));
consumer_->startConsuming(std::move(mergeReceiver));
auto [receiver0, sender0] = Channel<ProducedValue>::create();
producers_.at(0)->startProducing(std::move(sender0), std::nullopt);
mergeChannel.addNewReceiver(0 /* subscriptionId */, std::move(receiver0));
sleepFor(kTestTimeout / 3);
auto [receiver1, sender1] = Channel<ProducedValue>::create();
producers_.at(1)->startProducing(std::move(sender1), std::nullopt);
mergeChannel.addNewReceiver(1 /* subscriptionId */, std::move(receiver1));
sleepFor(kTestTimeout / 3);
mergeChannel.removeReceiver(0 /* subscriptionId */);
sleepFor(kTestTimeout / 3);
consumer_->cancel();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Cancelled);
}
TEST_F(MergeChannelFixtureStress, Cancelled_ThenHandleClosedImmediately) {
folly::CPUThreadPoolExecutor mergeChannelExecutor(1);
auto [mergeReceiver, mergeChannel] = createMergeChannel<ProducedValue, int>(
folly::SerialExecutor::create(&mergeChannelExecutor));
consumer_->startConsuming(std::move(mergeReceiver));
auto [receiver0, sender0] = Channel<ProducedValue>::create();
producers_.at(0)->startProducing(std::move(sender0), std::nullopt);
mergeChannel.addNewReceiver(0 /* subscriptionId */, std::move(receiver0));
sleepFor(kTestTimeout / 3);
auto [receiver1, sender1] = Channel<ProducedValue>::create();
producers_.at(1)->startProducing(std::move(sender1), std::nullopt);
mergeChannel.addNewReceiver(1 /* subscriptionId */, std::move(receiver1));
sleepFor(kTestTimeout / 3);
mergeChannel.removeReceiver(0 /* subscriptionId */);
sleepFor(kTestTimeout / 3);
consumer_->cancel();
std::move(mergeChannel).close();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
TEST_F(MergeChannelFixtureStress, HandleClosed_ThenCancelledImmediately) {
folly::CPUThreadPoolExecutor mergeChannelExecutor(1);
auto [mergeReceiver, mergeChannel] = createMergeChannel<ProducedValue, int>(
folly::SerialExecutor::create(&mergeChannelExecutor));
consumer_->startConsuming(std::move(mergeReceiver));
auto [receiver0, sender0] = Channel<ProducedValue>::create();
producers_.at(0)->startProducing(std::move(sender0), std::nullopt);
mergeChannel.addNewReceiver(0 /* subscriptionId */, std::move(receiver0));
sleepFor(kTestTimeout / 3);
auto [receiver1, sender1] = Channel<ProducedValue>::create();
producers_.at(1)->startProducing(std::move(sender1), std::nullopt);
mergeChannel.addNewReceiver(1 /* subscriptionId */, std::move(receiver1));
sleepFor(kTestTimeout / 3);
mergeChannel.removeReceiver(0 /* subscriptionId */);
sleepFor(kTestTimeout / 3);
std::move(mergeChannel).close();
consumer_->cancel();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/executors/ManualExecutor.h>
#include <folly/executors/SerialExecutor.h>
#include <folly/experimental/channels/ConsumeChannel.h>
#include <folly/experimental/channels/Merge.h>
#include <folly/experimental/channels/test/ChannelTestUtil.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
namespace folly {
namespace channels {
using namespace testing;
class MergeFixture : public Test {
protected:
~MergeFixture() { executor_.drain(); }
ChannelCallbackHandle processValues(Receiver<int> receiver) {
return consumeChannelWithCallback(
std::move(receiver),
&executor_,
[=](folly::Try<int> resultTry) -> folly::coro::Task<bool> {
onNext_(std::move(resultTry));
co_return true;
});
}
folly::ManualExecutor executor_;
StrictMock<MockNextCallback<int>> onNext_;
};
TEST_F(MergeFixture, ReceiveValues_ReturnMergedValues) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto mergedReceiver = merge(
toVector(
std::move(receiver1), std::move(receiver2), std::move(receiver3)),
&executor_);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onValue(4));
EXPECT_CALL(onNext_, onValue(5));
EXPECT_CALL(onNext_, onValue(6));
EXPECT_CALL(onNext_, onClosed());
sender1.write(1);
sender2.write(2);
sender3.write(3);
executor_.drain();
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(4);
sender2.write(5);
sender3.write(6);
executor_.drain();
std::move(sender1).close();
std::move(sender2).close();
std::move(sender3).close();
executor_.drain();
}
TEST_F(MergeFixture, OneInputClosed_ContinuesMerging) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto mergedReceiver = merge(
toVector(
std::move(receiver1), std::move(receiver2), std::move(receiver3)),
&executor_);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onValue(4));
EXPECT_CALL(onNext_, onValue(5));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2.write(2);
sender3.write(3);
std::move(sender3).close();
executor_.drain();
sender1.write(4);
sender2.write(5);
executor_.drain();
std::move(sender1).close();
std::move(sender2).close();
executor_.drain();
}
TEST_F(MergeFixture, OneInputThrows_OutputClosedWithException) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto mergedReceiver = merge(
toVector(
std::move(receiver1), std::move(receiver2), std::move(receiver3)),
&executor_);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(mergedReceiver));
executor_.drain();
sender1.write(1);
sender2.write(2);
sender3.write(3);
std::move(sender3).close(std::runtime_error("Error"));
executor_.drain();
sender1.write(4);
sender2.write(5);
std::move(sender1).close();
std::move(sender2).close();
executor_.drain();
}
TEST_F(MergeFixture, Cancelled) {
auto [receiver1, sender1] = Channel<int>::create();
auto [receiver2, sender2] = Channel<int>::create();
auto [receiver3, sender3] = Channel<int>::create();
auto mergedReceiver = merge(
toVector(
std::move(receiver1), std::move(receiver2), std::move(receiver3)),
&executor_);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onValue(2));
EXPECT_CALL(onNext_, onValue(3));
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(mergedReceiver));
sender1.write(1);
sender2.write(2);
sender3.write(3);
executor_.drain();
callbackHandle.reset();
executor_.drain();
sender1.write(4);
sender2.write(5);
sender3.write(6);
executor_.drain();
std::move(sender1).close();
std::move(sender2).close();
std::move(sender3).close();
executor_.drain();
}
struct ProducedValue {
int producerIndex;
int value;
};
class MergeFixtureStress : public Test {
protected:
MergeFixtureStress()
: producers_(toVector(makeProducer(0), makeProducer(1), makeProducer(2))),
consumer_(makeConsumer()) {}
static std::unique_ptr<StressTestProducer<ProducedValue>> makeProducer(
int index) {
return std::make_unique<StressTestProducer<ProducedValue>>(
[index, value = 0]() mutable {
return ProducedValue{index, value++};
});
}
static std::unique_ptr<StressTestConsumer<ProducedValue>> makeConsumer() {
return std::make_unique<StressTestConsumer<ProducedValue>>(
ConsumptionMode::CallbackWithHandle,
[lastReceived = toVector(-1, -1, -1)](ProducedValue value) mutable {
EXPECT_EQ(value.value, ++lastReceived[value.producerIndex]);
});
}
static constexpr std::chrono::milliseconds kTestTimeout =
std::chrono::milliseconds{5000};
std::vector<std::unique_ptr<StressTestProducer<ProducedValue>>> producers_;
std::unique_ptr<StressTestConsumer<ProducedValue>> consumer_;
};
TEST_F(MergeFixtureStress, Close_NoException) {
auto receivers = std::vector<Receiver<ProducedValue>>();
for (auto& producer : producers_) {
auto [receiver, sender] = Channel<ProducedValue>::create();
receivers.push_back(std::move(receiver));
producer->startProducing(std::move(sender), std::nullopt /* closeEx */);
}
folly::CPUThreadPoolExecutor mergeExecutor(1);
consumer_->startConsuming(merge(
std::move(receivers), folly::SerialExecutor::create(&mergeExecutor)));
for (auto& producer : producers_) {
/* sleep override */
std::this_thread::sleep_for(kTestTimeout / 3);
producer->stopProducing();
}
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::NoException);
}
TEST_F(MergeFixtureStress, Close_Exception) {
auto receivers = std::vector<Receiver<ProducedValue>>();
for (size_t i = 0; i < producers_.size(); i++) {
auto [receiver, sender] = Channel<ProducedValue>::create();
receivers.push_back(std::move(receiver));
producers_.at(i)->startProducing(
std::move(sender),
i == 1 ? std::make_optional(
folly::make_exception_wrapper<std::runtime_error>("Error"))
: std::nullopt /* closeEx */);
}
folly::CPUThreadPoolExecutor mergeExecutor(1);
consumer_->startConsuming(merge(
std::move(receivers), folly::SerialExecutor::create(&mergeExecutor)));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout / 2);
producers_.at(0)->stopProducing();
/* sleep override */
std::this_thread::sleep_for(kTestTimeout / 2);
producers_.at(1)->stopProducing();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Exception);
}
TEST_F(MergeFixtureStress, Cancelled) {
auto receivers = std::vector<Receiver<ProducedValue>>();
for (size_t i = 0; i < producers_.size(); i++) {
auto [receiver, sender] = Channel<ProducedValue>::create();
receivers.push_back(std::move(receiver));
producers_.at(i)->startProducing(
std::move(sender), std::nullopt /* closeEx */);
}
folly::CPUThreadPoolExecutor mergeExecutor(1);
consumer_->startConsuming(merge(
std::move(receivers), folly::SerialExecutor::create(&mergeExecutor)));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
consumer_->cancel();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Cancelled);
}
TEST_F(MergeFixtureStress, Close_NoException_ThenCancelledImmediately) {
auto receivers = std::vector<Receiver<ProducedValue>>();
for (size_t i = 0; i < producers_.size(); i++) {
auto [receiver, sender] = Channel<ProducedValue>::create();
receivers.push_back(std::move(receiver));
producers_.at(i)->startProducing(
std::move(sender), std::nullopt /* closeEx */);
}
folly::CPUThreadPoolExecutor mergeExecutor(1);
consumer_->startConsuming(merge(
std::move(receivers), folly::SerialExecutor::create(&mergeExecutor)));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
for (auto& producer : producers_) {
producer->stopProducing();
}
consumer_->cancel();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
TEST_F(MergeFixtureStress, Cancelled_ThenClosedImmediately_NoException) {
auto receivers = std::vector<Receiver<ProducedValue>>();
for (size_t i = 0; i < producers_.size(); i++) {
auto [receiver, sender] = Channel<ProducedValue>::create();
receivers.push_back(std::move(receiver));
producers_.at(i)->startProducing(
std::move(sender), std::nullopt /* closeEx */);
}
folly::CPUThreadPoolExecutor mergeExecutor(1);
consumer_->startConsuming(merge(
std::move(receivers), folly::SerialExecutor::create(&mergeExecutor)));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
consumer_->cancel();
for (auto& producer : producers_) {
producer->stopProducing();
}
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/executors/ManualExecutor.h>
#include <folly/experimental/channels/Producer.h>
#include <folly/experimental/channels/test/ChannelTestUtil.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
namespace folly {
namespace channels {
using namespace testing;
class ProducerFixture : public Test {
protected:
ProducerFixture() {}
~ProducerFixture() { executor_.drain(); }
ChannelCallbackHandle processValues(Receiver<int> receiver) {
return consumeChannelWithCallback(
std::move(receiver),
&executor_,
[=](folly::Try<int> resultTry) -> folly::coro::Task<bool> {
onNext_(std::move(resultTry));
co_return true;
});
}
folly::ManualExecutor executor_;
StrictMock<MockNextCallback<int>> onNext_;
};
TEST_F(ProducerFixture, Write_ThenCloseWithoutException) {
class TestProducer : public Producer<int> {
public:
TestProducer(
Sender<int> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
: Producer<int>(std::move(sender), std::move(executor)) {
write(1);
close();
}
};
auto receiver = makeProducer<TestProducer>(&executor_);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(receiver));
executor_.drain();
}
TEST_F(ProducerFixture, Write_ThenCloseWithException) {
class TestProducer : public Producer<int> {
public:
TestProducer(
Sender<int> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor)
: Producer<int>(std::move(sender), std::move(executor)) {
write(1);
close(std::runtime_error("Error"));
}
};
auto receiver = makeProducer<TestProducer>(&executor_);
EXPECT_CALL(onNext_, onValue(1));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(receiver));
executor_.drain();
}
TEST_F(ProducerFixture, KeepAliveExists_DelaysDestruction) {
class TestProducer : public Producer<int> {
public:
TestProducer(
Sender<int> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
folly::SemiFuture<folly::Unit> future,
bool& destructed)
: Producer<int>(std::move(sender), std::move(executor)),
destructed_(destructed) {
folly::coro::co_invoke(
[keepAlive = getKeepAlive(),
future = std::move(future)]() mutable -> folly::coro::Task<void> {
co_await std::move(future);
})
.scheduleOn(getExecutor())
.start();
}
~TestProducer() { destructed_ = true; }
bool& destructed_;
};
auto promise = folly::Promise<folly::Unit>();
bool destructed = false;
auto receiver = makeProducer<TestProducer>(
&executor_, promise.getSemiFuture(), destructed);
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(receiver));
executor_.drain();
callbackHandle.reset();
executor_.drain();
EXPECT_FALSE(destructed);
promise.setValue();
executor_.drain();
EXPECT_TRUE(destructed);
}
TEST_F(
ProducerFixture,
ConsumerStopsConsumingReceiver_OnCancelledCalled_ThenDestructed) {
class TestProducer : public Producer<int> {
public:
TestProducer(
Sender<int> sender,
folly::Executor::KeepAlive<folly::SequencedExecutor> executor,
folly::Promise<folly::Unit> onCancelledStarted,
folly::SemiFuture<folly::Unit> onCancelledCompleted,
bool& destructed)
: Producer<int>(std::move(sender), std::move(executor)),
onCancelledStarted_(std::move(onCancelledStarted)),
onCancelledCompleted_(std::move(onCancelledCompleted)),
destructed_(destructed) {}
folly::coro::Task<void> onClosed() override {
onCancelledStarted_.setValue();
co_await std::move(onCancelledCompleted_);
}
~TestProducer() override { destructed_ = true; }
folly::Promise<folly::Unit> onCancelledStarted_;
folly::SemiFuture<folly::Unit> onCancelledCompleted_;
bool& destructed_;
};
auto onCancelledStartedPromise = folly::Promise<folly::Unit>();
auto onCancelledStartedFuture = onCancelledStartedPromise.getSemiFuture();
auto onCancelledCompletedPromise = folly::Promise<folly::Unit>();
auto onCancelledCompletedFuture = onCancelledCompletedPromise.getSemiFuture();
bool destructed = false;
auto receiver = makeProducer<TestProducer>(
&executor_,
std::move(onCancelledStartedPromise),
std::move(onCancelledCompletedFuture),
destructed);
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(receiver));
executor_.drain();
EXPECT_FALSE(onCancelledStartedFuture.isReady());
EXPECT_FALSE(destructed);
callbackHandle.reset();
executor_.drain();
EXPECT_TRUE(onCancelledStartedFuture.isReady());
EXPECT_FALSE(destructed);
onCancelledCompletedPromise.setValue();
executor_.drain();
EXPECT_TRUE(destructed);
}
} // namespace channels
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/ScopeGuard.h>
#include <folly/executors/ManualExecutor.h>
#include <folly/executors/SerialExecutor.h>
#include <folly/experimental/channels/ConsumeChannel.h>
#include <folly/experimental/channels/Transform.h>
#include <folly/experimental/channels/test/ChannelTestUtil.h>
#include <folly/experimental/coro/AsyncGenerator.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
namespace folly {
namespace channels {
using namespace testing;
using namespace std::string_literals;
class TransformFixture : public Test {
protected:
TransformFixture() {}
~TransformFixture() { executor_.drain(); }
ChannelCallbackHandle processValues(Receiver<std::string> receiver) {
return consumeChannelWithCallback(
std::move(receiver),
&executor_,
[=](folly::Try<std::string> resultTry) -> folly::coro::Task<bool> {
onNext_(std::move(resultTry));
co_return true;
});
}
folly::ManualExecutor executor_;
StrictMock<MockNextCallback<std::string>> onNext_;
};
class SimpleTransformFixture : public TransformFixture {};
TEST_F(SimpleTransformFixture, ReceiveValue_ReturnTransformedValue) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
if (result.value() % 2 == 0) {
co_yield folly::to<std::string>(result.value());
} else {
co_return;
}
});
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onValue("4"));
EXPECT_CALL(onNext_, onClosed());
sender.write(1);
sender.write(2);
executor_.drain();
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(3);
sender.write(4);
executor_.drain();
std::move(sender).close();
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveValue_Close) {
bool close = false;
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (close) {
throw OnClosedException();
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
close = true;
sender.write(3);
sender.write(4);
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveValue_Throw_InCoroutine) {
bool throwException = false;
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (throwException) {
throw std::runtime_error("Error");
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
throwException = true;
sender.write(3);
sender.write(4);
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveValue_Throw_InNonCoroutine) {
bool throwException = false;
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (throwException) {
throw std::runtime_error("Error");
}
return folly::coro::co_invoke(
[=]() -> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
});
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
throwException = true;
sender.write(3);
sender.write(4);
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveClosed_Close) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (result.hasException()) {
EXPECT_TRUE(result.hasException<OnClosedException>());
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close();
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveClosed_Throw) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (result.hasException()) {
EXPECT_TRUE(result.hasException<OnClosedException>());
throw std::runtime_error("Error");
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close();
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveException_Close) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (result.hasException()) {
EXPECT_THROW(result.throwUnlessValue(), std::runtime_error);
// We will swallow the exception and move on.
throw OnClosedException();
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close(std::runtime_error("Error"));
executor_.drain();
}
TEST_F(SimpleTransformFixture, ReceiveException_Throw) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (result.hasException()) {
EXPECT_THROW(result.throwUnlessValue(), std::runtime_error);
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close(std::runtime_error("Error"));
executor_.drain();
}
TEST_F(SimpleTransformFixture, Cancelled) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = transform(
std::move(untransformedReceiver),
&executor_,
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
LOG(INFO) << "Cancelling...";
callbackHandle.reset();
executor_.drain();
LOG(INFO) << "Finished cancelling";
}
TEST_F(SimpleTransformFixture, Chained) {
auto [receiver, sender] = Channel<int>::create();
for (int i = 0; i < 10; i++) {
receiver = transform(
std::move(receiver),
&executor_,
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<int> {
co_yield result.value() + 1;
});
}
auto callbackHandle = processValues(transform(
std::move(receiver),
&executor_,
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
}));
executor_.drain();
EXPECT_CALL(onNext_, onValue("11"));
EXPECT_CALL(onNext_, onValue("12"));
EXPECT_CALL(onNext_, onValue("13"));
EXPECT_CALL(onNext_, onValue("14"));
EXPECT_CALL(onNext_, onClosed());
sender.write(1);
sender.write(2);
sender.write(3);
sender.write(4);
std::move(sender).close();
executor_.drain();
}
class TransformFixtureStress : public Test {
protected:
TransformFixtureStress()
: producer_(std::make_unique<StressTestProducer<int>>(
[value = 0]() mutable { return value++; })),
consumer_(std::make_unique<StressTestConsumer<std::string>>(
ConsumptionMode::CallbackWithHandle,
[lastReceived = -1](std::string value) mutable {
EXPECT_EQ(folly::to<int>(value), ++lastReceived);
})) {}
static constexpr std::chrono::milliseconds kTestTimeout =
std::chrono::milliseconds{5000};
std::unique_ptr<StressTestProducer<int>> producer_;
std::unique_ptr<StressTestConsumer<std::string>> consumer_;
};
TEST_F(TransformFixtureStress, Close) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
folly::CPUThreadPoolExecutor transformExecutor(1);
consumer_->startConsuming(transform(
std::move(receiver),
folly::SerialExecutor::create(&transformExecutor),
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string> {
co_yield folly::to<std::string>(std::move(result.value()));
}));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
producer_->stopProducing();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::NoException);
}
TEST_F(TransformFixtureStress, Cancel) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
folly::CPUThreadPoolExecutor transformExecutor(1);
consumer_->startConsuming(transform(
std::move(receiver),
folly::SerialExecutor::create(&transformExecutor),
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string> {
co_yield folly::to<std::string>(std::move(result.value()));
}));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
consumer_->cancel();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Cancelled);
}
TEST_F(TransformFixtureStress, Close_ThenCancelImmediately) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
folly::CPUThreadPoolExecutor transformExecutor(1);
consumer_->startConsuming(transform(
std::move(receiver),
folly::SerialExecutor::create(&transformExecutor),
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string> {
co_yield folly::to<std::string>(std::move(result.value()));
}));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
producer_->stopProducing();
consumer_->cancel();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
TEST_F(TransformFixtureStress, Cancel_ThenCloseImmediately) {
auto [receiver, sender] = Channel<int>::create();
producer_->startProducing(std::move(sender), std::nullopt /* closeEx */);
folly::CPUThreadPoolExecutor transformExecutor(1);
consumer_->startConsuming(transform(
std::move(receiver),
folly::SerialExecutor::create(&transformExecutor),
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string> {
co_yield folly::to<std::string>(std::move(result.value()));
}));
/* sleep override */
std::this_thread::sleep_for(kTestTimeout);
consumer_->cancel();
producer_->stopProducing();
EXPECT_THAT(
consumer_->waitForClose().get(),
AnyOf(Eq(CloseType::NoException), Eq(CloseType::Cancelled)));
}
class ResumableTransformFixture : public TransformFixture {};
TEST_F(
ResumableTransformFixture,
InitializesAndReturnsTransformedValues_ThenClosed) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = resumableTransform(
&executor_,
[alreadyInitialized = false,
receiver = std::move(untransformedReceiver)]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
if (alreadyInitialized) {
throw OnClosedException();
}
alreadyInitialized = true;
co_return std::make_pair(toVector("abc"s, "def"s), std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("abc"));
EXPECT_CALL(onNext_, onValue("def"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onValue("3"));
EXPECT_CALL(onNext_, onValue("4"));
EXPECT_CALL(onNext_, onClosed());
sender.write(1);
sender.write(2);
executor_.drain();
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(3);
sender.write(4);
executor_.drain();
std::move(sender).close();
executor_.drain();
}
TEST_F(
ResumableTransformFixture,
InitializesAndReturnsTransformedValues_ThenCancelled) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = resumableTransform(
&executor_,
[alreadyInitialized = false,
receiver = std::move(untransformedReceiver)]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
if (alreadyInitialized) {
throw OnClosedException();
}
alreadyInitialized = true;
co_return std::make_pair(toVector("abc"s, "def"s), std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("abc"));
EXPECT_CALL(onNext_, onValue("def"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onValue("3"));
EXPECT_CALL(onNext_, onValue("4"));
EXPECT_CALL(onNext_, onCancelled());
sender.write(1);
sender.write(2);
executor_.drain();
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(3);
sender.write(4);
executor_.drain();
callbackHandle.reset();
executor_.drain();
}
TEST_F(
ResumableTransformFixture,
InitializesAndReturnsTransformedValues_ThenClosed_CancelledBeforeReinit) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = resumableTransform(
&executor_,
[alreadyInitialized = false,
receiver = std::move(untransformedReceiver)]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
if (alreadyInitialized) {
throw OnClosedException();
}
alreadyInitialized = true;
co_return std::make_pair(toVector("abc"s, "def"s), std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
LOG(INFO) << "Got value " << result.hasException();
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("abc"));
EXPECT_CALL(onNext_, onValue("def"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close();
callbackHandle.reset();
executor_.drain();
}
TEST_F(
ResumableTransformFixture,
FirstReceiverCloses_ReinitializesWithNewReceiver_ThenClosed) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = resumableTransform(
&executor_,
[numTimesInitialized = 0, &receiver = untransformedReceiver]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
if (numTimesInitialized > 1) {
throw OnClosedException();
}
numTimesInitialized++;
co_return std::make_pair(
toVector(folly::to<std::string>("abc", numTimesInitialized)),
std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("abc1"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onValue("abc2"));
EXPECT_CALL(onNext_, onValue("3"));
EXPECT_CALL(onNext_, onValue("4"));
EXPECT_CALL(onNext_, onClosed());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close();
std::tie(untransformedReceiver, sender) = Channel<int>::create();
executor_.drain();
sender.write(3);
sender.write(4);
executor_.drain();
std::move(sender).close();
executor_.drain();
}
TEST_F(
ResumableTransformFixture,
FirstReceiverClosesWithException_NoReinitialization_Rethrows) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = resumableTransform(
&executor_,
[alreadyInitialized = false,
receiver = std::move(untransformedReceiver)]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
CHECK(!alreadyInitialized);
alreadyInitialized = true;
co_return std::make_pair(toVector("abc"s), std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("abc"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close(std::runtime_error("Error"));
executor_.drain();
}
TEST_F(
ResumableTransformFixture,
FirstReceiverClosesWithException_TransformSwallows_Reinitialization) {
auto [untransformedReceiver, sender] = Channel<int>::create();
auto transformedReceiver = resumableTransform(
&executor_,
[numTimesInitialized = 0, &receiver = untransformedReceiver]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
numTimesInitialized++;
co_return std::make_pair(
toVector(folly::to<std::string>("abc", numTimesInitialized)),
std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string&&> {
if (result.hasValue()) {
co_yield folly::to<std::string>(result.value());
} else {
EXPECT_THROW(result.throwUnlessValue(), std::runtime_error);
}
});
EXPECT_CALL(onNext_, onValue("abc1"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onValue("abc2"));
EXPECT_CALL(onNext_, onValue("3"));
EXPECT_CALL(onNext_, onValue("4"));
EXPECT_CALL(onNext_, onCancelled());
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
std::move(sender).close(std::runtime_error("Error"));
std::tie(untransformedReceiver, sender) = Channel<int>::create();
executor_.drain();
sender.write(3);
sender.write(4);
executor_.drain();
callbackHandle.reset();
executor_.drain();
}
TEST_F(ResumableTransformFixture, TransformThrows_NoReinitialization_Rethrows) {
auto [untransformedReceiver, sender] = Channel<int>::create();
bool transformThrows = false;
auto transformedReceiver = resumableTransform(
&executor_,
[alreadyInitialized = false, &receiver = untransformedReceiver]() mutable
-> folly::coro::Task<std::pair<std::vector<std::string>, Receiver<int>>> {
CHECK(!alreadyInitialized);
alreadyInitialized = true;
co_return std::make_pair(
toVector(folly::to<std::string>("abc")), std::move(receiver));
},
[&](folly::Try<int> result)
-> folly::coro::AsyncGenerator<std::string&&> {
if (transformThrows) {
throw std::runtime_error("Error");
}
co_yield folly::to<std::string>(result.value());
});
EXPECT_CALL(onNext_, onValue("abc"));
EXPECT_CALL(onNext_, onValue("1"));
EXPECT_CALL(onNext_, onValue("2"));
EXPECT_CALL(onNext_, onRuntimeError("std::runtime_error: Error"));
auto callbackHandle = processValues(std::move(transformedReceiver));
sender.write(1);
sender.write(2);
executor_.drain();
transformThrows = true;
sender.write(100);
executor_.drain();
}
class ResumableTransformFixtureStress : public Test {
protected:
ResumableTransformFixtureStress()
: consumer_(std::make_unique<StressTestConsumer<std::string>>(
ConsumptionMode::CallbackWithHandle,
[lastReceived = -1](std::string value) mutable {
if (value == "start") {
lastReceived = -1;
} else {
EXPECT_EQ(folly::to<int>(value), ++lastReceived);
}
})) {}
std::unique_ptr<StressTestProducer<int>> makeProducer() {
return std::make_unique<StressTestProducer<int>>(
[value = 0]() mutable { return value++; });
}
void setProducer(std::unique_ptr<StressTestProducer<int>> producer) {
producer_ = std::move(producer);
producerReady_.post();
}
void waitForProducer() {
producerReady_.wait();
LOG(INFO) << "Finished waiting!";
producerReady_.reset();
}
StressTestProducer<int>* getProducer() { return producer_.get(); }
static constexpr std::chrono::milliseconds kTestTimeout =
std::chrono::milliseconds{10};
std::unique_ptr<StressTestProducer<int>> producer_;
folly::Baton<> producerReady_;
std::unique_ptr<StressTestConsumer<std::string>> consumer_;
};
TEST_F(ResumableTransformFixtureStress, Close) {
folly::CPUThreadPoolExecutor transformExecutor(1);
bool close = false;
consumer_->startConsuming(resumableTransform(
folly::SerialExecutor::create(&transformExecutor),
[&]() -> folly::coro::Task<
std::pair<std::vector<std::string>, Receiver<int>>> {
if (close) {
throw OnClosedException();
}
auto [receiver, sender] = Channel<int>::create();
auto newProducer = makeProducer();
newProducer->startProducing(
std::move(sender), std::nullopt /* closeEx */);
setProducer(std::move(newProducer));
co_return std::make_pair(toVector("start"s), std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string> {
co_yield folly::to<std::string>(std::move(result.value()));
}));
waitForProducer();
/* sleep override */
std::this_thread::sleep_for(kTestTimeout / 2);
getProducer()->stopProducing();
waitForProducer();
/* sleep override */
std::this_thread::sleep_for(kTestTimeout / 2);
close = true;
getProducer()->stopProducing();
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::NoException);
}
TEST_F(ResumableTransformFixtureStress, CancelDuringReinitialization) {
folly::CPUThreadPoolExecutor transformExecutor(1);
auto initializationStarted = folly::SharedPromise<folly::Unit>();
auto initializationWait = folly::SharedPromise<folly::Unit>();
auto resumableTransformDestroyed = folly::SharedPromise<folly::Unit>();
auto guard =
folly::makeGuard([&]() { resumableTransformDestroyed.setValue(); });
consumer_->startConsuming(resumableTransform(
folly::SerialExecutor::create(&transformExecutor),
[&, g = std::move(guard)]()
-> folly::coro::Task<
std::pair<std::vector<std::string>, Receiver<int>>> {
initializationStarted.setValue(folly::unit);
co_await folly::coro::detachOnCancel(
initializationWait.getSemiFuture());
initializationWait = folly::SharedPromise<folly::Unit>();
auto [receiver, sender] = Channel<int>::create();
auto newProducer = makeProducer();
newProducer->startProducing(
std::move(sender), std::nullopt /* closeEx */);
setProducer(std::move(newProducer));
co_return std::make_pair(toVector("start"s), std::move(receiver));
},
[](folly::Try<int> result) -> folly::coro::AsyncGenerator<std::string> {
co_yield folly::to<std::string>(std::move(result.value()));
}));
initializationStarted.getSemiFuture().get();
initializationStarted = folly::SharedPromise<folly::Unit>();
initializationWait.setValue(folly::unit);
waitForProducer();
/* sleep override */
std::this_thread::sleep_for(kTestTimeout / 2);
getProducer()->stopProducing();
initializationStarted.getSemiFuture().get();
initializationStarted = folly::SharedPromise<folly::Unit>();
consumer_->cancel();
initializationWait.setValue(folly::unit);
EXPECT_EQ(consumer_->waitForClose().get(), CloseType::Cancelled);
resumableTransformDestroyed.getSemiFuture().get();
}
} // namespace channels
} // namespace folly
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