View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.concurrent.Callable;
29  
30  import org.apache.commons.lang.exception.ExceptionUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.commons.logging.impl.Log4JLogger;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.fs.FileSystem;
36  import org.apache.hadoop.fs.Path;
37  import org.apache.hadoop.hbase.Cell;
38  import org.apache.hadoop.hbase.CellUtil;
39  import org.apache.hadoop.hbase.CoordinatedStateManager;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.HTableDescriptor;
44  import org.apache.hadoop.hbase.HTestConst;
45  import org.apache.hadoop.hbase.KeyValue;
46  import org.apache.hadoop.hbase.KeyValue.KVComparator;
47  import org.apache.hadoop.hbase.TableName;
48  import org.apache.hadoop.hbase.client.Put;
49  import org.apache.hadoop.hbase.client.Result;
50  import org.apache.hadoop.hbase.client.ResultScanner;
51  import org.apache.hadoop.hbase.client.Scan;
52  import org.apache.hadoop.hbase.client.ScannerCallable;
53  import org.apache.hadoop.hbase.client.Table;
54  import org.apache.hadoop.hbase.filter.Filter;
55  import org.apache.hadoop.hbase.filter.FilterBase;
56  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest;
57  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanResponse;
58  import org.apache.hadoop.hbase.regionserver.HRegion.RegionScannerImpl;
59  import org.apache.hadoop.hbase.testclassification.MediumTests;
60  import org.apache.hadoop.hbase.util.Bytes;
61  import org.apache.hadoop.hbase.wal.WAL;
62  import org.apache.log4j.Level;
63  import org.junit.After;
64  import org.junit.AfterClass;
65  import org.junit.Before;
66  import org.junit.BeforeClass;
67  import org.junit.Test;
68  import org.junit.experimental.categories.Category;
69  
70  import com.google.protobuf.RpcController;
71  import com.google.protobuf.ServiceException;
72  
73  /**
74   * Here we test to make sure that scans return the expected Results when the server is sending the
75   * Client heartbeat messages. Heartbeat messages are essentially keep-alive messages (they prevent
76   * the scanner on the client side from timing out). A heartbeat message is sent from the server to
77   * the client when the server has exceeded the time limit during the processing of the scan. When
78   * the time limit is reached, the server will return to the Client whatever Results it has
79   * accumulated (potentially empty).
80   */
81  @Category(MediumTests.class)
82  public class TestScannerHeartbeatMessages {
83    private static final Log LOG = LogFactory.getLog(TestScannerHeartbeatMessages.class);
84  
85    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
86  
87    private static Table TABLE = null;
88  
89    /**
90     * Table configuration
91     */
92    private static TableName TABLE_NAME = TableName.valueOf("testScannerHeartbeatMessagesTable");
93  
94    private static int NUM_ROWS = 5;
95    private static byte[] ROW = Bytes.toBytes("testRow");
96    private static byte[][] ROWS = HTestConst.makeNAscii(ROW, NUM_ROWS);
97  
98    private static int NUM_FAMILIES = 4;
99    private static byte[] FAMILY = Bytes.toBytes("testFamily");
100   private static byte[][] FAMILIES = HTestConst.makeNAscii(FAMILY, NUM_FAMILIES);
101 
102   private static int NUM_QUALIFIERS = 3;
103   private static byte[] QUALIFIER = Bytes.toBytes("testQualifier");
104   private static byte[][] QUALIFIERS = HTestConst.makeNAscii(QUALIFIER, NUM_QUALIFIERS);
105 
106   private static int VALUE_SIZE = 128;
107   private static byte[] VALUE = Bytes.createMaxByteArray(VALUE_SIZE);
108 
109   // The time limit should be based on the rpc timeout at client, or the client will regards
110   // the request as timeout before server return a heartbeat.
111   private static int SERVER_TIMEOUT = 60000;
112 
113   // Time, in milliseconds, that the client will wait for a response from the server before timing
114   // out. This value is used server side to determine when it is necessary to send a heartbeat
115   // message to the client. Time limit will be 500 ms.
116   private static int CLIENT_TIMEOUT = 1000;
117 
118   // In this test, we sleep after reading each row. So we should make sure after we get some number
119   // of rows and sleep same times we must reach time limit, and do not timeout after next sleeping.
120   // So set this to 200, we will get 3 rows and reach time limit at the start of 4th row, then sleep
121   // for the 4th time. Total time is 800 ms so we will not timeout.
122   private static int DEFAULT_ROW_SLEEP_TIME = 200;
123 
124   // Similar with row sleep time.
125   private static int DEFAULT_CF_SLEEP_TIME = 200;
126 
127   @BeforeClass
128   public static void setUpBeforeClass() throws Exception {
129     ((Log4JLogger) ScannerCallable.LOG).getLogger().setLevel(Level.ALL);
130     ((Log4JLogger) HeartbeatRPCServices.LOG).getLogger().setLevel(Level.ALL);
131     Configuration conf = TEST_UTIL.getConfiguration();
132 
133     conf.setStrings(HConstants.REGION_IMPL, HeartbeatHRegion.class.getName());
134     conf.setStrings(HConstants.REGION_SERVER_IMPL, HeartbeatHRegionServer.class.getName());
135     conf.setInt(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, SERVER_TIMEOUT);
136     conf.setInt(HConstants.HBASE_RPC_TIMEOUT_KEY, SERVER_TIMEOUT);
137     conf.setInt(HConstants.HBASE_CLIENT_PAUSE, 1);
138 
139     // Check the timeout condition after every cell
140     conf.setLong(StoreScanner.HBASE_CELLS_SCANNED_PER_HEARTBEAT_CHECK, 1);
141     TEST_UTIL.startMiniCluster(1);
142 
143     TABLE = createTestTable(TABLE_NAME, ROWS, FAMILIES, QUALIFIERS, VALUE);
144   }
145 
146   static Table createTestTable(TableName name, byte[][] rows, byte[][] families,
147       byte[][] qualifiers, byte[] cellValue) throws IOException {
148     Table ht = TEST_UTIL.createTable(name, families);
149     List<Put> puts = createPuts(rows, families, qualifiers, cellValue);
150     ht.put(puts);
151     ht.getConfiguration().setInt(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, CLIENT_TIMEOUT);
152     return ht;
153   }
154 
155   /**
156    * Make puts to put the input value into each combination of row, family, and qualifier
157    * @param rows
158    * @param families
159    * @param qualifiers
160    * @param value
161    * @return
162    * @throws IOException
163    */
164   static ArrayList<Put> createPuts(byte[][] rows, byte[][] families, byte[][] qualifiers,
165       byte[] value) throws IOException {
166     Put put;
167     ArrayList<Put> puts = new ArrayList<>();
168 
169     for (int row = 0; row < rows.length; row++) {
170       put = new Put(rows[row]);
171       for (int fam = 0; fam < families.length; fam++) {
172         for (int qual = 0; qual < qualifiers.length; qual++) {
173           KeyValue kv = new KeyValue(rows[row], families[fam], qualifiers[qual], qual, value);
174           put.add(kv);
175         }
176       }
177       puts.add(put);
178     }
179 
180     return puts;
181   }
182 
183   @AfterClass
184   public static void tearDownAfterClass() throws Exception {
185     TEST_UTIL.deleteTable(TABLE_NAME);
186     TEST_UTIL.shutdownMiniCluster();
187   }
188 
189   @Before
190   public void setupBeforeTest() throws Exception {
191     disableSleeping();
192   }
193 
194   @After
195   public void teardownAfterTest() throws Exception {
196     disableSleeping();
197   }
198 
199   /**
200    * Test a variety of scan configurations to ensure that they return the expected Results when
201    * heartbeat messages are necessary. These tests are accumulated under one test case to ensure
202    * that they don't run in parallel. If the tests ran in parallel, they may conflict with each
203    * other due to changing static variables
204    */
205   @Test
206   public void testScannerHeartbeatMessages() throws Exception {
207     testImportanceOfHeartbeats(testHeartbeatBetweenRows());
208     testImportanceOfHeartbeats(testHeartbeatBetweenColumnFamilies());
209     testImportanceOfHeartbeats(testHeartbeatWithSparseFilter());
210   }
211 
212   /**
213    * Run the test callable when heartbeats are enabled/disabled. We expect all tests to only pass
214    * when heartbeat messages are enabled (otherwise the test is pointless). When heartbeats are
215    * disabled, the test should throw an exception.
216    * @param testCallable
217    * @throws InterruptedException
218    */
219   public void testImportanceOfHeartbeats(Callable<Void> testCallable) throws InterruptedException {
220     HeartbeatRPCServices.heartbeatsEnabled = true;
221 
222     try {
223       testCallable.call();
224     } catch (Exception e) {
225       fail("Heartbeat messages are enabled, exceptions should NOT be thrown. Exception trace:"
226           + ExceptionUtils.getStackTrace(e));
227     }
228 
229     HeartbeatRPCServices.heartbeatsEnabled = false;
230     try {
231       testCallable.call();
232     } catch (Exception e) {
233       return;
234     } finally {
235       HeartbeatRPCServices.heartbeatsEnabled = true;
236     }
237     fail("Heartbeats messages are disabled, an exception should be thrown. If an exception "
238         + " is not thrown, the test case is not testing the importance of heartbeat messages");
239   }
240 
241   /**
242    * Test the case that the time limit for the scan is reached after each full row of cells is
243    * fetched.
244    * @throws Exception
245    */
246   public Callable<Void> testHeartbeatBetweenRows() throws Exception {
247     return new Callable<Void>() {
248 
249       @Override
250       public Void call() throws Exception {
251         // Configure the scan so that it can read the entire table in a single RPC. We want to test
252         // the case where a scan stops on the server side due to a time limit
253         Scan scan = new Scan();
254         scan.setMaxResultSize(Long.MAX_VALUE);
255         scan.setCaching(Integer.MAX_VALUE);
256 
257         testEquivalenceOfScanWithHeartbeats(scan, DEFAULT_ROW_SLEEP_TIME, -1, false);
258         return null;
259       }
260     };
261   }
262 
263   /**
264    * Test the case that the time limit for scans is reached in between column families
265    * @throws Exception
266    */
267   public Callable<Void> testHeartbeatBetweenColumnFamilies() throws Exception {
268     return new Callable<Void>() {
269       @Override
270       public Void call() throws Exception {
271         // Configure the scan so that it can read the entire table in a single RPC. We want to test
272         // the case where a scan stops on the server side due to a time limit
273         Scan baseScan = new Scan();
274         baseScan.setMaxResultSize(Long.MAX_VALUE);
275         baseScan.setCaching(Integer.MAX_VALUE);
276 
277         // Copy the scan before each test. When a scan object is used by a scanner, some of its
278         // fields may be changed such as start row
279         Scan scanCopy = new Scan(baseScan);
280         testEquivalenceOfScanWithHeartbeats(scanCopy, -1, DEFAULT_CF_SLEEP_TIME, false);
281         scanCopy = new Scan(baseScan);
282         testEquivalenceOfScanWithHeartbeats(scanCopy, -1, DEFAULT_CF_SLEEP_TIME, true);
283         return null;
284       }
285     };
286   }
287 
288   public static class SparseFilter extends FilterBase{
289 
290     @Override
291     public ReturnCode filterKeyValue(Cell v) throws IOException {
292       try {
293         Thread.sleep(CLIENT_TIMEOUT/2 + 10);
294       } catch (InterruptedException e) {
295         Thread.currentThread().interrupt();
296       }
297       return Bytes.equals(CellUtil.cloneRow(v), ROWS[NUM_ROWS - 1]) ?
298           ReturnCode.INCLUDE :
299           ReturnCode.SKIP;
300     }
301 
302     public static Filter parseFrom(final byte [] pbBytes){
303       return new SparseFilter();
304     }
305   }
306 
307   /**
308    * Test the case that there is a filter which filters most of cells
309    * @throws Exception
310    */
311   public Callable<Void> testHeartbeatWithSparseFilter() throws Exception {
312     return new Callable<Void>() {
313       @Override
314       public Void call() throws Exception {
315         Scan scan = new Scan();
316         scan.setMaxResultSize(Long.MAX_VALUE);
317         scan.setCaching(Integer.MAX_VALUE);
318         scan.setFilter(new SparseFilter());
319         ResultScanner scanner = TABLE.getScanner(scan);
320         int num = 0;
321         while (scanner.next() != null) {
322           num++;
323         }
324         assertEquals(1, num);
325         scanner.close();
326 
327         scan = new Scan();
328         scan.setMaxResultSize(Long.MAX_VALUE);
329         scan.setCaching(Integer.MAX_VALUE);
330         scan.setFilter(new SparseFilter());
331         scan.setAllowPartialResults(true);
332         scanner = TABLE.getScanner(scan);
333         num = 0;
334         while (scanner.next() != null) {
335           num++;
336         }
337         assertEquals(NUM_FAMILIES * NUM_QUALIFIERS, num);
338         scanner.close();
339 
340         return null;
341       }
342     };
343   }
344 
345   /**
346    * Test the equivalence of a scan versus the same scan executed when heartbeat messages are
347    * necessary
348    * @param scan The scan configuration being tested
349    * @param rowSleepTime The time to sleep between fetches of row cells
350    * @param cfSleepTime The time to sleep between fetches of column family cells
351    * @param sleepBeforeCf set to true when column family sleeps should occur before the cells for
352    *          that column family are fetched
353    * @throws Exception
354    */
355   public void testEquivalenceOfScanWithHeartbeats(final Scan scan, int rowSleepTime,
356       int cfSleepTime, boolean sleepBeforeCf) throws Exception {
357     disableSleeping();
358     final ResultScanner scanner = TABLE.getScanner(scan);
359     final ResultScanner scannerWithHeartbeats = TABLE.getScanner(scan);
360 
361     Result r1 = null;
362     Result r2 = null;
363 
364     while ((r1 = scanner.next()) != null) {
365       // Enforce the specified sleep conditions during calls to the heartbeat scanner
366       configureSleepTime(rowSleepTime, cfSleepTime, sleepBeforeCf);
367       r2 = scannerWithHeartbeats.next();
368       disableSleeping();
369 
370       assertTrue(r2 != null);
371       try {
372         Result.compareResults(r1, r2);
373       } catch (Exception e) {
374         fail(e.getMessage());
375       }
376     }
377 
378     assertTrue(scannerWithHeartbeats.next() == null);
379     scanner.close();
380     scannerWithHeartbeats.close();
381   }
382 
383   /**
384    * Helper method for setting the time to sleep between rows and column families. If a sleep time
385    * is negative then that sleep will be disabled
386    * @param rowSleepTime
387    * @param cfSleepTime
388    */
389   private static void configureSleepTime(int rowSleepTime, int cfSleepTime, boolean sleepBeforeCf) {
390     HeartbeatHRegion.sleepBetweenRows = rowSleepTime > 0;
391     HeartbeatHRegion.rowSleepTime = rowSleepTime;
392 
393     HeartbeatHRegion.sleepBetweenColumnFamilies = cfSleepTime > 0;
394     HeartbeatHRegion.columnFamilySleepTime = cfSleepTime;
395     HeartbeatHRegion.sleepBeforeColumnFamily = sleepBeforeCf;
396   }
397 
398   /**
399    * Disable the sleeping mechanism server side.
400    */
401   private static void disableSleeping() {
402     HeartbeatHRegion.sleepBetweenRows = false;
403     HeartbeatHRegion.sleepBetweenColumnFamilies = false;
404   }
405 
406   /**
407    * Custom HRegionServer instance that instantiates {@link HeartbeatRPCServices} in place of
408    * {@link RSRpcServices} to allow us to toggle support for heartbeat messages
409    */
410   private static class HeartbeatHRegionServer extends HRegionServer {
411     public HeartbeatHRegionServer(Configuration conf) throws IOException, InterruptedException {
412       super(conf);
413     }
414 
415     public HeartbeatHRegionServer(Configuration conf, CoordinatedStateManager csm)
416         throws IOException, InterruptedException {
417       super(conf, csm);
418     }
419 
420     @Override
421     protected RSRpcServices createRpcServices() throws IOException {
422       return new HeartbeatRPCServices(this);
423     }
424   }
425 
426   /**
427    * Custom RSRpcServices instance that allows heartbeat support to be toggled
428    */
429   private static class HeartbeatRPCServices extends RSRpcServices {
430     private static boolean heartbeatsEnabled = true;
431 
432     public HeartbeatRPCServices(HRegionServer rs) throws IOException {
433       super(rs);
434     }
435 
436     @Override
437     public ScanResponse scan(RpcController controller, ScanRequest request) 
438         throws ServiceException {
439       ScanRequest.Builder builder = ScanRequest.newBuilder(request);
440       builder.setClientHandlesHeartbeats(heartbeatsEnabled);
441       return super.scan(controller, builder.build());
442     }
443   }
444 
445   /**
446    * Custom HRegion class that instantiates {@link RegionScanner}s with configurable sleep times
447    * between fetches of row Results and/or column family cells. Useful for emulating an instance
448    * where the server is taking a long time to process a client's scan request
449    */
450   private static class HeartbeatHRegion extends HRegion {
451     // Row sleeps occur AFTER each row worth of cells is retrieved.
452     private static int rowSleepTime = DEFAULT_ROW_SLEEP_TIME;
453     private static boolean sleepBetweenRows = false;
454 
455     // The sleep for column families can be initiated before or after we fetch the cells for the
456     // column family. If the sleep occurs BEFORE then the time limits will be reached inside
457     // StoreScanner while we are fetching individual cells. If the sleep occurs AFTER then the time
458     // limit will be reached inside RegionScanner after all the cells for a column family have been
459     // retrieved.
460     private static boolean sleepBeforeColumnFamily = false;
461     private static int columnFamilySleepTime = DEFAULT_CF_SLEEP_TIME;
462     private static boolean sleepBetweenColumnFamilies = false;
463 
464     public HeartbeatHRegion(Path tableDir, WAL wal, FileSystem fs, Configuration confParam,
465         HRegionInfo regionInfo, HTableDescriptor htd, RegionServerServices rsServices) {
466       super(tableDir, wal, fs, confParam, regionInfo, htd, rsServices);
467     }
468 
469     public HeartbeatHRegion(HRegionFileSystem fs, WAL wal, Configuration confParam,
470         HTableDescriptor htd, RegionServerServices rsServices) {
471       super(fs, wal, confParam, htd, rsServices);
472     }
473 
474     private static void columnFamilySleep() {
475       if (HeartbeatHRegion.sleepBetweenColumnFamilies) {
476         try {
477           Thread.sleep(HeartbeatHRegion.columnFamilySleepTime);
478         } catch (InterruptedException e) {
479         }
480       }
481     }
482 
483     private static void rowSleep() {
484       try {
485         if (HeartbeatHRegion.sleepBetweenRows) {
486           Thread.sleep(HeartbeatHRegion.rowSleepTime);
487         }
488       } catch (InterruptedException e) {
489       }
490     }
491 
492     // Instantiate the custom heartbeat region scanners
493     @Override
494     protected RegionScanner instantiateRegionScanner(Scan scan,
495         List<KeyValueScanner> additionalScanners, long nonceGroup, long nonce) throws IOException {
496       if (scan.isReversed()) {
497         if (scan.getFilter() != null) {
498           scan.getFilter().setReversed(true);
499         }
500         return new HeartbeatReversedRegionScanner(scan, additionalScanners, this);
501       }
502       return new HeartbeatRegionScanner(scan, additionalScanners, this);
503     }
504   }
505 
506   /**
507    * Custom ReversedRegionScanner that can be configured to sleep between retrievals of row Results
508    * and/or column family cells
509    */
510   private static class HeartbeatReversedRegionScanner extends ReversedRegionScannerImpl {
511     HeartbeatReversedRegionScanner(Scan scan, List<KeyValueScanner> additionalScanners,
512         HRegion region) throws IOException {
513       super(scan, additionalScanners, region);
514     }
515 
516     @Override
517     public boolean nextRaw(List<Cell> outResults, ScannerContext context)
518         throws IOException {
519       boolean moreRows = super.nextRaw(outResults, context);
520       HeartbeatHRegion.rowSleep();
521       return moreRows;
522     }
523 
524     @Override
525     protected void initializeKVHeap(List<KeyValueScanner> scanners,
526         List<KeyValueScanner> joinedScanners, HRegion region) throws IOException {
527       this.storeHeap = new HeartbeatReversedKVHeap(scanners, region.getComparator());
528       if (!joinedScanners.isEmpty()) {
529         this.joinedHeap = new HeartbeatReversedKVHeap(joinedScanners, region.getComparator());
530       }
531     }
532   }
533 
534   /**
535    * Custom RegionScanner that can be configured to sleep between retrievals of row Results and/or
536    * column family cells
537    */
538   private static class HeartbeatRegionScanner extends RegionScannerImpl {
539     HeartbeatRegionScanner(Scan scan, List<KeyValueScanner> additionalScanners, HRegion region)
540         throws IOException {
541       region.super(scan, additionalScanners, region);
542     }
543 
544     @Override
545     public boolean nextRaw(List<Cell> outResults, ScannerContext context)
546         throws IOException {
547       boolean moreRows = super.nextRaw(outResults, context);
548       HeartbeatHRegion.rowSleep();
549       return moreRows;
550     }
551 
552     @Override
553     protected void initializeKVHeap(List<KeyValueScanner> scanners,
554         List<KeyValueScanner> joinedScanners, HRegion region) throws IOException {
555       this.storeHeap = new HeartbeatKVHeap(scanners, region.getComparator());
556       if (!joinedScanners.isEmpty()) {
557         this.joinedHeap = new HeartbeatKVHeap(joinedScanners, region.getComparator());
558       }
559     }
560   }
561 
562   /**
563    * Custom KV Heap that can be configured to sleep/wait in between retrievals of column family
564    * cells. Useful for testing
565    */
566   private static final class HeartbeatKVHeap extends KeyValueHeap {
567     public HeartbeatKVHeap(List<? extends KeyValueScanner> scanners, KVComparator comparator)
568         throws IOException {
569       super(scanners, comparator);
570     }
571 
572     HeartbeatKVHeap(List<? extends KeyValueScanner> scanners, KVScannerComparator comparator)
573         throws IOException {
574       super(scanners, comparator);
575     }
576 
577     @Override
578     public boolean next(List<Cell> result, ScannerContext context)
579         throws IOException {
580       if (HeartbeatHRegion.sleepBeforeColumnFamily) HeartbeatHRegion.columnFamilySleep();
581       boolean moreRows = super.next(result, context);
582       if (!HeartbeatHRegion.sleepBeforeColumnFamily) HeartbeatHRegion.columnFamilySleep();
583       return moreRows;
584     }
585   }
586 
587   /**
588    * Custom reversed KV Heap that can be configured to sleep in between retrievals of column family
589    * cells.
590    */
591   private static final class HeartbeatReversedKVHeap extends ReversedKeyValueHeap {
592     public HeartbeatReversedKVHeap(List<? extends KeyValueScanner> scanners, 
593         KVComparator comparator) throws IOException {
594       super(scanners, comparator);
595     }
596 
597     @Override
598     public boolean next(List<Cell> result, ScannerContext context)
599         throws IOException {
600       if (HeartbeatHRegion.sleepBeforeColumnFamily) HeartbeatHRegion.columnFamilySleep();
601       boolean moreRows = super.next(result, context);
602       if (!HeartbeatHRegion.sleepBeforeColumnFamily) HeartbeatHRegion.columnFamilySleep();
603       return moreRows;
604     }
605   }
606 }