VLink  2.0.0
A high-performance communication middleware
mpmc_queue.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file mpmc_queue.h
26  * @brief Bounded lock-free multi-producer multi-consumer ring buffer with optional cv blocking.
27  *
28  * @details
29  * @c MpmcQueue is a fixed-capacity ring buffer based on a turn-counter algorithm. Each slot
30  * holds a per-slot atomic @c turn counter that encodes whether the slot is currently empty
31  * (ready for a producer) or full (ready for a consumer).
32  *
33  * @par Algorithm summary
34  * - Producers atomically increment @c head_ to claim a slot and spin until
35  * @c chunk.turn @c == @c turn(head) @c * @c 2 (slot empty), then construct the value and
36  * publish @c turn @c = @c turn(head) @c * @c 2 @c + @c 1 (slot full).
37  * - Consumers atomically increment @c tail_ to claim a slot and spin until
38  * @c chunk.turn @c == @c turn(tail) @c * @c 2 @c + @c 1 (slot full), then move the value
39  * out and publish @c turn @c = @c turn(tail) @c * @c 2 @c + @c 2 (slot empty for next round).
40  * - Spinning runs for @c kFirstSpinTimes (@c 32) iterations before calling @c yield_cpu.
41  * - The struct is cache-line aligned: @c head_, @c tail_ and each @c Chunk live in dedicated
42  * 64-byte regions to avoid false sharing.
43  *
44  * @par Producer / consumer guarantees
45  *
46  * | Behaviour | Push contract | Pop contract |
47  * | --------------------- | -------------------------------------------- | --------------------------------------- |
48  * | @c kNoBehavior | No notification on success or failure | Spin-wait only |
49  * | @c kConditionBehavior | Acquire @c cv_mtx_, notify @c not_empty | Wake @c not_full waiter on pop |
50  * | Blocking variants | @c emplace / @c push spin until success | @c pop spins until a slot is full |
51  * | Non-blocking variants | @c try_emplace / @c try_push return on full | @c try_pop returns false on empty |
52  * | Quit signal | @c notify_to_quit drops further pushes | Pop returns without value once quit set |
53  *
54  * @par Example
55  * @code
56  * vlink::MpmcQueue<int> q(1024);
57  *
58  * // Producer:
59  * q.push<vlink::MpmcQueue<int>::kConditionBehavior>(42);
60  *
61  * // Consumer:
62  * q.wait_not_empty();
63  * int val = 0;
64  * q.pop<vlink::MpmcQueue<int>::kConditionBehavior>(val);
65  * @endcode
66  *
67  * @note @c emplace / @c pop block by spinning; for bounded producers prefer the @c try_* forms.
68  * Capacity must be @c >= @c 1 (otherwise @c std::invalid_argument is thrown).
69  * @c notify_to_quit gracefully drains pending waiters and silently drops further pushes.
70  */
71 
72 #pragma once
73 
74 #include <array>
75 #include <atomic>
76 #include <chrono>
77 #include <cstddef>
78 #include <limits>
79 #include <memory>
80 #include <mutex>
81 #include <new>
82 #include <utility>
83 
84 #include "./condition_variable.h"
85 #include "./macros.h"
86 #include "./utils.h"
87 
88 #if defined(__linux__)
89 #define VLINK_NO_INSTRUMENT __attribute__((no_instrument_function))
90 #else
91 #define VLINK_NO_INSTRUMENT
92 #endif
93 
94 namespace vlink {
95 
96 /**
97  * @class MpmcQueueBase
98  * @brief Non-template base class that owns every element-type-independent piece of state.
99  *
100  * @details
101  * Hosts the cache-line aligned @c head_ / @c tail_ cursors, the optional condition variables,
102  * the quit flag, the shared capacity and the @c void* slot for the @c Chunk array storage.
103  * The element type @c T -- including the @c Chunk struct and the move / destroy plumbing --
104  * lives in @c MpmcQueue<T>, which accesses slots via a typed cast of @c chunk_storage_.
105  */
107  public:
108  /**
109  * @brief Per-call notification behaviour selector.
110  *
111  * @details
112  * Selected via the @c BehaviorT template argument to @c emplace / @c push / @c pop and the
113  * @c try_* variants. Use @c kConditionBehavior whenever consumers or producers may block on
114  * @c wait_not_empty / @c wait_not_full; otherwise the cv notifications are pure overhead.
115  */
116  enum Behavior : uint8_t { kNoBehavior = 0, kConditionBehavior = 1 };
117 
118  /**
119  * @brief Returns the fixed capacity chosen at construction.
120  *
121  * @return Slot count (always @c >= @c 1).
122  */
123  [[nodiscard]] size_t capacity() const noexcept VLINK_NO_INSTRUMENT;
124 
125  /**
126  * @brief Returns an approximate count of currently enqueued elements.
127  *
128  * @details
129  * Computes @c head_ @c - @c tail_ using acquire loads of both cursors; concurrent updates
130  * may produce a slightly stale value. @p real @c == @c true retries up to 50 times until
131  * the @c tail_ reading is stable, at the cost of latency. The result is clamped at @c 0
132  * to suppress transient @c head_ @c < @c tail_ wrap-arounds.
133  *
134  * @param real When @c true, retries for a stable snapshot. Default: @c false.
135  * @return Approximate number of enqueued elements.
136  */
137  [[nodiscard]] size_t size(bool real = false) const noexcept VLINK_NO_INSTRUMENT;
138 
139  /**
140  * @brief Returns @c true when the queue appears empty.
141  *
142  * @param real Pass @c true to use the retrying @c size variant.
143  * @return @c true when no elements are currently enqueued (approximately).
144  */
145  [[nodiscard]] bool empty(bool real = false) const noexcept VLINK_NO_INSTRUMENT;
146 
147  /**
148  * @brief Returns @c true when the queue appears full.
149  *
150  * @param real Pass @c true to use the retrying @c size variant.
151  * @return @c true when the queue is at or above capacity.
152  */
153  [[nodiscard]] bool is_full(bool real = false) const noexcept VLINK_NO_INSTRUMENT;
154 
155  /**
156  * @brief Blocks until the queue is non-empty or @p timeout elapses.
157  *
158  * @details
159  * Fast path: returns immediately when @c empty(true) is @c false. Slow path: waits on
160  * @c cv_not_empty until a producer publishes under @c kConditionBehavior or
161  * @c notify_to_quit fires.
162  *
163  * @param timeout Maximum wait; @c 0 means wait forever. Default: @c 0.
164  * @return @c true when the queue became non-empty; @c false on timeout or quit.
165  */
166  bool wait_not_empty(std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) noexcept VLINK_NO_INSTRUMENT;
167 
168  /**
169  * @brief Blocks until the queue has free space or @p timeout elapses.
170  *
171  * @details
172  * Mirror of @c wait_not_empty for producers; waits on @c cv_not_full.
173  *
174  * @param timeout Maximum wait; @c 0 means wait forever. Default: @c 0.
175  * @return @c true when space became available; @c false on timeout or quit.
176  */
177  bool wait_not_full(std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) noexcept VLINK_NO_INSTRUMENT;
178 
179  /**
180  * @brief Signals graceful shutdown to every waiter and rejects further pushes.
181  *
182  * @details
183  * Sets the internal quit flag with release ordering and broadcasts both condition variables.
184  * Existing @c wait_not_empty / @c wait_not_full calls return @c false; subsequent
185  * @c emplace / @c push silently drop their payload; @c try_pop returns @c false without
186  * touching the output. Safe to call multiple times.
187  */
188  void notify_to_quit() noexcept VLINK_NO_INSTRUMENT;
189 
190  protected:
191  static constexpr std::memory_order kMemoryOrderAcquire = std::memory_order_acquire;
192  static constexpr std::memory_order kMemoryOrderRelease = std::memory_order_release;
193  static constexpr std::memory_order kMemoryOrderRelaxed = std::memory_order_relaxed;
194 
195  static constexpr size_t kInterferenceSize = 64U;
196  static constexpr size_t kFirstSpinTimes = 32U;
197 
198  explicit MpmcQueueBase(size_t capacity);
199 
200  ~MpmcQueueBase() noexcept = default;
201 
202  [[noreturn]] static void throw_mpmc_invalid_capacity();
203 
204  [[noreturn]] static void throw_mpmc_alignment_failure();
205 
206  [[nodiscard]] constexpr size_t idx(size_t i) const noexcept { return i % capacity_; }
207 
208  [[nodiscard]] constexpr size_t turn(size_t i) const noexcept { return i / capacity_; }
209 
210  alignas(kInterferenceSize) std::atomic<size_t> head_{0U};
211 
212  void* chunk_storage_{nullptr};
213  size_t capacity_{0};
214 
215  mutable std::mutex cv_mtx_;
216 
217  alignas(kInterferenceSize) std::atomic<size_t> tail_{0U};
218 
221 
222  struct alignas(kInterferenceSize) QuitFlag {
223  std::atomic_bool value{false};
224  char padding[kInterferenceSize - sizeof(std::atomic_bool)];
225  } quit_flag_;
226 };
227 
228 /**
229  * @class MpmcQueue
230  * @brief Fixed-capacity lock-free MPMC ring buffer over @c T.
231  *
232  * @details
233  * Allocates @c capacity @c + @c 1 cache-line-aligned slots, validates alignment, and provides
234  * per-slot turn counters for non-blocking enqueue / dequeue. Element type must be movable.
235  *
236  * @tparam T Element type stored in each slot.
237  */
238 template <typename T>
239 class MpmcQueue : public MpmcQueueBase {
240  public:
241  /**
242  * @brief Constructs a queue with the given fixed capacity.
243  *
244  * @details
245  * Allocates @c capacity @c + @c 1 slots via the aligned allocator (extra trailing guard slot
246  * keeps index arithmetic simple). Validates that the allocation is aligned to
247  * @c kInterferenceSize bytes; misaligned allocations throw @c std::bad_alloc. Each slot's
248  * turn counter is default-initialised to zero.
249  *
250  * @param capacity Maximum number of elements; must be @c >= @c 1.
251  * @throws std::invalid_argument when @p capacity is below @c 1.
252  * @throws std::bad_alloc when allocation fails or returns a misaligned pointer.
253  */
254  explicit MpmcQueue(size_t capacity) VLINK_NO_INSTRUMENT;
255 
256  /**
257  * @brief Destructor; destroys still-occupied slots and releases the chunk array.
258  *
259  * @details
260  * Not thread-safe with concurrent producers or consumers; the caller must quiesce the queue
261  * before destruction.
262  */
263  ~MpmcQueue() noexcept VLINK_NO_INSTRUMENT;
264 
265  /**
266  * @brief In-place constructs an element and blocks until a slot is available.
267  *
268  * @details
269  * Claims the next slot via @c head_.fetch_add and spins on the turn counter until the slot
270  * is empty. When @c BehaviorT is @c kConditionBehavior the producer first awaits free space
271  * via @c wait_not_full and notifies one @c wait_not_empty waiter after publishing. Returns
272  * silently when @c notify_to_quit is observed.
273  *
274  * @tparam BehaviorT Notification behaviour. Default: @c kNoBehavior.
275  * @tparam Args Constructor argument types forwarded to @c T.
276  * @param args Arguments forwarded to @c T 's constructor.
277  */
278  template <Behavior BehaviorT = kNoBehavior, typename... Args>
279  void emplace(Args&&... args) noexcept VLINK_NO_INSTRUMENT;
280 
281  /**
282  * @brief Non-blocking in-place construction.
283  *
284  * @details
285  * Reads @c head_, attempts a CAS to claim the slot and retries when the head moves; returns
286  * @c false when the queue is full or @c notify_to_quit has been observed. On success
287  * publishes the slot and, when @c BehaviorT is @c kConditionBehavior, notifies a waiter.
288  *
289  * @tparam BehaviorT Notification behaviour. Default: @c kNoBehavior.
290  * @tparam Args Constructor argument types forwarded to @c T.
291  * @param args Arguments forwarded to @c T 's constructor.
292  * @return @c true on successful enqueue; @c false on full queue or quit.
293  */
294  template <Behavior BehaviorT = kNoBehavior, typename... Args>
295  [[nodiscard]] bool try_emplace(Args&&... args) noexcept VLINK_NO_INSTRUMENT;
296 
297  /**
298  * @brief Pushes a perfect-forwarded value, blocking until a slot is available.
299  *
300  * @details
301  * Wrapper around @c emplace<BehaviorT>(std::forward<P>(v)).
302  *
303  * @tparam BehaviorT Notification behaviour. Default: @c kNoBehavior.
304  * @tparam P Value type accepted by @c T 's constructor.
305  * @param v Value to push.
306  */
307  template <Behavior BehaviorT = kNoBehavior, typename P>
308  void push(P&& v) noexcept VLINK_NO_INSTRUMENT;
309 
310  /**
311  * @brief Non-blocking push wrapper around @c try_emplace.
312  *
313  * @tparam BehaviorT Notification behaviour. Default: @c kNoBehavior.
314  * @tparam P Value type accepted by @c T 's constructor.
315  * @param v Value to push.
316  * @return @c true on successful enqueue; @c false on full queue or quit.
317  */
318  template <Behavior BehaviorT = kNoBehavior, typename P>
319  [[nodiscard]] bool try_push(P&& v) noexcept VLINK_NO_INSTRUMENT;
320 
321  /**
322  * @brief Pops a value by move; blocks until an element is available.
323  *
324  * @details
325  * Claims the next slot via @c tail_.fetch_add, spins until the slot is full, moves the value
326  * into @p v and republishes the slot as empty. When @c BehaviorT is @c kConditionBehavior
327  * a @c wait_not_full waiter is notified. Returns without altering @p v when
328  * @c notify_to_quit is observed mid-wait.
329  *
330  * @tparam BehaviorT Notification behaviour. Default: @c kNoBehavior.
331  * @param v Destination assigned via @c std::move.
332  */
333  template <Behavior BehaviorT = kNoBehavior>
334  void pop(T& v) noexcept VLINK_NO_INSTRUMENT;
335 
336  /**
337  * @brief Non-blocking pop; returns @c false when the queue is empty.
338  *
339  * @details
340  * Uses CAS retry on @c tail_; @p v is untouched on failure.
341  *
342  * @tparam BehaviorT Notification behaviour. Default: @c kNoBehavior.
343  * @param v Destination assigned via @c std::move on success.
344  * @return @c true on successful pop; @c false on empty or quit.
345  */
346  template <Behavior BehaviorT = kNoBehavior>
347  [[nodiscard]] bool try_pop(T& v) noexcept VLINK_NO_INSTRUMENT;
348 
349  private:
350 #if defined(__cpp_aligned_new)
351  template <typename ChunkT>
352  using AlignedAllocator = std::allocator<ChunkT>;
353 #else
354  template <typename ChunkT>
355  struct AlignedAllocator {
356  using value_type = ChunkT;
357 
358  ChunkT* allocate(size_t n) {
359  if VUNLIKELY (n > std::numeric_limits<size_t>::max() / sizeof(ChunkT)) {
360  throw std::bad_array_new_length();
361  }
362 
363 #ifdef _WIN32
364  auto* p = static_cast<ChunkT*>(_aligned_malloc(sizeof(ChunkT) * n, alignof(ChunkT)));
365 
366  if VUNLIKELY (p == nullptr) {
367  throw std::bad_alloc();
368  }
369 #else
370  ChunkT* p;
371 
372  if VUNLIKELY (posix_memalign(reinterpret_cast<void**>(&p), alignof(ChunkT), sizeof(ChunkT) * n) != 0) {
373  throw std::bad_alloc();
374  }
375 #endif
376 
377  return p;
378  }
379 
380  void deallocate(ChunkT* p, size_t) {
381 #ifdef _WIN32
382  _aligned_free(p);
383 #else
384  free(p);
385 #endif
386  }
387  };
388 #endif
389 
390  struct Chunk {
391  ~Chunk() noexcept {
392  if ((turn.load(kMemoryOrderAcquire) & 1U) != 0U) {
393  destroy();
394  }
395  }
396 
397  template <typename... Args>
398  void construct(Args&&... args) noexcept {
399  new (storage.data()) T(std::forward<Args>(args)...);
400  }
401 
402  void destroy() noexcept { std::launder(reinterpret_cast<T*>(storage.data()))->~T(); }
403 
404  [[nodiscard]] T&& move() noexcept { return std::move(*std::launder(reinterpret_cast<T*>(storage.data()))); }
405 
406  alignas(kInterferenceSize) std::atomic<size_t> turn{0U};
407  alignas(alignof(T)) std::array<uint8_t, sizeof(T)> storage;
408  };
409 
410  [[nodiscard]] Chunk* chunks() noexcept { return static_cast<Chunk*>(chunk_storage_); }
411 
412  [[nodiscard]] const Chunk* chunks() const noexcept { return static_cast<const Chunk*>(chunk_storage_); }
413 
415 };
416 
417 ////////////////////////////////////////////////////////////////
418 /// Details
419 ////////////////////////////////////////////////////////////////
420 
421 template <typename T>
422 inline MpmcQueue<T>::MpmcQueue(size_t capacity) : MpmcQueueBase(capacity) {
423  static_assert(alignof(Chunk) == kInterferenceSize,
424  "Slot must be aligned to cache line boundary to prevent false sharing");
425  static_assert(sizeof(Chunk) % kInterferenceSize == 0,
426  "Slot size must be a multiple of cache line size to prevent "
427  "false sharing between adjacent slots");
428  static_assert(sizeof(MpmcQueue) % kInterferenceSize == 0,
429  "Queue size must be a multiple of cache line size to "
430  "prevent false sharing between adjacent queues");
431 
432  AlignedAllocator<Chunk> allocator;
433  Chunk* raw = allocator.allocate(capacity_ + 1);
434 
435  if VUNLIKELY (reinterpret_cast<size_t>(raw) % alignof(Chunk) != 0U) {
436  allocator.deallocate(raw, capacity_ + 1); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
437  throw_mpmc_alignment_failure(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
438  }
439 
440  chunk_storage_ = raw;
441 
442  for (size_t i = 0U; i < capacity_; ++i) {
443  new (&raw[i]) Chunk();
444  }
445 }
446 
447 template <typename T>
448 inline MpmcQueue<T>::~MpmcQueue() noexcept {
449  Chunk* raw = chunks();
450 
451  for (size_t i = 0U; i < capacity_; ++i) {
452  raw[i].~Chunk();
453  }
454 
455  AlignedAllocator<Chunk> allocator;
456  allocator.deallocate(raw, capacity_ + 1);
457 }
458 
459 template <typename T>
460 template <typename MpmcQueue<T>::Behavior BehaviorT, typename... Args>
461 inline void MpmcQueue<T>::emplace(Args&&... args) noexcept {
462  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
463  return;
464  }
465 
466  if constexpr (BehaviorT == kConditionBehavior) {
467  wait_not_full(std::chrono::milliseconds(0));
468 
469  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
470  return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
471  }
472  }
473 
474  const auto head = head_.fetch_add(1U, std::memory_order_seq_cst);
475  auto& chunk = chunks()[idx(head)];
476 
477  size_t spin = 0;
478 
479  while (turn(head) * 2U != chunk.turn.load(kMemoryOrderAcquire)) {
480  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
481  return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
482  }
483 
484  if (++spin < kFirstSpinTimes) {
485  } else {
487  }
488  }
489 
490  chunk.construct(std::forward<Args>(args)...);
491  chunk.turn.store((turn(head) * 2U) + 1U, kMemoryOrderRelease);
492 
493  if constexpr (BehaviorT == kConditionBehavior) {
494  std::lock_guard lock(cv_mtx_);
495  cv_not_empty_.notify_one();
496  }
497 }
498 
499 template <typename T>
500 template <typename MpmcQueue<T>::Behavior BehaviorT, typename... Args>
501 inline bool MpmcQueue<T>::try_emplace(Args&&... args) noexcept {
502  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
503  return false;
504  }
505 
506  auto head = head_.load(kMemoryOrderAcquire);
507 
508  for (;;) {
509  auto& chunk = chunks()[idx(head)];
510 
511  if VLIKELY (turn(head) * 2U == chunk.turn.load(kMemoryOrderAcquire)) {
512  if (head_.compare_exchange_strong(head, head + 1U, std::memory_order_seq_cst, std::memory_order_seq_cst)) {
513  chunk.construct(std::forward<Args>(args)...);
514  chunk.turn.store((turn(head) * 2U) + 1U, kMemoryOrderRelease);
515 
516  if constexpr (BehaviorT == kConditionBehavior) {
517  std::lock_guard lock(cv_mtx_);
518  cv_not_empty_.notify_one();
519  }
520 
521  return true;
522  }
523  } else {
524  const auto prev_head = head;
525  head = head_.load(kMemoryOrderAcquire);
526 
527  if VUNLIKELY (head == prev_head) {
528  return false;
529  }
530  }
531  }
532 }
533 
534 template <typename T>
535 template <typename MpmcQueue<T>::Behavior BehaviorT, typename P>
536 inline void MpmcQueue<T>::push(P&& v) noexcept {
537  emplace<BehaviorT>(std::forward<P>(v));
538 }
539 
540 template <typename T>
541 template <typename MpmcQueue<T>::Behavior BehaviorT, typename P>
542 inline bool MpmcQueue<T>::try_push(P&& v) noexcept {
543  return try_emplace<BehaviorT>(std::forward<P>(v));
544 }
545 
546 template <typename T>
547 template <typename MpmcQueue<T>::Behavior BehaviorT>
548 inline void MpmcQueue<T>::pop(T& v) noexcept {
549  auto const tail = tail_.fetch_add(1U, std::memory_order_seq_cst);
550  auto& chunk = chunks()[idx(tail)];
551 
552  size_t spin = 0;
553 
554  while (turn(tail) * 2U + 1U != chunk.turn.load(kMemoryOrderAcquire)) {
555  // LCOV_EXCL_START GCOVR_EXCL_START
556  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
557  return;
558  }
559 
560  if (++spin < kFirstSpinTimes) {
561  } else {
563  }
564  // LCOV_EXCL_STOP GCOVR_EXCL_STOP
565  }
566 
567  v = chunk.move();
568  chunk.destroy();
569  chunk.turn.store((turn(tail) * 2U) + 2U, kMemoryOrderRelease);
570 
571  if constexpr (BehaviorT == kConditionBehavior) {
572  std::lock_guard lock(cv_mtx_);
573  cv_not_full_.notify_one();
574  }
575 }
576 
577 template <typename T>
578 template <typename MpmcQueue<T>::Behavior BehaviorT>
579 inline bool MpmcQueue<T>::try_pop(T& v) noexcept {
580  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
581  return false;
582  }
583 
584  auto tail = tail_.load(kMemoryOrderAcquire);
585 
586  for (;;) {
587  auto& chunk = chunks()[idx(tail)];
588 
589  if VLIKELY (turn(tail) * 2U + 1U == chunk.turn.load(kMemoryOrderAcquire)) {
590  if (tail_.compare_exchange_strong(tail, tail + 1U, std::memory_order_seq_cst, std::memory_order_seq_cst)) {
591  v = chunk.move();
592  chunk.destroy();
593  chunk.turn.store((turn(tail) * 2U) + 2U, kMemoryOrderRelease);
594 
595  if constexpr (BehaviorT == kConditionBehavior) {
596  std::lock_guard lock(cv_mtx_);
597  cv_not_full_.notify_one();
598  }
599 
600  return true;
601  }
602  } else {
603  const auto prev_tail = tail;
604  tail = tail_.load(kMemoryOrderAcquire);
605 
606  if VUNLIKELY (tail == prev_tail) {
607  return false;
608  }
609  }
610 
611  if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
612  return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
613  }
614  }
615 }
616 
617 } // namespace vlink
Monotonic-clock condition variable replacement immune to system clock jumps.
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81
#define VLIKELY(...)
Short alias for VLINK_LIKELY.
Definition: macros.h:284
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
#define VLINK_NO_INSTRUMENT
Definition: mpmc_queue.h:91
Portable host-system utility surface used across the VLink runtime.