Skip to content

Commit 7d8e187

Browse files
hgromerbbeaudreault
authored andcommitted
HBASE-27950 ClientSideRegionScanner does not adhere to RegionScanner.nextRaw contract (#5304)
Signed-off-by: Duo Zhang <zhangduo@apache.org> Signed-off-by: Bryan Beaudreault <bbeaudreault@apache.org>
1 parent 0356384 commit 7d8e187

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/client/ClientSideRegionScanner.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class ClientSideRegionScanner extends AbstractClientScanner {
4848
private HRegion region;
4949
RegionScanner scanner;
5050
List<Cell> values;
51+
boolean hasMore = true;
5152

5253
public ClientSideRegionScanner(Configuration conf, FileSystem fs, Path rootDir,
5354
TableDescriptor htd, RegionInfo hri, Scan scan, ScanMetrics scanMetrics) throws IOException {
@@ -90,12 +91,13 @@ public ClientSideRegionScanner(Configuration conf, FileSystem fs, Path rootDir,
9091

9192
@Override
9293
public Result next() throws IOException {
93-
values.clear();
94-
scanner.nextRaw(values);
95-
if (values.isEmpty()) {
96-
// we are done
97-
return null;
98-
}
94+
do {
95+
if (!hasMore) {
96+
return null;
97+
}
98+
values.clear();
99+
this.hasMore = scanner.nextRaw(values);
100+
} while (values.isEmpty());
99101

100102
Result result = Result.create(values);
101103
if (this.scanMetrics != null) {

hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestClientSideRegionScanner.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,34 @@
1717
*/
1818
package org.apache.hadoop.hbase.client;
1919

20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
2022
import static org.junit.Assert.assertNotNull;
2123
import static org.junit.Assert.assertNull;
2224
import static org.junit.Assert.assertTrue;
25+
import static org.mockito.ArgumentMatchers.anyList;
26+
import static org.mockito.Mockito.spy;
27+
import static org.mockito.Mockito.times;
28+
import static org.mockito.Mockito.verify;
2329

2430
import java.io.IOException;
31+
import java.util.Arrays;
2532
import org.apache.hadoop.conf.Configuration;
2633
import org.apache.hadoop.fs.FileSystem;
2734
import org.apache.hadoop.fs.Path;
35+
import org.apache.hadoop.hbase.Cell;
2836
import org.apache.hadoop.hbase.HBaseClassTestRule;
2937
import org.apache.hadoop.hbase.HBaseTestingUtility;
3038
import org.apache.hadoop.hbase.HConstants;
3139
import org.apache.hadoop.hbase.TableName;
40+
import org.apache.hadoop.hbase.filter.FilterBase;
3241
import org.apache.hadoop.hbase.io.hfile.BlockCache;
3342
import org.apache.hadoop.hbase.io.hfile.IndexOnlyLruBlockCache;
43+
import org.apache.hadoop.hbase.regionserver.RegionScanner;
44+
import org.apache.hadoop.hbase.regionserver.StoreScanner;
3445
import org.apache.hadoop.hbase.testclassification.ClientTests;
3546
import org.apache.hadoop.hbase.testclassification.SmallTests;
47+
import org.apache.hadoop.hbase.util.Bytes;
3648
import org.junit.AfterClass;
3749
import org.junit.Before;
3850
import org.junit.BeforeClass;
@@ -47,6 +59,8 @@ public class TestClientSideRegionScanner {
4759
HBaseClassTestRule.forClass(TestClientSideRegionScanner.class);
4860

4961
private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
62+
private static final TableName TABLE_NAME = TableName.valueOf("test");
63+
private static final byte[] FAM_NAME = Bytes.toBytes("f");
5064

5165
private Configuration conf;
5266
private Path rootDir;
@@ -113,4 +127,78 @@ public void testNoBlockCache() throws IOException {
113127
BlockCache blockCache = clientSideRegionScanner.getRegion().getBlockCache();
114128
assertNull(blockCache);
115129
}
130+
131+
@Test
132+
public void testContinuesToScanIfHasMore() throws IOException {
133+
// Conditions for this test to set up RegionScannerImpl to bail on the scan
134+
// after a single iteration
135+
// 1. Configure preadMaxBytes to something small to trigger scannerContext#returnImmediately
136+
// 2. Configure a filter to filter out some rows, in this case rows with values < 5
137+
// 3. Configure the filter's hasFilterRow to return true so RegionScannerImpl sets
138+
// the limitScope to something with a depth of 0, so we bail on the scan after the first
139+
// iteration
140+
141+
Configuration copyConf = new Configuration(conf);
142+
copyConf.setLong(StoreScanner.STORESCANNER_PREAD_MAX_BYTES, 1);
143+
Scan scan = new Scan();
144+
scan.setFilter(new FiltersRowsLessThan5());
145+
scan.setLimit(1);
146+
147+
try (Table table = TEST_UTIL.createTable(TABLE_NAME, FAM_NAME)) {
148+
TableDescriptor htd = TEST_UTIL.getAdmin().getDescriptor(TABLE_NAME);
149+
RegionInfo hri = TEST_UTIL.getAdmin().getRegions(TABLE_NAME).get(0);
150+
151+
for (int i = 0; i < 10; ++i) {
152+
table.put(createPut(i));
153+
}
154+
155+
// Flush contents to disk so we can scan the fs
156+
TEST_UTIL.getAdmin().flush(TABLE_NAME);
157+
158+
ClientSideRegionScanner clientSideRegionScanner =
159+
new ClientSideRegionScanner(copyConf, fs, rootDir, htd, hri, scan, null);
160+
RegionScanner scannerSpy = spy(clientSideRegionScanner.scanner);
161+
clientSideRegionScanner.scanner = scannerSpy;
162+
Result result = clientSideRegionScanner.next();
163+
164+
verify(scannerSpy, times(6)).nextRaw(anyList());
165+
assertNotNull(result);
166+
assertEquals(Bytes.toInt(result.getRow()), 5);
167+
assertTrue(clientSideRegionScanner.hasMore);
168+
169+
for (int i = 6; i < 10; ++i) {
170+
result = clientSideRegionScanner.next();
171+
verify(scannerSpy, times(i + 1)).nextRaw(anyList());
172+
assertNotNull(result);
173+
assertEquals(Bytes.toInt(result.getRow()), i);
174+
}
175+
176+
result = clientSideRegionScanner.next();
177+
assertNull(result);
178+
assertFalse(clientSideRegionScanner.hasMore);
179+
}
180+
}
181+
182+
private static Put createPut(int rowAsInt) {
183+
byte[] row = Bytes.toBytes(rowAsInt);
184+
Put put = new Put(row);
185+
put.addColumn(FAM_NAME, row, row);
186+
return put;
187+
}
188+
189+
private static class FiltersRowsLessThan5 extends FilterBase {
190+
191+
@Override
192+
public boolean filterRowKey(Cell cell) {
193+
byte[] rowKey = Arrays.copyOfRange(cell.getRowArray(), cell.getRowOffset(),
194+
cell.getRowLength() + cell.getRowOffset());
195+
int intValue = Bytes.toInt(rowKey);
196+
return intValue < 5;
197+
}
198+
199+
@Override
200+
public boolean hasFilterRow() {
201+
return true;
202+
}
203+
}
116204
}

0 commit comments

Comments
 (0)