Skip to content

Fix release fence ordering in inner_enqueue() to prevent size_approx() race on AArch64 - #172

Open
cppmage wants to merge 1 commit into
cameron314:masterfrom
cppmage:cppmage-fix-tailblock-next-race
Open

Fix release fence ordering in inner_enqueue() to prevent size_approx() race on AArch64#172
cppmage wants to merge 1 commit into
cameron314:masterfrom
cppmage:cppmage-fix-tailblock-next-race

Conversation

@cppmage

@cppmage cppmage commented Aug 15, 2026

Copy link
Copy Markdown

Fixes #171

What changed

Move fence(memory_order_release) in inner_enqueue()'s CanAlloc
branch so it precedes both writes that publish a newly allocated block
(tailBlock_->next = newBlock and tailBlock = newBlock), instead of
only the second one. See #171 for the full root-cause
analysis and repro.

Also expanded the comment at that call site to note that
size_approx() (and any other Block::next-chain traversal) depends
on this ordering too, not just try_dequeue().

Testing

  • Reproduced the original crash on AArch64 with the existing
    size_approx stress test run in a loop (1000 iterations).
  • Confirmed the crash no longer reproduces after the fix under the
    same loop (1000 iterations).
  • Note: the existing size_approx stress test has other pre-existing
    assertion failures, unrelated to this change and not addressed here.

Move fence(memory_order_release) before both writes that publish a
newly allocated block (tailBlock_->next and tailBlock), not just the
second one. size_approx() reaches blocks via the next-chain and never
reads tailBlock, so it wasn't covered by the existing fence placement.

Fixes cameron314#171
@cppmage cppmage changed the title fix: move release fence in inner_enqueue() Fix release fence ordering in inner_enqueue() to prevent size_approx() race on AArch64 Aug 15, 2026

@cameron314 cameron314 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this. It does seem like a real bug.

Comment thread readerwriterqueue.h
Comment on lines 631 to 636
@@ -627,7 +634,6 @@ class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue
// case where it could try to read the next is if it's already at the tailBlock,
// and it won't advance past tailBlock in any circumstance).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment was to justify the previous placement of the fence, and can be removed, since it clearly missed a case in its reasoning :)

Comment thread readerwriterqueue.h
// and it won't advance past tailBlock in any circumstance).

fence(memory_order_release);
tailBlock = newBlock;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need a release fence before setting tailBlock itself? Otherwise anything reading tailBlock might not see the latest value of tailBlock->next, which would be the opposite bug.

@cameron314

Copy link
Copy Markdown
Owner

Note that at the time this code was written, TSan generated false positives with lock-free data structures, which is why it wasn't used. I should probably try running under TSan again at some point, it's come a long way since then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition in inner_enqueue() causes SIGSEGV in size_approx() on AArch64

2 participants