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}