Skip to content

Commit 5de1e4f

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

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
@Override

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
@@ -221,8 +221,10 @@ public void testReferenceCount() {
221221
assertEquals(0, buf2.refCnt());
222222
assertEquals(0, dup2.refCnt());
223223
assertEquals(0, alloc.getFreeBufferCount());
224-
assertException(dup2::position);
225-
assertException(buf2::position);
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();
226228

227229
// duplicate the buf1, if the dup1 released, buf1 will also be released (MultipleByteBuffer)
228230
ByteBuff dup1 = buf1.duplicate();
@@ -415,4 +417,103 @@ public void testHeapAllocationRatio() {
415417
alloc1.allocate(1024);
416418
Assert.assertEquals(getHeapAllocationRatio(HEAP, HEAP, alloc1), 1024f / (1024f + 2048f), 1e-6);
417419
}
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+
418519
}

0 commit comments

Comments
 (0)