001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024import static org.mockito.ArgumentMatchers.any;
025import static org.mockito.Mockito.mock;
026import static org.mockito.Mockito.spy;
027import static org.mockito.Mockito.times;
028import static org.mockito.Mockito.verify;
029import static org.mockito.Mockito.when;
030
031import java.io.IOException;
032import java.lang.ref.SoftReference;
033import java.security.PrivilegedExceptionAction;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.Collection;
037import java.util.Collections;
038import java.util.Iterator;
039import java.util.List;
040import java.util.ListIterator;
041import java.util.NavigableSet;
042import java.util.TreeSet;
043import java.util.concurrent.ConcurrentSkipListSet;
044import java.util.concurrent.CountDownLatch;
045import java.util.concurrent.ExecutorService;
046import java.util.concurrent.Executors;
047import java.util.concurrent.TimeUnit;
048import java.util.concurrent.atomic.AtomicBoolean;
049import java.util.concurrent.atomic.AtomicInteger;
050import org.apache.hadoop.conf.Configuration;
051import org.apache.hadoop.fs.FSDataOutputStream;
052import org.apache.hadoop.fs.FileStatus;
053import org.apache.hadoop.fs.FileSystem;
054import org.apache.hadoop.fs.FilterFileSystem;
055import org.apache.hadoop.fs.LocalFileSystem;
056import org.apache.hadoop.fs.Path;
057import org.apache.hadoop.fs.permission.FsPermission;
058import org.apache.hadoop.hbase.Cell;
059import org.apache.hadoop.hbase.CellBuilderFactory;
060import org.apache.hadoop.hbase.CellBuilderType;
061import org.apache.hadoop.hbase.CellComparator;
062import org.apache.hadoop.hbase.CellComparatorImpl;
063import org.apache.hadoop.hbase.CellUtil;
064import org.apache.hadoop.hbase.HBaseClassTestRule;
065import org.apache.hadoop.hbase.HBaseConfiguration;
066import org.apache.hadoop.hbase.HBaseTestingUtility;
067import org.apache.hadoop.hbase.HConstants;
068import org.apache.hadoop.hbase.KeyValue;
069import org.apache.hadoop.hbase.MemoryCompactionPolicy;
070import org.apache.hadoop.hbase.PrivateCellUtil;
071import org.apache.hadoop.hbase.TableName;
072import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
073import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
074import org.apache.hadoop.hbase.client.Get;
075import org.apache.hadoop.hbase.client.RegionInfo;
076import org.apache.hadoop.hbase.client.RegionInfoBuilder;
077import org.apache.hadoop.hbase.client.Scan;
078import org.apache.hadoop.hbase.client.TableDescriptor;
079import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
080import org.apache.hadoop.hbase.exceptions.IllegalArgumentIOException;
081import org.apache.hadoop.hbase.filter.Filter;
082import org.apache.hadoop.hbase.filter.FilterBase;
083import org.apache.hadoop.hbase.io.compress.Compression;
084import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
085import org.apache.hadoop.hbase.io.hfile.CacheConfig;
086import org.apache.hadoop.hbase.io.hfile.HFile;
087import org.apache.hadoop.hbase.io.hfile.HFileContext;
088import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
089import org.apache.hadoop.hbase.monitoring.MonitoredTask;
090import org.apache.hadoop.hbase.regionserver.compactions.CompactionConfiguration;
091import org.apache.hadoop.hbase.regionserver.compactions.DefaultCompactor;
092import org.apache.hadoop.hbase.regionserver.querymatcher.ScanQueryMatcher;
093import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
094import org.apache.hadoop.hbase.security.User;
095import org.apache.hadoop.hbase.testclassification.MediumTests;
096import org.apache.hadoop.hbase.testclassification.RegionServerTests;
097import org.apache.hadoop.hbase.util.Bytes;
098import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
099import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
100import org.apache.hadoop.hbase.util.FSUtils;
101import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
102import org.apache.hadoop.hbase.util.ManualEnvironmentEdge;
103import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
104import org.apache.hadoop.hbase.wal.WALFactory;
105import org.apache.hadoop.util.Progressable;
106import org.junit.After;
107import org.junit.AfterClass;
108import org.junit.Before;
109import org.junit.ClassRule;
110import org.junit.Rule;
111import org.junit.Test;
112import org.junit.experimental.categories.Category;
113import org.junit.rules.TestName;
114import org.mockito.Mockito;
115import org.slf4j.Logger;
116import org.slf4j.LoggerFactory;
117
118import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
119
120/**
121 * Test class for the HStore
122 */
123@Category({ RegionServerTests.class, MediumTests.class })
124public class TestHStore {
125
126  @ClassRule
127  public static final HBaseClassTestRule CLASS_RULE =
128      HBaseClassTestRule.forClass(TestHStore.class);
129
130  private static final Logger LOG = LoggerFactory.getLogger(TestHStore.class);
131  @Rule
132  public TestName name = new TestName();
133
134  HRegion region;
135  HStore store;
136  byte [] table = Bytes.toBytes("table");
137  byte [] family = Bytes.toBytes("family");
138
139  byte [] row = Bytes.toBytes("row");
140  byte [] row2 = Bytes.toBytes("row2");
141  byte [] qf1 = Bytes.toBytes("qf1");
142  byte [] qf2 = Bytes.toBytes("qf2");
143  byte [] qf3 = Bytes.toBytes("qf3");
144  byte [] qf4 = Bytes.toBytes("qf4");
145  byte [] qf5 = Bytes.toBytes("qf5");
146  byte [] qf6 = Bytes.toBytes("qf6");
147
148  NavigableSet<byte[]> qualifiers = new ConcurrentSkipListSet<>(Bytes.BYTES_COMPARATOR);
149
150  List<Cell> expected = new ArrayList<>();
151  List<Cell> result = new ArrayList<>();
152
153  long id = System.currentTimeMillis();
154  Get get = new Get(row);
155
156  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
157  private static final String DIR = TEST_UTIL.getDataTestDir("TestStore").toString();
158
159
160  /**
161   * Setup
162   * @throws IOException
163   */
164  @Before
165  public void setUp() throws IOException {
166    qualifiers.add(qf1);
167    qualifiers.add(qf3);
168    qualifiers.add(qf5);
169
170    Iterator<byte[]> iter = qualifiers.iterator();
171    while(iter.hasNext()){
172      byte [] next = iter.next();
173      expected.add(new KeyValue(row, family, next, 1, (byte[])null));
174      get.addColumn(family, next);
175    }
176  }
177
178  private void init(String methodName) throws IOException {
179    init(methodName, TEST_UTIL.getConfiguration());
180  }
181
182  private HStore init(String methodName, Configuration conf) throws IOException {
183    // some of the tests write 4 versions and then flush
184    // (with HBASE-4241, lower versions are collected on flush)
185    return init(methodName, conf,
186      ColumnFamilyDescriptorBuilder.newBuilder(family).setMaxVersions(4).build());
187  }
188
189  private HStore init(String methodName, Configuration conf, ColumnFamilyDescriptor hcd)
190      throws IOException {
191    return init(methodName, conf, TableDescriptorBuilder.newBuilder(TableName.valueOf(table)), hcd);
192  }
193
194  private HStore init(String methodName, Configuration conf, TableDescriptorBuilder builder,
195      ColumnFamilyDescriptor hcd) throws IOException {
196    return init(methodName, conf, builder, hcd, null);
197  }
198
199  private HStore init(String methodName, Configuration conf, TableDescriptorBuilder builder,
200      ColumnFamilyDescriptor hcd, MyStoreHook hook) throws IOException {
201    return init(methodName, conf, builder, hcd, hook, false);
202  }
203
204  private void initHRegion(String methodName, Configuration conf, TableDescriptorBuilder builder,
205      ColumnFamilyDescriptor hcd, MyStoreHook hook, boolean switchToPread) throws IOException {
206    TableDescriptor htd = builder.setColumnFamily(hcd).build();
207    Path basedir = new Path(DIR + methodName);
208    Path tableDir = FSUtils.getTableDir(basedir, htd.getTableName());
209    final Path logdir = new Path(basedir, AbstractFSWALProvider.getWALDirectoryName(methodName));
210
211    FileSystem fs = FileSystem.get(conf);
212
213    fs.delete(logdir, true);
214    ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false,
215      MemStoreLABImpl.CHUNK_SIZE_DEFAULT, 1, 0, null);
216    RegionInfo info = RegionInfoBuilder.newBuilder(htd.getTableName()).build();
217    Configuration walConf = new Configuration(conf);
218    FSUtils.setRootDir(walConf, basedir);
219    WALFactory wals = new WALFactory(walConf, methodName);
220    region = new HRegion(new HRegionFileSystem(conf, fs, tableDir, info), wals.getWAL(info), conf,
221        htd, null);
222  }
223
224  private HStore init(String methodName, Configuration conf, TableDescriptorBuilder builder,
225      ColumnFamilyDescriptor hcd, MyStoreHook hook, boolean switchToPread) throws IOException {
226    initHRegion(methodName, conf, builder, hcd, hook, switchToPread);
227    if (hook == null) {
228      store = new HStore(region, hcd, conf);
229    } else {
230      store = new MyStore(region, hcd, conf, hook, switchToPread);
231    }
232    return store;
233  }
234
235  /**
236   * Test we do not lose data if we fail a flush and then close.
237   * Part of HBase-10466
238   * @throws Exception
239   */
240  @Test
241  public void testFlushSizeSizing() throws Exception {
242    LOG.info("Setting up a faulty file system that cannot write in " + this.name.getMethodName());
243    final Configuration conf = HBaseConfiguration.create(TEST_UTIL.getConfiguration());
244    // Only retry once.
245    conf.setInt("hbase.hstore.flush.retries.number", 1);
246    User user = User.createUserForTesting(conf, this.name.getMethodName(),
247      new String[]{"foo"});
248    // Inject our faulty LocalFileSystem
249    conf.setClass("fs.file.impl", FaultyFileSystem.class, FileSystem.class);
250    user.runAs(new PrivilegedExceptionAction<Object>() {
251      @Override
252      public Object run() throws Exception {
253        // Make sure it worked (above is sensitive to caching details in hadoop core)
254        FileSystem fs = FileSystem.get(conf);
255        assertEquals(FaultyFileSystem.class, fs.getClass());
256        FaultyFileSystem ffs = (FaultyFileSystem)fs;
257
258        // Initialize region
259        init(name.getMethodName(), conf);
260
261        MemStoreSize mss = store.memstore.getFlushableSize();
262        assertEquals(0, mss.getDataSize());
263        LOG.info("Adding some data");
264        MemStoreSizing kvSize = new NonThreadSafeMemStoreSizing();
265        store.add(new KeyValue(row, family, qf1, 1, (byte[]) null), kvSize);
266        // add the heap size of active (mutable) segment
267        kvSize.incMemStoreSize(0, MutableSegment.DEEP_OVERHEAD, 0, 0);
268        mss = store.memstore.getFlushableSize();
269        assertEquals(kvSize.getMemStoreSize(), mss);
270        // Flush.  Bug #1 from HBASE-10466.  Make sure size calculation on failed flush is right.
271        try {
272          LOG.info("Flushing");
273          flushStore(store, id++);
274          fail("Didn't bubble up IOE!");
275        } catch (IOException ioe) {
276          assertTrue(ioe.getMessage().contains("Fault injected"));
277        }
278        // due to snapshot, change mutable to immutable segment
279        kvSize.incMemStoreSize(0,
280          CSLMImmutableSegment.DEEP_OVERHEAD_CSLM - MutableSegment.DEEP_OVERHEAD, 0, 0);
281        mss = store.memstore.getFlushableSize();
282        assertEquals(kvSize.getMemStoreSize(), mss);
283        MemStoreSizing kvSize2 = new NonThreadSafeMemStoreSizing();
284        store.add(new KeyValue(row, family, qf2, 2, (byte[]) null), kvSize2);
285        kvSize2.incMemStoreSize(0, MutableSegment.DEEP_OVERHEAD, 0, 0);
286        // Even though we add a new kv, we expect the flushable size to be 'same' since we have
287        // not yet cleared the snapshot -- the above flush failed.
288        assertEquals(kvSize.getMemStoreSize(), mss);
289        ffs.fault.set(false);
290        flushStore(store, id++);
291        mss = store.memstore.getFlushableSize();
292        // Size should be the foreground kv size.
293        assertEquals(kvSize2.getMemStoreSize(), mss);
294        flushStore(store, id++);
295        mss = store.memstore.getFlushableSize();
296        assertEquals(0, mss.getDataSize());
297        assertEquals(MutableSegment.DEEP_OVERHEAD, mss.getHeapSize());
298        return null;
299      }
300    });
301  }
302
303  /**
304   * Verify that compression and data block encoding are respected by the
305   * Store.createWriterInTmp() method, used on store flush.
306   */
307  @Test
308  public void testCreateWriter() throws Exception {
309    Configuration conf = HBaseConfiguration.create();
310    FileSystem fs = FileSystem.get(conf);
311
312    ColumnFamilyDescriptor hcd = ColumnFamilyDescriptorBuilder.newBuilder(family)
313        .setCompressionType(Compression.Algorithm.GZ).setDataBlockEncoding(DataBlockEncoding.DIFF)
314        .build();
315    init(name.getMethodName(), conf, hcd);
316
317    // Test createWriterInTmp()
318    StoreFileWriter writer =
319        store.createWriterInTmp(4, hcd.getCompressionType(), false, true, false, false);
320    Path path = writer.getPath();
321    writer.append(new KeyValue(row, family, qf1, Bytes.toBytes(1)));
322    writer.append(new KeyValue(row, family, qf2, Bytes.toBytes(2)));
323    writer.append(new KeyValue(row2, family, qf1, Bytes.toBytes(3)));
324    writer.append(new KeyValue(row2, family, qf2, Bytes.toBytes(4)));
325    writer.close();
326
327    // Verify that compression and encoding settings are respected
328    HFile.Reader reader = HFile.createReader(fs, path, new CacheConfig(conf), true, conf);
329    assertEquals(hcd.getCompressionType(), reader.getCompressionAlgorithm());
330    assertEquals(hcd.getDataBlockEncoding(), reader.getDataBlockEncoding());
331    reader.close();
332  }
333
334  @Test
335  public void testDeleteExpiredStoreFiles() throws Exception {
336    testDeleteExpiredStoreFiles(0);
337    testDeleteExpiredStoreFiles(1);
338  }
339
340  /*
341   * @param minVersions the MIN_VERSIONS for the column family
342   */
343  public void testDeleteExpiredStoreFiles(int minVersions) throws Exception {
344    int storeFileNum = 4;
345    int ttl = 4;
346    IncrementingEnvironmentEdge edge = new IncrementingEnvironmentEdge();
347    EnvironmentEdgeManagerTestHelper.injectEdge(edge);
348
349    Configuration conf = HBaseConfiguration.create();
350    // Enable the expired store file deletion
351    conf.setBoolean("hbase.store.delete.expired.storefile", true);
352    // Set the compaction threshold higher to avoid normal compactions.
353    conf.setInt(CompactionConfiguration.HBASE_HSTORE_COMPACTION_MIN_KEY, 5);
354
355    init(name.getMethodName() + "-" + minVersions, conf, ColumnFamilyDescriptorBuilder
356        .newBuilder(family).setMinVersions(minVersions).setTimeToLive(ttl).build());
357
358    long storeTtl = this.store.getScanInfo().getTtl();
359    long sleepTime = storeTtl / storeFileNum;
360    long timeStamp;
361    // There are 4 store files and the max time stamp difference among these
362    // store files will be (this.store.ttl / storeFileNum)
363    for (int i = 1; i <= storeFileNum; i++) {
364      LOG.info("Adding some data for the store file #" + i);
365      timeStamp = EnvironmentEdgeManager.currentTime();
366      this.store.add(new KeyValue(row, family, qf1, timeStamp, (byte[]) null), null);
367      this.store.add(new KeyValue(row, family, qf2, timeStamp, (byte[]) null), null);
368      this.store.add(new KeyValue(row, family, qf3, timeStamp, (byte[]) null), null);
369      flush(i);
370      edge.incrementTime(sleepTime);
371    }
372
373    // Verify the total number of store files
374    assertEquals(storeFileNum, this.store.getStorefiles().size());
375
376     // Each call will find one expired store file and delete it before compaction happens.
377     // There will be no compaction due to threshold above. Last file will not be replaced.
378    for (int i = 1; i <= storeFileNum - 1; i++) {
379      // verify the expired store file.
380      assertFalse(this.store.requestCompaction().isPresent());
381      Collection<HStoreFile> sfs = this.store.getStorefiles();
382      // Ensure i files are gone.
383      if (minVersions == 0) {
384        assertEquals(storeFileNum - i, sfs.size());
385        // Ensure only non-expired files remain.
386        for (HStoreFile sf : sfs) {
387          assertTrue(sf.getReader().getMaxTimestamp() >= (edge.currentTime() - storeTtl));
388        }
389      } else {
390        assertEquals(storeFileNum, sfs.size());
391      }
392      // Let the next store file expired.
393      edge.incrementTime(sleepTime);
394    }
395    assertFalse(this.store.requestCompaction().isPresent());
396
397    Collection<HStoreFile> sfs = this.store.getStorefiles();
398    // Assert the last expired file is not removed.
399    if (minVersions == 0) {
400      assertEquals(1, sfs.size());
401    }
402    long ts = sfs.iterator().next().getReader().getMaxTimestamp();
403    assertTrue(ts < (edge.currentTime() - storeTtl));
404
405    for (HStoreFile sf : sfs) {
406      sf.closeStoreFile(true);
407    }
408  }
409
410  @Test
411  public void testLowestModificationTime() throws Exception {
412    Configuration conf = HBaseConfiguration.create();
413    FileSystem fs = FileSystem.get(conf);
414    // Initialize region
415    init(name.getMethodName(), conf);
416
417    int storeFileNum = 4;
418    for (int i = 1; i <= storeFileNum; i++) {
419      LOG.info("Adding some data for the store file #"+i);
420      this.store.add(new KeyValue(row, family, qf1, i, (byte[])null), null);
421      this.store.add(new KeyValue(row, family, qf2, i, (byte[])null), null);
422      this.store.add(new KeyValue(row, family, qf3, i, (byte[])null), null);
423      flush(i);
424    }
425    // after flush; check the lowest time stamp
426    long lowestTimeStampFromManager = StoreUtils.getLowestTimestamp(store.getStorefiles());
427    long lowestTimeStampFromFS = getLowestTimeStampFromFS(fs, store.getStorefiles());
428    assertEquals(lowestTimeStampFromManager,lowestTimeStampFromFS);
429
430    // after compact; check the lowest time stamp
431    store.compact(store.requestCompaction().get(), NoLimitThroughputController.INSTANCE, null);
432    lowestTimeStampFromManager = StoreUtils.getLowestTimestamp(store.getStorefiles());
433    lowestTimeStampFromFS = getLowestTimeStampFromFS(fs, store.getStorefiles());
434    assertEquals(lowestTimeStampFromManager, lowestTimeStampFromFS);
435  }
436
437  private static long getLowestTimeStampFromFS(FileSystem fs,
438      final Collection<HStoreFile> candidates) throws IOException {
439    long minTs = Long.MAX_VALUE;
440    if (candidates.isEmpty()) {
441      return minTs;
442    }
443    Path[] p = new Path[candidates.size()];
444    int i = 0;
445    for (HStoreFile sf : candidates) {
446      p[i] = sf.getPath();
447      ++i;
448    }
449
450    FileStatus[] stats = fs.listStatus(p);
451    if (stats == null || stats.length == 0) {
452      return minTs;
453    }
454    for (FileStatus s : stats) {
455      minTs = Math.min(minTs, s.getModificationTime());
456    }
457    return minTs;
458  }
459
460  //////////////////////////////////////////////////////////////////////////////
461  // Get tests
462  //////////////////////////////////////////////////////////////////////////////
463
464  private static final int BLOCKSIZE_SMALL = 8192;
465  /**
466   * Test for hbase-1686.
467   * @throws IOException
468   */
469  @Test
470  public void testEmptyStoreFile() throws IOException {
471    init(this.name.getMethodName());
472    // Write a store file.
473    this.store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
474    this.store.add(new KeyValue(row, family, qf2, 1, (byte[])null), null);
475    flush(1);
476    // Now put in place an empty store file.  Its a little tricky.  Have to
477    // do manually with hacked in sequence id.
478    HStoreFile f = this.store.getStorefiles().iterator().next();
479    Path storedir = f.getPath().getParent();
480    long seqid = f.getMaxSequenceId();
481    Configuration c = HBaseConfiguration.create();
482    FileSystem fs = FileSystem.get(c);
483    HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL).build();
484    StoreFileWriter w = new StoreFileWriter.Builder(c, new CacheConfig(c),
485        fs)
486            .withOutputDir(storedir)
487            .withFileContext(meta)
488            .build();
489    w.appendMetadata(seqid + 1, false);
490    w.close();
491    this.store.close();
492    // Reopen it... should pick up two files
493    this.store = new HStore(this.store.getHRegion(), this.store.getColumnFamilyDescriptor(), c);
494    assertEquals(2, this.store.getStorefilesCount());
495
496    result = HBaseTestingUtility.getFromStoreFile(store,
497        get.getRow(),
498        qualifiers);
499    assertEquals(1, result.size());
500  }
501
502  /**
503   * Getting data from memstore only
504   * @throws IOException
505   */
506  @Test
507  public void testGet_FromMemStoreOnly() throws IOException {
508    init(this.name.getMethodName());
509
510    //Put data in memstore
511    this.store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
512    this.store.add(new KeyValue(row, family, qf2, 1, (byte[])null), null);
513    this.store.add(new KeyValue(row, family, qf3, 1, (byte[])null), null);
514    this.store.add(new KeyValue(row, family, qf4, 1, (byte[])null), null);
515    this.store.add(new KeyValue(row, family, qf5, 1, (byte[])null), null);
516    this.store.add(new KeyValue(row, family, qf6, 1, (byte[])null), null);
517
518    //Get
519    result = HBaseTestingUtility.getFromStoreFile(store,
520        get.getRow(), qualifiers);
521
522    //Compare
523    assertCheck();
524  }
525
526  @Test
527  public void testTimeRangeIfSomeCellsAreDroppedInFlush() throws IOException {
528    testTimeRangeIfSomeCellsAreDroppedInFlush(1);
529    testTimeRangeIfSomeCellsAreDroppedInFlush(3);
530    testTimeRangeIfSomeCellsAreDroppedInFlush(5);
531  }
532
533  private void testTimeRangeIfSomeCellsAreDroppedInFlush(int maxVersion) throws IOException {
534    init(this.name.getMethodName(), TEST_UTIL.getConfiguration(),
535    ColumnFamilyDescriptorBuilder.newBuilder(family).setMaxVersions(maxVersion).build());
536    long currentTs = 100;
537    long minTs = currentTs;
538    // the extra cell won't be flushed to disk,
539    // so the min of timerange will be different between memStore and hfile.
540    for (int i = 0; i != (maxVersion + 1); ++i) {
541      this.store.add(new KeyValue(row, family, qf1, ++currentTs, (byte[])null), null);
542      if (i == 1) {
543        minTs = currentTs;
544      }
545    }
546    flushStore(store, id++);
547
548    Collection<HStoreFile> files = store.getStorefiles();
549    assertEquals(1, files.size());
550    HStoreFile f = files.iterator().next();
551    f.initReader();
552    StoreFileReader reader = f.getReader();
553    assertEquals(minTs, reader.timeRange.getMin());
554    assertEquals(currentTs, reader.timeRange.getMax());
555  }
556
557  /**
558   * Getting data from files only
559   * @throws IOException
560   */
561  @Test
562  public void testGet_FromFilesOnly() throws IOException {
563    init(this.name.getMethodName());
564
565    //Put data in memstore
566    this.store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
567    this.store.add(new KeyValue(row, family, qf2, 1, (byte[])null), null);
568    //flush
569    flush(1);
570
571    //Add more data
572    this.store.add(new KeyValue(row, family, qf3, 1, (byte[])null), null);
573    this.store.add(new KeyValue(row, family, qf4, 1, (byte[])null), null);
574    //flush
575    flush(2);
576
577    //Add more data
578    this.store.add(new KeyValue(row, family, qf5, 1, (byte[])null), null);
579    this.store.add(new KeyValue(row, family, qf6, 1, (byte[])null), null);
580    //flush
581    flush(3);
582
583    //Get
584    result = HBaseTestingUtility.getFromStoreFile(store,
585        get.getRow(),
586        qualifiers);
587    //this.store.get(get, qualifiers, result);
588
589    //Need to sort the result since multiple files
590    Collections.sort(result, CellComparatorImpl.COMPARATOR);
591
592    //Compare
593    assertCheck();
594  }
595
596  /**
597   * Getting data from memstore and files
598   * @throws IOException
599   */
600  @Test
601  public void testGet_FromMemStoreAndFiles() throws IOException {
602    init(this.name.getMethodName());
603
604    //Put data in memstore
605    this.store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
606    this.store.add(new KeyValue(row, family, qf2, 1, (byte[])null), null);
607    //flush
608    flush(1);
609
610    //Add more data
611    this.store.add(new KeyValue(row, family, qf3, 1, (byte[])null), null);
612    this.store.add(new KeyValue(row, family, qf4, 1, (byte[])null), null);
613    //flush
614    flush(2);
615
616    //Add more data
617    this.store.add(new KeyValue(row, family, qf5, 1, (byte[])null), null);
618    this.store.add(new KeyValue(row, family, qf6, 1, (byte[])null), null);
619
620    //Get
621    result = HBaseTestingUtility.getFromStoreFile(store,
622        get.getRow(), qualifiers);
623
624    //Need to sort the result since multiple files
625    Collections.sort(result, CellComparatorImpl.COMPARATOR);
626
627    //Compare
628    assertCheck();
629  }
630
631  private void flush(int storeFilessize) throws IOException{
632    this.store.snapshot();
633    flushStore(store, id++);
634    assertEquals(storeFilessize, this.store.getStorefiles().size());
635    assertEquals(0, ((AbstractMemStore)this.store.memstore).getActive().getCellsCount());
636  }
637
638  private void assertCheck() {
639    assertEquals(expected.size(), result.size());
640    for(int i=0; i<expected.size(); i++) {
641      assertEquals(expected.get(i), result.get(i));
642    }
643  }
644
645  @After
646  public void tearDown() throws Exception {
647    EnvironmentEdgeManagerTestHelper.reset();
648    if (store != null) {
649      try {
650        store.close();
651      } catch (IOException e) {
652      }
653      store = null;
654    }
655    if (region != null) {
656      region.close();
657      region = null;
658    }
659  }
660
661  @AfterClass
662  public static void tearDownAfterClass() throws IOException {
663    TEST_UTIL.cleanupTestDir();
664  }
665
666  @Test
667  public void testHandleErrorsInFlush() throws Exception {
668    LOG.info("Setting up a faulty file system that cannot write");
669
670    final Configuration conf = HBaseConfiguration.create(TEST_UTIL.getConfiguration());
671    User user = User.createUserForTesting(conf,
672        "testhandleerrorsinflush", new String[]{"foo"});
673    // Inject our faulty LocalFileSystem
674    conf.setClass("fs.file.impl", FaultyFileSystem.class,
675        FileSystem.class);
676    user.runAs(new PrivilegedExceptionAction<Object>() {
677      @Override
678      public Object run() throws Exception {
679        // Make sure it worked (above is sensitive to caching details in hadoop core)
680        FileSystem fs = FileSystem.get(conf);
681        assertEquals(FaultyFileSystem.class, fs.getClass());
682
683        // Initialize region
684        init(name.getMethodName(), conf);
685
686        LOG.info("Adding some data");
687        store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
688        store.add(new KeyValue(row, family, qf2, 1, (byte[])null), null);
689        store.add(new KeyValue(row, family, qf3, 1, (byte[])null), null);
690
691        LOG.info("Before flush, we should have no files");
692
693        Collection<StoreFileInfo> files =
694          store.getRegionFileSystem().getStoreFiles(store.getColumnFamilyName());
695        assertEquals(0, files != null ? files.size() : 0);
696
697        //flush
698        try {
699          LOG.info("Flushing");
700          flush(1);
701          fail("Didn't bubble up IOE!");
702        } catch (IOException ioe) {
703          assertTrue(ioe.getMessage().contains("Fault injected"));
704        }
705
706        LOG.info("After failed flush, we should still have no files!");
707        files = store.getRegionFileSystem().getStoreFiles(store.getColumnFamilyName());
708        assertEquals(0, files != null ? files.size() : 0);
709        store.getHRegion().getWAL().close();
710        return null;
711      }
712    });
713    FileSystem.closeAllForUGI(user.getUGI());
714  }
715
716  /**
717   * Faulty file system that will fail if you write past its fault position the FIRST TIME
718   * only; thereafter it will succeed.  Used by {@link TestHRegion} too.
719   */
720  static class FaultyFileSystem extends FilterFileSystem {
721    List<SoftReference<FaultyOutputStream>> outStreams = new ArrayList<>();
722    private long faultPos = 200;
723    AtomicBoolean fault = new AtomicBoolean(true);
724
725    public FaultyFileSystem() {
726      super(new LocalFileSystem());
727      System.err.println("Creating faulty!");
728    }
729
730    @Override
731    public FSDataOutputStream create(Path p) throws IOException {
732      return new FaultyOutputStream(super.create(p), faultPos, fault);
733    }
734
735    @Override
736    public FSDataOutputStream create(Path f, FsPermission permission,
737        boolean overwrite, int bufferSize, short replication, long blockSize,
738        Progressable progress) throws IOException {
739      return new FaultyOutputStream(super.create(f, permission,
740          overwrite, bufferSize, replication, blockSize, progress), faultPos, fault);
741    }
742
743    @Override
744    public FSDataOutputStream createNonRecursive(Path f, boolean overwrite,
745        int bufferSize, short replication, long blockSize, Progressable progress)
746    throws IOException {
747      // Fake it.  Call create instead.  The default implementation throws an IOE
748      // that this is not supported.
749      return create(f, overwrite, bufferSize, replication, blockSize, progress);
750    }
751  }
752
753  static class FaultyOutputStream extends FSDataOutputStream {
754    volatile long faultPos = Long.MAX_VALUE;
755    private final AtomicBoolean fault;
756
757    public FaultyOutputStream(FSDataOutputStream out, long faultPos, final AtomicBoolean fault)
758    throws IOException {
759      super(out, null);
760      this.faultPos = faultPos;
761      this.fault = fault;
762    }
763
764    @Override
765    public synchronized void write(byte[] buf, int offset, int length) throws IOException {
766      System.err.println("faulty stream write at pos " + getPos());
767      injectFault();
768      super.write(buf, offset, length);
769    }
770
771    private void injectFault() throws IOException {
772      if (this.fault.get() && getPos() >= faultPos) {
773        throw new IOException("Fault injected");
774      }
775    }
776  }
777
778  private static void flushStore(HStore store, long id) throws IOException {
779    StoreFlushContext storeFlushCtx = store.createFlushContext(id, FlushLifeCycleTracker.DUMMY);
780    storeFlushCtx.prepare();
781    storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
782    storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
783  }
784
785  /**
786   * Generate a list of KeyValues for testing based on given parameters
787   * @param timestamps
788   * @param numRows
789   * @param qualifier
790   * @param family
791   * @return
792   */
793  List<Cell> getKeyValueSet(long[] timestamps, int numRows,
794      byte[] qualifier, byte[] family) {
795    List<Cell> kvList = new ArrayList<>();
796    for (int i=1;i<=numRows;i++) {
797      byte[] b = Bytes.toBytes(i);
798      for (long timestamp: timestamps) {
799        kvList.add(new KeyValue(b, family, qualifier, timestamp, b));
800      }
801    }
802    return kvList;
803  }
804
805  /**
806   * Test to ensure correctness when using Stores with multiple timestamps
807   * @throws IOException
808   */
809  @Test
810  public void testMultipleTimestamps() throws IOException {
811    int numRows = 1;
812    long[] timestamps1 = new long[] {1,5,10,20};
813    long[] timestamps2 = new long[] {30,80};
814
815    init(this.name.getMethodName());
816
817    List<Cell> kvList1 = getKeyValueSet(timestamps1,numRows, qf1, family);
818    for (Cell kv : kvList1) {
819      this.store.add(kv, null);
820    }
821
822    this.store.snapshot();
823    flushStore(store, id++);
824
825    List<Cell> kvList2 = getKeyValueSet(timestamps2,numRows, qf1, family);
826    for(Cell kv : kvList2) {
827      this.store.add(kv, null);
828    }
829
830    List<Cell> result;
831    Get get = new Get(Bytes.toBytes(1));
832    get.addColumn(family,qf1);
833
834    get.setTimeRange(0,15);
835    result = HBaseTestingUtility.getFromStoreFile(store, get);
836    assertTrue(result.size()>0);
837
838    get.setTimeRange(40,90);
839    result = HBaseTestingUtility.getFromStoreFile(store, get);
840    assertTrue(result.size()>0);
841
842    get.setTimeRange(10,45);
843    result = HBaseTestingUtility.getFromStoreFile(store, get);
844    assertTrue(result.size()>0);
845
846    get.setTimeRange(80,145);
847    result = HBaseTestingUtility.getFromStoreFile(store, get);
848    assertTrue(result.size()>0);
849
850    get.setTimeRange(1,2);
851    result = HBaseTestingUtility.getFromStoreFile(store, get);
852    assertTrue(result.size()>0);
853
854    get.setTimeRange(90,200);
855    result = HBaseTestingUtility.getFromStoreFile(store, get);
856    assertTrue(result.size()==0);
857  }
858
859  /**
860   * Test for HBASE-3492 - Test split on empty colfam (no store files).
861   *
862   * @throws IOException When the IO operations fail.
863   */
864  @Test
865  public void testSplitWithEmptyColFam() throws IOException {
866    init(this.name.getMethodName());
867    assertFalse(store.getSplitPoint().isPresent());
868    store.getHRegion().forceSplit(null);
869    assertFalse(store.getSplitPoint().isPresent());
870    store.getHRegion().clearSplit();
871  }
872
873  @Test
874  public void testStoreUsesConfigurationFromHcdAndHtd() throws Exception {
875    final String CONFIG_KEY = "hbase.regionserver.thread.compaction.throttle";
876    long anyValue = 10;
877
878    // We'll check that it uses correct config and propagates it appropriately by going thru
879    // the simplest "real" path I can find - "throttleCompaction", which just checks whether
880    // a number we pass in is higher than some config value, inside compactionPolicy.
881    Configuration conf = HBaseConfiguration.create();
882    conf.setLong(CONFIG_KEY, anyValue);
883    init(name.getMethodName() + "-xml", conf);
884    assertTrue(store.throttleCompaction(anyValue + 1));
885    assertFalse(store.throttleCompaction(anyValue));
886
887    // HTD overrides XML.
888    --anyValue;
889    init(name.getMethodName() + "-htd", conf, TableDescriptorBuilder
890        .newBuilder(TableName.valueOf(table)).setValue(CONFIG_KEY, Long.toString(anyValue)),
891      ColumnFamilyDescriptorBuilder.of(family));
892    assertTrue(store.throttleCompaction(anyValue + 1));
893    assertFalse(store.throttleCompaction(anyValue));
894
895    // HCD overrides them both.
896    --anyValue;
897    init(name.getMethodName() + "-hcd", conf,
898      TableDescriptorBuilder.newBuilder(TableName.valueOf(table)).setValue(CONFIG_KEY,
899        Long.toString(anyValue)),
900      ColumnFamilyDescriptorBuilder.newBuilder(family).setValue(CONFIG_KEY, Long.toString(anyValue))
901          .build());
902    assertTrue(store.throttleCompaction(anyValue + 1));
903    assertFalse(store.throttleCompaction(anyValue));
904  }
905
906  public static class DummyStoreEngine extends DefaultStoreEngine {
907    public static DefaultCompactor lastCreatedCompactor = null;
908
909    @Override
910    protected void createComponents(Configuration conf, HStore store, CellComparator comparator)
911        throws IOException {
912      super.createComponents(conf, store, comparator);
913      lastCreatedCompactor = this.compactor;
914    }
915  }
916
917  @Test
918  public void testStoreUsesSearchEngineOverride() throws Exception {
919    Configuration conf = HBaseConfiguration.create();
920    conf.set(StoreEngine.STORE_ENGINE_CLASS_KEY, DummyStoreEngine.class.getName());
921    init(this.name.getMethodName(), conf);
922    assertEquals(DummyStoreEngine.lastCreatedCompactor,
923      this.store.storeEngine.getCompactor());
924  }
925
926  private void addStoreFile() throws IOException {
927    HStoreFile f = this.store.getStorefiles().iterator().next();
928    Path storedir = f.getPath().getParent();
929    long seqid = this.store.getMaxSequenceId().orElse(0L);
930    Configuration c = TEST_UTIL.getConfiguration();
931    FileSystem fs = FileSystem.get(c);
932    HFileContext fileContext = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL).build();
933    StoreFileWriter w = new StoreFileWriter.Builder(c, new CacheConfig(c),
934        fs)
935            .withOutputDir(storedir)
936            .withFileContext(fileContext)
937            .build();
938    w.appendMetadata(seqid + 1, false);
939    w.close();
940    LOG.info("Added store file:" + w.getPath());
941  }
942
943  private void archiveStoreFile(int index) throws IOException {
944    Collection<HStoreFile> files = this.store.getStorefiles();
945    HStoreFile sf = null;
946    Iterator<HStoreFile> it = files.iterator();
947    for (int i = 0; i <= index; i++) {
948      sf = it.next();
949    }
950    store.getRegionFileSystem().removeStoreFiles(store.getColumnFamilyName(), Lists.newArrayList(sf));
951  }
952
953  private void closeCompactedFile(int index) throws IOException {
954    Collection<HStoreFile> files =
955        this.store.getStoreEngine().getStoreFileManager().getCompactedfiles();
956    HStoreFile sf = null;
957    Iterator<HStoreFile> it = files.iterator();
958    for (int i = 0; i <= index; i++) {
959      sf = it.next();
960    }
961    sf.closeStoreFile(true);
962    store.getStoreEngine().getStoreFileManager().removeCompactedFiles(Lists.newArrayList(sf));
963  }
964
965  @Test
966  public void testRefreshStoreFiles() throws Exception {
967    init(name.getMethodName());
968
969    assertEquals(0, this.store.getStorefilesCount());
970
971    // Test refreshing store files when no store files are there
972    store.refreshStoreFiles();
973    assertEquals(0, this.store.getStorefilesCount());
974
975    // add some data, flush
976    this.store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
977    flush(1);
978    assertEquals(1, this.store.getStorefilesCount());
979
980    // add one more file
981    addStoreFile();
982
983    assertEquals(1, this.store.getStorefilesCount());
984    store.refreshStoreFiles();
985    assertEquals(2, this.store.getStorefilesCount());
986
987    // add three more files
988    addStoreFile();
989    addStoreFile();
990    addStoreFile();
991
992    assertEquals(2, this.store.getStorefilesCount());
993    store.refreshStoreFiles();
994    assertEquals(5, this.store.getStorefilesCount());
995
996    closeCompactedFile(0);
997    archiveStoreFile(0);
998
999    assertEquals(5, this.store.getStorefilesCount());
1000    store.refreshStoreFiles();
1001    assertEquals(4, this.store.getStorefilesCount());
1002
1003    archiveStoreFile(0);
1004    archiveStoreFile(1);
1005    archiveStoreFile(2);
1006
1007    assertEquals(4, this.store.getStorefilesCount());
1008    store.refreshStoreFiles();
1009    assertEquals(1, this.store.getStorefilesCount());
1010
1011    archiveStoreFile(0);
1012    store.refreshStoreFiles();
1013    assertEquals(0, this.store.getStorefilesCount());
1014  }
1015
1016  @Test
1017  public void testRefreshStoreFilesNotChanged() throws IOException {
1018    init(name.getMethodName());
1019
1020    assertEquals(0, this.store.getStorefilesCount());
1021
1022    // add some data, flush
1023    this.store.add(new KeyValue(row, family, qf1, 1, (byte[])null), null);
1024    flush(1);
1025    // add one more file
1026    addStoreFile();
1027
1028    HStore spiedStore = spy(store);
1029
1030    // call first time after files changed
1031    spiedStore.refreshStoreFiles();
1032    assertEquals(2, this.store.getStorefilesCount());
1033    verify(spiedStore, times(1)).replaceStoreFiles(any(), any());
1034
1035    // call second time
1036    spiedStore.refreshStoreFiles();
1037
1038    //ensure that replaceStoreFiles is not called if files are not refreshed
1039    verify(spiedStore, times(0)).replaceStoreFiles(null, null);
1040  }
1041
1042  private long countMemStoreScanner(StoreScanner scanner) {
1043    if (scanner.currentScanners == null) {
1044      return 0;
1045    }
1046    return scanner.currentScanners.stream()
1047            .filter(s -> !s.isFileScanner())
1048            .count();
1049  }
1050
1051  @Test
1052  public void testNumberOfMemStoreScannersAfterFlush() throws IOException {
1053    long seqId = 100;
1054    long timestamp = System.currentTimeMillis();
1055    Cell cell0 = CellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(row).setFamily(family)
1056        .setQualifier(qf1).setTimestamp(timestamp).setType(Cell.Type.Put)
1057        .setValue(qf1).build();
1058    PrivateCellUtil.setSequenceId(cell0, seqId);
1059    testNumberOfMemStoreScannersAfterFlush(Arrays.asList(cell0), Collections.emptyList());
1060
1061    Cell cell1 = CellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(row).setFamily(family)
1062        .setQualifier(qf2).setTimestamp(timestamp).setType(Cell.Type.Put)
1063        .setValue(qf1).build();
1064    PrivateCellUtil.setSequenceId(cell1, seqId);
1065    testNumberOfMemStoreScannersAfterFlush(Arrays.asList(cell0), Arrays.asList(cell1));
1066
1067    seqId = 101;
1068    timestamp = System.currentTimeMillis();
1069    Cell cell2 = CellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(row2).setFamily(family)
1070        .setQualifier(qf2).setTimestamp(timestamp).setType(Cell.Type.Put)
1071        .setValue(qf1).build();
1072    PrivateCellUtil.setSequenceId(cell2, seqId);
1073    testNumberOfMemStoreScannersAfterFlush(Arrays.asList(cell0), Arrays.asList(cell1, cell2));
1074  }
1075
1076  private void testNumberOfMemStoreScannersAfterFlush(List<Cell> inputCellsBeforeSnapshot,
1077      List<Cell> inputCellsAfterSnapshot) throws IOException {
1078    init(this.name.getMethodName() + "-" + inputCellsBeforeSnapshot.size());
1079    TreeSet<byte[]> quals = new TreeSet<>(Bytes.BYTES_COMPARATOR);
1080    long seqId = Long.MIN_VALUE;
1081    for (Cell c : inputCellsBeforeSnapshot) {
1082      quals.add(CellUtil.cloneQualifier(c));
1083      seqId = Math.max(seqId, c.getSequenceId());
1084    }
1085    for (Cell c : inputCellsAfterSnapshot) {
1086      quals.add(CellUtil.cloneQualifier(c));
1087      seqId = Math.max(seqId, c.getSequenceId());
1088    }
1089    inputCellsBeforeSnapshot.forEach(c -> store.add(c, null));
1090    StoreFlushContext storeFlushCtx = store.createFlushContext(id++, FlushLifeCycleTracker.DUMMY);
1091    storeFlushCtx.prepare();
1092    inputCellsAfterSnapshot.forEach(c -> store.add(c, null));
1093    int numberOfMemScannersBeforeFlush = inputCellsAfterSnapshot.isEmpty() ? 1 : 2;
1094    try (StoreScanner s = (StoreScanner) store.getScanner(new Scan(), quals, seqId)) {
1095      // snapshot + active (if inputCellsAfterSnapshot isn't empty)
1096      assertEquals(numberOfMemScannersBeforeFlush, countMemStoreScanner(s));
1097      storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
1098      storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
1099      // snapshot has no data after flush
1100      int numberOfMemScannersAfterFlush = inputCellsAfterSnapshot.isEmpty() ? 0 : 1;
1101      boolean more;
1102      int cellCount = 0;
1103      do {
1104        List<Cell> cells = new ArrayList<>();
1105        more = s.next(cells);
1106        cellCount += cells.size();
1107        assertEquals(more ? numberOfMemScannersAfterFlush : 0, countMemStoreScanner(s));
1108      } while (more);
1109      assertEquals("The number of cells added before snapshot is " + inputCellsBeforeSnapshot.size()
1110          + ", The number of cells added after snapshot is " + inputCellsAfterSnapshot.size(),
1111          inputCellsBeforeSnapshot.size() + inputCellsAfterSnapshot.size(), cellCount);
1112      // the current scanners is cleared
1113      assertEquals(0, countMemStoreScanner(s));
1114    }
1115  }
1116
1117  private Cell createCell(byte[] qualifier, long ts, long sequenceId, byte[] value)
1118      throws IOException {
1119    return createCell(row, qualifier, ts, sequenceId, value);
1120  }
1121
1122  private Cell createCell(byte[] row, byte[] qualifier, long ts, long sequenceId, byte[] value)
1123      throws IOException {
1124    Cell c = CellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(row).setFamily(family)
1125        .setQualifier(qualifier).setTimestamp(ts).setType(Cell.Type.Put)
1126        .setValue(value).build();
1127    PrivateCellUtil.setSequenceId(c, sequenceId);
1128    return c;
1129  }
1130
1131  @Test
1132  public void testFlushBeforeCompletingScanWoFilter() throws IOException, InterruptedException {
1133    final AtomicBoolean timeToGoNextRow = new AtomicBoolean(false);
1134    final int expectedSize = 3;
1135    testFlushBeforeCompletingScan(new MyListHook() {
1136      @Override
1137      public void hook(int currentSize) {
1138        if (currentSize == expectedSize - 1) {
1139          try {
1140            flushStore(store, id++);
1141            timeToGoNextRow.set(true);
1142          } catch (IOException e) {
1143            throw new RuntimeException(e);
1144          }
1145        }
1146      }
1147    }, new FilterBase() {
1148      @Override
1149      public Filter.ReturnCode filterCell(final Cell c) throws IOException {
1150        return ReturnCode.INCLUDE;
1151      }
1152    }, expectedSize);
1153  }
1154
1155  @Test
1156  public void testFlushBeforeCompletingScanWithFilter() throws IOException, InterruptedException {
1157    final AtomicBoolean timeToGoNextRow = new AtomicBoolean(false);
1158    final int expectedSize = 2;
1159    testFlushBeforeCompletingScan(new MyListHook() {
1160      @Override
1161      public void hook(int currentSize) {
1162        if (currentSize == expectedSize - 1) {
1163          try {
1164            flushStore(store, id++);
1165            timeToGoNextRow.set(true);
1166          } catch (IOException e) {
1167            throw new RuntimeException(e);
1168          }
1169        }
1170      }
1171    }, new FilterBase() {
1172      @Override
1173      public Filter.ReturnCode filterCell(final Cell c) throws IOException {
1174        if (timeToGoNextRow.get()) {
1175          timeToGoNextRow.set(false);
1176          return ReturnCode.NEXT_ROW;
1177        } else {
1178          return ReturnCode.INCLUDE;
1179        }
1180      }
1181    }, expectedSize);
1182  }
1183
1184  @Test
1185  public void testFlushBeforeCompletingScanWithFilterHint() throws IOException,
1186      InterruptedException {
1187    final AtomicBoolean timeToGetHint = new AtomicBoolean(false);
1188    final int expectedSize = 2;
1189    testFlushBeforeCompletingScan(new MyListHook() {
1190      @Override
1191      public void hook(int currentSize) {
1192        if (currentSize == expectedSize - 1) {
1193          try {
1194            flushStore(store, id++);
1195            timeToGetHint.set(true);
1196          } catch (IOException e) {
1197            throw new RuntimeException(e);
1198          }
1199        }
1200      }
1201    }, new FilterBase() {
1202      @Override
1203      public Filter.ReturnCode filterCell(final Cell c) throws IOException {
1204        if (timeToGetHint.get()) {
1205          timeToGetHint.set(false);
1206          return Filter.ReturnCode.SEEK_NEXT_USING_HINT;
1207        } else {
1208          return Filter.ReturnCode.INCLUDE;
1209        }
1210      }
1211      @Override
1212      public Cell getNextCellHint(Cell currentCell) throws IOException {
1213        return currentCell;
1214      }
1215    }, expectedSize);
1216  }
1217
1218  private void testFlushBeforeCompletingScan(MyListHook hook, Filter filter, int expectedSize)
1219          throws IOException, InterruptedException {
1220    Configuration conf = HBaseConfiguration.create();
1221    byte[] r0 = Bytes.toBytes("row0");
1222    byte[] r1 = Bytes.toBytes("row1");
1223    byte[] r2 = Bytes.toBytes("row2");
1224    byte[] value0 = Bytes.toBytes("value0");
1225    byte[] value1 = Bytes.toBytes("value1");
1226    byte[] value2 = Bytes.toBytes("value2");
1227    MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing();
1228    long ts = EnvironmentEdgeManager.currentTime();
1229    long seqId = 100;
1230    init(name.getMethodName(), conf, TableDescriptorBuilder.newBuilder(TableName.valueOf(table)),
1231      ColumnFamilyDescriptorBuilder.newBuilder(family).setMaxVersions(1).build(),
1232      new MyStoreHook() {
1233        @Override
1234        public long getSmallestReadPoint(HStore store) {
1235          return seqId + 3;
1236        }
1237      });
1238    // The cells having the value0 won't be flushed to disk because the value of max version is 1
1239    store.add(createCell(r0, qf1, ts, seqId, value0), memStoreSizing);
1240    store.add(createCell(r0, qf2, ts, seqId, value0), memStoreSizing);
1241    store.add(createCell(r0, qf3, ts, seqId, value0), memStoreSizing);
1242    store.add(createCell(r1, qf1, ts + 1, seqId + 1, value1), memStoreSizing);
1243    store.add(createCell(r1, qf2, ts + 1, seqId + 1, value1), memStoreSizing);
1244    store.add(createCell(r1, qf3, ts + 1, seqId + 1, value1), memStoreSizing);
1245    store.add(createCell(r2, qf1, ts + 2, seqId + 2, value2), memStoreSizing);
1246    store.add(createCell(r2, qf2, ts + 2, seqId + 2, value2), memStoreSizing);
1247    store.add(createCell(r2, qf3, ts + 2, seqId + 2, value2), memStoreSizing);
1248    store.add(createCell(r1, qf1, ts + 3, seqId + 3, value1), memStoreSizing);
1249    store.add(createCell(r1, qf2, ts + 3, seqId + 3, value1), memStoreSizing);
1250    store.add(createCell(r1, qf3, ts + 3, seqId + 3, value1), memStoreSizing);
1251    List<Cell> myList = new MyList<>(hook);
1252    Scan scan = new Scan()
1253            .withStartRow(r1)
1254            .setFilter(filter);
1255    try (InternalScanner scanner = (InternalScanner) store.getScanner(
1256          scan, null, seqId + 3)){
1257      // r1
1258      scanner.next(myList);
1259      assertEquals(expectedSize, myList.size());
1260      for (Cell c : myList) {
1261        byte[] actualValue = CellUtil.cloneValue(c);
1262        assertTrue("expected:" + Bytes.toStringBinary(value1)
1263          + ", actual:" + Bytes.toStringBinary(actualValue)
1264          , Bytes.equals(actualValue, value1));
1265      }
1266      List<Cell> normalList = new ArrayList<>(3);
1267      // r2
1268      scanner.next(normalList);
1269      assertEquals(3, normalList.size());
1270      for (Cell c : normalList) {
1271        byte[] actualValue = CellUtil.cloneValue(c);
1272        assertTrue("expected:" + Bytes.toStringBinary(value2)
1273          + ", actual:" + Bytes.toStringBinary(actualValue)
1274          , Bytes.equals(actualValue, value2));
1275      }
1276    }
1277  }
1278
1279  @Test
1280  public void testCreateScannerAndSnapshotConcurrently() throws IOException, InterruptedException {
1281    Configuration conf = HBaseConfiguration.create();
1282    conf.set(HStore.MEMSTORE_CLASS_NAME, MyCompactingMemStore.class.getName());
1283    init(name.getMethodName(), conf, ColumnFamilyDescriptorBuilder.newBuilder(family)
1284        .setInMemoryCompaction(MemoryCompactionPolicy.BASIC).build());
1285    byte[] value = Bytes.toBytes("value");
1286    MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing();
1287    long ts = EnvironmentEdgeManager.currentTime();
1288    long seqId = 100;
1289    // older data whihc shouldn't be "seen" by client
1290    store.add(createCell(qf1, ts, seqId, value), memStoreSizing);
1291    store.add(createCell(qf2, ts, seqId, value), memStoreSizing);
1292    store.add(createCell(qf3, ts, seqId, value), memStoreSizing);
1293    TreeSet<byte[]> quals = new TreeSet<>(Bytes.BYTES_COMPARATOR);
1294    quals.add(qf1);
1295    quals.add(qf2);
1296    quals.add(qf3);
1297    StoreFlushContext storeFlushCtx = store.createFlushContext(id++, FlushLifeCycleTracker.DUMMY);
1298    MyCompactingMemStore.START_TEST.set(true);
1299    Runnable flush = () -> {
1300      // this is blocked until we create first scanner from pipeline and snapshot -- phase (1/5)
1301      // recreate the active memstore -- phase (4/5)
1302      storeFlushCtx.prepare();
1303    };
1304    ExecutorService service = Executors.newSingleThreadExecutor();
1305    service.submit(flush);
1306    // we get scanner from pipeline and snapshot but they are empty. -- phase (2/5)
1307    // this is blocked until we recreate the active memstore -- phase (3/5)
1308    // we get scanner from active memstore but it is empty -- phase (5/5)
1309    InternalScanner scanner = (InternalScanner) store.getScanner(
1310          new Scan(new Get(row)), quals, seqId + 1);
1311    service.shutdown();
1312    service.awaitTermination(20, TimeUnit.SECONDS);
1313    try {
1314      try {
1315        List<Cell> results = new ArrayList<>();
1316        scanner.next(results);
1317        assertEquals(3, results.size());
1318        for (Cell c : results) {
1319          byte[] actualValue = CellUtil.cloneValue(c);
1320          assertTrue("expected:" + Bytes.toStringBinary(value)
1321            + ", actual:" + Bytes.toStringBinary(actualValue)
1322            , Bytes.equals(actualValue, value));
1323        }
1324      } finally {
1325        scanner.close();
1326      }
1327    } finally {
1328      MyCompactingMemStore.START_TEST.set(false);
1329      storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
1330      storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
1331    }
1332  }
1333
1334  @Test
1335  public void testScanWithDoubleFlush() throws IOException {
1336    Configuration conf = HBaseConfiguration.create();
1337    // Initialize region
1338    MyStore myStore = initMyStore(name.getMethodName(), conf, new MyStoreHook(){
1339      @Override
1340      public void getScanners(MyStore store) throws IOException {
1341        final long tmpId = id++;
1342        ExecutorService s = Executors.newSingleThreadExecutor();
1343        s.submit(() -> {
1344          try {
1345            // flush the store before storescanner updates the scanners from store.
1346            // The current data will be flushed into files, and the memstore will
1347            // be clear.
1348            // -- phase (4/4)
1349            flushStore(store, tmpId);
1350          }catch (IOException ex) {
1351            throw new RuntimeException(ex);
1352          }
1353        });
1354        s.shutdown();
1355        try {
1356          // wait for the flush, the thread will be blocked in HStore#notifyChangedReadersObservers.
1357          s.awaitTermination(3, TimeUnit.SECONDS);
1358        } catch (InterruptedException ex) {
1359        }
1360      }
1361    });
1362    byte[] oldValue = Bytes.toBytes("oldValue");
1363    byte[] currentValue = Bytes.toBytes("currentValue");
1364    MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing();
1365    long ts = EnvironmentEdgeManager.currentTime();
1366    long seqId = 100;
1367    // older data whihc shouldn't be "seen" by client
1368    myStore.add(createCell(qf1, ts, seqId, oldValue), memStoreSizing);
1369    myStore.add(createCell(qf2, ts, seqId, oldValue), memStoreSizing);
1370    myStore.add(createCell(qf3, ts, seqId, oldValue), memStoreSizing);
1371    long snapshotId = id++;
1372    // push older data into snapshot -- phase (1/4)
1373    StoreFlushContext storeFlushCtx = store.createFlushContext(snapshotId, FlushLifeCycleTracker
1374        .DUMMY);
1375    storeFlushCtx.prepare();
1376
1377    // insert current data into active -- phase (2/4)
1378    myStore.add(createCell(qf1, ts + 1, seqId + 1, currentValue), memStoreSizing);
1379    myStore.add(createCell(qf2, ts + 1, seqId + 1, currentValue), memStoreSizing);
1380    myStore.add(createCell(qf3, ts + 1, seqId + 1, currentValue), memStoreSizing);
1381    TreeSet<byte[]> quals = new TreeSet<>(Bytes.BYTES_COMPARATOR);
1382    quals.add(qf1);
1383    quals.add(qf2);
1384    quals.add(qf3);
1385    try (InternalScanner scanner = (InternalScanner) myStore.getScanner(
1386        new Scan(new Get(row)), quals, seqId + 1)) {
1387      // complete the flush -- phase (3/4)
1388      storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
1389      storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
1390
1391      List<Cell> results = new ArrayList<>();
1392      scanner.next(results);
1393      assertEquals(3, results.size());
1394      for (Cell c : results) {
1395        byte[] actualValue = CellUtil.cloneValue(c);
1396        assertTrue("expected:" + Bytes.toStringBinary(currentValue)
1397          + ", actual:" + Bytes.toStringBinary(actualValue)
1398          , Bytes.equals(actualValue, currentValue));
1399      }
1400    }
1401  }
1402
1403  @Test
1404  public void testReclaimChunkWhenScaning() throws IOException {
1405    init("testReclaimChunkWhenScaning");
1406    long ts = EnvironmentEdgeManager.currentTime();
1407    long seqId = 100;
1408    byte[] value = Bytes.toBytes("value");
1409    // older data whihc shouldn't be "seen" by client
1410    store.add(createCell(qf1, ts, seqId, value), null);
1411    store.add(createCell(qf2, ts, seqId, value), null);
1412    store.add(createCell(qf3, ts, seqId, value), null);
1413    TreeSet<byte[]> quals = new TreeSet<>(Bytes.BYTES_COMPARATOR);
1414    quals.add(qf1);
1415    quals.add(qf2);
1416    quals.add(qf3);
1417    try (InternalScanner scanner = (InternalScanner) store.getScanner(
1418        new Scan(new Get(row)), quals, seqId)) {
1419      List<Cell> results = new MyList<>(size -> {
1420        switch (size) {
1421          // 1) we get the first cell (qf1)
1422          // 2) flush the data to have StoreScanner update inner scanners
1423          // 3) the chunk will be reclaimed after updaing
1424          case 1:
1425            try {
1426              flushStore(store, id++);
1427            } catch (IOException e) {
1428              throw new RuntimeException(e);
1429            }
1430            break;
1431          // 1) we get the second cell (qf2)
1432          // 2) add some cell to fill some byte into the chunk (we have only one chunk)
1433          case 2:
1434            try {
1435              byte[] newValue = Bytes.toBytes("newValue");
1436              // older data whihc shouldn't be "seen" by client
1437              store.add(createCell(qf1, ts + 1, seqId + 1, newValue), null);
1438              store.add(createCell(qf2, ts + 1, seqId + 1, newValue), null);
1439              store.add(createCell(qf3, ts + 1, seqId + 1, newValue), null);
1440            } catch (IOException e) {
1441              throw new RuntimeException(e);
1442            }
1443            break;
1444          default:
1445            break;
1446        }
1447      });
1448      scanner.next(results);
1449      assertEquals(3, results.size());
1450      for (Cell c : results) {
1451        byte[] actualValue = CellUtil.cloneValue(c);
1452        assertTrue("expected:" + Bytes.toStringBinary(value)
1453          + ", actual:" + Bytes.toStringBinary(actualValue)
1454          , Bytes.equals(actualValue, value));
1455      }
1456    }
1457  }
1458
1459  /**
1460   * If there are two running InMemoryFlushRunnable, the later InMemoryFlushRunnable
1461   * may change the versionedList. And the first InMemoryFlushRunnable will use the chagned
1462   * versionedList to remove the corresponding segments.
1463   * In short, there will be some segements which isn't in merge are removed.
1464   * @throws IOException
1465   * @throws InterruptedException
1466   */
1467  @Test
1468  public void testRunDoubleMemStoreCompactors() throws IOException, InterruptedException {
1469    int flushSize = 500;
1470    Configuration conf = HBaseConfiguration.create();
1471    conf.set(HStore.MEMSTORE_CLASS_NAME, MyCompactingMemStoreWithCustomCompactor.class.getName());
1472    conf.setDouble(CompactingMemStore.IN_MEMORY_FLUSH_THRESHOLD_FACTOR_KEY, 0.25);
1473    MyCompactingMemStoreWithCustomCompactor.RUNNER_COUNT.set(0);
1474    conf.set(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, String.valueOf(flushSize));
1475    // Set the lower threshold to invoke the "MERGE" policy
1476    conf.set(MemStoreCompactionStrategy.COMPACTING_MEMSTORE_THRESHOLD_KEY, String.valueOf(0));
1477    init(name.getMethodName(), conf, ColumnFamilyDescriptorBuilder.newBuilder(family)
1478        .setInMemoryCompaction(MemoryCompactionPolicy.BASIC).build());
1479    byte[] value = Bytes.toBytes("thisisavarylargevalue");
1480    MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing();
1481    long ts = EnvironmentEdgeManager.currentTime();
1482    long seqId = 100;
1483    // older data whihc shouldn't be "seen" by client
1484    store.add(createCell(qf1, ts, seqId, value), memStoreSizing);
1485    store.add(createCell(qf2, ts, seqId, value), memStoreSizing);
1486    store.add(createCell(qf3, ts, seqId, value), memStoreSizing);
1487    assertEquals(1, MyCompactingMemStoreWithCustomCompactor.RUNNER_COUNT.get());
1488    StoreFlushContext storeFlushCtx = store.createFlushContext(id++, FlushLifeCycleTracker.DUMMY);
1489    storeFlushCtx.prepare();
1490    // This shouldn't invoke another in-memory flush because the first compactor thread
1491    // hasn't accomplished the in-memory compaction.
1492    store.add(createCell(qf1, ts + 1, seqId + 1, value), memStoreSizing);
1493    store.add(createCell(qf1, ts + 1, seqId + 1, value), memStoreSizing);
1494    store.add(createCell(qf1, ts + 1, seqId + 1, value), memStoreSizing);
1495    assertEquals(1, MyCompactingMemStoreWithCustomCompactor.RUNNER_COUNT.get());
1496    //okay. Let the compaction be completed
1497    MyMemStoreCompactor.START_COMPACTOR_LATCH.countDown();
1498    CompactingMemStore mem = (CompactingMemStore) ((HStore)store).memstore;
1499    while (mem.isMemStoreFlushingInMemory()) {
1500      TimeUnit.SECONDS.sleep(1);
1501    }
1502    // This should invoke another in-memory flush.
1503    store.add(createCell(qf1, ts + 2, seqId + 2, value), memStoreSizing);
1504    store.add(createCell(qf1, ts + 2, seqId + 2, value), memStoreSizing);
1505    store.add(createCell(qf1, ts + 2, seqId + 2, value), memStoreSizing);
1506    assertEquals(2, MyCompactingMemStoreWithCustomCompactor.RUNNER_COUNT.get());
1507    conf.set(HConstants.HREGION_MEMSTORE_FLUSH_SIZE,
1508      String.valueOf(TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE));
1509    storeFlushCtx.flushCache(Mockito.mock(MonitoredTask.class));
1510    storeFlushCtx.commit(Mockito.mock(MonitoredTask.class));
1511  }
1512
1513  @Test
1514  public void testAge() throws IOException {
1515    long currentTime = System.currentTimeMillis();
1516    ManualEnvironmentEdge edge = new ManualEnvironmentEdge();
1517    edge.setValue(currentTime);
1518    EnvironmentEdgeManager.injectEdge(edge);
1519    Configuration conf = TEST_UTIL.getConfiguration();
1520    ColumnFamilyDescriptor hcd = ColumnFamilyDescriptorBuilder.of(family);
1521    initHRegion(name.getMethodName(), conf,
1522      TableDescriptorBuilder.newBuilder(TableName.valueOf(table)), hcd, null, false);
1523    HStore store = new HStore(region, hcd, conf) {
1524
1525      @Override
1526      protected StoreEngine<?, ?, ?, ?> createStoreEngine(HStore store, Configuration conf,
1527          CellComparator kvComparator) throws IOException {
1528        List<HStoreFile> storefiles =
1529            Arrays.asList(mockStoreFile(currentTime - 10), mockStoreFile(currentTime - 100),
1530              mockStoreFile(currentTime - 1000), mockStoreFile(currentTime - 10000));
1531        StoreFileManager sfm = mock(StoreFileManager.class);
1532        when(sfm.getStorefiles()).thenReturn(storefiles);
1533        StoreEngine<?, ?, ?, ?> storeEngine = mock(StoreEngine.class);
1534        when(storeEngine.getStoreFileManager()).thenReturn(sfm);
1535        return storeEngine;
1536      }
1537    };
1538    assertEquals(10L, store.getMinStoreFileAge().getAsLong());
1539    assertEquals(10000L, store.getMaxStoreFileAge().getAsLong());
1540    assertEquals((10 + 100 + 1000 + 10000) / 4.0, store.getAvgStoreFileAge().getAsDouble(), 1E-4);
1541  }
1542
1543  private HStoreFile mockStoreFile(long createdTime) {
1544    StoreFileInfo info = mock(StoreFileInfo.class);
1545    when(info.getCreatedTimestamp()).thenReturn(createdTime);
1546    HStoreFile sf = mock(HStoreFile.class);
1547    when(sf.getReader()).thenReturn(mock(StoreFileReader.class));
1548    when(sf.isHFile()).thenReturn(true);
1549    when(sf.getFileInfo()).thenReturn(info);
1550    return sf;
1551  }
1552
1553  private MyStore initMyStore(String methodName, Configuration conf, MyStoreHook hook)
1554      throws IOException {
1555    return (MyStore) init(methodName, conf,
1556      TableDescriptorBuilder.newBuilder(TableName.valueOf(table)),
1557      ColumnFamilyDescriptorBuilder.newBuilder(family).setMaxVersions(5).build(), hook);
1558  }
1559
1560  private static class MyStore extends HStore {
1561    private final MyStoreHook hook;
1562
1563    MyStore(final HRegion region, final ColumnFamilyDescriptor family, final Configuration
1564        confParam, MyStoreHook hook, boolean switchToPread) throws IOException {
1565      super(region, family, confParam);
1566      this.hook = hook;
1567    }
1568
1569    @Override
1570    public List<KeyValueScanner> getScanners(List<HStoreFile> files, boolean cacheBlocks,
1571        boolean usePread, boolean isCompaction, ScanQueryMatcher matcher, byte[] startRow,
1572        boolean includeStartRow, byte[] stopRow, boolean includeStopRow, long readPt,
1573        boolean includeMemstoreScanner) throws IOException {
1574      hook.getScanners(this);
1575      return super.getScanners(files, cacheBlocks, usePread, isCompaction, matcher, startRow, true,
1576        stopRow, false, readPt, includeMemstoreScanner);
1577    }
1578
1579    @Override
1580    public long getSmallestReadPoint() {
1581      return hook.getSmallestReadPoint(this);
1582    }
1583  }
1584
1585  private abstract static class MyStoreHook {
1586
1587    void getScanners(MyStore store) throws IOException {
1588    }
1589
1590    long getSmallestReadPoint(HStore store) {
1591      return store.getHRegion().getSmallestReadPoint();
1592    }
1593  }
1594
1595  @Test
1596  public void testSwitchingPreadtoStreamParallelyWithCompactionDischarger() throws Exception {
1597    Configuration conf = HBaseConfiguration.create();
1598    conf.set("hbase.hstore.engine.class", DummyStoreEngine.class.getName());
1599    conf.setLong(StoreScanner.STORESCANNER_PREAD_MAX_BYTES, 0);
1600    // Set the lower threshold to invoke the "MERGE" policy
1601    MyStore store = initMyStore(name.getMethodName(), conf, new MyStoreHook() {});
1602    MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing();
1603    long ts = System.currentTimeMillis();
1604    long seqID = 1L;
1605    // Add some data to the region and do some flushes
1606    for (int i = 1; i < 10; i++) {
1607      store.add(createCell(Bytes.toBytes("row" + i), qf1, ts, seqID++, Bytes.toBytes("")),
1608        memStoreSizing);
1609    }
1610    // flush them
1611    flushStore(store, seqID);
1612    for (int i = 11; i < 20; i++) {
1613      store.add(createCell(Bytes.toBytes("row" + i), qf1, ts, seqID++, Bytes.toBytes("")),
1614        memStoreSizing);
1615    }
1616    // flush them
1617    flushStore(store, seqID);
1618    for (int i = 21; i < 30; i++) {
1619      store.add(createCell(Bytes.toBytes("row" + i), qf1, ts, seqID++, Bytes.toBytes("")),
1620        memStoreSizing);
1621    }
1622    // flush them
1623    flushStore(store, seqID);
1624
1625    assertEquals(3, store.getStorefilesCount());
1626    Scan scan = new Scan();
1627    scan.addFamily(family);
1628    Collection<HStoreFile> storefiles2 = store.getStorefiles();
1629    ArrayList<HStoreFile> actualStorefiles = Lists.newArrayList(storefiles2);
1630    StoreScanner storeScanner =
1631        (StoreScanner) store.getScanner(scan, scan.getFamilyMap().get(family), Long.MAX_VALUE);
1632    // get the current heap
1633    KeyValueHeap heap = storeScanner.heap;
1634    // create more store files
1635    for (int i = 31; i < 40; i++) {
1636      store.add(createCell(Bytes.toBytes("row" + i), qf1, ts, seqID++, Bytes.toBytes("")),
1637        memStoreSizing);
1638    }
1639    // flush them
1640    flushStore(store, seqID);
1641
1642    for (int i = 41; i < 50; i++) {
1643      store.add(createCell(Bytes.toBytes("row" + i), qf1, ts, seqID++, Bytes.toBytes("")),
1644        memStoreSizing);
1645    }
1646    // flush them
1647    flushStore(store, seqID);
1648    storefiles2 = store.getStorefiles();
1649    ArrayList<HStoreFile> actualStorefiles1 = Lists.newArrayList(storefiles2);
1650    actualStorefiles1.removeAll(actualStorefiles);
1651    // Do compaction
1652    MyThread thread = new MyThread(storeScanner);
1653    thread.start();
1654    store.replaceStoreFiles(actualStorefiles, actualStorefiles1);
1655    thread.join();
1656    KeyValueHeap heap2 = thread.getHeap();
1657    assertFalse(heap.equals(heap2));
1658  }
1659
1660  private static class MyThread extends Thread {
1661    private StoreScanner scanner;
1662    private KeyValueHeap heap;
1663
1664    public MyThread(StoreScanner scanner) {
1665      this.scanner = scanner;
1666    }
1667
1668    public KeyValueHeap getHeap() {
1669      return this.heap;
1670    }
1671
1672    @Override
1673    public void run() {
1674      scanner.trySwitchToStreamRead();
1675      heap = scanner.heap;
1676    }
1677  }
1678
1679  private static class MyMemStoreCompactor extends MemStoreCompactor {
1680    private static final AtomicInteger RUNNER_COUNT = new AtomicInteger(0);
1681    private static final CountDownLatch START_COMPACTOR_LATCH = new CountDownLatch(1);
1682    public MyMemStoreCompactor(CompactingMemStore compactingMemStore, MemoryCompactionPolicy
1683        compactionPolicy) throws IllegalArgumentIOException {
1684      super(compactingMemStore, compactionPolicy);
1685    }
1686
1687    @Override
1688    public boolean start() throws IOException {
1689      boolean isFirst = RUNNER_COUNT.getAndIncrement() == 0;
1690      boolean rval = super.start();
1691      if (isFirst) {
1692        try {
1693          START_COMPACTOR_LATCH.await();
1694        } catch (InterruptedException ex) {
1695          throw new RuntimeException(ex);
1696        }
1697      }
1698      return rval;
1699    }
1700  }
1701
1702  public static class MyCompactingMemStoreWithCustomCompactor extends CompactingMemStore {
1703    private static final AtomicInteger RUNNER_COUNT = new AtomicInteger(0);
1704    public MyCompactingMemStoreWithCustomCompactor(Configuration conf, CellComparatorImpl c,
1705        HStore store, RegionServicesForStores regionServices,
1706        MemoryCompactionPolicy compactionPolicy) throws IOException {
1707      super(conf, c, store, regionServices, compactionPolicy);
1708    }
1709
1710    @Override
1711    protected MemStoreCompactor createMemStoreCompactor(MemoryCompactionPolicy compactionPolicy)
1712        throws IllegalArgumentIOException {
1713      return new MyMemStoreCompactor(this, compactionPolicy);
1714    }
1715
1716    @Override
1717    protected boolean shouldFlushInMemory() {
1718      boolean rval = super.shouldFlushInMemory();
1719      if (rval) {
1720        RUNNER_COUNT.incrementAndGet();
1721        if (LOG.isDebugEnabled()) {
1722          LOG.debug("runner count: " + RUNNER_COUNT.get());
1723        }
1724      }
1725      return rval;
1726    }
1727  }
1728
1729  public static class MyCompactingMemStore extends CompactingMemStore {
1730    private static final AtomicBoolean START_TEST = new AtomicBoolean(false);
1731    private final CountDownLatch getScannerLatch = new CountDownLatch(1);
1732    private final CountDownLatch snapshotLatch = new CountDownLatch(1);
1733    public MyCompactingMemStore(Configuration conf, CellComparatorImpl c,
1734        HStore store, RegionServicesForStores regionServices,
1735        MemoryCompactionPolicy compactionPolicy) throws IOException {
1736      super(conf, c, store, regionServices, compactionPolicy);
1737    }
1738
1739    @Override
1740    protected List<KeyValueScanner> createList(int capacity) {
1741      if (START_TEST.get()) {
1742        try {
1743          getScannerLatch.countDown();
1744          snapshotLatch.await();
1745        } catch (InterruptedException e) {
1746          throw new RuntimeException(e);
1747        }
1748      }
1749      return new ArrayList<>(capacity);
1750    }
1751    @Override
1752    protected void pushActiveToPipeline(MutableSegment active) {
1753      if (START_TEST.get()) {
1754        try {
1755          getScannerLatch.await();
1756        } catch (InterruptedException e) {
1757          throw new RuntimeException(e);
1758        }
1759      }
1760
1761      super.pushActiveToPipeline(active);
1762      if (START_TEST.get()) {
1763        snapshotLatch.countDown();
1764      }
1765    }
1766  }
1767
1768  interface MyListHook {
1769    void hook(int currentSize);
1770  }
1771
1772  private static class MyList<T> implements List<T> {
1773    private final List<T> delegatee = new ArrayList<>();
1774    private final MyListHook hookAtAdd;
1775    MyList(final MyListHook hookAtAdd) {
1776      this.hookAtAdd = hookAtAdd;
1777    }
1778    @Override
1779    public int size() {return delegatee.size();}
1780
1781    @Override
1782    public boolean isEmpty() {return delegatee.isEmpty();}
1783
1784    @Override
1785    public boolean contains(Object o) {return delegatee.contains(o);}
1786
1787    @Override
1788    public Iterator<T> iterator() {return delegatee.iterator();}
1789
1790    @Override
1791    public Object[] toArray() {return delegatee.toArray();}
1792
1793    @Override
1794    public <R> R[] toArray(R[] a) {return delegatee.toArray(a);}
1795
1796    @Override
1797    public boolean add(T e) {
1798      hookAtAdd.hook(size());
1799      return delegatee.add(e);
1800    }
1801
1802    @Override
1803    public boolean remove(Object o) {return delegatee.remove(o);}
1804
1805    @Override
1806    public boolean containsAll(Collection<?> c) {return delegatee.containsAll(c);}
1807
1808    @Override
1809    public boolean addAll(Collection<? extends T> c) {return delegatee.addAll(c);}
1810
1811    @Override
1812    public boolean addAll(int index, Collection<? extends T> c) {return delegatee.addAll(index, c);}
1813
1814    @Override
1815    public boolean removeAll(Collection<?> c) {return delegatee.removeAll(c);}
1816
1817    @Override
1818    public boolean retainAll(Collection<?> c) {return delegatee.retainAll(c);}
1819
1820    @Override
1821    public void clear() {delegatee.clear();}
1822
1823    @Override
1824    public T get(int index) {return delegatee.get(index);}
1825
1826    @Override
1827    public T set(int index, T element) {return delegatee.set(index, element);}
1828
1829    @Override
1830    public void add(int index, T element) {delegatee.add(index, element);}
1831
1832    @Override
1833    public T remove(int index) {return delegatee.remove(index);}
1834
1835    @Override
1836    public int indexOf(Object o) {return delegatee.indexOf(o);}
1837
1838    @Override
1839    public int lastIndexOf(Object o) {return delegatee.lastIndexOf(o);}
1840
1841    @Override
1842    public ListIterator<T> listIterator() {return delegatee.listIterator();}
1843
1844    @Override
1845    public ListIterator<T> listIterator(int index) {return delegatee.listIterator(index);}
1846
1847    @Override
1848    public List<T> subList(int fromIndex, int toIndex) {return delegatee.subList(fromIndex, toIndex);}
1849  }
1850}