blob: e8eaca060d48b82b4a02d6c6dd0437741f38dab0 [file]
/*
* Copyright 2010-2023 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.
*/
/*
* An implementation of Dmitry Vyukov's Bounded Multi-producer/multi-consumer bounded queue.
*
* Copyright (c) 2010-2011, Dmitry Vyukov. All rights reserved.
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted
* as representing official policies, either expressed or implied, of Dmitry Vyukov.
*/
#pragma once
#include <atomic>
#include <optional>
#include "Utils.hpp"
#include "ManuallyScoped.hpp"
namespace kotlin {
/**
* A fixed-size concurrent multi-producer/multi-consumer queue.
* @tparam kCapacity is suggested to set to a power of 2.
*/
template<typename T, std::size_t kCapacity>
class BoundedQueue : private Pinned {
public:
BoundedQueue() {
for (size_t i = 0; i < kCapacity; ++i) {
buffer_[i].sequence_.store(i, std::memory_order_relaxed);
}
enqueuePos_.store(0, std::memory_order_relaxed);
dequeuePos_.store(0, std::memory_order_relaxed);
}
bool enqueue(T&& value) {
Cell* cell;
std::size_t pos = enqueuePos_.load(std::memory_order_relaxed);
while (true) {
cell = &buffer_[pos % kCapacity];
std::size_t seq = cell->sequence_.load(std::memory_order_acquire);
std::intptr_t dif = static_cast<std::intptr_t>(seq) - static_cast<std::intptr_t>(pos);
if (dif == 0) {
if (enqueuePos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) {
break;
}
} else if (dif < 0) {
return false;
} else {
pos = enqueuePos_.load(std::memory_order_relaxed);
}
}
cell->data_.construct(std::move(value));
cell->sequence_.store(pos + 1, std::memory_order_release);
return true;
}
std::optional<T> dequeue() {
Cell* cell;
std::size_t pos = dequeuePos_.load(std::memory_order_relaxed);
while (true) {
cell = &buffer_[pos % kCapacity];
std::size_t seq = cell->sequence_.load(std::memory_order_acquire);
std::intptr_t dif = static_cast<std::intptr_t>(seq) - static_cast<std::intptr_t>(pos + 1);
if (dif == 0) {
if (dequeuePos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) {
break;
}
} else if (dif < 0) {
return std::nullopt;
} else {
pos = dequeuePos_.load(std::memory_order_relaxed);
}
}
auto result = std::move(*cell->data_);
cell->data_.destroy();
cell->sequence_.store(pos + kCapacity, std::memory_order_release);
return std::move(result);
}
/** Returns the number of items ever added to the queue: both present in the queue and already dequeed. */
size_t cumulativeThroughput() const noexcept {
return enqueuePos_.load();
}
private:
struct Cell {
// TODO describe
std::atomic<size_t> sequence_;
ManuallyScoped<T> data_;
};
constexpr static auto kCacheLineSize = 128;
alignas(kCacheLineSize) Cell buffer_[kCapacity];
alignas(kCacheLineSize) std::atomic<size_t> enqueuePos_;
alignas(kCacheLineSize) std::atomic<size_t> dequeuePos_;
};
} // namespace kotlin