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.assertNotNull;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.List;
029import java.util.Objects;
030import java.util.concurrent.atomic.AtomicLong;
031import java.util.concurrent.atomic.AtomicReference;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.fs.Path;
034import org.apache.hadoop.hbase.Cell;
035import org.apache.hadoop.hbase.CellComparatorImpl;
036import org.apache.hadoop.hbase.CellUtil;
037import org.apache.hadoop.hbase.HBaseClassTestRule;
038import org.apache.hadoop.hbase.HBaseConfiguration;
039import org.apache.hadoop.hbase.HBaseTestingUtility;
040import org.apache.hadoop.hbase.HConstants;
041import org.apache.hadoop.hbase.KeepDeletedCells;
042import org.apache.hadoop.hbase.KeyValue;
043import org.apache.hadoop.hbase.KeyValueTestUtil;
044import org.apache.hadoop.hbase.KeyValueUtil;
045import org.apache.hadoop.hbase.TableName;
046import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
047import org.apache.hadoop.hbase.client.Put;
048import org.apache.hadoop.hbase.client.RegionInfo;
049import org.apache.hadoop.hbase.client.RegionInfoBuilder;
050import org.apache.hadoop.hbase.client.Scan;
051import org.apache.hadoop.hbase.client.TableDescriptor;
052import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
053import org.apache.hadoop.hbase.exceptions.UnexpectedStateException;
054import org.apache.hadoop.hbase.testclassification.MediumTests;
055import org.apache.hadoop.hbase.testclassification.RegionServerTests;
056import org.apache.hadoop.hbase.util.Bytes;
057import org.apache.hadoop.hbase.util.EnvironmentEdge;
058import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
059import org.apache.hadoop.hbase.util.FSTableDescriptors;
060import org.apache.hadoop.hbase.wal.WALFactory;
061import org.junit.AfterClass;
062import org.junit.Before;
063import org.junit.ClassRule;
064import org.junit.Rule;
065import org.junit.Test;
066import org.junit.experimental.categories.Category;
067import org.junit.rules.TestName;
068import org.slf4j.Logger;
069import org.slf4j.LoggerFactory;
070
071import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
072import org.apache.hbase.thirdparty.com.google.common.collect.Iterables;
073import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
074
075/** memstore test case */
076@Category({RegionServerTests.class, MediumTests.class})
077public class TestDefaultMemStore {
078
079  @ClassRule
080  public static final HBaseClassTestRule CLASS_RULE =
081      HBaseClassTestRule.forClass(TestDefaultMemStore.class);
082
083  private static final Logger LOG = LoggerFactory.getLogger(TestDefaultMemStore.class);
084  @Rule public TestName name = new TestName();
085  protected AbstractMemStore memstore;
086  protected static final int ROW_COUNT = 10;
087  protected static final int QUALIFIER_COUNT = ROW_COUNT;
088  protected static final byte[] FAMILY = Bytes.toBytes("column");
089  protected MultiVersionConcurrencyControl mvcc;
090  protected AtomicLong startSeqNum = new AtomicLong(0);
091  protected ChunkCreator chunkCreator;
092
093  private String getName() {
094    return this.name.getMethodName();
095  }
096
097  @Before
098  public void setUp() throws Exception {
099    internalSetUp();
100    // no pool
101    this.chunkCreator =
102        ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null);
103    this.memstore = new DefaultMemStore();
104  }
105
106  @AfterClass
107  public static void tearDownClass() throws Exception {
108    ChunkCreator.getInstance().clearChunkIds();
109  }
110
111  protected void internalSetUp() throws Exception {
112    this.mvcc = new MultiVersionConcurrencyControl();
113  }
114
115  @Test
116  public void testPutSameKey() {
117    byte[] bytes = Bytes.toBytes(getName());
118    KeyValue kv = new KeyValue(bytes, bytes, bytes, bytes);
119    this.memstore.add(kv, null);
120    byte[] other = Bytes.toBytes("somethingelse");
121    KeyValue samekey = new KeyValue(bytes, bytes, bytes, other);
122    this.memstore.add(samekey, null);
123    Cell found = this.memstore.getActive().first();
124    assertEquals(1, this.memstore.getActive().getCellsCount());
125    assertTrue(Bytes.toString(found.getValueArray()), CellUtil.matchingValue(samekey, found));
126  }
127
128  @Test
129  public void testPutSameCell() {
130    byte[] bytes = Bytes.toBytes(getName());
131    KeyValue kv = new KeyValue(bytes, bytes, bytes, bytes);
132    MemStoreSizing sizeChangeForFirstCell = new NonThreadSafeMemStoreSizing();
133    this.memstore.add(kv, sizeChangeForFirstCell);
134    MemStoreSizing sizeChangeForSecondCell = new NonThreadSafeMemStoreSizing();
135    this.memstore.add(kv, sizeChangeForSecondCell);
136    // make sure memstore size increase won't double-count MSLAB chunk size
137    assertEquals(Segment.getCellLength(kv), sizeChangeForFirstCell.getMemStoreSize().getDataSize());
138    Segment segment = this.memstore.getActive();
139    MemStoreLAB msLab = segment.getMemStoreLAB();
140    if (msLab != null) {
141      if (msLab.isOnHeap()) {
142        assertTrue("HeapSize should always bigger or equal than data size",
143            sizeChangeForFirstCell.getHeapSize() >= sizeChangeForFirstCell
144                .getDataSize());
145        assertTrue("HeapSize should always bigger or equal than data size",
146            sizeChangeForSecondCell.getHeapSize() >= sizeChangeForSecondCell
147                .getDataSize());
148      } else {
149        assertTrue("OffHeapSize should always bigger or equal than data size",
150            sizeChangeForFirstCell.getOffHeapSize() >= sizeChangeForFirstCell
151                .getDataSize());
152        assertTrue("OffHeapSize should always bigger or equal than data size",
153            sizeChangeForSecondCell.getOffHeapSize() >= sizeChangeForSecondCell
154                .getDataSize());
155      }
156      // make sure memstore size increased even when writing the same cell, if using MSLAB
157      assertEquals(Segment.getCellLength(kv),
158          sizeChangeForSecondCell.getMemStoreSize().getDataSize());
159      // make sure chunk size increased even when writing the same cell, if using MSLAB
160      if (msLab instanceof MemStoreLABImpl) {
161        // since we add the chunkID at the 0th offset of the chunk and the
162        // chunkid is an int we need to account for those 4 bytes
163        assertEquals(2 * Segment.getCellLength(kv) + Bytes.SIZEOF_INT,
164          ((MemStoreLABImpl) msLab).getCurrentChunk().getNextFreeOffset());
165      }
166    } else {
167      // make sure no memstore size change w/o MSLAB
168      assertEquals(0, sizeChangeForSecondCell.getMemStoreSize().getDataSize());
169      assertEquals(0, sizeChangeForSecondCell.getMemStoreSize().getHeapSize());
170    }
171  }
172
173  /**
174   * Test memstore snapshot happening while scanning.
175   * @throws IOException
176   */
177  @Test
178  public void testScanAcrossSnapshot() throws IOException {
179    int rowCount = addRows(this.memstore);
180    List<KeyValueScanner> memstorescanners = this.memstore.getScanners(0);
181    Scan scan = new Scan();
182    List<Cell> result = new ArrayList<>();
183    Configuration conf = HBaseConfiguration.create();
184    ScanInfo scanInfo = new ScanInfo(conf, null, 0, 1, HConstants.LATEST_TIMESTAMP,
185        KeepDeletedCells.FALSE, HConstants.DEFAULT_BLOCKSIZE, 0, this.memstore.getComparator(), false);
186    int count = 0;
187    try (StoreScanner s = new StoreScanner(scan, scanInfo, null, memstorescanners)) {
188      while (s.next(result)) {
189        LOG.info(Objects.toString(result));
190        count++;
191        // Row count is same as column count.
192        assertEquals(rowCount, result.size());
193        result.clear();
194      }
195    }
196    assertEquals(rowCount, count);
197    for (KeyValueScanner scanner : memstorescanners) {
198      scanner.close();
199    }
200
201    memstorescanners = this.memstore.getScanners(mvcc.getReadPoint());
202    // Now assert can count same number even if a snapshot mid-scan.
203    count = 0;
204    try (StoreScanner s = new StoreScanner(scan, scanInfo, null, memstorescanners)) {
205      while (s.next(result)) {
206        LOG.info(Objects.toString(result));
207        // Assert the stuff is coming out in right order.
208        assertTrue(CellUtil.matchingRows(result.get(0), Bytes.toBytes(count)));
209        count++;
210        // Row count is same as column count.
211        assertEquals(rowCount, result.size());
212        if (count == 2) {
213          this.memstore.snapshot();
214          LOG.info("Snapshotted");
215        }
216        result.clear();
217      }
218    }
219    assertEquals(rowCount, count);
220    for (KeyValueScanner scanner : memstorescanners) {
221      scanner.close();
222    }
223    memstorescanners = this.memstore.getScanners(mvcc.getReadPoint());
224    // Assert that new values are seen in kvset as we scan.
225    long ts = System.currentTimeMillis();
226    count = 0;
227    int snapshotIndex = 5;
228    try (StoreScanner s = new StoreScanner(scan, scanInfo, null, memstorescanners)) {
229      while (s.next(result)) {
230        LOG.info(Objects.toString(result));
231        // Assert the stuff is coming out in right order.
232        assertTrue(CellUtil.matchingRows(result.get(0), Bytes.toBytes(count)));
233        // Row count is same as column count.
234        assertEquals("count=" + count + ", result=" + result, rowCount, result.size());
235        count++;
236        if (count == snapshotIndex) {
237          MemStoreSnapshot snapshot = this.memstore.snapshot();
238          this.memstore.clearSnapshot(snapshot.getId());
239          // Added more rows into kvset. But the scanner wont see these rows.
240          addRows(this.memstore, ts);
241          LOG.info("Snapshotted, cleared it and then added values (which wont be seen)");
242        }
243        result.clear();
244      }
245    }
246    assertEquals(rowCount, count);
247  }
248
249  /**
250   * A simple test which verifies the 3 possible states when scanning across snapshot.
251   * @throws IOException
252   * @throws CloneNotSupportedException
253   */
254  @Test
255  public void testScanAcrossSnapshot2() throws IOException, CloneNotSupportedException {
256    // we are going to the scanning across snapshot with two kvs
257    // kv1 should always be returned before kv2
258    final byte[] one = Bytes.toBytes(1);
259    final byte[] two = Bytes.toBytes(2);
260    final byte[] f = Bytes.toBytes("f");
261    final byte[] q = Bytes.toBytes("q");
262    final byte[] v = Bytes.toBytes(3);
263
264    final KeyValue kv1 = new KeyValue(one, f, q, v);
265    final KeyValue kv2 = new KeyValue(two, f, q, v);
266
267    // use case 1: both kvs in kvset
268    this.memstore.add(kv1.clone(), null);
269    this.memstore.add(kv2.clone(), null);
270    verifyScanAcrossSnapshot2(kv1, kv2);
271
272    // use case 2: both kvs in snapshot
273    this.memstore.snapshot();
274    verifyScanAcrossSnapshot2(kv1, kv2);
275
276    // use case 3: first in snapshot second in kvset
277    this.memstore = new DefaultMemStore();
278    this.memstore.add(kv1.clone(), null);
279    this.memstore.snapshot();
280    this.memstore.add(kv2.clone(), null);
281    verifyScanAcrossSnapshot2(kv1, kv2);
282  }
283
284  protected void verifyScanAcrossSnapshot2(KeyValue kv1, KeyValue kv2)
285      throws IOException {
286    List<KeyValueScanner> memstorescanners = this.memstore.getScanners(mvcc.getReadPoint());
287    assertEquals(2, memstorescanners.size());
288    final KeyValueScanner scanner0 = memstorescanners.get(0);
289    final KeyValueScanner scanner1 = memstorescanners.get(1);
290    scanner0.seek(KeyValueUtil.createFirstOnRow(HConstants.EMPTY_START_ROW));
291    scanner1.seek(KeyValueUtil.createFirstOnRow(HConstants.EMPTY_START_ROW));
292    Cell n0 = scanner0.next();
293    Cell n1 = scanner1.next();
294    assertTrue(kv1.equals(n0) || kv1.equals(n1));
295    assertTrue(kv2.equals(n0)
296        || kv2.equals(n1)
297        || kv2.equals(scanner0.next())
298        || kv2.equals(scanner1.next()));
299    assertNull(scanner0.next());
300    assertNull(scanner1.next());
301  }
302
303  protected void assertScannerResults(KeyValueScanner scanner, KeyValue[] expected)
304      throws IOException {
305    scanner.seek(KeyValueUtil.createFirstOnRow(new byte[]{}));
306    List<Cell> returned = Lists.newArrayList();
307
308    while (true) {
309      Cell next = scanner.next();
310      if (next == null) break;
311      returned.add(next);
312    }
313
314    assertTrue(
315        "Got:\n" + Joiner.on("\n").join(returned) +
316        "\nExpected:\n" + Joiner.on("\n").join(expected),
317        Iterables.elementsEqual(Arrays.asList(expected), returned));
318    assertNull(scanner.peek());
319  }
320
321  @Test
322  public void testMemstoreConcurrentControl() throws IOException {
323    final byte[] row = Bytes.toBytes(1);
324    final byte[] f = Bytes.toBytes("family");
325    final byte[] q1 = Bytes.toBytes("q1");
326    final byte[] q2 = Bytes.toBytes("q2");
327    final byte[] v = Bytes.toBytes("value");
328
329    MultiVersionConcurrencyControl.WriteEntry w =
330        mvcc.begin();
331
332    KeyValue kv1 = new KeyValue(row, f, q1, v);
333    kv1.setSequenceId(w.getWriteNumber());
334    memstore.add(kv1, null);
335
336    KeyValueScanner s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
337    assertScannerResults(s, new KeyValue[]{});
338
339    mvcc.completeAndWait(w);
340
341    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
342    assertScannerResults(s, new KeyValue[]{kv1});
343
344    w = mvcc.begin();
345    KeyValue kv2 = new KeyValue(row, f, q2, v);
346    kv2.setSequenceId(w.getWriteNumber());
347    memstore.add(kv2, null);
348
349    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
350    assertScannerResults(s, new KeyValue[]{kv1});
351
352    mvcc.completeAndWait(w);
353
354    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
355    assertScannerResults(s, new KeyValue[]{kv1, kv2});
356  }
357
358  /**
359   * Regression test for HBASE-2616, HBASE-2670.
360   * When we insert a higher-memstoreTS version of a cell but with
361   * the same timestamp, we still need to provide consistent reads
362   * for the same scanner.
363   */
364  @Test
365  public void testMemstoreEditsVisibilityWithSameKey() throws IOException {
366    final byte[] row = Bytes.toBytes(1);
367    final byte[] f = Bytes.toBytes("family");
368    final byte[] q1 = Bytes.toBytes("q1");
369    final byte[] q2 = Bytes.toBytes("q2");
370    final byte[] v1 = Bytes.toBytes("value1");
371    final byte[] v2 = Bytes.toBytes("value2");
372
373    // INSERT 1: Write both columns val1
374    MultiVersionConcurrencyControl.WriteEntry w =
375        mvcc.begin();
376
377    KeyValue kv11 = new KeyValue(row, f, q1, v1);
378    kv11.setSequenceId(w.getWriteNumber());
379    memstore.add(kv11, null);
380
381    KeyValue kv12 = new KeyValue(row, f, q2, v1);
382    kv12.setSequenceId(w.getWriteNumber());
383    memstore.add(kv12, null);
384    mvcc.completeAndWait(w);
385
386    // BEFORE STARTING INSERT 2, SEE FIRST KVS
387    KeyValueScanner s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
388    assertScannerResults(s, new KeyValue[]{kv11, kv12});
389
390    // START INSERT 2: Write both columns val2
391    w = mvcc.begin();
392    KeyValue kv21 = new KeyValue(row, f, q1, v2);
393    kv21.setSequenceId(w.getWriteNumber());
394    memstore.add(kv21, null);
395
396    KeyValue kv22 = new KeyValue(row, f, q2, v2);
397    kv22.setSequenceId(w.getWriteNumber());
398    memstore.add(kv22, null);
399
400    // BEFORE COMPLETING INSERT 2, SEE FIRST KVS
401    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
402    assertScannerResults(s, new KeyValue[]{kv11, kv12});
403
404    // COMPLETE INSERT 2
405    mvcc.completeAndWait(w);
406
407    // NOW SHOULD SEE NEW KVS IN ADDITION TO OLD KVS.
408    // See HBASE-1485 for discussion about what we should do with
409    // the duplicate-TS inserts
410    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
411    assertScannerResults(s, new KeyValue[]{kv21, kv11, kv22, kv12});
412  }
413
414  /**
415   * When we insert a higher-memstoreTS deletion of a cell but with
416   * the same timestamp, we still need to provide consistent reads
417   * for the same scanner.
418   */
419  @Test
420  public void testMemstoreDeletesVisibilityWithSameKey() throws IOException {
421    final byte[] row = Bytes.toBytes(1);
422    final byte[] f = Bytes.toBytes("family");
423    final byte[] q1 = Bytes.toBytes("q1");
424    final byte[] q2 = Bytes.toBytes("q2");
425    final byte[] v1 = Bytes.toBytes("value1");
426    // INSERT 1: Write both columns val1
427    MultiVersionConcurrencyControl.WriteEntry w =
428        mvcc.begin();
429
430    KeyValue kv11 = new KeyValue(row, f, q1, v1);
431    kv11.setSequenceId(w.getWriteNumber());
432    memstore.add(kv11, null);
433
434    KeyValue kv12 = new KeyValue(row, f, q2, v1);
435    kv12.setSequenceId(w.getWriteNumber());
436    memstore.add(kv12, null);
437    mvcc.completeAndWait(w);
438
439    // BEFORE STARTING INSERT 2, SEE FIRST KVS
440    KeyValueScanner s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
441    assertScannerResults(s, new KeyValue[]{kv11, kv12});
442
443    // START DELETE: Insert delete for one of the columns
444    w = mvcc.begin();
445    KeyValue kvDel = new KeyValue(row, f, q2, kv11.getTimestamp(),
446        KeyValue.Type.DeleteColumn);
447    kvDel.setSequenceId(w.getWriteNumber());
448    memstore.add(kvDel, null);
449
450    // BEFORE COMPLETING DELETE, SEE FIRST KVS
451    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
452    assertScannerResults(s, new KeyValue[]{kv11, kv12});
453
454    // COMPLETE DELETE
455    mvcc.completeAndWait(w);
456
457    // NOW WE SHOULD SEE DELETE
458    s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
459    assertScannerResults(s, new KeyValue[]{kv11, kvDel, kv12});
460  }
461
462
463  private static class ReadOwnWritesTester extends Thread {
464    static final int NUM_TRIES = 1000;
465
466    final byte[] row;
467
468    final byte[] f = Bytes.toBytes("family");
469    final byte[] q1 = Bytes.toBytes("q1");
470
471    final MultiVersionConcurrencyControl mvcc;
472    final MemStore memstore;
473
474    AtomicReference<Throwable> caughtException;
475
476
477    public ReadOwnWritesTester(int id, MemStore memstore, MultiVersionConcurrencyControl mvcc,
478        AtomicReference<Throwable> caughtException) {
479      this.mvcc = mvcc;
480      this.memstore = memstore;
481      this.caughtException = caughtException;
482      row = Bytes.toBytes(id);
483    }
484
485    @Override
486    public void run() {
487      try {
488        internalRun();
489      } catch (Throwable t) {
490        caughtException.compareAndSet(null, t);
491      }
492    }
493
494    private void internalRun() throws IOException {
495      for (long i = 0; i < NUM_TRIES && caughtException.get() == null; i++) {
496        MultiVersionConcurrencyControl.WriteEntry w =
497            mvcc.begin();
498
499        // Insert the sequence value (i)
500        byte[] v = Bytes.toBytes(i);
501
502        KeyValue kv = new KeyValue(row, f, q1, i, v);
503        kv.setSequenceId(w.getWriteNumber());
504        memstore.add(kv, null);
505        mvcc.completeAndWait(w);
506
507        // Assert that we can read back
508        KeyValueScanner s = this.memstore.getScanners(mvcc.getReadPoint()).get(0);
509        s.seek(kv);
510
511        Cell ret = s.next();
512        assertNotNull("Didnt find own write at all", ret);
513        assertEquals("Didnt read own writes",
514                     kv.getTimestamp(), ret.getTimestamp());
515      }
516    }
517  }
518
519  @Test
520  public void testReadOwnWritesUnderConcurrency() throws Throwable {
521    int NUM_THREADS = 8;
522
523    ReadOwnWritesTester threads[] = new ReadOwnWritesTester[NUM_THREADS];
524    AtomicReference<Throwable> caught = new AtomicReference<>();
525
526    for (int i = 0; i < NUM_THREADS; i++) {
527      threads[i] = new ReadOwnWritesTester(i, memstore, mvcc, caught);
528      threads[i].start();
529    }
530
531    for (int i = 0; i < NUM_THREADS; i++) {
532      threads[i].join();
533    }
534
535    if (caught.get() != null) {
536      throw caught.get();
537    }
538  }
539
540  /**
541   * Test memstore snapshots
542   * @throws IOException
543   */
544  @Test
545  public void testSnapshotting() throws IOException {
546    final int snapshotCount = 5;
547    // Add some rows, run a snapshot. Do it a few times.
548    for (int i = 0; i < snapshotCount; i++) {
549      addRows(this.memstore);
550      runSnapshot(this.memstore);
551      assertEquals("History not being cleared", 0, this.memstore.getSnapshot().getCellsCount());
552    }
553  }
554
555  @Test
556  public void testMultipleVersionsSimple() throws Exception {
557    DefaultMemStore m = new DefaultMemStore(new Configuration(), CellComparatorImpl.COMPARATOR);
558    byte [] row = Bytes.toBytes("testRow");
559    byte [] family = Bytes.toBytes("testFamily");
560    byte [] qf = Bytes.toBytes("testQualifier");
561    long [] stamps = {1,2,3};
562    byte [][] values = {Bytes.toBytes("value0"), Bytes.toBytes("value1"),
563        Bytes.toBytes("value2")};
564    KeyValue key0 = new KeyValue(row, family, qf, stamps[0], values[0]);
565    KeyValue key1 = new KeyValue(row, family, qf, stamps[1], values[1]);
566    KeyValue key2 = new KeyValue(row, family, qf, stamps[2], values[2]);
567
568    m.add(key0, null);
569    m.add(key1, null);
570    m.add(key2, null);
571
572    assertTrue("Expected memstore to hold 3 values, actually has " +
573        m.getActive().getCellsCount(), m.getActive().getCellsCount() == 3);
574  }
575
576  //////////////////////////////////////////////////////////////////////////////
577  // Get tests
578  //////////////////////////////////////////////////////////////////////////////
579
580  /** Test getNextRow from memstore
581   * @throws InterruptedException
582   */
583  @Test
584  public void testGetNextRow() throws Exception {
585    addRows(this.memstore);
586    // Add more versions to make it a little more interesting.
587    Thread.sleep(1);
588    addRows(this.memstore);
589    Cell closestToEmpty = ((DefaultMemStore) this.memstore).getNextRow(KeyValue.LOWESTKEY);
590    assertTrue(CellComparatorImpl.COMPARATOR.compareRows(closestToEmpty,
591        new KeyValue(Bytes.toBytes(0), System.currentTimeMillis())) == 0);
592    for (int i = 0; i < ROW_COUNT; i++) {
593      Cell nr = ((DefaultMemStore) this.memstore).getNextRow(new KeyValue(Bytes.toBytes(i),
594          System.currentTimeMillis()));
595      if (i + 1 == ROW_COUNT) {
596        assertNull(nr);
597      } else {
598        assertTrue(CellComparatorImpl.COMPARATOR.compareRows(nr,
599            new KeyValue(Bytes.toBytes(i + 1), System.currentTimeMillis())) == 0);
600      }
601    }
602    //starting from each row, validate results should contain the starting row
603    Configuration conf = HBaseConfiguration.create();
604    for (int startRowId = 0; startRowId < ROW_COUNT; startRowId++) {
605      ScanInfo scanInfo =
606          new ScanInfo(conf, FAMILY, 0, 1, Integer.MAX_VALUE, KeepDeletedCells.FALSE,
607              HConstants.DEFAULT_BLOCKSIZE, 0, this.memstore.getComparator(), false);
608      try (InternalScanner scanner =
609          new StoreScanner(new Scan().withStartRow(Bytes.toBytes(startRowId)), scanInfo, null,
610              memstore.getScanners(0))) {
611        List<Cell> results = new ArrayList<>();
612        for (int i = 0; scanner.next(results); i++) {
613          int rowId = startRowId + i;
614          Cell left = results.get(0);
615          byte[] row1 = Bytes.toBytes(rowId);
616          assertTrue("Row name",
617            CellComparatorImpl.COMPARATOR.compareRows(left, row1, 0, row1.length) == 0);
618          assertEquals("Count of columns", QUALIFIER_COUNT, results.size());
619          List<Cell> row = new ArrayList<>();
620          for (Cell kv : results) {
621            row.add(kv);
622          }
623          isExpectedRowWithoutTimestamps(rowId, row);
624          // Clear out set. Otherwise row results accumulate.
625          results.clear();
626        }
627      }
628    }
629  }
630
631  @Test
632  public void testGet_memstoreAndSnapShot() throws IOException {
633    byte [] row = Bytes.toBytes("testrow");
634    byte [] fam = Bytes.toBytes("testfamily");
635    byte [] qf1 = Bytes.toBytes("testqualifier1");
636    byte [] qf2 = Bytes.toBytes("testqualifier2");
637    byte [] qf3 = Bytes.toBytes("testqualifier3");
638    byte [] qf4 = Bytes.toBytes("testqualifier4");
639    byte [] qf5 = Bytes.toBytes("testqualifier5");
640    byte [] val = Bytes.toBytes("testval");
641
642    //Setting up memstore
643    memstore.add(new KeyValue(row, fam, qf1, val), null);
644    memstore.add(new KeyValue(row, fam, qf2, val), null);
645    memstore.add(new KeyValue(row, fam, qf3, val), null);
646    //Creating a snapshot
647    memstore.snapshot();
648    assertEquals(3, memstore.getSnapshot().getCellsCount());
649    //Adding value to "new" memstore
650    assertEquals(0, memstore.getActive().getCellsCount());
651    memstore.add(new KeyValue(row, fam ,qf4, val), null);
652    memstore.add(new KeyValue(row, fam ,qf5, val), null);
653    assertEquals(2, memstore.getActive().getCellsCount());
654  }
655
656  //////////////////////////////////////////////////////////////////////////////
657  // Delete tests
658  //////////////////////////////////////////////////////////////////////////////
659  @Test
660  public void testGetWithDelete() throws IOException {
661    byte [] row = Bytes.toBytes("testrow");
662    byte [] fam = Bytes.toBytes("testfamily");
663    byte [] qf1 = Bytes.toBytes("testqualifier");
664    byte [] val = Bytes.toBytes("testval");
665
666    long ts1 = System.nanoTime();
667    KeyValue put1 = new KeyValue(row, fam, qf1, ts1, val);
668    long ts2 = ts1 + 1;
669    KeyValue put2 = new KeyValue(row, fam, qf1, ts2, val);
670    long ts3 = ts2 + 1;
671    KeyValue put3 = new KeyValue(row, fam, qf1, ts3, val);
672    memstore.add(put1, null);
673    memstore.add(put2, null);
674    memstore.add(put3, null);
675
676    assertEquals(3, memstore.getActive().getCellsCount());
677
678    KeyValue del2 = new KeyValue(row, fam, qf1, ts2, KeyValue.Type.Delete, val);
679    memstore.add(del2, null);
680
681    List<Cell> expected = new ArrayList<>();
682    expected.add(put3);
683    expected.add(del2);
684    expected.add(put2);
685    expected.add(put1);
686
687    assertEquals(4, memstore.getActive().getCellsCount());
688    int i = 0;
689    for(Cell cell : memstore.getActive().getCellSet()) {
690      assertEquals(expected.get(i++), cell);
691    }
692  }
693
694  @Test
695  public void testGetWithDeleteColumn() throws IOException {
696    byte [] row = Bytes.toBytes("testrow");
697    byte [] fam = Bytes.toBytes("testfamily");
698    byte [] qf1 = Bytes.toBytes("testqualifier");
699    byte [] val = Bytes.toBytes("testval");
700
701    long ts1 = System.nanoTime();
702    KeyValue put1 = new KeyValue(row, fam, qf1, ts1, val);
703    long ts2 = ts1 + 1;
704    KeyValue put2 = new KeyValue(row, fam, qf1, ts2, val);
705    long ts3 = ts2 + 1;
706    KeyValue put3 = new KeyValue(row, fam, qf1, ts3, val);
707    memstore.add(put1, null);
708    memstore.add(put2, null);
709    memstore.add(put3, null);
710
711    assertEquals(3, memstore.getActive().getCellsCount());
712
713    KeyValue del2 =
714      new KeyValue(row, fam, qf1, ts2, KeyValue.Type.DeleteColumn, val);
715    memstore.add(del2, null);
716
717    List<Cell> expected = new ArrayList<>();
718    expected.add(put3);
719    expected.add(del2);
720    expected.add(put2);
721    expected.add(put1);
722
723    assertEquals(4, memstore.getActive().getCellsCount());
724    int i = 0;
725    for (Cell cell : memstore.getActive().getCellSet()) {
726      assertEquals(expected.get(i++), cell);
727    }
728  }
729
730  @Test
731  public void testGetWithDeleteFamily() throws IOException {
732    byte [] row = Bytes.toBytes("testrow");
733    byte [] fam = Bytes.toBytes("testfamily");
734    byte [] qf1 = Bytes.toBytes("testqualifier1");
735    byte [] qf2 = Bytes.toBytes("testqualifier2");
736    byte [] qf3 = Bytes.toBytes("testqualifier3");
737    byte [] val = Bytes.toBytes("testval");
738    long ts = System.nanoTime();
739
740    KeyValue put1 = new KeyValue(row, fam, qf1, ts, val);
741    KeyValue put2 = new KeyValue(row, fam, qf2, ts, val);
742    KeyValue put3 = new KeyValue(row, fam, qf3, ts, val);
743    KeyValue put4 = new KeyValue(row, fam, qf3, ts+1, val);
744
745    memstore.add(put1, null);
746    memstore.add(put2, null);
747    memstore.add(put3, null);
748    memstore.add(put4, null);
749
750    KeyValue del =
751      new KeyValue(row, fam, null, ts, KeyValue.Type.DeleteFamily, val);
752    memstore.add(del, null);
753
754    List<Cell> expected = new ArrayList<>();
755    expected.add(del);
756    expected.add(put1);
757    expected.add(put2);
758    expected.add(put4);
759    expected.add(put3);
760
761    assertEquals(5, memstore.getActive().getCellsCount());
762    int i = 0;
763    for (Cell cell : memstore.getActive().getCellSet()) {
764      assertEquals(expected.get(i++), cell);
765    }
766  }
767
768  @Test
769  public void testKeepDeleteInmemstore() {
770    byte [] row = Bytes.toBytes("testrow");
771    byte [] fam = Bytes.toBytes("testfamily");
772    byte [] qf = Bytes.toBytes("testqualifier");
773    byte [] val = Bytes.toBytes("testval");
774    long ts = System.nanoTime();
775    memstore.add(new KeyValue(row, fam, qf, ts, val), null);
776    KeyValue delete = new KeyValue(row, fam, qf, ts, KeyValue.Type.Delete, val);
777    memstore.add(delete, null);
778    assertEquals(2, memstore.getActive().getCellsCount());
779    assertEquals(delete, memstore.getActive().first());
780  }
781
782  @Test
783  public void testRetainsDeleteVersion() throws IOException {
784    // add a put to memstore
785    memstore.add(KeyValueTestUtil.create("row1", "fam", "a", 100, "dont-care"), null);
786
787    // now process a specific delete:
788    KeyValue delete = KeyValueTestUtil.create(
789        "row1", "fam", "a", 100, KeyValue.Type.Delete, "dont-care");
790    memstore.add(delete, null);
791
792    assertEquals(2, memstore.getActive().getCellsCount());
793    assertEquals(delete, memstore.getActive().first());
794  }
795
796  @Test
797  public void testRetainsDeleteColumn() throws IOException {
798    // add a put to memstore
799    memstore.add(KeyValueTestUtil.create("row1", "fam", "a", 100, "dont-care"), null);
800
801    // now process a specific delete:
802    KeyValue delete = KeyValueTestUtil.create("row1", "fam", "a", 100,
803        KeyValue.Type.DeleteColumn, "dont-care");
804    memstore.add(delete, null);
805
806    assertEquals(2, memstore.getActive().getCellsCount());
807    assertEquals(delete, memstore.getActive().first());
808  }
809
810  @Test
811  public void testRetainsDeleteFamily() throws IOException {
812    // add a put to memstore
813    memstore.add(KeyValueTestUtil.create("row1", "fam", "a", 100, "dont-care"), null);
814
815    // now process a specific delete:
816    KeyValue delete = KeyValueTestUtil.create("row1", "fam", "a", 100,
817        KeyValue.Type.DeleteFamily, "dont-care");
818    memstore.add(delete, null);
819
820    assertEquals(2, memstore.getActive().getCellsCount());
821    assertEquals(delete, memstore.getActive().first());
822  }
823
824  //////////////////////////////////////////////////////////////////////////////
825  // Helpers
826  //////////////////////////////////////////////////////////////////////////////
827  private static byte [] makeQualifier(final int i1, final int i2){
828    return Bytes.toBytes(Integer.toString(i1) + ";" +
829        Integer.toString(i2));
830  }
831
832  /**
833   * Add keyvalues with a fixed memstoreTs, and checks that memstore size is decreased
834   * as older keyvalues are deleted from the memstore.
835   * @throws Exception
836   */
837  @Test
838  public void testUpsertMemstoreSize() throws Exception {
839    Configuration conf = HBaseConfiguration.create();
840    memstore = new DefaultMemStore(conf, CellComparatorImpl.COMPARATOR);
841    MemStoreSize oldSize = memstore.size();
842
843    List<Cell> l = new ArrayList<>();
844    KeyValue kv1 = KeyValueTestUtil.create("r", "f", "q", 100, "v");
845    KeyValue kv2 = KeyValueTestUtil.create("r", "f", "q", 101, "v");
846    KeyValue kv3 = KeyValueTestUtil.create("r", "f", "q", 102, "v");
847
848    kv1.setSequenceId(1); kv2.setSequenceId(1);kv3.setSequenceId(1);
849    l.add(kv1); l.add(kv2); l.add(kv3);
850
851    this.memstore.upsert(l, 2, null);// readpoint is 2
852    MemStoreSize newSize = this.memstore.size();
853    assert (newSize.getDataSize() > oldSize.getDataSize());
854    //The kv1 should be removed.
855    assert(memstore.getActive().getCellsCount() == 2);
856
857    KeyValue kv4 = KeyValueTestUtil.create("r", "f", "q", 104, "v");
858    kv4.setSequenceId(1);
859    l.clear(); l.add(kv4);
860    this.memstore.upsert(l, 3, null);
861    assertEquals(newSize, this.memstore.size());
862    //The kv2 should be removed.
863    assert(memstore.getActive().getCellsCount() == 2);
864    //this.memstore = null;
865  }
866
867  ////////////////////////////////////
868  // Test for periodic memstore flushes
869  // based on time of oldest edit
870  ////////////////////////////////////
871
872  /**
873   * Tests that the timeOfOldestEdit is updated correctly for the
874   * various edit operations in memstore.
875   * @throws Exception
876   */
877  @Test
878  public void testUpdateToTimeOfOldestEdit() throws Exception {
879    try {
880      EnvironmentEdgeForMemstoreTest edge = new EnvironmentEdgeForMemstoreTest();
881      EnvironmentEdgeManager.injectEdge(edge);
882      DefaultMemStore memstore = new DefaultMemStore();
883      long t = memstore.timeOfOldestEdit();
884      assertEquals(Long.MAX_VALUE, t);
885
886      // test the case that the timeOfOldestEdit is updated after a KV add
887      memstore.add(KeyValueTestUtil.create("r", "f", "q", 100, "v"), null);
888      t = memstore.timeOfOldestEdit();
889      assertTrue(t == 1234);
890      // snapshot() will reset timeOfOldestEdit. The method will also assert the
891      // value is reset to Long.MAX_VALUE
892      t = runSnapshot(memstore);
893
894      // test the case that the timeOfOldestEdit is updated after a KV delete
895      memstore.add(KeyValueTestUtil.create("r", "f", "q", 100, KeyValue.Type.Delete, "v"), null);
896      t = memstore.timeOfOldestEdit();
897      assertTrue(t == 1234);
898      t = runSnapshot(memstore);
899
900      // test the case that the timeOfOldestEdit is updated after a KV upsert
901      List<Cell> l = new ArrayList<>();
902      KeyValue kv1 = KeyValueTestUtil.create("r", "f", "q", 100, "v");
903      kv1.setSequenceId(100);
904      l.add(kv1);
905      memstore.upsert(l, 1000, null);
906      t = memstore.timeOfOldestEdit();
907      assertTrue(t == 1234);
908    } finally {
909      EnvironmentEdgeManager.reset();
910    }
911  }
912
913  /**
914   * Tests the HRegion.shouldFlush method - adds an edit in the memstore
915   * and checks that shouldFlush returns true, and another where it disables
916   * the periodic flush functionality and tests whether shouldFlush returns
917   * false.
918   * @throws Exception
919   */
920  @Test
921  public void testShouldFlush() throws Exception {
922    Configuration conf = new Configuration();
923    conf.setInt(HRegion.MEMSTORE_PERIODIC_FLUSH_INTERVAL, 1000);
924    checkShouldFlush(conf, true);
925    // test disable flush
926    conf.setInt(HRegion.MEMSTORE_PERIODIC_FLUSH_INTERVAL, 0);
927    checkShouldFlush(conf, false);
928  }
929
930  protected void checkShouldFlush(Configuration conf, boolean expected) throws Exception {
931    try {
932      EnvironmentEdgeForMemstoreTest edge = new EnvironmentEdgeForMemstoreTest();
933      EnvironmentEdgeManager.injectEdge(edge);
934      HBaseTestingUtility hbaseUtility = HBaseTestingUtility.createLocalHTU(conf);
935      String cf = "foo";
936      HRegion region =
937          hbaseUtility.createTestRegion("foobar", ColumnFamilyDescriptorBuilder.of(cf));
938
939      edge.setCurrentTimeMillis(1234);
940      Put p = new Put(Bytes.toBytes("r"));
941      p.add(KeyValueTestUtil.create("r", cf, "q", 100, "v"));
942      region.put(p);
943      edge.setCurrentTimeMillis(1234 + 100);
944      StringBuilder sb = new StringBuilder();
945      assertTrue(!region.shouldFlush(sb));
946      edge.setCurrentTimeMillis(1234 + 10000);
947      assertTrue(region.shouldFlush(sb) == expected);
948    } finally {
949      EnvironmentEdgeManager.reset();
950    }
951  }
952
953  @Test
954  public void testShouldFlushMeta() throws Exception {
955    // write an edit in the META and ensure the shouldFlush (that the periodic memstore
956    // flusher invokes) returns true after SYSTEM_CACHE_FLUSH_INTERVAL (even though
957    // the MEMSTORE_PERIODIC_FLUSH_INTERVAL is set to a higher value)
958    Configuration conf = new Configuration();
959    conf.setInt(HRegion.MEMSTORE_PERIODIC_FLUSH_INTERVAL, HRegion.SYSTEM_CACHE_FLUSH_INTERVAL * 10);
960    HBaseTestingUtility hbaseUtility = HBaseTestingUtility.createLocalHTU(conf);
961    Path testDir = hbaseUtility.getDataTestDir();
962    EnvironmentEdgeForMemstoreTest edge = new EnvironmentEdgeForMemstoreTest();
963    EnvironmentEdgeManager.injectEdge(edge);
964    edge.setCurrentTimeMillis(1234);
965    WALFactory wFactory = new WALFactory(conf, "1234");
966    HRegion meta = HRegion.createHRegion(RegionInfoBuilder.FIRST_META_REGIONINFO, testDir,
967        conf, FSTableDescriptors.createMetaTableDescriptor(conf),
968        wFactory.getWAL(RegionInfoBuilder.FIRST_META_REGIONINFO));
969    // parameterized tests add [#] suffix get rid of [ and ].
970    TableDescriptor desc = TableDescriptorBuilder
971        .newBuilder(TableName.valueOf(name.getMethodName().replaceAll("[\\[\\]]", "_")))
972        .setColumnFamily(ColumnFamilyDescriptorBuilder.of("foo")).build();
973    RegionInfo hri = RegionInfoBuilder.newBuilder(desc.getTableName())
974        .setStartKey(Bytes.toBytes("row_0200")).setEndKey(Bytes.toBytes("row_0300")).build();
975    HRegion r = HRegion.createHRegion(hri, testDir, conf, desc, wFactory.getWAL(hri));
976    addRegionToMETA(meta, r);
977    edge.setCurrentTimeMillis(1234 + 100);
978    StringBuilder sb = new StringBuilder();
979    assertTrue(meta.shouldFlush(sb) == false);
980    edge.setCurrentTimeMillis(edge.currentTime() + HRegion.SYSTEM_CACHE_FLUSH_INTERVAL + 1);
981    assertTrue(meta.shouldFlush(sb) == true);
982  }
983
984  /**
985   * Inserts a new region's meta information into the passed
986   * <code>meta</code> region. Used by the HMaster bootstrap code adding
987   * new table to hbase:meta table.
988   *
989   * @param meta hbase:meta HRegion to be updated
990   * @param r HRegion to add to <code>meta</code>
991   *
992   * @throws IOException
993   */
994  public static void addRegionToMETA(final HRegion meta, final HRegion r) throws IOException {
995    meta.checkResources();
996    // The row key is the region name
997    byte[] row = r.getRegionInfo().getRegionName();
998    final long now = EnvironmentEdgeManager.currentTime();
999    final List<Cell> cells = new ArrayList<>(2);
1000    cells.add(new KeyValue(row, HConstants.CATALOG_FAMILY,
1001      HConstants.REGIONINFO_QUALIFIER, now, RegionInfo.toByteArray(r.getRegionInfo())));
1002    // Set into the root table the version of the meta table.
1003    cells.add(new KeyValue(row, HConstants.CATALOG_FAMILY,
1004      HConstants.META_VERSION_QUALIFIER, now,
1005      Bytes.toBytes(HConstants.META_VERSION)));
1006    meta.put(row, HConstants.CATALOG_FAMILY, cells);
1007  }
1008
1009  private class EnvironmentEdgeForMemstoreTest implements EnvironmentEdge {
1010    long t = 1234;
1011    @Override
1012    public long currentTime() {
1013      return t;
1014    }
1015    public void setCurrentTimeMillis(long t) {
1016      this.t = t;
1017    }
1018  }
1019
1020  /**
1021   * Adds {@link #ROW_COUNT} rows and {@link #QUALIFIER_COUNT}
1022   * @param hmc Instance to add rows to.
1023   * @return How many rows we added.
1024   * @throws IOException
1025   */
1026  protected int addRows(final AbstractMemStore hmc) {
1027    return addRows(hmc, HConstants.LATEST_TIMESTAMP);
1028  }
1029
1030  /**
1031   * Adds {@link #ROW_COUNT} rows and {@link #QUALIFIER_COUNT}
1032   * @param hmc Instance to add rows to.
1033   * @return How many rows we added.
1034   * @throws IOException
1035   */
1036  protected int addRows(final MemStore hmc, final long ts) {
1037    for (int i = 0; i < ROW_COUNT; i++) {
1038      long timestamp = ts == HConstants.LATEST_TIMESTAMP ?
1039        System.currentTimeMillis() : ts;
1040      for (int ii = 0; ii < QUALIFIER_COUNT; ii++) {
1041        byte [] row = Bytes.toBytes(i);
1042        byte [] qf = makeQualifier(i, ii);
1043        hmc.add(new KeyValue(row, FAMILY, qf, timestamp, qf), null);
1044      }
1045    }
1046    return ROW_COUNT;
1047  }
1048
1049  private long runSnapshot(final AbstractMemStore hmc) throws UnexpectedStateException {
1050    // Save off old state.
1051    int oldHistorySize = hmc.getSnapshot().getCellsCount();
1052    MemStoreSnapshot snapshot = hmc.snapshot();
1053    // Make some assertions about what just happened.
1054    assertTrue("History size has not increased", oldHistorySize < hmc.getSnapshot().getCellsCount
1055        ());
1056    long t = memstore.timeOfOldestEdit();
1057    assertTrue("Time of oldest edit is not Long.MAX_VALUE", t == Long.MAX_VALUE);
1058    hmc.clearSnapshot(snapshot.getId());
1059    return t;
1060  }
1061
1062  private void isExpectedRowWithoutTimestamps(final int rowIndex,
1063      List<Cell> kvs) {
1064    int i = 0;
1065    for (Cell kv : kvs) {
1066      byte[] expectedColname = makeQualifier(rowIndex, i++);
1067      assertTrue("Column name", CellUtil.matchingQualifier(kv, expectedColname));
1068      // Value is column name as bytes.  Usually result is
1069      // 100 bytes in size at least. This is the default size
1070      // for BytesWriteable.  For comparison, convert bytes to
1071      // String and trim to remove trailing null bytes.
1072      assertTrue("Content", CellUtil.matchingValue(kv, expectedColname));
1073    }
1074  }
1075
1076  private static void addRows(int count, final MemStore mem) {
1077    long nanos = System.nanoTime();
1078
1079    for (int i = 0 ; i < count ; i++) {
1080      if (i % 1000 == 0) {
1081
1082        System.out.println(i + " Took for 1k usec: " + (System.nanoTime() - nanos)/1000);
1083        nanos = System.nanoTime();
1084      }
1085      long timestamp = System.currentTimeMillis();
1086
1087      for (int ii = 0; ii < QUALIFIER_COUNT ; ii++) {
1088        byte [] row = Bytes.toBytes(i);
1089        byte [] qf = makeQualifier(i, ii);
1090        mem.add(new KeyValue(row, FAMILY, qf, timestamp, qf), null);
1091      }
1092    }
1093  }
1094
1095  static void doScan(MemStore ms, int iteration) throws IOException {
1096    long nanos = System.nanoTime();
1097    KeyValueScanner s = ms.getScanners(0).get(0);
1098    s.seek(KeyValueUtil.createFirstOnRow(new byte[]{}));
1099
1100    System.out.println(iteration + " create/seek took: " + (System.nanoTime() - nanos)/1000);
1101    int cnt=0;
1102    while(s.next() != null) ++cnt;
1103
1104    System.out.println(iteration + " took usec: " + (System.nanoTime() - nanos) / 1000 + " for: "
1105        + cnt);
1106
1107  }
1108
1109  public static void main(String [] args) throws IOException {
1110    MemStore ms = new DefaultMemStore();
1111
1112    long n1 = System.nanoTime();
1113    addRows(25000, ms);
1114    System.out.println("Took for insert: " + (System.nanoTime()-n1)/1000);
1115
1116    System.out.println("foo");
1117
1118    for (int i = 0 ; i < 50 ; i++)
1119      doScan(ms, i);
1120  }
1121}
1122