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 */
018
019package org.apache.hadoop.hbase.io;
020
021import static org.apache.hadoop.hbase.io.ByteBuffAllocator.HEAP;
022import static org.apache.hadoop.hbase.io.ByteBuffAllocator.getHeapAllocationRatio;
023import static org.junit.Assert.assertEquals;
024import static org.junit.Assert.assertFalse;
025import static org.junit.Assert.assertTrue;
026import static org.junit.Assert.fail;
027
028import java.nio.ByteBuffer;
029
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
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 testAllocateByteBuffToReadInto() {
051    int maxBuffersInPool = 10;
052    int bufSize = 6 * 1024;
053    ByteBuffAllocator alloc = new ByteBuffAllocator(true, maxBuffersInPool, bufSize, bufSize / 6);
054    assertEquals(0, alloc.getUsedBufferCount());
055
056    ByteBuff buff = alloc.allocate(10 * bufSize);
057    assertEquals(61440, alloc.getPoolAllocationBytes());
058    assertEquals(0, alloc.getHeapAllocationBytes());
059    assertEquals(10, alloc.getUsedBufferCount());
060    buff.release();
061    // When the request size is less than 1/6th of the pool buffer size. We should use on demand
062    // created on heap Buffer
063    buff = alloc.allocate(200);
064    assertTrue(buff.hasArray());
065    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
066    assertEquals(maxBuffersInPool, alloc.getTotalBufferCount());
067    assertEquals(61440, alloc.getPoolAllocationBytes());
068    assertEquals(200, alloc.getHeapAllocationBytes());
069    assertEquals(10, alloc.getUsedBufferCount());
070    buff.release();
071    // When the request size is > 1/6th of the pool buffer size.
072    buff = alloc.allocate(1024);
073    assertFalse(buff.hasArray());
074    assertEquals(maxBuffersInPool - 1, alloc.getFreeBufferCount());
075    assertEquals(67584, alloc.getPoolAllocationBytes());
076    assertEquals(200, alloc.getHeapAllocationBytes());
077    assertEquals(10, alloc.getUsedBufferCount());
078    buff.release();// ByteBuff Recycler#free should put back the BB to pool.
079    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
080    // Request size> pool buffer size
081    buff = alloc.allocate(7 * 1024);
082    assertFalse(buff.hasArray());
083    assertTrue(buff instanceof MultiByteBuff);
084    ByteBuffer[] bbs = buff.nioByteBuffers();
085    assertEquals(2, bbs.length);
086    assertTrue(bbs[0].isDirect());
087    assertTrue(bbs[1].isDirect());
088    assertEquals(6 * 1024, bbs[0].limit());
089    assertEquals(1024, bbs[1].limit());
090    assertEquals(maxBuffersInPool - 2, alloc.getFreeBufferCount());
091    assertEquals(79872, alloc.getPoolAllocationBytes());
092    assertEquals(200, alloc.getHeapAllocationBytes());
093    assertEquals(10, alloc.getUsedBufferCount());
094    buff.release();
095    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
096
097    buff = alloc.allocate(6 * 1024 + 200);
098    assertFalse(buff.hasArray());
099    assertTrue(buff instanceof MultiByteBuff);
100    bbs = buff.nioByteBuffers();
101    assertEquals(2, bbs.length);
102    assertTrue(bbs[0].isDirect());
103    assertFalse(bbs[1].isDirect());
104    assertEquals(6 * 1024, bbs[0].limit());
105    assertEquals(200, bbs[1].limit());
106    assertEquals(maxBuffersInPool - 1, alloc.getFreeBufferCount());
107    assertEquals(86016, alloc.getPoolAllocationBytes());
108    assertEquals(400, alloc.getHeapAllocationBytes());
109    assertEquals(10, alloc.getUsedBufferCount());
110    buff.release();
111    assertEquals(maxBuffersInPool, alloc.getFreeBufferCount());
112
113    alloc.allocate(bufSize * (maxBuffersInPool - 1));
114    assertEquals(141312, alloc.getPoolAllocationBytes());
115    assertEquals(400, alloc.getHeapAllocationBytes());
116    assertEquals(10, alloc.getUsedBufferCount());
117
118    buff = alloc.allocate(20 * 1024);
119    assertFalse(buff.hasArray());
120    assertTrue(buff instanceof MultiByteBuff);
121    bbs = buff.nioByteBuffers();
122    assertEquals(2, bbs.length);
123    assertTrue(bbs[0].isDirect());
124    assertFalse(bbs[1].isDirect());
125    assertEquals(6 * 1024, bbs[0].limit());
126    assertEquals(14 * 1024, bbs[1].limit());
127    assertEquals(0, alloc.getFreeBufferCount());
128    assertEquals(147456, alloc.getPoolAllocationBytes());
129    assertEquals(14736, alloc.getHeapAllocationBytes());
130    assertEquals(10, alloc.getUsedBufferCount());
131
132    buff.release();
133    assertEquals(1, alloc.getFreeBufferCount());
134    alloc.allocateOneBuffer();
135    assertEquals(153600, alloc.getPoolAllocationBytes());
136    assertEquals(14736, alloc.getHeapAllocationBytes());
137    assertEquals(10, alloc.getUsedBufferCount());
138
139    buff = alloc.allocate(7 * 1024);
140    assertTrue(buff.hasArray());
141    assertTrue(buff instanceof SingleByteBuff);
142    assertEquals(7 * 1024, buff.nioByteBuffers()[0].limit());
143    assertEquals(153600, alloc.getPoolAllocationBytes());
144    assertEquals(21904, alloc.getHeapAllocationBytes());
145    assertEquals(10, alloc.getUsedBufferCount());
146    buff.release();
147  }
148
149  @Test
150  public void testNegativeAllocatedSize() {
151    int maxBuffersInPool = 10;
152    ByteBuffAllocator allocator = new ByteBuffAllocator(true, maxBuffersInPool, 6 * 1024, 1024);
153    try {
154      allocator.allocate(-1);
155      fail("Should throw exception when size < 0");
156    } catch (IllegalArgumentException e) {
157      // expected exception
158    }
159    ByteBuff bb = allocator.allocate(0);
160    assertEquals(0, allocator.getHeapAllocationBytes());
161    bb.release();
162  }
163
164  @Test
165  public void testAllocateOneBuffer() {
166    // Allocate from on-heap
167    ByteBuffAllocator allocator = HEAP;
168    ByteBuff buf = allocator.allocateOneBuffer();
169    assertTrue(buf.hasArray());
170    assertEquals(ByteBuffAllocator.DEFAULT_BUFFER_SIZE, buf.remaining());
171    buf.release();
172
173    // Allocate from off-heap
174    int bufSize = 10;
175    allocator = new ByteBuffAllocator(true, 1, 10, 3);
176    buf = allocator.allocateOneBuffer();
177    assertFalse(buf.hasArray());
178    assertEquals(buf.remaining(), bufSize);
179    // The another one will be allocated from on-heap because the pool has only one ByteBuffer,
180    // and still not be cleaned.
181    ByteBuff buf2 = allocator.allocateOneBuffer();
182    assertTrue(buf2.hasArray());
183    assertEquals(buf2.remaining(), bufSize);
184    // free the first one
185    buf.release();
186    // The next one will be off-heap again.
187    buf = allocator.allocateOneBuffer();
188    assertFalse(buf.hasArray());
189    assertEquals(buf.remaining(), bufSize);
190    buf.release();
191  }
192
193  @Test
194  public void testReferenceCount() {
195    int bufSize = 64;
196    ByteBuffAllocator alloc = new ByteBuffAllocator(true, 2, bufSize, 3);
197    ByteBuff buf1 = alloc.allocate(bufSize * 2);
198    assertFalse(buf1.hasArray());
199    // The next one will be allocated from heap
200    ByteBuff buf2 = alloc.allocateOneBuffer();
201    assertTrue(buf2.hasArray());
202
203    // duplicate the buf2, if the dup released, buf2 will also be released (SingleByteBuffer)
204    ByteBuff dup2 = buf2.duplicate();
205    dup2.release();
206    assertEquals(0, buf2.refCnt());
207    assertEquals(0, dup2.refCnt());
208    assertEquals(0, alloc.getFreeBufferCount());
209    assertException(dup2::position);
210    assertException(buf2::position);
211
212    // duplicate the buf1, if the dup1 released, buf1 will also be released (MultipleByteBuffer)
213    ByteBuff dup1 = buf1.duplicate();
214    dup1.release();
215    assertEquals(0, buf1.refCnt());
216    assertEquals(0, dup1.refCnt());
217    assertEquals(2, alloc.getFreeBufferCount());
218    assertException(dup1::position);
219    assertException(buf1::position);
220
221    // slice the buf3, if the slice3 released, buf3 will also be released (SingleByteBuffer)
222    ByteBuff buf3 = alloc.allocateOneBuffer();
223    assertFalse(buf3.hasArray());
224    ByteBuff slice3 = buf3.slice();
225    slice3.release();
226    assertEquals(0, buf3.refCnt());
227    assertEquals(0, slice3.refCnt());
228    assertEquals(2, alloc.getFreeBufferCount());
229
230    // slice the buf4, if the slice4 released, buf4 will also be released (MultipleByteBuffer)
231    ByteBuff buf4 = alloc.allocate(bufSize * 2);
232    assertFalse(buf4.hasArray());
233    ByteBuff slice4 = buf4.slice();
234    slice4.release();
235    assertEquals(0, buf4.refCnt());
236    assertEquals(0, slice4.refCnt());
237    assertEquals(2, alloc.getFreeBufferCount());
238
239    // Test multiple reference for the same ByteBuff (SingleByteBuff)
240    ByteBuff buf5 = alloc.allocateOneBuffer();
241    ByteBuff slice5 = buf5.duplicate().duplicate().duplicate().slice().slice();
242    slice5.release();
243    assertEquals(0, buf5.refCnt());
244    assertEquals(0, slice5.refCnt());
245    assertEquals(2, alloc.getFreeBufferCount());
246    assertException(slice5::position);
247    assertException(buf5::position);
248
249    // Test multiple reference for the same ByteBuff (SingleByteBuff)
250    ByteBuff buf6 = alloc.allocate(bufSize >> 2);
251    ByteBuff slice6 = buf6.duplicate().duplicate().duplicate().slice().slice();
252    slice6.release();
253    assertEquals(0, buf6.refCnt());
254    assertEquals(0, slice6.refCnt());
255    assertEquals(2, alloc.getFreeBufferCount());
256
257    // Test retain the parent SingleByteBuff (duplicate)
258    ByteBuff parent = alloc.allocateOneBuffer();
259    ByteBuff child = parent.duplicate();
260    child.retain();
261    parent.release();
262    assertEquals(1, child.refCnt());
263    assertEquals(1, parent.refCnt());
264    assertEquals(1, alloc.getFreeBufferCount());
265    parent.release();
266    assertEquals(0, child.refCnt());
267    assertEquals(0, parent.refCnt());
268    assertEquals(2, alloc.getFreeBufferCount());
269
270    // Test retain parent MultiByteBuff (duplicate)
271    parent = alloc.allocate(bufSize << 1);
272    child = parent.duplicate();
273    child.retain();
274    parent.release();
275    assertEquals(1, child.refCnt());
276    assertEquals(1, parent.refCnt());
277    assertEquals(0, alloc.getFreeBufferCount());
278    parent.release();
279    assertEquals(0, child.refCnt());
280    assertEquals(0, parent.refCnt());
281    assertEquals(2, alloc.getFreeBufferCount());
282
283    // Test retain the parent SingleByteBuff (slice)
284    parent = alloc.allocateOneBuffer();
285    child = parent.slice();
286    child.retain();
287    parent.release();
288    assertEquals(1, child.refCnt());
289    assertEquals(1, parent.refCnt());
290    assertEquals(1, alloc.getFreeBufferCount());
291    parent.release();
292    assertEquals(0, child.refCnt());
293    assertEquals(0, parent.refCnt());
294    assertEquals(2, alloc.getFreeBufferCount());
295
296    // Test retain parent MultiByteBuff (slice)
297    parent = alloc.allocate(bufSize << 1);
298    child = parent.slice();
299    child.retain();
300    parent.release();
301    assertEquals(1, child.refCnt());
302    assertEquals(1, parent.refCnt());
303    assertEquals(0, alloc.getFreeBufferCount());
304    parent.release();
305    assertEquals(0, child.refCnt());
306    assertEquals(0, parent.refCnt());
307    assertEquals(2, alloc.getFreeBufferCount());
308  }
309
310  @Test
311  public void testReverseRef() {
312    int bufSize = 64;
313    ByteBuffAllocator alloc = new ByteBuffAllocator(true, 1, bufSize, 3);
314    ByteBuff buf1 = alloc.allocate(bufSize);
315    ByteBuff dup1 = buf1.duplicate();
316    assertEquals(1, buf1.refCnt());
317    assertEquals(1, dup1.refCnt());
318    buf1.release();
319    assertEquals(0, buf1.refCnt());
320    assertEquals(0, dup1.refCnt());
321    assertEquals(1, alloc.getFreeBufferCount());
322    assertException(buf1::position);
323    assertException(dup1::position);
324  }
325
326  @Test
327  public void testByteBuffUnsupportedMethods() {
328    int bufSize = 64;
329    ByteBuffAllocator alloc = new ByteBuffAllocator(true, 1, bufSize, 3);
330    ByteBuff buf = alloc.allocate(bufSize);
331    assertException(() -> buf.retain(2));
332    assertException(() -> buf.release(2));
333    assertException(() -> buf.touch());
334    assertException(() -> buf.touch(new Object()));
335  }
336
337  private void assertException(Runnable r) {
338    try {
339      r.run();
340      fail();
341    } catch (Exception e) {
342      // expected exception.
343    }
344  }
345
346  @Test
347  public void testDeprecatedConfigs() {
348    Configuration conf = new Configuration();
349    conf.setInt(ByteBuffAllocator.DEPRECATED_MAX_BUFFER_COUNT_KEY, 10);
350    conf.setInt(ByteBuffAllocator.DEPRECATED_BUFFER_SIZE_KEY, 1024);
351    ByteBuffAllocator allocator = ByteBuffAllocator.create(conf, true);
352    Assert.assertEquals(1024, allocator.getBufferSize());
353    Assert.assertEquals(10, allocator.getTotalBufferCount());
354
355    conf = new Configuration();
356    conf.setInt(ByteBuffAllocator.MAX_BUFFER_COUNT_KEY, 11);
357    conf.setInt(ByteBuffAllocator.BUFFER_SIZE_KEY, 2048);
358    allocator = ByteBuffAllocator.create(conf, true);
359    Assert.assertEquals(2048, allocator.getBufferSize());
360    Assert.assertEquals(11, allocator.getTotalBufferCount());
361
362    conf = new Configuration();
363    conf.setBoolean(ByteBuffAllocator.DEPRECATED_ALLOCATOR_POOL_ENABLED_KEY, false);
364    Assert.assertFalse(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, true));
365    conf.setBoolean(ByteBuffAllocator.DEPRECATED_ALLOCATOR_POOL_ENABLED_KEY, true);
366    Assert.assertTrue(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, false));
367    conf.setBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, true);
368    Assert.assertTrue(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, false));
369    conf.setBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, false);
370    Assert.assertFalse(conf.getBoolean(ByteBuffAllocator.ALLOCATOR_POOL_ENABLED_KEY, true));
371  }
372
373  @Test
374  public void testHeapAllocationRatio() {
375    Configuration conf = new Configuration();
376    conf.setInt(ByteBuffAllocator.MAX_BUFFER_COUNT_KEY, 11);
377    conf.setInt(ByteBuffAllocator.BUFFER_SIZE_KEY, 2048);
378    ByteBuffAllocator alloc1 = ByteBuffAllocator.create(conf, true);
379    Assert.assertEquals(getHeapAllocationRatio(alloc1), 0.0f, 1e-6);
380    alloc1.allocate(1);
381    Assert.assertEquals(getHeapAllocationRatio(alloc1), 1.0f, 1e-6);
382
383    alloc1.allocate(2048 / 6 - 1);
384    Assert.assertEquals(getHeapAllocationRatio(alloc1), 1.0f, 1e-6);
385
386    alloc1.allocate(24);
387    alloc1.allocate(1024);
388    Assert.assertEquals(getHeapAllocationRatio(alloc1), 24 / (24f + 2048), 1e-6);
389    Assert.assertEquals(getHeapAllocationRatio(alloc1), 0.0f, 1e-6);
390
391    // Allocate something from HEAP
392    HEAP.allocate(1024);
393    alloc1.allocate(24);
394    alloc1.allocate(1024);
395    Assert.assertEquals(getHeapAllocationRatio(HEAP, alloc1), (1024f + 24) / (1024f + 24 + 2048),
396      1e-6);
397    Assert.assertEquals(getHeapAllocationRatio(HEAP, alloc1), 0.0f, 1e-6);
398
399    // Check duplicated heap allocator, say even if we passed (HEAP, HEAP, alloc1), it will only
400    // caculate the allocation from (HEAP, alloc1).
401    HEAP.allocate(1024);
402    alloc1.allocate(1024);
403    Assert.assertEquals(getHeapAllocationRatio(HEAP, HEAP, alloc1), 1024f / (1024f + 2048f), 1e-6);
404  }
405}