Repository navigation
[ISSUE #11441] fix(broker): wake the held pull requests when an arrival drained one bucket - #11442
Open
iloveeyjafjalla wants to merge 1 commit into
Open
iloveeyjafjalla wants to merge 1 commit into
iloveeyjafjalla wants to merge 1 commit into
Conversation
… arrival drained one bucket notifyMasterOnline() iterated the result of mpr.cloneListAndClear() directly. That method answers null - not an empty list - when the bucket is empty at drain time, and the arrival path drains the same bucket on the store's dispatch thread. An arrival that wins the race between the isEmpty() check and the drain therefore made the enhanced for throw a NullPointerException, which escaped into onMinBrokerChange and abandoned the sweep half way: every bucket that had not been visited yet kept its requests suspended until the client's own suspend timeout, which is the stall the master-online wake-up exists to avoid, and the min-broker update reported a failure. Drain into a local variable and skip the bucket that lost the race, mirroring the guard notifyMessageArriving already has around the same call.
RockteMQ-AI
approved these changes
Oct 14, 2026
RockteMQ-AI
left a comment
Contributor
There was a problem hiding this comment.
Review by github-manager-bot
Summary
Fixes a NullPointerException in PullRequestHoldService.notifyMasterOnline() caused by a race condition where a concurrent message arrival drains a bucket between the isEmpty() check and cloneListAndClear(), making the latter return null instead of an empty list. The null result then crashes the enhanced for-each loop, aborting the sweep and leaving remaining buckets' pull requests suspended until client timeout.
Findings
- [Info]
PullRequestHoldService.java:191— The fix correctly drains into a local variable and adds a null guard withcontinue, mirroring the existing pattern innotifyMessageArriving(). This is the minimal correct fix for the race. - [Info]
PullRequestHoldServiceTest.java— TheRacyBucketanonymous class is a clever deterministic reproduction of the race condition: overridingisEmpty()to self-drain simulates the exact interleaving. The test asserts both halves of the contract (no NPE + other buckets still processed).
Assessment
- Correctness: ✅ The null guard handles the race properly. The
continueensures only the drained bucket is skipped, not the entire sweep. - Performance: ✅ No impact — just a null check on a local variable.
- Tests: ✅ Good coverage. The test deterministically reproduces the race and verifies both the fix and the sweep continuation.
- Compatibility: ✅ No API changes, purely internal fix.
LGTM. Clean fix with solid test coverage.
Automated review by github-manager-bot
RockteMQ-AI
approved these changes
Oct 16, 2026
RockteMQ-AI
left a comment
Contributor
There was a problem hiding this comment.
Review by github-manager-bot
Summary
Fixes NPE in when returns due to a concurrent arrival draining the bucket first.
Findings
- [Info] PullRequestHoldService.java:191 — Clean fix that mirrors the existing null guard in . The race condition is well-documented in the comment.
- [Info] PullRequestHoldServiceTest.java — The test subclass is a clever way to deterministically reproduce the race. Good coverage of both halves of the contract (no NPE + other buckets still woken).
Verdict
LGTM. Clean, minimal fix with a solid regression test.
Automated review by github-manager-bot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
PullRequestHoldService#notifyMasterOnline()checked a bucket for emptiness and then iterated the result of the drain:ManyPullRequest#cloneListAndClear()answersnull, not an empty list, when the bucket is empty at drain time. The buckets are drained by the message-arrival path on the store's dispatch thread, so an arrival that wins the race between theisEmpty()check and the drain makes that call answernulland the enhancedforthrow aNullPointerException.The exception escaped
notifyMasterOnline()intoBrokerController#onMinBrokerChange(including theUPDATE_MIN_BROKERadmin path), abandoning the sweep half way: every bucket not visited yet kept its suspended pull requests until the client's own suspend timeout - the exact stall this wake-up exists to prevent, in the failover window it is meant for.Fix: drain into a local variable and skip the bucket that lost the race. This mirrors the guard
notifyMessageArrivingalready has around the same call in the same class, so the null contract is now handled consistently.Evidence
Red run before the fix (
broker, JDK 8), forced interleaving where the bucket is drained by a concurrent arrival right after the emptiness check:Green run after the fix, and the CI-shaped verification (checkstyle + spotbugs) on the module:
The new test
notifyMasterOnlineTestWakesTheBucketsLeftAfterAConcurrentArrivalDrainedOneTestasserts both halves of the contract: the sweep does not throw, and the held requests of the other queues are still woken (their bucket is drained by the sweep) instead of being skipped by an aborted sweep. The broker module's full test suite is not run here - it does not complete on this machine for unrelated reasons - so only the affected package's test classes were executed, as reported above.Fixes #11441