// 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 #include #include "yarpl/Common.h" #include "yarpl/Flowable.h" #include "yarpl/utils/credits.h" namespace yarpl { namespace observable { template class Observable; template class Observer; } // namespace observable template class BackpressureStrategyBase : public IBackpressureStrategy, public flowable::Subscription, public observable::Observer { protected: // // the following methods are to be overridden // virtual void onCreditsAvailable(int64_t /*credits*/) = 0; virtual void onNextWithoutCredits(T /*t*/) = 0; public: void init( std::shared_ptr> observable, std::shared_ptr> subscriber) override { observable_ = std::move(observable); subscriberWeak_ = subscriber; subscriber_ = subscriber; subscriber->onSubscribe(this->ref_from_this(this)); observable_->subscribe(this->ref_from_this(this)); } BackpressureStrategyBase() = default; BackpressureStrategyBase(BackpressureStrategyBase&&) = delete; BackpressureStrategyBase(const BackpressureStrategyBase&) = delete; BackpressureStrategyBase& operator=(BackpressureStrategyBase&&) = delete; BackpressureStrategyBase& operator=(const BackpressureStrategyBase&) = delete; // only for testing purposes void setTestSubscriber(std::shared_ptr> subscriber) { subscriberWeak_ = subscriber; subscriber_ = subscriber; subscriber->onSubscribe(this->ref_from_this(this)); } void request(int64_t n) override { if (n <= 0) { return; } auto r = credits::add(&requested_, n); if (r <= 0) { return; } // it is possible that after calling subscribe or in onCreditsAvailable // methods, there will be a stream of // onNext calls which the processing chain might cancel. The cancel signal // will remove all references to this class and we need to keep this // instance around to finish this method auto thisPtr = this->ref_from_this(this); if (r > 0) { onCreditsAvailable(r); } } void cancel() override { if (auto subscriber = subscriber_.exchange(nullptr)) { observable::Observer::unsubscribe(); observable_.reset(); } } // Observer override void onNext(T t) override { if (subscriberWeak_.expired()) { return; } if (requested_ > 0) { downstreamOnNext(std::move(t)); return; } onNextWithoutCredits(std::move(t)); } // Observer override void onComplete() override { downstreamOnComplete(); } // Observer override void onError(folly::exception_wrapper ex) override { downstreamOnError(std::move(ex)); } virtual void downstreamOnNext(T t) { credits::consume(&requested_, 1); if (auto subscriber = subscriberWeak_.lock()) { subscriber->onNext(std::move(t)); } } void downstreamOnComplete() { if (auto subscriber = subscriber_.exchange(nullptr)) { subscriber->onComplete(); observable::Observer::onComplete(); observable_.reset(); } } void downstreamOnError(folly::exception_wrapper error) { if (auto subscriber = subscriber_.exchange(nullptr)) { subscriber->onError(std::move(error)); observable::Observer::onError(folly::exception_wrapper()); observable_.reset(); } } void downstreamOnErrorAndCancel(folly::exception_wrapper error) { if (auto subscriber = subscriber_.exchange(nullptr)) { subscriber->onError(std::move(error)); observable_.reset(); observable::Observer::unsubscribe(); } } private: std::shared_ptr> observable_; folly::Synchronized>> subscriber_; std::weak_ptr> subscriberWeak_; std::atomic requested_{0}; }; template class DropBackpressureStrategy : public BackpressureStrategyBase { public: void onCreditsAvailable(int64_t /*credits*/) override {} void onNextWithoutCredits(T /*t*/) override { // drop anything while we don't have credits } }; template class ErrorBackpressureStrategy : public BackpressureStrategyBase { using Super = BackpressureStrategyBase; void onCreditsAvailable(int64_t /*credits*/) override {} void onNextWithoutCredits(T /*t*/) override { Super::downstreamOnErrorAndCancel(flowable::MissingBackpressureException()); } }; template class BufferBackpressureStrategy : public BackpressureStrategyBase { public: static constexpr size_t kNoLimit = 0; explicit BufferBackpressureStrategy(size_t bufferSizeLimit = kNoLimit) : buffer_(folly::in_place, bufferSizeLimit) {} private: using Super = BackpressureStrategyBase; void onComplete() override { if (!buffer_.rlock()->empty()) { // we have buffered some items so we will defer delivering on complete for // later completed_ = true; } else { Super::onComplete(); } } void onNext(T t) override { { auto buffer = buffer_.wlock(); if (!buffer->empty()) { if (buffer->push(std::move(t))) { return; } buffer.unlock(); Super::downstreamOnErrorAndCancel( flowable::MissingBackpressureException()); return; } } BackpressureStrategyBase::onNext(std::move(t)); } // // onError signal is delivered immediately by design // void onNextWithoutCredits(T t) override { if (buffer_.wlock()->push(std::move(t))) { return; } Super::downstreamOnErrorAndCancel(flowable::MissingBackpressureException()); } void onCreditsAvailable(int64_t credits) override { DCHECK(credits > 0); auto lockedBuffer = buffer_.wlock(); while (credits-- > 0 && !lockedBuffer->empty()) { Super::downstreamOnNext(std::move(lockedBuffer->front())); lockedBuffer->pop(); } if (lockedBuffer->empty() && completed_) { Super::onComplete(); } } struct Buffer { public: explicit Buffer(size_t sizeLimit) : sizeLimit_(sizeLimit) {} bool empty() const { return buffer_.empty(); } bool push(T&& value) { if (sizeLimit_ != kNoLimit && buffer_.size() >= sizeLimit_) { return false; } buffer_.push(std::move(value)); return true; } T& front() { return buffer_.front(); } void pop() { buffer_.pop(); } private: const size_t sizeLimit_; std::queue buffer_; }; folly::Synchronized buffer_; std::atomic completed_{false}; }; template class LatestBackpressureStrategy : public BackpressureStrategyBase { using Super = BackpressureStrategyBase; void onComplete() override { if (storesLatest_) { // we have buffered an item so we will defer delivering on complete for // later completed_ = true; } else { Super::onComplete(); } } // // onError signal is delivered immediately by design // void onNextWithoutCredits(T t) override { storesLatest_ = true; *latest_.wlock() = std::move(t); } void onCreditsAvailable(int64_t credits) override { DCHECK(credits > 0); if (storesLatest_) { storesLatest_ = false; Super::downstreamOnNext(std::move(*latest_.wlock())); if (completed_) { Super::onComplete(); } } } std::atomic storesLatest_{false}; std::atomic completed_{false}; folly::Synchronized latest_; }; template class MissingBackpressureStrategy : public BackpressureStrategyBase { using Super = BackpressureStrategyBase; void onCreditsAvailable(int64_t /*credits*/) override {} void onNextWithoutCredits(T t) override { // call onNext anyways (and potentially violating the protocol) Super::downstreamOnNext(std::move(t)); } }; template std::shared_ptr> IBackpressureStrategy::buffer() { return std::make_shared>(); } template std::shared_ptr> IBackpressureStrategy::drop() { return std::make_shared>(); } template std::shared_ptr> IBackpressureStrategy::error() { return std::make_shared>(); } template std::shared_ptr> IBackpressureStrategy::latest() { return std::make_shared>(); } template std::shared_ptr> IBackpressureStrategy::missing() { return std::make_shared>(); } } // namespace yarpl