View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.junit.Assert.*;
22  
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Random;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.HBaseConfiguration;
31  import org.apache.hadoop.hbase.MultithreadedTestUtil;
32  import org.apache.hadoop.hbase.MultithreadedTestUtil.TestThread;
33  import org.apache.hadoop.hbase.testclassification.SmallTests;
34  import org.apache.hadoop.hbase.util.ByteRange;
35  import org.junit.Test;
36  
37  import com.google.common.collect.Iterables;
38  import com.google.common.collect.Lists;
39  import com.google.common.collect.Maps;
40  import com.google.common.primitives.Ints;
41  
42  import org.junit.experimental.categories.Category;
43  
44  @Category(SmallTests.class)
45  public class TestMemStoreLAB {
46  
47    /**
48     * Test a bunch of random allocations
49     */
50    @Test
51    public void testLABRandomAllocation() {
52      Random rand = new Random();
53      MemStoreLAB mslab = new HeapMemStoreLAB();
54      int expectedOff = 0;
55      byte[] lastBuffer = null;
56      // 100K iterations by 0-1K alloc -> 50MB expected
57      // should be reasonable for unit test and also cover wraparound
58      // behavior
59      for (int i = 0; i < 100000; i++) {
60        int size = rand.nextInt(1000);
61        ByteRange alloc = mslab.allocateBytes(size);
62        
63        if (alloc.getBytes() != lastBuffer) {
64          expectedOff = 0;
65          lastBuffer = alloc.getBytes();
66        }
67        assertEquals(expectedOff, alloc.getOffset());
68        assertTrue("Allocation overruns buffer",
69            alloc.getOffset() + size <= alloc.getBytes().length);
70        expectedOff += size;
71      }
72    }
73  
74    @Test
75    public void testLABLargeAllocation() {
76      MemStoreLAB mslab = new HeapMemStoreLAB();
77      ByteRange alloc = mslab.allocateBytes(2*1024*1024);
78      assertNull("2MB allocation shouldn't be satisfied by LAB.",
79        alloc);
80    } 
81  
82    /**
83     * Test allocation from lots of threads, making sure the results don't
84     * overlap in any way
85     */
86    @Test
87    public void testLABThreading() throws Exception {
88      Configuration conf = new Configuration();
89      MultithreadedTestUtil.TestContext ctx =
90        new MultithreadedTestUtil.TestContext(conf);
91      
92      final AtomicInteger totalAllocated = new AtomicInteger();
93      
94      final MemStoreLAB mslab = new HeapMemStoreLAB();
95      List<List<AllocRecord>> allocations = Lists.newArrayList();
96      
97      for (int i = 0; i < 10; i++) {
98        final List<AllocRecord> allocsByThisThread = Lists.newLinkedList();
99        allocations.add(allocsByThisThread);
100       
101       TestThread t = new MultithreadedTestUtil.RepeatingTestThread(ctx) {
102         private Random r = new Random();
103         @Override
104         public void doAnAction() throws Exception {
105           int size = r.nextInt(1000);
106           ByteRange alloc = mslab.allocateBytes(size);
107           totalAllocated.addAndGet(size);
108           allocsByThisThread.add(new AllocRecord(alloc, size));
109         }
110       };
111       ctx.addThread(t);
112     }
113     
114     ctx.startThreads();
115     while (totalAllocated.get() < 50*1024*1024 && ctx.shouldRun()) {
116       Thread.sleep(10);
117     }
118     ctx.stop();
119     
120     // Partition the allocations by the actual byte[] they point into,
121     // make sure offsets are unique for each chunk
122     Map<byte[], Map<Integer, AllocRecord>> mapsByChunk =
123       Maps.newHashMap();
124     
125     int sizeCounted = 0;
126     for (AllocRecord rec : Iterables.concat(allocations)) {
127       sizeCounted += rec.size;
128       if (rec.size == 0) continue;
129       
130       Map<Integer, AllocRecord> mapForThisByteArray =
131         mapsByChunk.get(rec.alloc.getBytes());
132       if (mapForThisByteArray == null) {
133         mapForThisByteArray = Maps.newTreeMap();
134         mapsByChunk.put(rec.alloc.getBytes(), mapForThisByteArray);
135       }
136       AllocRecord oldVal = mapForThisByteArray.put(rec.alloc.getOffset(), rec);
137       assertNull("Already had an entry " + oldVal + " for allocation " + rec,
138           oldVal);
139     }
140     assertEquals("Sanity check test", sizeCounted, totalAllocated.get());
141     
142     // Now check each byte array to make sure allocations don't overlap
143     for (Map<Integer, AllocRecord> allocsInChunk : mapsByChunk.values()) {
144       int expectedOff = 0;
145       for (AllocRecord alloc : allocsInChunk.values()) {
146         assertEquals(expectedOff, alloc.alloc.getOffset());
147         assertTrue("Allocation overruns buffer",
148             alloc.alloc.getOffset() + alloc.size <= alloc.alloc.getBytes().length);
149         expectedOff += alloc.size;
150       }
151     }
152 
153   }
154 
155   /**
156    * Test frequent chunk retirement with chunk pool triggered by lots of threads, making sure
157    * there's no memory leak (HBASE-16195)
158    * @throws Exception if any error occurred
159    */
160   @Test
161   public void testLABChunkQueue() throws Exception {
162     HeapMemStoreLAB mslab = new HeapMemStoreLAB();
163     // by default setting, there should be no chunk queue initialized
164     assertNull(mslab.getChunkQueue());
165     // reset mslab with chunk pool
166     Configuration conf = HBaseConfiguration.create();
167     conf.setDouble(MemStoreChunkPool.CHUNK_POOL_MAXSIZE_KEY, 0.1);
168     // set chunk size to default max alloc size, so we could easily trigger chunk retirement
169     conf.setLong(HeapMemStoreLAB.CHUNK_SIZE_KEY, HeapMemStoreLAB.MAX_ALLOC_DEFAULT);
170     // reconstruct mslab
171     MemStoreChunkPool.clearDisableFlag();
172     mslab = new HeapMemStoreLAB(conf);
173     // launch multiple threads to trigger frequent chunk retirement
174     List<Thread> threads = new ArrayList<Thread>();
175     for (int i = 0; i < 10; i++) {
176       threads.add(getChunkQueueTestThread(mslab, "testLABChunkQueue-" + i));
177     }
178     for (Thread thread : threads) {
179       thread.start();
180     }
181     // let it run for some time
182     Thread.sleep(1000);
183     for (Thread thread : threads) {
184       thread.interrupt();
185     }
186     boolean threadsRunning = true;
187     while (threadsRunning) {
188       for (Thread thread : threads) {
189         if (thread.isAlive()) {
190           threadsRunning = true;
191           break;
192         }
193       }
194       threadsRunning = false;
195     }
196     // close the mslab
197     mslab.close();
198     // make sure all chunks reclaimed or removed from chunk queue
199     int queueLength = mslab.getChunkQueue().size();
200     assertTrue("All chunks in chunk queue should be reclaimed or removed"
201         + " after mslab closed but actually: " + queueLength, queueLength == 0);
202   }
203 
204   private Thread getChunkQueueTestThread(final HeapMemStoreLAB mslab, String threadName) {
205     Thread thread = new Thread() {
206       boolean stopped = false;
207 
208       @Override
209       public void run() {
210         while (!stopped) {
211           // keep triggering chunk retirement
212           mslab.allocateBytes(HeapMemStoreLAB.MAX_ALLOC_DEFAULT - 1);
213         }
214       }
215 
216       @Override
217       public void interrupt() {
218         this.stopped = true;
219       }
220     };
221     thread.setName(threadName);
222     thread.setDaemon(true);
223     return thread;
224   }
225 
226   private static class AllocRecord implements Comparable<AllocRecord>{
227     private final ByteRange alloc;
228     private final int size;
229     public AllocRecord(ByteRange alloc, int size) {
230       super();
231       this.alloc = alloc;
232       this.size = size;
233     }
234 
235     @Override
236     public int compareTo(AllocRecord e) {
237       if (alloc.getBytes() != e.alloc.getBytes()) {
238         throw new RuntimeException("Can only compare within a particular array");
239       }
240       return Ints.compare(alloc.getOffset(), e.alloc.getOffset());
241     }
242     
243     @Override
244     public String toString() {
245       return "AllocRecord(offset=" + alloc.getOffset() + ", size=" + size + ")";
246     }
247     
248   }
249 
250 }
251