blob: a0b562336110f74d4fb21c3af7fe8d8ee4b18b77 [file]
/*
* 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