This is an automated email from the ASF dual-hosted git repository.

Apache9 pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-3 by this push:
     new b0a5a63dcec HBASE-30340: CatalogJanitor can run concurrent scans due 
to incorrect alreadyRunning lock handling (#8569)
b0a5a63dcec is described below

commit b0a5a63dcec6ee128a77752993decc677934953f
Author: Sercan Tekin <[email protected]>
AuthorDate: Mon Aug 31 05:53:48 2026 -0400

    HBASE-30340: CatalogJanitor can run concurrent scans due to incorrect 
alreadyRunning lock handling (#8569)
    
    Signed-off by: Peng Lu <[email protected]>
    Signed-off by: Duo Zhang <[email protected]>
    
    (cherry picked from commit 1fc1aeb11d981d8e70cfc926167ee53d66499455)
---
 .../hbase/master/janitor/CatalogJanitor.java       | 14 +++----
 .../hbase/master/janitor/TestCatalogJanitor.java   | 48 ++++++++++++++++++++++
 2 files changed, 55 insertions(+), 7 deletions(-)

diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/janitor/CatalogJanitor.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/janitor/CatalogJanitor.java
index 14cf61ef970..a9fb510ca39 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/janitor/CatalogJanitor.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/janitor/CatalogJanitor.java
@@ -164,14 +164,14 @@ public class CatalogJanitor extends ScheduledChore {
    */
   public int scan() throws IOException {
     int gcs = 0;
-    try {
-      if (!alreadyRunning.compareAndSet(false, true)) {
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("CatalogJanitor already running");
-        }
-        // -1 indicates previous scan is in progress
-        return -1;
+    if (!alreadyRunning.compareAndSet(false, true)) {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("CatalogJanitor already running");
       }
+      // -1 indicates previous scan is in progress
+      return -1;
+    }
+    try {
       this.lastReport = scanForReport();
       if (!this.lastReport.isEmpty()) {
         LOG.warn(this.lastReport.toString());
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/janitor/TestCatalogJanitor.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/janitor/TestCatalogJanitor.java
index 259ff1636bf..647451057a3 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/janitor/TestCatalogJanitor.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/janitor/TestCatalogJanitor.java
@@ -35,6 +35,8 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
@@ -690,6 +692,52 @@ public class TestCatalogJanitor {
     assertTrue(gcValues.contains(-1), "One janitor.scan() call should have 
returned -1");
   }
 
+  @Test
+  public void testAlreadyRunningStatusDoesNotClearLock() throws Exception {
+    CatalogJanitor spy = spy(this.janitor);
+
+    CountDownLatch scanStarted = new CountDownLatch(1);
+    CountDownLatch allowScanToFinish = new CountDownLatch(1);
+
+    doAnswer(invocation -> {
+      scanStarted.countDown();
+      assertTrue(allowScanToFinish.await(15, TimeUnit.SECONDS),
+        "Timed out waiting for the test to release the first catalog janitor 
scan.");
+      return new CatalogJanitorReport();
+    }).when(spy).scanForReport();
+
+    Thread scanThread = new Thread(() -> {
+      try {
+        spy.scan();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    });
+
+    scanThread.start();
+    try {
+      // First scan acquires the lock and remains running.
+      assertTrue(scanStarted.await(5, TimeUnit.SECONDS));
+      LOG.info("First catalog janitor scan started and waiting to finish.");
+
+      // Second scan detects that another scan is running.
+      assertEquals(-1, spy.scan());
+      LOG.info("Second catalog janitor scan attempt returned -1.");
+
+      // The second scan must not clear the lock.
+      // Therefore, the third scan must also report that a scan is running.
+      int result = spy.scan();
+      LOG.info("Third catalog janitor scan attempt returned {}.", result);
+      assertEquals(-1, result);
+    } finally {
+      // Let the first scan finish.
+      LOG.info("Releasing first catalog janitor scan and waiting for it to 
complete.");
+      allowScanToFinish.countDown();
+      scanThread.join(5000);
+      LOG.info("First catalog janitor scan thread alive after join: {}", 
scanThread.isAlive());
+    }
+  }
+
   private FileStatus[] addMockStoreFiles(int count, MasterServices services, 
Path storedir)
     throws IOException {
     // get the existing store files

Reply via email to