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}