[ISSUE-10630] Fixes potential data race and TOCTOU inconsistency foun…#10631
[ISSUE-10630] Fixes potential data race and TOCTOU inconsistency foun…#10631somak2kai wants to merge 1 commit into
Conversation
…d in org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Review by github-manager-bot
Summary
Fixes data race and TOCTOU inconsistency in DefaultLitePullConsumerImpl.taskTable by wrapping the iterate-remove-then-add sequence in synchronized(taskTable) blocks across updatePullTask, updateAssignPullTask, and removePullTask.
Findings
-
[Critical]
DefaultLitePullConsumerImplTaskTableRaceTest.java:107— The test methodtestUpdatePullTaskDoesNotDoubleScheduleSameQueuecallsconsumer.updateAssignQueueAndStartPullTask(topic, Collections.emptySet(), mqDivided)directly, but this method does not exist in the diff (nor in the current codebase). The diff only modifiesupdatePullTask,updateAssignPullTask, andremovePullTask. This will cause a compilation error. Please either: (a) add the missing method, or (b) update the test to use the existingupdatePullTaskmethod via reflection, consistent with the other two test methods. -
[Info]
DefaultLitePullConsumerImpl.java:222,467,725— Synchronizing onthis.taskTable(aConcurrentHashMapinstance) is functionally correct since the reference is stable, but slightly unconventional. A dedicatedprivate final Object taskTableLock = new Object()would make the locking intent clearer and decouple the monitor from the map semantics. This is a minor style preference, not a blocker. -
[Info] The coarse-grained
synchronizedblocks serialize all task table mutations. Since rebalance is not a hot path, this is acceptable. No performance concern in practice.
Positive Aspects
- ✅ Core fix correctly addresses the root cause: the non-atomic "iterate+remove, then containsKey+put" sequence is now atomic
- ✅ All three mutation paths (
updatePullTask,updateAssignPullTask,removePullTask) are consistently synchronized - ✅ Test design using
CyclicBarrierfor concurrent reproduction is solid - ✅
removePullTasksynchronization prevents cross-topic taskTable corruption during concurrent add/remove - ✅ Minimal, focused changes — no unnecessary refactoring
Verdict
The synchronization approach is correct and well-targeted. However, the test file has a compilation error that must be fixed before merge. Once updateAssignQueueAndStartPullTask is resolved, this should be ready to merge.
Automated review by github-manager-bot
wait what compilation error? pls help |
Correction to previous review@somak2kai Apologies for the confusion — the previous review incorrectly flagged a compilation error regarding After re-checking, this method does exist in the codebase as a public method at public void updateAssignQueueAndStartPullTask(String topic, Set<MessageQueue> mqAll, Set<MessageQueue> mqDivided) {The test calling The rest of the review findings remain valid:
Sorry for the false alarm on the critical finding. Correction by github-manager-bot |
|
@RockteMQ-AI how cna i get this reviewed by someone..or atleat have the ci runs enabled? |
|
Hi @somak2kai, thanks for the contribution! A few notes on getting this PR moving:
Hope this helps! 🙌 Automated reply by github-manager-bot |
|
Hi @somak2kai! Thanks for the contribution. A few suggestions:
Hope this helps! |
RockteMQ-AI
left a comment
There was a problem hiding this comment.
LGTM. Good fix for the data race and TOCTOU issues in DefaultLitePullConsumerImpl.
Findings
Correctness — The three methods (updatePullTask, updateAssignPullTask, removePullTask) now consistently synchronize on taskTable for compound iterate-then-mutate operations. This prevents concurrent modification races where one thread iterates while another modifies the map.
Tests — The new DefaultLitePullConsumerImplTaskTableRaceTest uses CyclicBarrier to create concurrent pressure on taskTable, which is a good approach for validating the fix.
Minor observations:
-
Synchronization scope:
startPullTask(mqNewSet)is now called inside thesynchronizedblock. IfstartPullTaskperforms blocking operations (e.g., submitting tasks to an executor), this could increase contention on thetaskTablemonitor. Verify that the executor submission is non-blocking. -
taskTabletype: IftaskTableis aConcurrentHashMap, thesynchronizedblock makes the explicit iteration safe, but other unsynchronized callers oftaskTable.get/put/removeoutside these methods could still race. Ensure all access paths are covered. -
Lock object stability: Synchronizing on
this.taskTableis fine as long as the field is never reassigned. If there is any code path that replaces the map instance, the lock would silently become ineffective.
Automated review by github-manager-bot
|
@sombok2kai Thanks for the contribution! A review has just been posted on this PR. Regarding CI runs: for first-time contributors, CI workflows typically require approval from a committer before they can run. This is a GitHub security policy for fork-based PRs. A maintainer will need to approve the workflow runs — this usually happens once a reviewer engages with the PR. If CI doesn't start within a day or two after review, feel free to leave a polite follow-up comment. Automated reply by github-manager-bot |
|
Hi @somak2kai 👋 This PR has already been reviewed and approved by our automated review. However, merging requires approval from a project committer/maintainer. Regarding CI: it appears the checks haven't been triggered yet. This is common for first-time contributors — a maintainer needs to approve the CI run. Please be patient; the maintainers will get to it. In the meantime, you can:
The PR is in a good state (mergeable, reviewed). It just needs a committer to approve CI and give the final sign-off. |
|
@somak2kai Thanks for the ping! A few notes:
Automated reply by github-manager-bot |
|
@somak2kai This PR has been reviewed and approved. Regarding CI runs: for first-time contributors, CI workflows typically need to be manually triggered by a project committer/maintainer. This is a GitHub security policy for external contributions. A maintainer will need to approve the CI run — please be patient, or you can politely ping on the dev mailing list if you don't hear back within a few days. In the meantime, the code changes look good from the review side. 👍 Reply by github-manager-bot |
|
Hi @somak2kai, thanks for reaching out. I have already reviewed and approved this PR. However, I want to set the right expectations: Regarding CI workflows: Regarding human review:
Your fix for the data race in Good luck! 🚀 |
|
Hi @somak2kai, thanks for the PR! A few notes:
Thanks for your contribution! |
…d in org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl
Which Issue(s) This PR Fixes
Brief Description
A potential data race and TOCTOU inconsistency found in org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl
DefaultLitePullConsumerImpl maintains a ConcurrentMap<MessageQueue, PullTaskImpl> taskTable that tracks which queues currently have a live pull loop running.
Three methods mutate this map — updateAssignPullTask, updatePullTask, removePullTask — via a non-atomic "iterate/remove stale entries, then containsKey-check-and-put new entries" sequence (startPullTask), with no synchronization around any of it
Example concrete failure modes, both silent (no exception, no log line pointing at the cause):
Duplicate pull tasks. The containsKey → put check in startPullTask is not atomic. Two threads can both observe containsKey(mq) == false for a newly assigned queue and each construct and schedule their own PullTaskImpl. Only one survives in taskTable; the other keeps running, untracked, feeding consumeRequestCache independently.
Orphaned queue. One thread's it.remove() can wipe out an entry the other thread just added, leaving a queue with no live pull task and no map entry. Symptom: that queue silently stops being polled — no error, no log noise — until the next rebalance happens to touch its assignment again (which may not happen if group membership stays stable).
How Did You Test This Change?
I used the test case org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImplTaskTableRaceTest which both reproduces the condition as well verifies that the fix works