| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title AtomicQueue.cpp | ||
| 3 | // \author B. Duckett | ||
| 4 | // \brief Lock-free MPMC circular buffer with embedded buffer storage | ||
| 5 | // | ||
| 6 | // \copyright | ||
| 7 | // Copyright 2026, by the California Institute of Technology. | ||
| 8 | // ALL RIGHTS RESERVED. United States Government Sponsorship | ||
| 9 | // acknowledged. | ||
| 10 | // | ||
| 11 | // ====================================================================== | ||
| 12 | |||
| 13 | #include <Fw/Types/Assert.hpp> | ||
| 14 | #include <Os/Generic/Types/AtomicQueue.hpp> | ||
| 15 | #include <cstdio> | ||
| 16 | #include <cstring> | ||
| 17 | #include <new> | ||
| 18 | |||
| 19 | namespace Types { | ||
| 20 | |||
| 21 | ✗ | U32 AtomicQueue::computeChecksum(const U8* buffer, FwSizeType size) { | |
| 22 | ✗ | FW_ASSERT(buffer != nullptr); | |
| 23 | ✗ | U32 sum = 0; | |
| 24 | ✗ | for (FwSizeType i = 0; i < size; ++i) { | |
| 25 | ✗ | sum += buffer[i]; | |
| 26 | ✗ | sum = (sum << 1) | (sum >> 31); // Rotate left | |
| 27 | } | ||
| 28 | ✗ | return sum; | |
| 29 | } | ||
| 30 | |||
| 31 | 1418 | AtomicQueue::AtomicQueue() | |
| 32 | 1418 | : m_slots(nullptr), | |
| 33 | 1418 | m_bufferMemory(nullptr), | |
| 34 | 1418 | m_capacity(0), | |
| 35 | 1418 | m_bufferSize(0), | |
| 36 | 1418 | m_mask(0), | |
| 37 | 1418 | m_enqueuePos(0), | |
| 38 | 1418 | m_dequeuePos(0), | |
| 39 | 1418 | m_allocator(nullptr), | |
| 40 | 1418 | m_allocatorId(0), | |
| 41 | 1418 | m_notFullSem(nullptr) {} | |
| 42 | |||
| 43 | 1418 | AtomicQueue::~AtomicQueue() { | |
| 44 | 1418 | this->teardown(); | |
| 45 | 1418 | } | |
| 46 | |||
| 47 | 1418 | void AtomicQueue::create(FwSizeType numBuffers, | |
| 48 | FwSizeType bufferSize, | ||
| 49 | Fw::MemAllocator& allocator, | ||
| 50 | FwEnumStoreType allocatorId) { | ||
| 51 | 1418 | FW_ASSERT(numBuffers > 0, static_cast<FwAssertArgType>(numBuffers)); | |
| 52 | 1418 | FW_ASSERT(bufferSize > 0, static_cast<FwAssertArgType>(bufferSize)); | |
| 53 | |||
| 54 | 1418 | this->m_capacity = numBuffers; | |
| 55 | 1418 | this->m_bufferSize = bufferSize; | |
| 56 | 1418 | this->m_allocator = &allocator; | |
| 57 | 1418 | this->m_allocatorId = allocatorId; | |
| 58 | |||
| 59 | // Optimization: use bitwise AND for power-of-2, otherwise modulo | ||
| 60 | 1418 | bool isPowerOf2 = (numBuffers & (numBuffers - 1)) == 0; | |
| 61 |
2/2✓ Branch 0 taken 154 times.
✓ Branch 1 taken 1264 times.
|
1418 | this->m_mask = isPowerOf2 ? (numBuffers - 1) : 0; |
| 62 | |||
| 63 | // Allocate slot array (with overflow check) | ||
| 64 | 1418 | FW_ASSERT(numBuffers <= std::numeric_limits<FwSizeType>::max() / sizeof(Slot), | |
| 65 | static_cast<FwAssertArgType>(numBuffers), static_cast<FwAssertArgType>(sizeof(Slot))); | ||
| 66 | 1418 | FwSizeType slotsSize = numBuffers * sizeof(Slot); | |
| 67 |
1/1✓ Branch 4 taken 1418 times.
|
1418 | void* slotMem = allocator.checkedAllocate(allocatorId, slotsSize, alignof(Slot)); |
| 68 | 1418 | FW_ASSERT(slotMem != nullptr, static_cast<FwAssertArgType>(numBuffers), static_cast<FwAssertArgType>(bufferSize)); | |
| 69 | 1418 | this->m_slots = static_cast<Slot*>(slotMem); | |
| 70 | |||
| 71 | // Allocate contiguous buffer memory for all slots (with overflow check) | ||
| 72 | 1418 | FW_ASSERT(numBuffers <= std::numeric_limits<FwSizeType>::max() / bufferSize, | |
| 73 | static_cast<FwAssertArgType>(numBuffers), static_cast<FwAssertArgType>(bufferSize)); | ||
| 74 | 1418 | FwSizeType totalBufferSize = numBuffers * bufferSize; | |
| 75 |
1/1✓ Branch 4 taken 1418 times.
|
1418 | void* bufferMem = allocator.checkedAllocate(allocatorId, totalBufferSize, 64); |
| 76 | 1418 | FW_ASSERT(bufferMem != nullptr, static_cast<FwAssertArgType>(numBuffers), static_cast<FwAssertArgType>(bufferSize)); | |
| 77 | 1418 | this->m_bufferMemory = static_cast<U8*>(bufferMem); | |
| 78 | |||
| 79 | // Initialize all slots with placement new and assign buffer pointers | ||
| 80 |
2/2✓ Branch 0 taken 25748 times.
✓ Branch 1 taken 1418 times.
|
27166 | for (FwSizeType i = 0; i < numBuffers; ++i) { |
| 81 | 25748 | Slot* slot = new (&this->m_slots[i]) Slot(); | |
| 82 | |||
| 83 | // Assign buffer from contiguous memory block | ||
| 84 | 25748 | slot->buffer = this->m_bufferMemory + (i * bufferSize); | |
| 85 | 25748 | slot->size = 0; | |
| 86 | 25748 | slot->sequence.store(i, std::memory_order_relaxed); | |
| 87 | |||
| 88 | // Runtime verification that sequence atomics are lock-free | ||
| 89 | // This is critical for ISR safety and lock-free guarantee | ||
| 90 | 25748 | FW_ASSERT(slot->sequence.is_lock_free(), static_cast<FwAssertArgType>(i), | |
| 91 | static_cast<FwAssertArgType>(numBuffers)); | ||
| 92 | } | ||
| 93 | |||
| 94 | // Create semaphore for blocking enqueue support (all platforms) | ||
| 95 | // Allocate semaphore using provided allocator | ||
| 96 | 1418 | FwSizeType semSize = sizeof(Os::CountingSemaphore); | |
| 97 |
1/1✓ Branch 4 taken 1418 times.
|
1418 | void* semMem = allocator.checkedAllocate(allocatorId, semSize, alignof(Os::CountingSemaphore)); |
| 98 | 1418 | FW_ASSERT(semMem != nullptr, static_cast<FwAssertArgType>(numBuffers)); | |
| 99 | |||
| 100 | // Use placement new to construct semaphore with initial count = numBuffers (all slots available) | ||
| 101 |
1/3✓ Branch 3 taken 1418 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
|
1418 | this->m_notFullSem = new (semMem) Os::CountingSemaphore(static_cast<U32>(numBuffers)); |
| 102 | 1418 | FW_ASSERT(this->m_notFullSem != nullptr, static_cast<FwAssertArgType>(numBuffers)); | |
| 103 | |||
| 104 | 1418 | this->m_enqueuePos.store(0, std::memory_order_relaxed); | |
| 105 | 1418 | this->m_dequeuePos.store(0, std::memory_order_relaxed); | |
| 106 | 1418 | } | |
| 107 | |||
| 108 | 4254 | void AtomicQueue::teardown() { | |
| 109 | // Destroy and deallocate semaphore | ||
| 110 |
2/2✓ Branch 2 taken 1418 times.
✓ Branch 3 taken 2836 times.
|
4254 | if (this->m_notFullSem != nullptr) { |
| 111 | 1418 | FW_ASSERT(this->m_allocator != nullptr, 0); | |
| 112 | |||
| 113 | // Call destructor | ||
| 114 | 1418 | this->m_notFullSem->~CountingSemaphore(); | |
| 115 | |||
| 116 | // Deallocate memory | ||
| 117 | 1418 | this->m_allocator->deallocate(this->m_allocatorId, this->m_notFullSem); | |
| 118 | 1418 | this->m_notFullSem = nullptr; | |
| 119 | } | ||
| 120 | |||
| 121 | // Destroy slots and deallocate memory | ||
| 122 |
2/2✓ Branch 1 taken 1418 times.
✓ Branch 2 taken 2836 times.
|
4254 | if (this->m_slots != nullptr) { |
| 123 | 1418 | FW_ASSERT(this->m_capacity > 0, static_cast<FwAssertArgType>(this->m_capacity)); | |
| 124 | 1418 | FW_ASSERT(this->m_allocator != nullptr, 0); | |
| 125 | |||
| 126 | // Call destructors on slots | ||
| 127 |
2/2✓ Branch 2 taken 25748 times.
✓ Branch 3 taken 1418 times.
|
27166 | for (FwSizeType i = 0; i < this->m_capacity; ++i) { |
| 128 | 25748 | FW_ASSERT(i < this->m_capacity, static_cast<FwAssertArgType>(i), | |
| 129 | static_cast<FwAssertArgType>(this->m_capacity)); | ||
| 130 | 25748 | this->m_slots[i].~Slot(); | |
| 131 | } | ||
| 132 | |||
| 133 | // Deallocate buffer memory | ||
| 134 |
1/2✓ Branch 2 taken 1418 times.
✗ Branch 3 not taken.
|
1418 | if (this->m_bufferMemory != nullptr) { |
| 135 | 1418 | this->m_allocator->deallocate(this->m_allocatorId, this->m_bufferMemory); | |
| 136 | 1418 | this->m_bufferMemory = nullptr; | |
| 137 | } | ||
| 138 | |||
| 139 | // Deallocate slot array | ||
| 140 | 1418 | this->m_allocator->deallocate(this->m_allocatorId, this->m_slots); | |
| 141 | 1418 | this->m_slots = nullptr; | |
| 142 | } | ||
| 143 | |||
| 144 | 4254 | this->m_enqueuePos.store(0, std::memory_order_relaxed); | |
| 145 | 4254 | this->m_dequeuePos.store(0, std::memory_order_relaxed); | |
| 146 | 4254 | this->m_capacity = 0; | |
| 147 | 4254 | this->m_bufferSize = 0; | |
| 148 | 4254 | this->m_mask = 0; | |
| 149 | 4254 | this->m_allocator = nullptr; | |
| 150 | 4254 | } | |
| 151 | |||
| 152 | 278022 | bool AtomicQueue::enqueueInternal(const U8* buffer, FwSizeType size) { | |
| 153 | 278022 | FW_ASSERT(this->m_slots != nullptr, 0); | |
| 154 | 278100 | FW_ASSERT(buffer != nullptr, 0); | |
| 155 | 278100 | FW_ASSERT(size > 0, static_cast<FwAssertArgType>(size)); | |
| 156 | 278100 | FW_ASSERT(size <= this->m_bufferSize, static_cast<FwAssertArgType>(size), | |
| 157 | static_cast<FwAssertArgType>(this->m_bufferSize)); | ||
| 158 | |||
| 159 |
2/2✓ Branch 0 taken 276267 times.
✓ Branch 1 taken 1072 times.
|
277339 | for (FwSizeType retry = 0; retry < MAX_CAS_RETRIES; ++retry) { |
| 160 | 276267 | FW_ASSERT(retry < MAX_CAS_RETRIES, static_cast<FwAssertArgType>(retry)); | |
| 161 | |||
| 162 | // acquire-release on slot->sequence ensures coherence, enqueuePos & dequeuePos are relaxed since strong memory | ||
| 163 | // ordering is not required | ||
| 164 | |||
| 165 | // Get current enqueue position | ||
| 166 | 276267 | FwSizeType pos = this->m_enqueuePos.load(std::memory_order_relaxed); | |
| 167 | |||
| 168 | // Check against dequeue position to prevent lapping (detect full queue) | ||
| 169 | 272877 | FwSizeType deqPos = this->m_dequeuePos.load(std::memory_order_relaxed); | |
| 170 | 273402 | FwSignedSizeType queueDiff = static_cast<FwSignedSizeType>(pos) - static_cast<FwSignedSizeType>(deqPos); | |
| 171 |
2/2✓ Branch 2 taken 191628 times.
✓ Branch 3 taken 85922 times.
|
273402 | if (queueDiff >= static_cast<FwSignedSizeType>(this->m_capacity)) { |
| 172 | 191628 | return false; // Queue is full | |
| 173 | } | ||
| 174 | |||
| 175 |
1/1✓ Branch 3 taken 85839 times.
|
85922 | Slot* slot = &this->m_slots[this->getIndex(pos)]; |
| 176 | 85894 | FwSizeType seq = slot->sequence.load(std::memory_order_acquire); | |
| 177 | |||
| 178 | // Check if slot is ready for write (seq == pos means available) | ||
| 179 | 85546 | FwSignedSizeType diff = static_cast<FwSignedSizeType>(seq) - static_cast<FwSignedSizeType>(pos); | |
| 180 | |||
| 181 |
1/2✓ Branch 0 taken 85718 times.
✗ Branch 1 not taken.
|
85546 | if (diff == 0) { |
| 182 | // Slot available, try to claim it | ||
| 183 |
1/2✓ Branch 4 taken 86399 times.
✗ Branch 5 not taken.
|
171262 | if (this->m_enqueuePos.compare_exchange_weak(pos, pos + 1, std::memory_order_release, |
| 184 | std::memory_order_relaxed)) { | ||
| 185 | // Claimed the slot, copy message data | ||
| 186 | 86399 | FW_ASSERT(slot->buffer != nullptr, static_cast<FwAssertArgType>(pos)); | |
| 187 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 86106 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 86106 times.
|
86489 | (void)std::memcpy(slot->buffer, buffer, size); |
| 188 | 86106 | slot->size = size; | |
| 189 | |||
| 190 | // Mark slot as ready for read | ||
| 191 | 86033 | slot->sequence.store(pos + 1, std::memory_order_release); | |
| 192 | 85811 | return true; | |
| 193 | } | ||
| 194 | ✗ | } else if (diff < 0) { | |
| 195 | // Queue is full (wrapped around) | ||
| 196 | 22 | return false; | |
| 197 | } | ||
| 198 | // else: another producer claimed this slot, retry | ||
| 199 | } | ||
| 200 | |||
| 201 | 1072 | return false; | |
| 202 | } | ||
| 203 | |||
| 204 | 274142 | bool AtomicQueue::enqueue(const U8* buffer, FwSizeType size) { | |
| 205 | 274142 | bool success = this->enqueueInternal(buffer, size); | |
| 206 | |||
| 207 | // Decrement semaphore to track available slots (if semaphore exists) | ||
| 208 | // This ensures blocking sends see correct availability even when | ||
| 209 | // queue is filled via non-blocking sends. | ||
| 210 | // | ||
| 211 | // NOTE: tryWait() may fail due to race conditions when multiple threads | ||
| 212 | // concurrently enqueue. This is acceptable - the semaphore is a best-effort | ||
| 213 | // hint for blocking operations. The lock-free atomics in enqueueInternal() | ||
| 214 | // are the authoritative source of queue state. | ||
| 215 |
3/4✓ Branch 0 taken 86403 times.
✓ Branch 1 taken 190345 times.
✓ Branch 4 taken 86258 times.
✗ Branch 5 not taken.
|
276748 | if (success && this->m_notFullSem != nullptr) { |
| 216 | 86258 | (void)this->m_notFullSem->tryWait(); | |
| 217 | } | ||
| 218 | |||
| 219 | 276552 | return success; | |
| 220 | } | ||
| 221 | |||
| 222 | 1 | bool AtomicQueue::enqueueBlocking(const U8* buffer, FwSizeType size, bool blockIfFull) { | |
| 223 | 1 | FW_ASSERT(this->m_slots != nullptr, 0); | |
| 224 | 1 | FW_ASSERT(buffer != nullptr, 0); | |
| 225 | |||
| 226 | // If not blocking or no semaphore, just use non-blocking enqueue | ||
| 227 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
|
1 | if (!blockIfFull || this->m_notFullSem == nullptr) { |
| 228 | ✗ | return this->enqueue(buffer, size); | |
| 229 | } | ||
| 230 | |||
| 231 | // Wait-first pattern: reserve slot via semaphore, then enqueue | ||
| 232 | // This prevents semaphore count drift under contention | ||
| 233 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | for (FwSizeType attempt = 0; attempt < MAX_CAS_RETRIES; ++attempt) { |
| 234 | 1 | FW_ASSERT(attempt < MAX_CAS_RETRIES, static_cast<FwAssertArgType>(attempt)); | |
| 235 | |||
| 236 | // Reserve a slot by waiting on semaphore (blocks until space available) | ||
| 237 | 1 | Os::CountingSemaphoreInterface::Status status = this->m_notFullSem->wait(); | |
| 238 | 1 | FW_ASSERT(status == Os::CountingSemaphoreInterface::Status::OP_OK, static_cast<FwAssertArgType>(status)); | |
| 239 | |||
| 240 | // Try to enqueue (should succeed since we reserved a slot) | ||
| 241 | // Use internal method to avoid double-decrementing semaphore | ||
| 242 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | if (this->enqueueInternal(buffer, size)) { |
| 243 | 1 | return true; // Success | |
| 244 | } | ||
| 245 | |||
| 246 | // Extremely rare: slot was stolen between wait() and enqueue() | ||
| 247 | // Return the semaphore permit and retry | ||
| 248 | ✗ | status = this->m_notFullSem->post(); | |
| 249 | ✗ | FW_ASSERT(status == Os::CountingSemaphoreInterface::Status::OP_OK, static_cast<FwAssertArgType>(status)); | |
| 250 | } | ||
| 251 | |||
| 252 | // Exceeded retry limit (should never happen in practice) | ||
| 253 | ✗ | return false; | |
| 254 | } | ||
| 255 | |||
| 256 | 87194 | bool AtomicQueue::dequeue(U8* buffer, FwSizeType capacity, FwSizeType& actualSize) { | |
| 257 | 87194 | FW_ASSERT(this->m_slots != nullptr, 0); | |
| 258 | 87384 | FW_ASSERT(buffer != nullptr, 0); | |
| 259 | 87384 | FW_ASSERT(capacity > 0, static_cast<FwAssertArgType>(capacity)); | |
| 260 | |||
| 261 |
2/2✓ Branch 0 taken 96056 times.
✓ Branch 1 taken 810 times.
|
96866 | for (FwSizeType retry = 0; retry < MAX_CAS_RETRIES; ++retry) { |
| 262 | 96056 | FW_ASSERT(retry < MAX_CAS_RETRIES, static_cast<FwAssertArgType>(retry)); | |
| 263 | |||
| 264 | // acquire-release on slot->sequence ensures coherence, enqueuePos & dequeuePos are relaxed since strong memory | ||
| 265 | // ordering is not required | ||
| 266 | |||
| 267 | // Get current dequeue & enqueue positions | ||
| 268 | 96056 | FwSizeType pos = this->m_dequeuePos.load(std::memory_order_relaxed); | |
| 269 |
1/1✓ Branch 3 taken 95211 times.
|
95518 | Slot* slot = &this->m_slots[this->getIndex(pos)]; |
| 270 | 95254 | FwSizeType seq = slot->sequence.load(std::memory_order_acquire); | |
| 271 | |||
| 272 | // Check if slot is ready for read (seq == pos + 1 means data available) | ||
| 273 | 95135 | FwSignedSizeType diff = static_cast<FwSignedSizeType>(seq) - static_cast<FwSignedSizeType>(pos + 1); | |
| 274 | |||
| 275 |
2/2✓ Branch 0 taken 93412 times.
✓ Branch 1 taken 1723 times.
|
95135 | if (diff == 0) { |
| 276 | // Slot has data, try to claim it | ||
| 277 |
2/2✓ Branch 4 taken 85747 times.
✓ Branch 5 taken 6629 times.
|
185788 | if (this->m_dequeuePos.compare_exchange_weak(pos, pos + 1, std::memory_order_release, |
| 278 | std::memory_order_relaxed)) { | ||
| 279 | // Claimed the slot, check size and copy data | ||
| 280 | // Note: sequence.load(acquire) at 10 lines above already synchronizes with | ||
| 281 | // enqueue's sequence.store(release), ensuring size & buffer coherency | ||
| 282 | 85747 | FW_ASSERT(slot->buffer != nullptr, static_cast<FwAssertArgType>(pos)); | |
| 283 | 85767 | FW_ASSERT(slot->size > 0, static_cast<FwAssertArgType>(slot->size)); | |
| 284 | 85762 | FW_ASSERT(slot->size <= capacity, static_cast<FwAssertArgType>(slot->size), | |
| 285 | static_cast<FwAssertArgType>(capacity)); | ||
| 286 | |||
| 287 | 85755 | actualSize = slot->size; | |
| 288 |
2/4✗ Branch 2 not taken.
✓ Branch 3 taken 85758 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 85758 times.
|
85754 | (void)std::memcpy(buffer, slot->buffer, actualSize); |
| 289 | |||
| 290 | // Mark slot as available for next cycle (pos + capacity) | ||
| 291 | 85758 | slot->sequence.store(pos + this->m_capacity, std::memory_order_release); | |
| 292 | |||
| 293 | // Post semaphore to wake up blocked enqueue threads (if semaphore exists) | ||
| 294 | // ISR-SAFETY NOTE: Calling from ISR depends on platform semaphore implementation. | ||
| 295 | // See header comments for details. | ||
| 296 |
2/2✓ Branch 2 taken 85702 times.
✓ Branch 3 taken 55 times.
|
85716 | if (this->m_notFullSem != nullptr) { |
| 297 |
1/1✓ Branch 6 taken 84733 times.
|
85702 | Os::CountingSemaphoreInterface::Status status = this->m_notFullSem->post(); |
| 298 | 84733 | FW_ASSERT(status == Os::CountingSemaphoreInterface::Status::OP_OK, | |
| 299 | static_cast<FwAssertArgType>(status)); | ||
| 300 | } | ||
| 301 | |||
| 302 | 84698 | return true; | |
| 303 | } | ||
| 304 |
1/2✓ Branch 0 taken 2335 times.
✗ Branch 1 not taken.
|
1723 | } else if (diff < 0) { |
| 305 | // Queue is empty (no data written yet) | ||
| 306 | 2335 | return false; | |
| 307 | } | ||
| 308 | // else: another consumer claimed this slot, retry | ||
| 309 | } | ||
| 310 | |||
| 311 | 810 | return false; | |
| 312 | } | ||
| 313 | |||
| 314 | ✗ | bool AtomicQueue::isFull() const { | |
| 315 | // Queue is full if enqueue is exactly capacity ahead of dequeue | ||
| 316 | // Return false for uninitialized queue | ||
| 317 | ✗ | if (this->m_capacity == 0) { | |
| 318 | ✗ | return false; | |
| 319 | } | ||
| 320 | ✗ | return this->getSize() >= this->m_capacity; | |
| 321 | } | ||
| 322 | |||
| 323 | ✗ | bool AtomicQueue::isEmpty() const { | |
| 324 | ✗ | return this->getSize() == 0; | |
| 325 | } | ||
| 326 | |||
| 327 | 216791 | FwSizeType AtomicQueue::getSize() const { | |
| 328 | // Safe to call on uninitialized queue | ||
| 329 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 217419 times.
|
216791 | if (this->m_capacity == 0) { |
| 330 | ✗ | return 0; | |
| 331 | } | ||
| 332 | // A nonzero capacity implies the queue was created with backing storage | ||
| 333 | 217419 | FW_ASSERT(this->m_slots != nullptr); | |
| 334 | |||
| 335 | 217452 | FwSizeType enq = this->m_enqueuePos.load(std::memory_order_relaxed); | |
| 336 | 216017 | FwSizeType deq = this->m_dequeuePos.load(std::memory_order_relaxed); | |
| 337 | 216095 | FwSignedSizeType diff = static_cast<FwSignedSizeType>(enq) - static_cast<FwSignedSizeType>(deq); | |
| 338 | |||
| 339 | // Two independent relaxed loads provide no cross-variable consistency guarantee. | ||
| 340 | // Restrict to [0, capacity] rather than asserting — diff can be transiently negative | ||
| 341 | // or > capacity on concurrent access even though no real program state has that. | ||
| 342 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 216095 times.
|
216095 | if (diff < 0) { |
| 343 | ✗ | return 0; | |
| 344 | } | ||
| 345 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 216906 times.
|
216095 | if (static_cast<FwSizeType>(diff) > this->m_capacity) { |
| 346 | ✗ | return this->m_capacity; | |
| 347 | } | ||
| 348 | 216906 | return static_cast<FwSizeType>(diff); | |
| 349 | } | ||
| 350 | |||
| 351 | ✗ | FwSizeType AtomicQueue::getCapacity() const { | |
| 352 | // Safe to call on uninitialized queue - returns 0 if not created | ||
| 353 | ✗ | return this->m_capacity; | |
| 354 | } | ||
| 355 | |||
| 356 | 273601 | FwSizeType AtomicQueue::getBufferSize() const { | |
| 357 | // Safe to call on uninitialized queue - returns 0 if not created | ||
| 358 | 273601 | return this->m_bufferSize; | |
| 359 | } | ||
| 360 | |||
| 361 | } // namespace Types | ||
| 362 |