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