HBASE-30340: CatalogJanitor can run concurrent scans due to incorrect alreadyRunning lock handling - #8569
Conversation
There was a problem hiding this comment.
Thanks for the contribution!
Could we add a UT for this scenario @sercanCyberVision
f2219b7 to
8efe0fd
Compare
|
@guluo2016, thank you for reviewing the PR. I have added a UT for this scenario and ran the full I verified that the expected log messages are present in the corresponding I also executed the new test against the original implementation (before the fix). The test timed out after 60 seconds because the third |
8efe0fd to
e8d6ba3
Compare
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Fixes a concurrency bug in CatalogJanitor.scan() where a losing compareAndSet path could still clear alreadyRunning, allowing overlapping scans.
Changes:
- Move
alreadyRunning.compareAndSet(false, true)outside thetry/finallyso only the lock holder clears it. - Add a regression test to ensure a failed concurrent scan attempt does not clear the lock while the first scan is still running.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| hbase-server/src/main/java/org/apache/hadoop/hbase/master/janitor/CatalogJanitor.java | Fixes the lock lifecycle so concurrent scan attempts can’t clear alreadyRunning. |
| hbase-server/src/test/java/org/apache/hadoop/hbase/master/janitor/TestCatalogJanitor.java | Adds a regression test that exercises the and validates the corrected locking behavior under concurrency. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
e8d6ba3 to
ca1639f
Compare
|
@guluo2016, please find the changes:
I executed both the positive and negative test cases again. In the negative test case, on the build side: On the logs side: |
ca1639f to
238c735
Compare
| // The second scan must not clear the lock. | ||
| // Therefore, the third scan must also report that a scan is running. | ||
|
|
||
| assertTimeoutPreemptively( |
There was a problem hiding this comment.
I think the assertTimeoutPreemptively could be removed, since spy.scan() should return -1 immediately without needing a background thread.
In addition, the assertTimeoutPreemptively has undesirable side effects in some scenarios,details see:https://docs.junit.org/6.0.0/writing-tests/assertions.html?utm_source=chatgpt.com#:~:text=Preemptive%20Timeouts%20with,lang.ThreadLocal%20storage.
There was a problem hiding this comment.
You are right. We already have allowScanToFinish.await(15, TimeUnit.SECONDS),, which provides a timeout if the third scan incorrectly acquires the lock and starts running. I have removed assertTimeoutPreemptively
| + "the alreadyRunning lock mechanism may not be behaving as expected.", | ||
| e | ||
| ); | ||
| throw e; |
There was a problem hiding this comment.
I think this catch block also is unnecessary, since it doesn't add any assertion or change the test result.
There was a problem hiding this comment.
It was only there for logging purposes for the third scan attempt. Since assertTimeoutPreemptively has been removed, it is no longer needed, so I removed it as well.
| LOG.info("Releasing first catalog janitor scan and waiting for it to complete."); | ||
| allowScanToFinish.countDown(); | ||
| scanThread.join(5000); | ||
| assertFalse(scanThread.isAlive()); |
There was a problem hiding this comment.
The assertion also is unnecessary
Maybe we can simply log whether the scanThread is alive here.
… alreadyRunning lock handling
238c735 to
93fd3d6
Compare
CatalogJanitor.scan()can allow concurrent scans due to incorrect handling of thealreadyRunninglock.ROOT CAUSE
Currently, the lock acquisition is performed inside the
tryblock:When a scan is already running, a concurrent scan fails the
compareAndSet()andreturns immediately. However, because the lock acquisition is inside the
tryblock,the
finallyblock is still executed and resetsalreadyRunningto false.This allows another scan to acquire the lock while the original scan is still running.
SOLUTION
The lock acquisition should be moved before the
tryblock:This prevents another scan from starting until the current scan has completed.