| /* |
| * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license |
| * that can be found in the LICENSE file. |
| */ |
| |
| #ifndef RUNTIME_MULTI_SOURCE_QUEUE_H |
| #define RUNTIME_MULTI_SOURCE_QUEUE_H |
| |
| #include <atomic> |
| #include <list> |
| #include <memory> |
| #include <mutex> |
| |
| #include "concurrent/Mutex.hpp" |
| #include "Utils.hpp" |
| |
| namespace kotlin { |
| |
| // A queue that is constructed by collecting subqueues from several `Producer`s. |
| template <typename T, typename Mutex, typename Allocator = std::allocator<T>> |
| class MultiSourceQueue { |
| // Using `std::list` as it allows to implement `Collect` without memory allocations, |
| // which is important for GC mark phase. |
| template <typename U> |
| using List = std::list<U, typename std::allocator_traits<Allocator>::template rebind_alloc<U>>; |
| |
| public: |
| class Producer; |
| |
| // TODO: Consider switching from `std::list` to `SingleLockList` to hide the constructor |
| // and to not store the iterator. |
| class Node : private Pinned { |
| public: |
| Node(Producer* owner, const T& value) noexcept : value_(value), owner_(owner) {} |
| |
| template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>> |
| explicit Node(Producer* owner, Args&& ...args) noexcept : value_(std::forward<Args>(args)...), owner_(owner) {} |
| |
| T& operator*() noexcept { return value_; } |
| T* operator->() noexcept { return &value_; } |
| |
| static Node& fromValue(T& t) noexcept { |
| static_assert(std::is_base_of_v<Pinned, T>, "fromValue function only makes sense for non-movable object"); |
| return ownerOf(Node, value_, t); |
| } |
| |
| private: |
| friend class MultiSourceQueue; |
| |
| T value_; |
| std::atomic<Producer*> owner_; // `nullptr` signifies that `MultiSourceQueue` owns it. |
| typename List<Node>::iterator position_; |
| }; |
| |
| class Producer { |
| public: |
| explicit Producer(MultiSourceQueue& owner) noexcept : |
| owner_(owner), queue_(owner.queue_.get_allocator()), deletionQueue_(owner.deletionQueue_.get_allocator()) {} |
| |
| ~Producer() { Publish(); } |
| |
| Node* Insert(const T& value) noexcept { |
| queue_.emplace_back(this, value); |
| auto& node = queue_.back(); |
| node.position_ = std::prev(queue_.end()); |
| return &node; |
| } |
| |
| template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>> |
| Node* Emplace(Args&& ...value) noexcept { |
| queue_.emplace_back(this, std::forward<Args>(value)...); |
| auto& node = queue_.back(); |
| node.position_ = std::prev(queue_.end()); |
| return &node; |
| } |
| |
| void Erase(Node* node) noexcept { |
| if (node->owner_ == this) { |
| // If we own it, delete it immediately. |
| queue_.erase(node->position_); |
| return; |
| } |
| // If it's owned by the global queue or some other `Producer`, queue it. |
| deletionQueue_.push_back(node); |
| } |
| |
| // Merge `this` queue with owning `MultiSourceQueue`. `this` will have empty queue after the call. |
| // This call is performed without heap allocations. TODO: Test that no allocations are happening. |
| void Publish() noexcept { |
| for (auto& node : queue_) { |
| node.owner_ = nullptr; |
| } |
| std::lock_guard<Mutex> guard(owner_.mutex_); |
| RuntimeAssert(owner_.queue_.get_allocator() == queue_.get_allocator(), "queue_ allocators must match"); |
| owner_.queue_.splice(owner_.queue_.end(), queue_); |
| RuntimeAssert(owner_.deletionQueue_.get_allocator() == deletionQueue_.get_allocator(), "deletionQueue_ allocators must match"); |
| owner_.deletionQueue_.splice(owner_.deletionQueue_.end(), deletionQueue_); |
| } |
| |
| void ClearForTests() noexcept { |
| queue_.clear(); |
| deletionQueue_.clear(); |
| } |
| |
| protected: |
| template <typename F> |
| void forEachNode(F&& f) noexcept(noexcept(f(std::declval<T&>()))) { |
| for (auto& node : queue_) f(*node); |
| } |
| |
| private: |
| MultiSourceQueue& owner_; // weak |
| List<Node> queue_; |
| List<Node*> deletionQueue_; |
| }; |
| |
| class Iterator { |
| public: |
| T& operator*() noexcept { return **position_; } |
| T* operator->() noexcept { return &*this; } |
| |
| Iterator& operator++() noexcept { |
| ++position_; |
| return *this; |
| } |
| |
| void EraseAndAdvance() noexcept { owner_->EraseAndAdvance(*this); } |
| |
| bool operator==(const Iterator& rhs) const noexcept { return position_ == rhs.position_; } |
| |
| bool operator!=(const Iterator& rhs) const noexcept { return position_ != rhs.position_; } |
| |
| private: |
| friend class MultiSourceQueue; |
| |
| Iterator(MultiSourceQueue& owner, const typename List<Node>::iterator& position) noexcept : owner_(&owner), position_(position) {} |
| |
| MultiSourceQueue* owner_; |
| typename List<Node>::iterator position_; |
| }; |
| |
| class Iterable : MoveOnly { |
| public: |
| Iterator begin() noexcept { return Iterator(owner_, owner_.queue_.begin()); } |
| Iterator end() noexcept { return Iterator(owner_, owner_.queue_.end()); } |
| |
| void ApplyDeletions() noexcept { owner_.ApplyDeletionsUnsafe(); } |
| |
| private: |
| friend class MultiSourceQueue; |
| |
| explicit Iterable(MultiSourceQueue& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {} |
| |
| MultiSourceQueue& owner_; // weak |
| std::unique_lock<Mutex> guard_; |
| }; |
| |
| explicit MultiSourceQueue(const Allocator& allocator = Allocator()) noexcept : queue_(allocator), deletionQueue_(allocator) {} |
| |
| // Lock `MultiSourceQueue` for safe iteration. If element was scheduled for deletion, |
| // it'll still be iterated. Use `ApplyDeletions` to remove those elements. |
| Iterable LockForIter() noexcept { return Iterable(*this); } |
| |
| // Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published. |
| void ApplyDeletions() noexcept { |
| std::lock_guard<Mutex> guard(mutex_); |
| ApplyDeletionsUnsafe(); |
| } |
| |
| // requires LockForIter |
| void EraseAndAdvance(Iterator& it) { it.position_ = queue_.erase(it.position_); } |
| |
| void ClearForTests() noexcept { |
| queue_.clear(); |
| deletionQueue_.clear(); |
| } |
| |
| size_t GetSizeUnsafe() noexcept { return queue_.size(); } |
| |
| private: |
| // Requires a lock to be taken externally. |
| void ApplyDeletionsUnsafe() noexcept { |
| List<Node*> remainingDeletions(deletionQueue_.get_allocator()); |
| |
| auto it = deletionQueue_.begin(); |
| while (it != deletionQueue_.end()) { |
| auto next = std::next(it); |
| Node* node = *it; |
| |
| if (node->owner_ != nullptr) { |
| // If the `Node` is still owned by some `Producer`, skip it. |
| remainingDeletions.splice(remainingDeletions.end(), deletionQueue_, it); |
| } else { |
| queue_.erase(node->position_); |
| // `node` is invalid after this |
| } |
| |
| it = next; |
| } |
| deletionQueue_ = std::move(remainingDeletions); |
| } |
| |
| List<Node> queue_; |
| List<Node*> deletionQueue_; |
| Mutex mutex_; |
| }; |
| |
| } // namespace kotlin |
| |
| #endif // RUNTIME_MULTI_SOURCE_QUEUE_H |