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}