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

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


The following commit(s) were added to refs/heads/master by this push:
     new e1aa456ae54 HBASE-30251 Async prefetching causes 
TestClientScannerTimeouts flaky (#8402)
e1aa456ae54 is described below

commit e1aa456ae543474201798e2905eb8b0a6f460439
Author: Duo Zhang <[email protected]>
AuthorDate: Wed Jun 24 09:35:53 2026 +0800

    HBASE-30251 Async prefetching causes TestClientScannerTimeouts flaky (#8402)
    
    Signed-off-by: Xiao Liu <[email protected]>
---
 .../hbase/client/TestClientScannerTimeouts.java    | 100 +++++++++++----------
 1 file changed, 55 insertions(+), 45 deletions(-)

diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestClientScannerTimeouts.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestClientScannerTimeouts.java
index fafaf6b4cef..09e48a1c9f0 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestClientScannerTimeouts.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestClientScannerTimeouts.java
@@ -19,11 +19,14 @@ package org.apache.hadoop.hbase.client;
 
 import static 
org.apache.hadoop.hbase.client.ConnectionConfiguration.HBASE_CLIENT_META_READ_RPC_TIMEOUT_KEY;
 import static 
org.apache.hadoop.hbase.client.ConnectionConfiguration.HBASE_CLIENT_META_SCANNER_TIMEOUT;
+import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.IOException;
+import java.time.Duration;
 import java.util.function.Supplier;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseTestingUtil;
@@ -81,7 +84,7 @@ public class TestClientScannerTimeouts {
   public static void setUpBeforeClass() throws Exception {
     Configuration conf = TEST_UTIL.getConfiguration();
     // Don't report so often so easier to see other rpcs
-    conf.setInt("hbase.regionserver.msginterval", 3 * 10000);
+    // conf.setInt("hbase.regionserver.msginterval", 3 * 10000);
     conf.setInt(HConstants.HBASE_RPC_TIMEOUT_KEY, rpcTimeout);
     conf.setStrings(HConstants.REGION_SERVER_IMPL, 
RegionServerWithScanTimeout.class.getName());
     conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, CLIENT_RETRIES_NUMBER);
@@ -241,55 +244,52 @@ public class TestClientScannerTimeouts {
 
     LOG.info(
       "Opening scanner, expecting no errors from first next() call from 
openScanner response");
-    ResultScanner scanner = scannerSupplier.get();
-    Result result = scanner.next();
-    expectRow(ROW0, result);
-    expectNumTries(0);
+    try (ResultScanner scanner = scannerSupplier.get()) {
+      Result result = scanner.next();
+      expectRow(ROW0, result);
+      expectNumTries(0);
 
-    LOG.info("Making first next() RPC, expecting no errors for seqNo 0");
-    result = scanner.next();
-    expectRow(ROW1, result);
-    expectNumTries(0);
+      LOG.info("Making first next() RPC, expecting no errors for seqNo 0");
+      result = scanner.next();
+      expectRow(ROW1, result);
+      expectNumTries(0);
 
-    LOG.info(
-      "Making second next() RPC, expecting OutOfOrderScannerNextException and 
appropriate retry");
-    result = scanner.next();
-    expectRow(ROW2, result);
-    expectNumTries(1);
+      LOG.info(
+        "Making second next() RPC, expecting OutOfOrderScannerNextException 
and appropriate retry");
+      result = scanner.next();
+      expectRow(ROW2, result);
+      expectNumTries(1);
 
-    // reset so no errors. since last call restarted the scan and following
-    // call would otherwise fail
-    RSRpcServicesWithScanTimeout.seqNoToThrowOn = -1;
+      // reset so no errors. since last call restarted the scan and following
+      // call would otherwise fail
+      RSRpcServicesWithScanTimeout.seqNoToThrowOn = -1;
 
-    LOG.info("Finishing scan, expecting no errors");
-    result = scanner.next();
-    expectRow(ROW3, result);
-    scanner.close();
+      LOG.info("Finishing scan, expecting no errors");
+      result = scanner.next();
+      expectRow(ROW3, result);
+    }
+
+    // the close operation maybe asynchronous, wait until closed to avoid 
messing up later
+    // assertions.
+    await().atMost(Duration.ofSeconds(5)).until(() -> 
RSRpcServicesWithScanTimeout.closed);
 
     LOG.info("Testing always throw exception");
     byte[][] expectedResults = new byte[][] { ROW0, ROW1, ROW2, ROW3 };
-    int i = 0;
 
-    // test the case that RPC always throws
-    scanner = scannerSupplier.get();
+    RSRpcServicesWithScanTimeout.reset();
     RSRpcServicesWithScanTimeout.throwAlways = true;
 
-    while (true) {
-      LOG.info("Calling scanner.next()");
-      result = scanner.next();
-      if (result == null) {
-        break;
-      } else {
-        byte[] expectedResult = expectedResults[i++];
-        expectRow(expectedResult, result);
+    // test the case that RPC always throws
+    try (ResultScanner scanner = scannerSupplier.get()) {
+      for (int i = 0; i < expectedResults.length; i++) {
+        LOG.info("Calling scanner.next()");
+        Result result = scanner.next();
+        assertNotNull(result,
+          "missing row index=" + i + ", row=" + 
Bytes.toStringBinary(expectedResults[i]));
+        expectRow(expectedResults[i], result);
       }
     }
 
-    // ensure we verified all rows. this along with the expectRow check above
-    // proves that we didn't miss any rows.
-    assertEquals(expectedResults.length, i, "Expected to exhaust 
expectedResults array length="
-      + expectedResults.length + ", actual index=" + i);
-
     // expect all but the first row (which came from initial openScanner) to 
have thrown an error
     expectNumTries(expectedResults.length - 1);
 
@@ -345,6 +345,8 @@ public class TestClientScannerTimeouts {
   private ResultScanner getScanner() {
     Scan scan = new Scan();
     scan.setCaching(1);
+    // make sure we will not fetch rows in background
+    scan.setMaxResultSize(1);
     try {
       return table.getScanner(scan);
     } catch (IOException e) {
@@ -355,6 +357,8 @@ public class TestClientScannerTimeouts {
   private ResultScanner getAsyncScanner() {
     Scan scan = new Scan();
     scan.setCaching(1);
+    // make sure we will not fetch rows in background
+    scan.setMaxResultSize(1);
     return asyncTable.getScanner(scan);
   }
 
@@ -379,16 +383,17 @@ public class TestClientScannerTimeouts {
   private static class RSRpcServicesWithScanTimeout extends RSRpcServices {
     private long tableScannerId;
 
-    private static long seqNoToThrowOn = -1;
-    private static boolean throwAlways = false;
-    private static boolean threw;
+    private static volatile long seqNoToThrowOn = -1;
+    private static volatile boolean throwAlways = false;
+    private static volatile boolean threw;
 
-    private static long seqNoToSleepOn = -1;
-    private static boolean sleepOnOpen = false;
+    private static volatile long seqNoToSleepOn = -1;
+    private static volatile boolean sleepOnOpen = false;
     private static volatile boolean slept;
-    private static int tryNumber = 0;
+    private static volatile int tryNumber = 0;
+    private static volatile boolean closed = false;
 
-    private static int sleepTime = rpcTimeout + 500;
+    private static volatile int sleepTime = rpcTimeout + 500;
 
     public static void setSleepForTimeout(int timeout) {
       sleepTime = timeout + 500;
@@ -404,6 +409,7 @@ public class TestClientScannerTimeouts {
       sleepOnOpen = false;
       slept = false;
       tryNumber = 0;
+      closed = false;
     }
 
     public RSRpcServicesWithScanTimeout(HRegionServer rs) throws IOException {
@@ -415,7 +421,11 @@ public class TestClientScannerTimeouts {
       throws ServiceException {
       if (request.hasScannerId()) {
         ScanResponse scanResponse = super.scan(controller, request);
-        if (tableScannerId != request.getScannerId() || 
request.getCloseScanner()) {
+        if (tableScannerId != request.getScannerId()) {
+          return scanResponse;
+        }
+        if (request.getCloseScanner()) {
+          closed = true;
           return scanResponse;
         }
 

Reply via email to