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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022
023import java.lang.management.ManagementFactory;
024import java.nio.ByteBuffer;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Random;
028import java.util.concurrent.ThreadLocalRandom;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.ByteBufferKeyValue;
031import org.apache.hadoop.hbase.ExtendedCell;
032import org.apache.hadoop.hbase.HBaseConfiguration;
033import org.apache.hadoop.hbase.KeyValue;
034import org.apache.hadoop.hbase.testclassification.RegionServerTests;
035import org.apache.hadoop.hbase.testclassification.SmallTests;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.junit.jupiter.api.BeforeAll;
038import org.junit.jupiter.api.Disabled;
039import org.junit.jupiter.api.Tag;
040import org.junit.jupiter.api.Test;
041
042@Disabled // See HBASE-19742 for issue on reenabling.
043@Tag(RegionServerTests.TAG)
044@Tag(SmallTests.TAG)
045public class TestMemstoreLABWithoutPool {
046
047  private final static Configuration conf = new Configuration();
048
049  private static final byte[] rk = Bytes.toBytes("r1");
050  private static final byte[] cf = Bytes.toBytes("f");
051  private static final byte[] q = Bytes.toBytes("q");
052
053  @BeforeAll
054  public static void setUpBeforeClass() throws Exception {
055    long globalMemStoreLimit =
056      (long) (ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax() * 0.8);
057    // disable pool
058    ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT + Bytes.SIZEOF_LONG, false,
059      globalMemStoreLimit, 0.0f, MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT, null,
060      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
061  }
062
063  /**
064   * Test a bunch of random allocations
065   */
066  @Test
067  public void testLABRandomAllocation() {
068    MemStoreLAB mslab = new MemStoreLABImpl();
069    int expectedOff = 0;
070    ByteBuffer lastBuffer = null;
071    int lastChunkId = -1;
072    // 100K iterations by 0-1K alloc -> 50MB expected
073    // should be reasonable for unit test and also cover wraparound
074    // behavior
075    Random rand = ThreadLocalRandom.current();
076    for (int i = 0; i < 100000; i++) {
077      int valSize = rand.nextInt(1000);
078      KeyValue kv = new KeyValue(rk, cf, q, new byte[valSize]);
079      int size = kv.getSerializedSize();
080      ByteBufferKeyValue newKv = (ByteBufferKeyValue) mslab.copyCellInto(kv);
081      if (newKv.getBuffer() != lastBuffer) {
082        // since we add the chunkID at the 0th offset of the chunk and the
083        // chunkid is an int we need to account for those 4 bytes
084        expectedOff = Bytes.SIZEOF_INT;
085        lastBuffer = newKv.getBuffer();
086        int chunkId = newKv.getBuffer().getInt(0);
087        assertTrue(chunkId != lastChunkId, "chunkid should be different");
088        lastChunkId = chunkId;
089      }
090      assertEquals(expectedOff, newKv.getOffset());
091      assertTrue(newKv.getOffset() + size <= newKv.getBuffer().capacity(),
092        "Allocation overruns buffer");
093      expectedOff += size;
094    }
095  }
096
097  /**
098   * Test frequent chunk retirement with chunk pool triggered by lots of threads, making sure
099   * there's no memory leak (HBASE-16195)
100   * @throws Exception if any error occurred
101   */
102  @Test
103  public void testLABChunkQueueWithMultipleMSLABs() throws Exception {
104    Configuration conf = HBaseConfiguration.create();
105    MemStoreLABImpl[] mslab = new MemStoreLABImpl[10];
106    for (int i = 0; i < 10; i++) {
107      mslab[i] = new MemStoreLABImpl(conf);
108    }
109    // launch multiple threads to trigger frequent chunk retirement
110    List<Thread> threads = new ArrayList<>();
111    // create smaller sized kvs
112    final KeyValue kv =
113      new KeyValue(Bytes.toBytes("r"), Bytes.toBytes("f"), Bytes.toBytes("q"), new byte[0]);
114    for (int i = 0; i < 10; i++) {
115      for (int j = 0; j < 10; j++) {
116        threads.add(getChunkQueueTestThread(mslab[i], "testLABChunkQueue-" + j, kv));
117      }
118    }
119    for (Thread thread : threads) {
120      thread.start();
121    }
122    // let it run for some time
123    Thread.sleep(3000);
124    for (Thread thread : threads) {
125      thread.interrupt();
126    }
127    boolean threadsRunning = true;
128    boolean alive = false;
129    while (threadsRunning) {
130      alive = false;
131      for (Thread thread : threads) {
132        if (thread.isAlive()) {
133          alive = true;
134          break;
135        }
136      }
137      if (!alive) {
138        threadsRunning = false;
139      }
140    }
141    // close the mslab
142    for (int i = 0; i < 10; i++) {
143      mslab[i].close();
144    }
145    // all of the chunkIds would have been returned back
146    assertTrue(ChunkCreator.instance.numberOfMappedChunks() == 0,
147      "All the chunks must have been cleared");
148  }
149
150  private Thread getChunkQueueTestThread(final MemStoreLABImpl mslab, String threadName,
151    ExtendedCell cellToCopyInto) {
152    Thread thread = new Thread() {
153      volatile boolean stopped = false;
154
155      @Override
156      public void run() {
157        while (!stopped) {
158          // keep triggering chunk retirement
159          mslab.copyCellInto(cellToCopyInto);
160        }
161      }
162
163      @Override
164      public void interrupt() {
165        this.stopped = true;
166      }
167    };
168    thread.setName(threadName);
169    thread.setDaemon(true);
170    return thread;
171  }
172}