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