Skip to content

ARTEMIS-6179 Fix deleteReference/depage deadlock - #6606

Closed
shivantaher-oviva wants to merge 3 commits into
apache:mainfrom
shivantaher-oviva:ARTEMIS-6179
Closed

ARTEMIS-6179 Fix deleteReference/depage deadlock#6606
shivantaher-oviva wants to merge 3 commits into
apache:mainfrom
shivantaher-oviva:ARTEMIS-6179

Conversation

@shivantaher-oviva

@shivantaher-oviva shivantaher-oviva commented Aug 11, 2026

Copy link
Copy Markdown

https://issues.apache.org/jira/browse/ARTEMIS-6179

QueueImpl#deleteReference() is synchronized and calls iterQueue(), which blocks on depageLock.lock(). depage() does the reverse: it takes depageLock first (via tryLock()), then needs to enter synchronized(this). Two threads acquiring the same two locks in opposite order deadlock as soon as they interleave: a removeMessage() call holds the object monitor and blocks on depageLock, while depage() holds depageLock and blocks on the monitor. Neither ever releases.

This is the same bug class already fixed for copyReference() in ARTEMIS-5376 (30c8fc7). deleteReference() wasn't touched by that fix because it didn't call iterQueue() yet at the time — it was moved onto iterQueue() two weeks later and kept synchronized, reintroducing the same bug.

The fix

Drop synchronized from deleteReference(). iterQueue() already synchronizes internally via depageLock, so the outer lock is redundant and is exactly what causes the reversed order above.

Checked this doesn't introduce a new race: deleteReference()'s callback (incDelivering() + acknowledge()) now runs in the exact synchronization context that moveReference() and sendMessageToDeadLetterAddress() already use unsynchronized since ARTEMIS-5376, calling the same underlying acknowledge()/move() machinery.

Testing

Verified with a dedicated reproduction test (testRemoveMessageWhilstPagingAndConsuming, racing removeMessage() against depage() while consuming) that reliably deadlocks without this change and passes cleanly with it, plus the existing QueueControlTest, QueueControlUsingCoreTest, ManagementWithPagingServerTest, and LVQTest suites, all green. The reproduction test is in the first commit on this branch and removed again in the third, since it's a probabilistic race rather than a deterministic test and isn't suited to the permanent suite.

Adds testRemoveMessageWhilstPagingAndConsuming, mirroring the
existing testMoveMessageWhilstPagingAndConsuming/ManagementCopyThread
pattern that caught the equivalent copyReference() deadlock
(ARTEMIS-5376).

QueueImpl#deleteReference() is still synchronized and calls
iterQueue(), which locks depageLock, while QueueImpl#depage() locks
depageLock first and then enters a synchronized(this) block. Racing
QueueControl#removeMessage() against depaging while consuming can
deadlock the two threads against each other. Detection uses the
JVM's own ThreadMXBean deadlock detector instead of a fixed timeout,
since a hang here is the failure itself.
Removes synchronized from QueueImpl#deleteReference(). It calls
iterQueue(), which acquires depageLock internally, while depage()
acquires depageLock first and then enters a synchronized(this)
block. The reversed lock order deadlocks QueueControl#removeMessage()
against the paging executor whenever they race on an actively
paging queue.

This mirrors the fix already applied to copyReference() in
ARTEMIS-5376: iterQueue() already provides its own synchronization
via depageLock, so the outer synchronized on deleteReference() is
redundant and unsafe. Verified with
testRemoveMessageWhilstPagingAndConsuming, which reliably deadlocks
without this change and passes cleanly with it.
Removes testRemoveMessageWhilstPagingAndConsuming and
ManagementRemoveThread, added earlier on this branch to
demonstrate the deadlock before the fix.

The reproduction is a probabilistic race rather than a
deterministic test: it reliably caught the deadlock without the
fix and passed cleanly with it, but relies on winning a narrow
timing window rather than a guaranteed interleaving. Kept out of
the permanent suite for that reason; it remains in branch history
as evidence for the fix in the preceding two commits.
@clebertsuconic

Copy link
Copy Markdown
Contributor

I have brought your changes into #6607

I also did a little refactoring on the test.. and I'm keeping the test (I don't want to remove the good test you wrote.. although I made it run faster, while still reproduced the issue). (I want to keep it faster to we can run it on the CI)

@clebertsuconic

Copy link
Copy Markdown
Contributor

I'm closing this as I have created a separated PR for this.. will merge it shortly..

thanks a lot for this.

@shivantaher-oviva

Copy link
Copy Markdown
Author

Thank you for taking this up quickly!

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.

3 participants