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}