1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 12:17:44 +00:00

LibCore: Fix deadlock in SharedSingleProducerCircularQueue

This deadlock would incorrectly change the queue from almost empty to
full on dequeue because someone else could empty the queue after we had
checked its non-emptyness. The test would deadlock on this, which
doesn't happen anymore.
This commit is contained in:
kleines Filmröllchen 2022-08-22 19:23:01 +02:00 committed by Andreas Kling
parent be9d3f9aa4
commit 4355a01455

View file

@ -99,7 +99,7 @@ public:
return QueueStatus::Full; return QueueStatus::Full;
auto our_tail = m_queue->m_queue->m_tail.load() % Size; auto our_tail = m_queue->m_queue->m_tail.load() % Size;
m_queue->m_queue->m_data[our_tail] = to_insert; m_queue->m_queue->m_data[our_tail] = to_insert;
++m_queue->m_queue->m_tail; m_queue->m_queue->m_tail.fetch_add(1);
return {}; return {};
} }
@ -129,14 +129,15 @@ public:
{ {
VERIFY(!m_queue.is_null()); VERIFY(!m_queue.is_null());
while (true) { while (true) {
// The >= is not strictly necessary, but it feels safer :^)
if (head() >= m_queue->m_queue->m_tail.load())
return QueueStatus::Empty;
// This CAS only succeeds if nobody is currently dequeuing. // This CAS only succeeds if nobody is currently dequeuing.
auto size_max = NumericLimits<size_t>::max(); auto size_max = NumericLimits<size_t>::max();
if (m_queue->m_queue->m_head_protector.compare_exchange_strong(size_max, m_queue->m_queue->m_head.load())) { if (m_queue->m_queue->m_head_protector.compare_exchange_strong(size_max, m_queue->m_queue->m_head.load())) {
auto old_head = m_queue->m_queue->m_head.load(); auto old_head = m_queue->m_queue->m_head.load();
// This check looks like it's in a weird place (especially since we have to roll back the protector), but it's actually protecting against a race between multiple dequeuers.
if (old_head >= m_queue->m_queue->m_tail.load()) {
m_queue->m_queue->m_head_protector.store(NumericLimits<size_t>::max(), AK::MemoryOrder::memory_order_release);
return QueueStatus::Empty;
}
auto data = move(m_queue->m_queue->m_data[old_head % Size]); auto data = move(m_queue->m_queue->m_data[old_head % Size]);
m_queue->m_queue->m_head.fetch_add(1); m_queue->m_queue->m_head.fetch_add(1);
m_queue->m_queue->m_head_protector.store(NumericLimits<size_t>::max(), AK::MemoryOrder::memory_order_release); m_queue->m_queue->m_head_protector.store(NumericLimits<size_t>::max(), AK::MemoryOrder::memory_order_release);