25#ifndef JOIN_CORE_QUEUE_HPP
26#define JOIN_CORE_QUEUE_HPP
50 static constexpr uint64_t
MAGIC = 0x9F7E3B2A8D5C4E1B;
53 alignas (64) std::atomic_uint64_t
_magic;
56 alignas (64) std::atomic_uint64_t
_head;
62 alignas (64) std::atomic_uint64_t
_tail;
71 template <
typename Type>
81 template <
typename Type>
85 alignas (64) std::atomic_uint64_t
_seq;
91 char _padding[(64 - ((
sizeof (std::atomic_uint64_t) +
sizeof (Type)) % 64)) % 64];
97 template <
typename Type,
typename Slot>
108 template <
typename Type,
typename Backend>
115 template <
typename SyncPolicy>
123 template <
typename Type,
typename Backend>
131 template <
typename Type,
typename Backend,
typename SyncPolicy>
134 static_assert (std::is_trivially_copyable<Type>::value,
"type must be trivially copyable");
135 static_assert (std::is_trivially_destructible<Type>::value,
"type must be trivially destructible");
148 template <
typename... Args>
151 , _mask (_capacity - 1)
152 , _elementSize (sizeof (
Slot))
153 , _totalSize (sizeof (
QueueSync) + (_capacity * _elementSize))
154 , _backend (_totalSize,
std::forward<Args> (args)...)
155 , _segment (static_cast<
Segment*> (_backend.get ()))
157 uint64_t expected = 0;
159 if (_segment->
_sync.
_magic.compare_exchange_strong (expected, 0xFFFFFFFFFFFFFFFF,
160 std::memory_order_acq_rel))
162 _segment->_sync._head.store (0, std::memory_order_relaxed);
163 _segment->_sync._cachedTail = 0;
164 _segment->_sync._tail.store (0, std::memory_order_relaxed);
165 _segment->_sync._cachedHead = 0;
167 initSlots<needs_seq<SyncPolicy>::value> ();
169 _segment->_sync._magic.store (QueueSync::MAGIC, std::memory_order_release);
174 while (_segment->_sync._magic.load (std::memory_order_acquire) != QueueSync::MAGIC)
217 int tryPush (const Type& element) noexcept
219 return SyncPolicy::tryPush (_segment, element, _capacity, _mask);
228 ssize_t
tryPush (
const Type* elements,
size_t size)
noexcept
230 return SyncPolicy::tryPush (_segment, elements, size, _capacity, _mask);
238 int push (
const Type& element)
noexcept
242 while (tryPush (element) == -1)
261 int push (
const Type* elements,
size_t size)
noexcept
266 while (pushed < size)
268 ssize_t n = tryPush (elements + pushed, size - pushed);
280 pushed +=
static_cast<uint64_t
> (n);
294 return SyncPolicy::tryPop (_segment, element, _capacity, _mask);
303 ssize_t
tryPop (Type* elements,
size_t size)
noexcept
305 return SyncPolicy::tryPop (_segment, elements, size, _capacity, _mask);
313 int pop (Type& element)
noexcept
317 while (tryPop (element) == -1)
336 int pop (Type* elements,
size_t size)
noexcept
341 while (popped < size)
343 ssize_t n = tryPop (elements + popped, size - popped);
355 popped +=
static_cast<uint64_t
> (n);
372 auto head = _segment->_sync._head.load (std::memory_order_acquire);
373 auto tail = _segment->_sync._tail.load (std::memory_order_relaxed);
387 return _capacity - pending ();
400 return pending () == _capacity;
413 return pending () == 0;
422 int mbind (
int numa)
const noexcept
424 return _backend.mbind (numa);
434 return _backend.mlock ();
441 template <bool NeedsSeq, typename std::enable_if<!NeedsSeq>::type* =
nullptr>
442 void initSlots () noexcept
450 template <bool NeedsSeq, typename std::enable_if<NeedsSeq>::type* =
nullptr>
451 void initSlots () noexcept
453 for (uint64_t i = 0; i < _capacity; ++i)
455 _segment->_elements[i]._seq.store (i, std::memory_order_relaxed);
460 alignas (64)
const uint64_t _capacity = 0;
463 const uint64_t _mask = 0;
466 const uint64_t _elementSize = 0;
469 const uint64_t _totalSize = 0;
475 Segment* _segment =
nullptr;
481 template <
typename Type,
typename Backend>
495 static int tryPush (
Segment* segment,
const Type& element, uint64_t capacity, uint64_t mask)
noexcept
505 auto& sync = segment->_sync;
506 uint64_t head = sync._head.load (std::memory_order_relaxed);
510 sync._cachedTail = sync._tail.load (std::memory_order_acquire);
511 if ((head - sync._cachedTail) == capacity)
518 segment->_elements[head & mask].data = element;
519 sync._head.store (head + 1, std::memory_order_release);
533 static ssize_t
tryPush (
Segment* segment,
const Type* elements,
size_t size, uint64_t capacity,
534 uint64_t mask)
noexcept
536 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
542 auto& sync = segment->_sync;
543 uint64_t head = sync._head.load (std::memory_order_relaxed);
544 uint64_t avail = capacity - (head - sync._cachedTail);
548 sync._cachedTail = sync._tail.load (std::memory_order_acquire);
549 avail = capacity - (head - sync._cachedTail);
557 uint64_t toWrite = std::min (
static_cast<uint64_t
> (size), avail);
558 uint64_t offset = head & mask;
559 uint64_t first = std::min (toWrite, capacity - offset);
561 std::memcpy (&segment->_elements[offset], elements, first * sizeof (Type));
565 std::memcpy (&segment->_elements[0], elements + first, (toWrite - first) * sizeof (Type));
568 sync._head.store (head + toWrite, std::memory_order_release);
570 return static_cast<ssize_t
> (toWrite);
581 static int tryPop (
Segment* segment, Type& element, uint64_t , uint64_t mask)
noexcept
591 auto& sync = segment->_sync;
592 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
594 if (sync._cachedHead == tail)
596 sync._cachedHead = sync._head.load (std::memory_order_acquire);
597 if (sync._cachedHead == tail)
604 element = segment->_elements[tail & mask].data;
605 sync._tail.store (tail + 1, std::memory_order_release);
619 static ssize_t
tryPop (
Segment* segment, Type* elements,
size_t size, uint64_t capacity, uint64_t mask)
noexcept
621 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
627 auto& sync = segment->_sync;
628 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
629 uint64_t pending = sync._cachedHead - tail;
633 sync._cachedHead = sync._head.load (std::memory_order_acquire);
634 pending = sync._cachedHead - tail;
642 uint64_t toRead = std::min (
static_cast<uint64_t
> (size), pending);
643 uint64_t offset = tail & mask;
644 uint64_t first = std::min (toRead, capacity - offset);
646 std::memcpy (elements, &segment->_elements[offset], first * sizeof (Type));
650 std::memcpy (elements + first, &segment->_elements[0], (toRead - first) * sizeof (Type));
653 sync._tail.store (tail + toRead, std::memory_order_release);
655 return static_cast<ssize_t
> (toRead);
662 template <
typename Type,
typename Backend>
676 static int tryPush (
Segment* segment,
const Type& element, uint64_t , uint64_t mask)
noexcept
686 auto& sync = segment->_sync;
687 uint64_t head = sync._head.load (std::memory_order_relaxed);
692 auto* slot = &segment->_elements[head & mask];
693 uint64_t seq = slot->_seq.load (std::memory_order_acquire);
697 if (
JOIN_LIKELY (sync._head.compare_exchange_weak (head, head + 1, std::memory_order_acquire,
698 std::memory_order_relaxed)))
700 slot->data = element;
701 slot->_seq.store (head + 1, std::memory_order_release);
713 head = sync._head.load (std::memory_order_relaxed);
727 static ssize_t
tryPush (
Segment* segment,
const Type* elements,
size_t size, uint64_t capacity,
728 uint64_t mask)
noexcept
730 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
737 auto& sync = segment->_sync;
738 uint64_t head = sync._head.load (std::memory_order_relaxed);
742 uint64_t tail = sync._tail.load (std::memory_order_acquire);
743 uint64_t toWrite = std::min (
static_cast<uint64_t
> (size), capacity - (head - tail));
751 if (
JOIN_LIKELY (sync._head.compare_exchange_weak (head, head + toWrite, std::memory_order_acquire,
752 std::memory_order_relaxed)))
754 for (uint64_t i = 0; i < toWrite; ++i)
756 auto* slot = &segment->_elements[(head + i) & mask];
757 slot->data = elements[i];
758 slot->_seq.store (head + i + 1, std::memory_order_release);
761 return static_cast<ssize_t
> (toWrite);
776 static int tryPop (
Segment* segment, Type& element, uint64_t capacity, uint64_t mask)
noexcept
786 auto& sync = segment->_sync;
787 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
788 auto* slot = &segment->_elements[tail & mask];
789 uint64_t seq = slot->_seq.load (std::memory_order_acquire);
797 element = slot->data;
798 slot->_seq.store (tail + capacity, std::memory_order_release);
799 sync._tail.store (tail + 1, std::memory_order_release);
813 static ssize_t
tryPop (
Segment* segment, Type* elements,
size_t size, uint64_t capacity, uint64_t mask)
noexcept
815 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
821 auto& sync = segment->_sync;
822 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
823 uint64_t head = sync._head.load (std::memory_order_acquire);
824 uint64_t toRead = std::min (
static_cast<uint64_t
> (size), head - tail);
827 for (uint64_t i = 0; i < toRead; ++i)
829 auto* slot = &segment->_elements[(tail + i) & mask];
831 if (
JOIN_UNLIKELY (slot->_seq.load (std::memory_order_acquire) != tail + i + 1))
836 elements[i] = slot->data;
837 slot->_seq.store (tail + i + capacity, std::memory_order_release);
843 sync._tail.store (tail + popped, std::memory_order_release);
844 return static_cast<ssize_t
> (popped);
855 template <
typename Type,
typename Backend>
869 static int tryPush (
Segment* segment,
const Type& element, uint64_t capacity, uint64_t mask)
noexcept
883 static ssize_t
tryPush (
Segment* segment,
const Type* elements,
size_t size, uint64_t capacity,
884 uint64_t mask)
noexcept
886 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
893 auto& sync = segment->_sync;
894 uint64_t head = sync._head.load (std::memory_order_relaxed);
898 uint64_t tail = sync._tail.load (std::memory_order_acquire);
899 uint64_t toWrite = std::min (
static_cast<uint64_t
> (size), capacity - (head - tail));
907 if (
JOIN_LIKELY (sync._head.compare_exchange_weak (head, head + toWrite, std::memory_order_acquire,
908 std::memory_order_relaxed)))
910 for (uint64_t i = 0; i < toWrite; ++i)
912 auto* slot = &segment->_elements[(head + i) & mask];
914 while (slot->_seq.load (std::memory_order_acquire) != head + i)
918 slot->data = elements[i];
919 slot->_seq.store (head + i + 1, std::memory_order_release);
922 return static_cast<ssize_t
> (toWrite);
937 static int tryPop (
Segment* segment, Type& element, uint64_t capacity, uint64_t mask)
noexcept
947 auto& sync = segment->_sync;
948 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
953 auto* slot = &segment->_elements[tail & mask];
954 uint64_t seq = slot->_seq.load (std::memory_order_acquire);
956 if (seq == (tail + 1))
958 Type local = slot->data;
959 if (
JOIN_LIKELY (sync._tail.compare_exchange_weak (tail, tail + 1, std::memory_order_acquire,
960 std::memory_order_relaxed)))
963 slot->_seq.store (tail + capacity, std::memory_order_release);
975 tail = sync._tail.load (std::memory_order_relaxed);
989 static ssize_t
tryPop (
Segment* segment, Type* elements,
size_t size, uint64_t capacity, uint64_t mask)
noexcept
991 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
998 auto& sync = segment->_sync;
999 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
1003 uint64_t head = sync._head.load (std::memory_order_acquire);
1004 uint64_t toRead = std::min (
static_cast<uint64_t
> (size), head - tail);
1013 for (; ready < toRead; ++ready)
1015 auto* slot = &segment->_elements[(tail + ready) & mask];
1016 if (
JOIN_UNLIKELY (slot->_seq.load (std::memory_order_acquire) != tail + ready + 1))
1020 elements[ready] = slot->data;
1029 if (
JOIN_LIKELY (sync._tail.compare_exchange_weak (tail, tail + ready, std::memory_order_acquire,
1030 std::memory_order_relaxed)))
1032 for (uint64_t i = 0; i < ready; ++i)
1034 segment->_elements[(tail + i) & mask]._seq.store (tail + i + capacity,
1035 std::memory_order_release);
1038 return static_cast<ssize_t
> (ready);
1049 template <
typename Backend,
template <
typename,
typename>
class SyncPolicy>
1053 template <
typename Type>
adaptive backoff strategy for busy-wait loops.
Definition backoff.hpp:43
queue base class.
Definition queue.hpp:133
int push(const Type *elements, size_t size) noexcept
push multiple elements into the ring buffer.
Definition queue.hpp:261
BasicQueue(uint64_t capacity, Args &&... args)
create instance.
Definition queue.hpp:149
typename std::conditional< needs_seq< SyncPolicy >::value, QueueSlotFull< Type >, QueueSlotLight< Type > >::type Slot
Definition queue.hpp:139
int push(const Type &element) noexcept
push element into the ring buffer.
Definition queue.hpp:238
int mlock() const noexcept
lock memory in RAM.
Definition queue.hpp:432
ssize_t tryPop(Type *elements, size_t size) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:303
int tryPop(Type &element) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:292
~BasicQueue() noexcept=default
destroy queue instance.
int pop(Type *elements, size_t size) noexcept
pop multiple elements from the ring buffer.
Definition queue.hpp:336
BasicQueue(BasicQueue &&other)=delete
move constructor.
int pop(Type &element) noexcept
pop element from the ring buffer.
Definition queue.hpp:313
bool full() const noexcept
check if the ring buffer is full.
Definition queue.hpp:394
ssize_t tryPush(const Type *elements, size_t size) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:228
bool empty() const noexcept
check if the ring buffer is empty.
Definition queue.hpp:407
uint64_t pending() const noexcept
get the number of pending elements for reading.
Definition queue.hpp:366
uint64_t available() const noexcept
get the number of available slots for writing.
Definition queue.hpp:381
Type ValueType
Definition queue.hpp:138
BasicQueue(const BasicQueue &other)=delete
copy constructor.
Definition acceptor.hpp:32
constexpr uint64_t nextPow2(uint64_t value) noexcept
round up to the next power of two.
Definition utils.hpp:506
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
multiple producer multiple consumer ring buffer.
Definition queue.hpp:857
static ssize_t tryPush(Segment *segment, const Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:883
static ssize_t tryPop(Segment *segment, Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:989
static int tryPush(Segment *segment, const Type &element, uint64_t capacity, uint64_t mask) noexcept
try to push element into the ring buffer.
Definition queue.hpp:869
static int tryPop(Segment *segment, Type &element, uint64_t capacity, uint64_t mask) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:937
multiple producer single consumer ring buffer.
Definition queue.hpp:664
static ssize_t tryPop(Segment *segment, Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:813
static int tryPush(Segment *segment, const Type &element, uint64_t, uint64_t mask) noexcept
try to push element into the ring buffer.
Definition queue.hpp:676
static int tryPop(Segment *segment, Type &element, uint64_t capacity, uint64_t mask) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:776
static ssize_t tryPush(Segment *segment, const Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:727
queue memory segment.
Definition queue.hpp:99
Slot _elements[]
flexible array of queue slots.
Definition queue.hpp:104
QueueSync _sync
synchronization primitives.
Definition queue.hpp:101
full queue slot used by MPSC/MPMC.
Definition queue.hpp:83
Type data
stored element data.
Definition queue.hpp:88
std::atomic_uint64_t _seq
sequence number for synchronization.
Definition queue.hpp:85
char _padding[(64 -((sizeof(std::atomic_uint64_t)+sizeof(Type)) % 64)) % 64]
padding to prevent false sharing.
Definition queue.hpp:91
lightweight queue slot used by SPSC.
Definition queue.hpp:73
Type data
stored element data.
Definition queue.hpp:75
queue synchronization primitives.
Definition queue.hpp:48
std::atomic_uint64_t _head
write position.
Definition queue.hpp:56
uint64_t _cachedTail
cached tail index for the producer side.
Definition queue.hpp:59
uint64_t _cachedHead
cached head index for the consumer side.
Definition queue.hpp:65
static constexpr uint64_t MAGIC
magic number for initialization detection.
Definition queue.hpp:50
std::atomic_uint64_t _tail
read position.
Definition queue.hpp:62
std::atomic_uint64_t _magic
initialization state atomic.
Definition queue.hpp:53
single producer single consumer ring buffer.
Definition queue.hpp:483
static ssize_t tryPop(Segment *segment, Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:619
static ssize_t tryPush(Segment *segment, const Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:533
static int tryPop(Segment *segment, Type &element, uint64_t, uint64_t mask) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:581
static int tryPush(Segment *segment, const Type &element, uint64_t capacity, uint64_t mask) noexcept
try to push element into the ring buffer.
Definition queue.hpp:495
queue forward declarations.
Definition queue.hpp:1051
primary trait: all sync policies need sequence numbers by default.
Definition queue.hpp:117
#define JOIN_LIKELY(x)
Definition utils.hpp:45
#define JOIN_UNLIKELY(x)
Definition utils.hpp:46