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.io;
019
020import static org.apache.hadoop.hbase.io.ByteBuffAllocator.HEAP;
021import static org.apache.hadoop.hbase.io.ByteBuffAllocator.getHeapAllocationRatio;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertFalse;
024import static org.junit.Assert.assertSame;
025import static org.junit.Assert.assertTrue;
026import static org.junit.Assert.fail;
027
028import java.nio.ByteBuffer;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.HBaseClassTestRule;
031import org.apache.hadoop.hbase.HBaseConfiguration;
032import org.apache.hadoop.hbase.nio.ByteBuff;
033import org.apache.hadoop.hbase.nio.MultiByteBuff;
034import org.apache.hadoop.hbase.nio.SingleByteBuff;
035import org.apache.hadoop.hbase.testclassification.RPCTests;
036import org.apache.hadoop.hbase.testclassification.SmallTests;
037import org.junit.Assert;
038import org.junit.ClassRule;
039import org.junit.Test;
040import org.junit.experimental.categories.Category;
041
042@Category({ RPCTests.class, SmallTests.class })
043public class TestByteBuffAllocator {
044
045  @ClassRule
046  public static final HBaseClassTestRule CLASS_RULE =
047    HBaseClassTestRule.forClass(TestByteBuffAllocator.class);
048
049  @Test
050  public void testRecycleOnlyPooledBuffers() {
051    int maxBuffersInPool = 10;
052    int bufSize = 1024;
053    int minSize = bufSize / 8;
054    ByteBuffAllocator alloc = new ByteBuffAllocator(true, maxBuffersInPool, bufSize, minSize);
055
056    ByteBuff buff = alloc.allocate(minSize - 1);
057    assertSame(ByteBuffAllocator.NONE, buff.getRefCnt().getRecycler());
058
059    alloc = new ByteBuffAllocator(true, 0, bufSize, minSize);
060    buff = alloc.allocate(minSize * 2);
061    assertSame(ByteBuffAllocator.NONE, buff.getRefCnt().getRecycler());
062  }
063
064  @Test
065  public void testAllocateByteBuffToReadInto() {
066    int maxBuffersInPool = 10;
067    int bufSize = 6 * 1024;
068    ByteBuffAllocator alloc = new ByteBuffAllocator(true, maxBuffersInPool, bufSize, bufSize / 6);
069    assertEquals(0, alloc.getUsedBufferCount());
070
071    ByteBuff buff = alloc.allocate(10 * bufSize);
072    assertEquals(61440, alloc.getPoolAllocationBytes());
073    assertEquals(0, alloc.getHeapAllocationBytes());
074    assertEquals(10, alloc.getUsedBufferCount());
075    buff.release();
076    // When the request size is less than 1/6th of the pool buffer size. We should use on demand
077    // created on heap Buffer
078    buff = alloc.allocate(200);
079    assertTrue(buff.hasArray());
080    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
081    assertEquals(maxBuffersInPool, alloc.getTotalBufferCount());
082    assertEquals(61440, alloc.getPoolAllocationBytes());
083    assertEquals(200, alloc.getHeapAllocationBytes());
084    assertEquals(10, alloc.getUsedBufferCount());
085    buff.release();
086    // When the request size is > 1/6th of the pool buffer size.
087    buff = alloc.allocate(1024);
088    assertFalse(buff.hasArray());
089    assertEquals(maxBuffersInPool - 1, alloc.getFreeBufferCount());
090    assertEquals(67584, alloc.getPoolAllocationBytes());
091    assertEquals(200, alloc.getHeapAllocationBytes());
092    assertEquals(10, alloc.getUsedBufferCount());
093    buff.release();// ByteBuff Recycler#free should put back the BB to pool.
094    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
095    // Request size> pool buffer size
096    buff = alloc.allocate(7 * 1024);
097    assertFalse(buff.hasArray());
098    assertTrue(buff instanceof MultiByteBuff);
099    ByteBuffer[] bbs = buff.nioByteBuffers();
100    assertEquals(2, bbs.length);
101    assertTrue(bbs[0].isDirect());
102    assertTrue(bbs[1].isDirect());
103    assertEquals(6 * 1024, bbs[0].limit());
104    assertEquals(1024, bbs[1].limit());
105    assertEquals(maxBuffersInPool - 2, alloc.getFreeBufferCount());
106    assertEquals(79872, alloc.getPoolAllocationBytes());
107    assertEquals(200, alloc.getHeapAllocationBytes());
108    assertEquals(10, alloc.getUsedBufferCount());
109    buff.release();
110    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
111
112    buff = alloc.allocate(6 * 1024 + 200);
113    assertFalse(buff.hasArray());
114    assertTrue(buff instanceof MultiByteBuff);
115    bbs = buff.nioByteBuffers();
116    assertEquals(2, bbs.length);
117    assertTrue(bbs[0].isDirect());
118    assertFalse(bbs[1].isDirect());
119    assertEquals(6 * 1024, bbs[0].limit());
120    assertEquals(200, bbs[1].limit());
121    assertEquals(maxBuffersInPool - 1, alloc.getFreeBufferCount());
122    assertEquals(86016, alloc.getPoolAllocationBytes());
123    assertEquals(400, alloc.getHeapAllocationBytes());
124    assertEquals(10, alloc.getUsedBufferCount());
125    buff.release();
126    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
127
128    alloc.allocate(bufSize * (maxBuffersInPool - 1));
129    assertEquals(141312, alloc.getPoolAllocationBytes());
130    assertEquals(400, alloc.getHeapAllocationBytes());
131    assertEquals(10, alloc.getUsedBufferCount());
132
133    buff = alloc.allocate(20 * 1024);
134    assertFalse(buff.hasArray());
135    assertTrue(buff instanceof MultiByteBuff);
136    bbs = buff.nioByteBuffers();
137    assertEquals(2, bbs.length);
138    assertTrue(bbs[0].isDirect());
139    assertFalse(bbs[1].isDirect());
140    assertEquals(6 * 1024, bbs[0].limit());
141    assertEquals(14 * 1024, bbs[1].limit());
142    assertEquals(0, alloc.getFreeBufferCount());
143    assertEquals(147456, alloc.getPoolAllocationBytes());
144    assertEquals(14736, alloc.getHeapAllocationBytes());
145    assertEquals(10, alloc.getUsedBufferCount());
146
147    buff.release();
148    assertEquals(1, alloc.getFreeBufferCount());
149    alloc.allocateOneBuffer();
150    assertEquals(153600, alloc.getPoolAllocationBytes());
151    assertEquals(14736, alloc.getHeapAllocationBytes());
152    assertEquals(10, alloc.getUsedBufferCount());
153
154    buff = alloc.allocate(7 * 1024);
155    assertTrue(buff.hasArray());
156    assertTrue(buff instanceof SingleByteBuff);
157    assertEquals(7 * 1024, buff.nioByteBuffers()[0].limit());
158    assertEquals(153600, alloc.getPoolAllocationBytes());
159    assertEquals(21904, alloc.getHeapAllocationBytes());
160    assertEquals(10, alloc.getUsedBufferCount());
161    buff.release();
162  }
163
164  @Test
165  public void testNegativeAllocatedSize() {
166    int maxBuffersInPool = 10;
167    ByteBuffAllocator allocator = new ByteBuffAllocator(true, maxBuffersInPool, 6 * 1024, 1024);
168    try {
169      allocator.allocate(-1);
170      fail("Should throw exception when size < 0");
171    } catch (IllegalArgumentException e) {
172      // expected exception
173    }
174    ByteBuff bb = allocator.allocate(0);
175    assertEquals(0, allocator.getHeapAllocationBytes());
176    bb.release();
177  }
178
179  @Test
180  public void testAllocateOneBuffer() {
181    // Allocate from on-heap
182    ByteBuffAllocator allocator = HEAP;
183    ByteBuff buf = allocator.allocateOneBuffer();
184    assertTrue(buf.hasArray());
185    assertEquals(ByteBuffAllocator.DEFAULT_BUFFER_SIZE, buf.remaining());
186    buf.release();
187
188    // Allocate from off-heap
189    int bufSize = 10;
190    allocator = new ByteBuffAllocator(true, 1, 10, 3);
191    buf = allocator.allocateOneBuffer();
192    assertFalse(buf.hasArray());
193    assertEquals(buf.remaining(), bufSize);
194    // The another one will be allocated from on-heap because the pool has only one ByteBuffer,
195    // and still not be cleaned.
196    ByteBuff buf2 = allocator.allocateOneBuffer();
197    assertTrue(buf2.hasArray());
198    assertEquals(buf2.remaining(), bufSize);
199    // free the first one
200    buf.release();
201    // The next one will be off-heap again.
202    buf = allocator.allocateOneBuffer();
203    assertFalse(buf.hasArray());
204    assertEquals(buf.remaining(), bufSize);
205    buf.release();
206  }
207
208  @Test
209  public void testReferenceCount() {
210    int bufSize = 64;
211    ByteBuffAllocator alloc = new ByteBuffAllocator(true, 2, bufSize, 3);
212    ByteBuff buf1 = alloc.allocate(bufSize * 2);
213    assertFalse(buf1.hasArray());
214    // The next one will be allocated from heap
215    ByteBuff buf2 = alloc.allocateOneBuffer();
216    assertTrue(buf2.hasArray());
217
218    // duplicate the buf2, if the dup released, buf2 will also be released (SingleByteBuffer)
219    ByteBuff dup2 = buf2.duplicate();
220    dup2.release();
221    assertEquals(0, buf2.refCnt());
222    assertEquals(0, dup2.refCnt());
223    assertEquals(0, alloc.getFreeBufferCount());
224    // these should not throw an exception because they are heap buffers.
225    // off-heap buffers would throw an exception if trying to call a method once released.
226    dup2.position();
227    buf2.position();
228
229    // duplicate the buf1, if the dup1 released, buf1 will also be released (MultipleByteBuffer)
230    ByteBuff dup1 = buf1.duplicate();
231    dup1.release();
232    assertEquals(0, buf1.refCnt());
233    assertEquals(0, dup1.refCnt());
234    assertEquals(2, alloc.getFreeBufferCount());
235    assertException(dup1::position);
236    assertException(buf1::position);
237
238    // slice the buf3, if the slice3 released, buf3 will also be released (SingleByteBuffer)
239    ByteBuff buf3 = alloc.allocateOneBuffer();
240    assertFalse(buf3.hasArray());
241    ByteBuff slice3 = buf3.slice();
242    slice3.release();
243    assertEquals(0, buf3.refCnt());
244    assertEquals(0, slice3.refCnt());
245    assertEquals(2, alloc.getFreeBufferCount());
246
247    // slice the buf4, if the slice4 released, buf4 will also be released (MultipleByteBuffer)
248    ByteBuff buf4 = alloc.allocate(bufSize * 2);
249    assertFalse(buf4.hasArray());
250    ByteBuff slice4 = buf4.slice();
251    slice4.release();
252    assertEquals(0, buf4.refCnt());
253    assertEquals(0, slice4.refCnt());
254    assertEquals(2, alloc.getFreeBufferCount());
255
256    // Test multiple reference for the same ByteBuff (SingleByteBuff)
257    ByteBuff buf5 = alloc.allocateOneBuffer();
258    ByteBuff slice5 = buf5.duplicate().duplicate().duplicate().slice().slice();
259    slice5.release();
260    assertEquals(0, buf5.refCnt());
261    assertEquals(0, slice5.refCnt());
262    assertEquals(2, alloc.getFreeBufferCount());
263    assertException(slice5::position);
264    assertException(buf5::position);
265
266    // Test multiple reference for the same ByteBuff (SingleByteBuff)
267    ByteBuff buf6 = alloc.allocate(bufSize >> 2);
268    ByteBuff slice6 = buf6.duplicate().duplicate().duplicate().slice().slice();
269    slice6.release();
270    assertEquals(0, buf6.refCnt());
271    assertEquals(0, slice6.refCnt());
272    assertEquals(2, alloc.getFreeBufferCount());
273
274    // Test retain the parent SingleByteBuff (duplicate)
275    ByteBuff parent = alloc.allocateOneBuffer();
276    ByteBuff child = parent.duplicate();
277    child.retain();
278    parent.release();
279    assertEquals(1, child.refCnt());
280    assertEquals(1, parent.refCnt());
281    assertEquals(1, alloc.getFreeBufferCount());
282    parent.release();
283    assertEquals(0, child.refCnt());
284    assertEquals(0, parent.refCnt());
285    assertEquals(2, alloc.getFreeBufferCount());
286
287    // Test retain parent MultiByteBuff (duplicate)
288    parent = alloc.allocate(bufSize << 1);
289    child = parent.duplicate();
290    child.retain();
291    parent.release();
292    assertEquals(1, child.refCnt());
293    assertEquals(1, parent.refCnt());
294    assertEquals(0, alloc.getFreeBufferCount());
295    parent.release();
296    assertEquals(0, child.refCnt());
297    assertEquals(0, parent.refCnt());
298    assertEquals(2, alloc.getFreeBufferCount());
299
300    // Test retain the parent SingleByteBuff (slice)
301    parent = alloc.allocateOneBuffer();
302    child = parent.slice();
303    child.retain();
304    parent.release();
305    assertEquals(1, child.refCnt());
306    assertEquals(1, parent.refCnt());
307    assertEquals(1, alloc.getFreeBufferCount());
308    parent.release();
309    assertEquals(0, child.refCnt());
310    assertEquals(0, parent.refCnt());
311    assertEquals(2, alloc.getFreeBufferCount());
312
313    // Test retain parent MultiByteBuff (slice)
314    parent = alloc.allocate(bufSize << 1);
315    child = parent.slice();
316    child.retain();
317    parent.release();
318    assertEquals(1, child.refCnt());
319    assertEquals(1, parent.refCnt());
320    assertEquals(0, alloc.getFreeBufferCount());
321    parent.release();
322    assertEquals(0, child.refCnt());
323    assertEquals(0, parent.refCnt());
324    assertEquals(2, alloc.getFreeBufferCount());
325  }
326
327  @Test
328  public void testReverseRef() {
329    int bufSize = 64;
330    ByteBuffAllocator alloc = new ByteBuffAllocator(true, 1, bufSize, 3);
331    ByteBuff buf1 = alloc.allocate(bufSize);
332    ByteBuff dup1 = buf1.duplicate();
333    assertEquals(1, buf1.refCnt());
334    assertEquals(1, dup1.refCnt());
335    buf1.release();
336    assertEquals(0, buf1.refCnt());
337    assertEquals(0, dup1.refCnt());
338    assertEquals(1, alloc.getFreeBufferCount());
339    assertException(buf1::position);
340    assertException(dup1::position);
341  }
342
343  @Test
344  public void testByteBuffUnsupportedMethods() {
345    int bufSize = 64;
346    ByteBuffAllocator alloc = new ByteBuffAllocator(true, 1, bufSize, 3);
347    ByteBuff buf = alloc.allocate(bufSize);
348    assertException(() -> buf.retain(2));
349    assertException(() -> buf.release(2));
350  }
351
352  private void assertException(Runnable r) {
353    try {
354      r.run();
355      fail();
356    } catch (Exception e) {
357      // expected exception.
358    }
359  }
360
361  @Test
362  public void testDeprecatedConfigs() {
363    Configuration conf = HBaseConfiguration.create();
364    conf.setInt(ByteBuffAllocator.DEPRECATED_MAX_BUFFER_COUNT_KEY, 10);
365    conf.setInt(ByteBuffAllocator.DEPRECATED_BUFFER_SIZE_KEY, 1024);
366    ByteBuffAllocator allocator = ByteBuffAllocator.create(conf, true);
367    Assert.assertEquals(1024, allocator.getBufferSize());
368    Assert.assertEquals(10, allocator.getTotalBufferCount());
369
370    conf = new Configuration();
371    conf.setInt(ByteBuffAllocator.MAX_BUFFER_COUNT_KEY, 11);
372    conf.setInt(ByteBuffAllocator.BUFFER_SIZE_KEY, 2048);
373    allocator = ByteBuffAllocator.create(conf, true);
374    Assert.assertEquals(2048, allocator.getBufferSize());
375    Assert.assertEquals(11, allocator.getTotalBufferCount());
376
377    conf = new Configuration();
378    conf.setBoolean(ByteBuffAllocator.DEPRECATED_ALLOCATOR_POOL_ENABLED_KEY, false);
379    Assert.assertFalse(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, true));
380    conf.setBoolean(ByteBuffAllocator.DEPRECATED_ALLOCATOR_POOL_ENABLED_KEY, true);
381    Assert.assertTrue(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, false));
382    conf.setBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, true);
383    Assert.assertTrue(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, false));
384    conf.setBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, false);
385    Assert.assertFalse(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, true));
386  }
387
388  @Test
389  public void testHeapAllocationRatio() {
390    Configuration conf = new Configuration();
391    conf.setInt(ByteBuffAllocator.MAX_BUFFER_COUNT_KEY, 11);
392    conf.setInt(ByteBuffAllocator.BUFFER_SIZE_KEY, 2048);
393    ByteBuffAllocator alloc1 = ByteBuffAllocator.create(conf, true);
394    Assert.assertEquals(getHeapAllocationRatio(alloc1), 0.0f, 1e-6);
395    alloc1.allocate(1);
396    Assert.assertEquals(getHeapAllocationRatio(alloc1), 1.0f, 1e-6);
397
398    alloc1.allocate(2048 / 6 - 1);
399    Assert.assertEquals(getHeapAllocationRatio(alloc1), 1.0f, 1e-6);
400
401    alloc1.allocate(24);
402    alloc1.allocate(1024);
403    Assert.assertEquals(getHeapAllocationRatio(alloc1), 24 / (24f + 2048), 1e-6);
404    Assert.assertEquals(getHeapAllocationRatio(alloc1), 0.0f, 1e-6);
405
406    // Allocate something from HEAP
407    HEAP.allocate(1024);
408    alloc1.allocate(24);
409    alloc1.allocate(1024);
410    Assert.assertEquals(getHeapAllocationRatio(HEAP, alloc1), (1024f + 24) / (1024f + 24 + 2048),
411      1e-6);
412    Assert.assertEquals(getHeapAllocationRatio(HEAP, alloc1), 0.0f, 1e-6);
413
414    // Check duplicated heap allocator, say even if we passed (HEAP, HEAP, alloc1), it will only
415    // caculate the allocation from (HEAP, alloc1).
416    HEAP.allocate(1024);
417    alloc1.allocate(1024);
418    Assert.assertEquals(getHeapAllocationRatio(HEAP, HEAP, alloc1), 1024f / (1024f + 2048f), 1e-6);
419  }
420
421  /**
422   * Tests that we only call the logic in checkRefCount for ByteBuff's that have a non-NONE
423   * recycler.
424   */
425  @Test
426  public void testCheckRefCountOnlyWithRecycler() {
427    TrackingSingleByteBuff singleBuff = new TrackingSingleByteBuff(ByteBuffer.allocate(1));
428    singleBuff.get();
429    assertEquals(1, singleBuff.getCheckRefCountCalls());
430    assertEquals(0, singleBuff.getRefCntCalls());
431    singleBuff = new TrackingSingleByteBuff(() -> {
432      // do nothing, just so we dont use NONE recycler
433    }, ByteBuffer.allocate(1));
434    singleBuff.get();
435    assertEquals(1, singleBuff.getCheckRefCountCalls());
436    assertEquals(1, singleBuff.getRefCntCalls());
437
438    TrackingMultiByteBuff multiBuff = new TrackingMultiByteBuff(ByteBuffer.allocate(1));
439
440    multiBuff.get();
441    assertEquals(1, multiBuff.getCheckRefCountCalls());
442    assertEquals(0, multiBuff.getRefCntCalls());
443    multiBuff = new TrackingMultiByteBuff(() -> {
444      // do nothing, just so we dont use NONE recycler
445    }, ByteBuffer.allocate(1));
446    multiBuff.get();
447    assertEquals(1, multiBuff.getCheckRefCountCalls());
448    assertEquals(1, multiBuff.getRefCntCalls());
449  }
450
451  private static class TrackingSingleByteBuff extends SingleByteBuff {
452
453    int refCntCalls = 0;
454    int checkRefCountCalls = 0;
455
456    public TrackingSingleByteBuff(ByteBuffer buf) {
457      super(buf);
458    }
459
460    public TrackingSingleByteBuff(ByteBuffAllocator.Recycler recycler, ByteBuffer buf) {
461      super(recycler, buf);
462    }
463
464    public int getRefCntCalls() {
465      return refCntCalls;
466    }
467
468    public int getCheckRefCountCalls() {
469      return checkRefCountCalls;
470    }
471
472    @Override
473    protected void checkRefCount() {
474      checkRefCountCalls++;
475      super.checkRefCount();
476    }
477
478    @Override
479    public int refCnt() {
480      refCntCalls++;
481      return super.refCnt();
482    }
483  }
484
485  private static class TrackingMultiByteBuff extends MultiByteBuff {
486
487    int refCntCalls = 0;
488    int checkRefCountCalls = 0;
489
490    public TrackingMultiByteBuff(ByteBuffer... items) {
491      super(items);
492    }
493
494    public TrackingMultiByteBuff(ByteBuffAllocator.Recycler recycler, ByteBuffer... items) {
495      super(recycler, items);
496    }
497
498    public int getRefCntCalls() {
499      return refCntCalls;
500    }
501
502    public int getCheckRefCountCalls() {
503      return checkRefCountCalls;
504    }
505
506    @Override
507    protected void checkRefCount() {
508      checkRefCountCalls++;
509      super.checkRefCount();
510    }
511
512    @Override
513    public int refCnt() {
514      refCntCalls++;
515      return super.refCnt();
516    }
517  }
518
519}