Skip to content

Commit acda5b7

Browse files
committed
HBASE-27710 ByteBuff ref counting is too expensive for on-heap buffers (#5115)
Signed-off-by: Duo Zhang <[email protected]>
1 parent 5f2f73d commit acda5b7

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

hbase-common/src/main/java/org/apache/hadoop/hbase/nio/ByteBuff.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,17 @@ public abstract class ByteBuff implements HBaseReferenceCounted {
6565

6666
/*************************** Methods for reference count **********************************/
6767

68+
/**
69+
* Checks that there are still references to the buffer. This protects against the case where a
70+
* ByteBuff method (i.e. slice, get, etc) could be called against a buffer whose backing data may
71+
* have been released. We only need to do this check if the refCnt has a recycler. If there's no
72+
* recycler, the backing data will be handled by normal java GC and won't get incorrectly
73+
* released. So we can avoid the overhead of checking the refCnt on every call. See HBASE-27710.
74+
*/
6875
protected void checkRefCount() {
69-
ObjectUtil.checkPositive(refCnt(), REFERENCE_COUNT_NAME);
76+
if (refCnt.hasRecycler()) {
77+
ObjectUtil.checkPositive(refCnt(), REFERENCE_COUNT_NAME);
78+
}
7079
}
7180

7281
public int refCnt() {

hbase-common/src/main/java/org/apache/hadoop/hbase/nio/RefCnt.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ public RefCnt(Recycler recycler) {
5959
this.leak = recycler == ByteBuffAllocator.NONE ? null : detector.track(this);
6060
}
6161

62+
/**
63+
* Returns true if this refCnt has a recycler.
64+
*/
65+
public boolean hasRecycler() {
66+
return recycler != ByteBuffAllocator.NONE;
67+
}
68+
6269
@Override
6370
public ReferenceCounted retain() {
6471
maybeRecord();

hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestByteBuffAllocator.java

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,10 @@ public void testReferenceCount() {
220220
assertEquals(0, buf2.refCnt());
221221
assertEquals(0, dup2.refCnt());
222222
assertEquals(0, alloc.getFreeBufferCount());
223-
assertException(dup2::position);
224-
assertException(buf2::position);
223+
// these should not throw an exception because they are heap buffers.
224+
// off-heap buffers would throw an exception if trying to call a method once released.
225+
dup2.position();
226+
buf2.position();
225227

226228
// duplicate the buf1, if the dup1 released, buf1 will also be released (MultipleByteBuffer)
227229
ByteBuff dup1 = buf1.duplicate();
@@ -414,4 +416,103 @@ public void testHeapAllocationRatio() {
414416
alloc1.allocate(1024);
415417
Assert.assertEquals(getHeapAllocationRatio(HEAP, HEAP, alloc1), 1024f / (1024f + 2048f), 1e-6);
416418
}
419+
420+
/**
421+
* Tests that we only call the logic in checkRefCount for ByteBuff's that have a non-NONE
422+
* recycler.
423+
*/
424+
@Test
425+
public void testCheckRefCountOnlyWithRecycler() {
426+
TrackingSingleByteBuff singleBuff = new TrackingSingleByteBuff(ByteBuffer.allocate(1));
427+
singleBuff.get();
428+
assertEquals(1, singleBuff.getCheckRefCountCalls());
429+
assertEquals(0, singleBuff.getRefCntCalls());
430+
singleBuff = new TrackingSingleByteBuff(() -> {
431+
// do nothing, just so we dont use NONE recycler
432+
}, ByteBuffer.allocate(1));
433+
singleBuff.get();
434+
assertEquals(1, singleBuff.getCheckRefCountCalls());
435+
assertEquals(1, singleBuff.getRefCntCalls());
436+
437+
TrackingMultiByteBuff multiBuff = new TrackingMultiByteBuff(ByteBuffer.allocate(1));
438+
439+
multiBuff.get();
440+
assertEquals(1, multiBuff.getCheckRefCountCalls());
441+
assertEquals(0, multiBuff.getRefCntCalls());
442+
multiBuff = new TrackingMultiByteBuff(() -> {
443+
// do nothing, just so we dont use NONE recycler
444+
}, ByteBuffer.allocate(1));
445+
multiBuff.get();
446+
assertEquals(1, multiBuff.getCheckRefCountCalls());
447+
assertEquals(1, multiBuff.getRefCntCalls());
448+
}
449+
450+
private static class TrackingSingleByteBuff extends SingleByteBuff {
451+
452+
int refCntCalls = 0;
453+
int checkRefCountCalls = 0;
454+
455+
public TrackingSingleByteBuff(ByteBuffer buf) {
456+
super(buf);
457+
}
458+
459+
public TrackingSingleByteBuff(ByteBuffAllocator.Recycler recycler, ByteBuffer buf) {
460+
super(recycler, buf);
461+
}
462+
463+
public int getRefCntCalls() {
464+
return refCntCalls;
465+
}
466+
467+
public int getCheckRefCountCalls() {
468+
return checkRefCountCalls;
469+
}
470+
471+
@Override
472+
protected void checkRefCount() {
473+
checkRefCountCalls++;
474+
super.checkRefCount();
475+
}
476+
477+
@Override
478+
public int refCnt() {
479+
refCntCalls++;
480+
return super.refCnt();
481+
}
482+
}
483+
484+
private static class TrackingMultiByteBuff extends MultiByteBuff {
485+
486+
int refCntCalls = 0;
487+
int checkRefCountCalls = 0;
488+
489+
public TrackingMultiByteBuff(ByteBuffer... items) {
490+
super(items);
491+
}
492+
493+
public TrackingMultiByteBuff(ByteBuffAllocator.Recycler recycler, ByteBuffer... items) {
494+
super(recycler, items);
495+
}
496+
497+
public int getRefCntCalls() {
498+
return refCntCalls;
499+
}
500+
501+
public int getCheckRefCountCalls() {
502+
return checkRefCountCalls;
503+
}
504+
505+
@Override
506+
protected void checkRefCount() {
507+
checkRefCountCalls++;
508+
super.checkRefCount();
509+
}
510+
511+
@Override
512+
public int refCnt() {
513+
refCntCalls++;
514+
return super.refCnt();
515+
}
516+
}
517+
417518
}

0 commit comments

Comments
 (0)