Skip to content

HBASE-26281 DBB got from BucketCache would be freed unexpectedly befo… #3680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;

import org.apache.hadoop.hbase.io.ByteBuffAllocator;
import org.apache.hadoop.hbase.io.ByteBuffAllocator.Recycler;
import org.apache.hadoop.hbase.io.hfile.BlockPriority;
import org.apache.hadoop.hbase.io.hfile.Cacheable;
import org.apache.hadoop.hbase.io.hfile.CacheableDeserializer;
Expand Down Expand Up @@ -88,16 +90,21 @@ class BucketEntry implements HBaseReferenceCounted {
private final long cachedTime = System.nanoTime();

BucketEntry(long offset, int length, long accessCounter, boolean inMemory) {
this(offset, length, accessCounter, inMemory, RefCnt.create(), ByteBuffAllocator.HEAP);
this(offset, length, accessCounter, inMemory, null, ByteBuffAllocator.HEAP);
}

BucketEntry(long offset, int length, long accessCounter, boolean inMemory, RefCnt refCnt,
BucketEntry(long offset, int length, long accessCounter, boolean inMemory,
Function<BucketEntry, Recycler> createRecycler,
ByteBuffAllocator allocator) {
setOffset(offset);
this.length = length;
this.accessCounter = accessCounter;
this.priority = inMemory ? BlockPriority.MEMORY : BlockPriority.MULTI;
this.refCnt = refCnt;
if (createRecycler == null) {
this.refCnt = RefCnt.create();
} else {
this.refCnt = RefCnt.create(createRecycler.apply(this));
}
this.markedAsEvicted = new AtomicBoolean(false);
this.allocator = allocator;
}
Expand Down Expand Up @@ -165,19 +172,6 @@ boolean markAsEvicted() {
return false;
}

/**
* Mark as evicted only when NO RPC references. Mainly used for eviction when cache size exceed
* the max acceptable size.
* @return true if we deallocate this entry successfully.
*/
boolean markStaleAsEvicted() {
if (!markedAsEvicted.get() && this.refCnt() == 1) {
// The only reference was coming from backingMap, now release the stale entry.
return this.markAsEvicted();
}
return false;
}

/**
* Check whether have some RPC patch referring this block. There're two case: <br>
* 1. If current refCnt is greater than 1, there must be at least one referring RPC path; <br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.hadoop.hbase.client;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -43,6 +44,8 @@
import org.apache.hadoop.hbase.io.hfile.BlockCacheKey;
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
import org.apache.hadoop.hbase.io.hfile.CachedBlock;
import org.apache.hadoop.hbase.io.hfile.CombinedBlockCache;
import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
import org.apache.hadoop.hbase.regionserver.DelegatingInternalScanner;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.HStore;
Expand Down Expand Up @@ -372,7 +375,11 @@ public void run() {
CachedBlock next = iterator.next();
BlockCacheKey cacheKey = new BlockCacheKey(next.getFilename(), next.getOffset());
cacheList.add(cacheKey);
cache.evictBlock(cacheKey);
/**
* There is only one Block referenced by rpc,here we evict blocks which have no rpc
* referenced.
*/
evictBlock(cache, cacheKey);
}
try {
Thread.sleep(1);
Expand Down Expand Up @@ -434,4 +441,20 @@ public void run() {
assertEquals("Count should give all rows ", 10, count);
}
}

/**
* For {@link BucketCache},we only evict Block if there is no rpc referenced.
*/
private void evictBlock(BlockCache blockCache, BlockCacheKey blockCacheKey) {
assertTrue(blockCache instanceof CombinedBlockCache);
BlockCache[] blockCaches = blockCache.getBlockCaches();
for (BlockCache currentBlockCache : blockCaches) {
if (currentBlockCache instanceof BucketCache) {
((BucketCache) currentBlockCache).evictBlockIfNoRpcReferenced(blockCacheKey);
} else {
currentBlockCache.evictBlock(blockCacheKey);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,26 @@ public void run() {
assertEquals(1, cache.getBlockCount());
lock.writeLock().unlock();
evictThread.join();
assertEquals(0, cache.getBlockCount());
assertEquals(cache.getCurrentSize(), 0L);
/**
* <pre>
* The asserts here before HBASE-21957 are:
* assertEquals(1L, cache.getBlockCount());
* assertTrue(cache.getCurrentSize() > 0L);
* assertTrue("We should have a block!", cache.iterator().hasNext());
*
* The asserts here after HBASE-21957 are:
* assertEquals(0, cache.getBlockCount());
* assertEquals(cache.getCurrentSize(), 0L);
*
* I think the asserts before HBASE-21957 is more reasonable,because
* {@link BucketCache#evictBlock} should only evict the {@link BucketEntry}
* it had seen, and newly added Block after the {@link BucketEntry}
* it had seen should not be evicted.
* </pre>
*/
assertEquals(1L, cache.getBlockCount());
assertTrue(cache.getCurrentSize() > 0L);
assertTrue("We should have a block!", cache.iterator().hasNext());
}

@Test
Expand Down Expand Up @@ -613,8 +631,8 @@ public void testRAMCache() {
HFileBlock.FILL_HEADER, -1, 52, -1, meta, ByteBuffAllocator.HEAP);
HFileBlock blk2 = new HFileBlock(BlockType.DATA, size, size, -1, ByteBuff.wrap(buf),
HFileBlock.FILL_HEADER, -1, -1, -1, meta, ByteBuffAllocator.HEAP);
RAMQueueEntry re1 = new RAMQueueEntry(key1, blk1, 1, false, ByteBuffAllocator.NONE);
RAMQueueEntry re2 = new RAMQueueEntry(key1, blk2, 1, false, ByteBuffAllocator.NONE);
RAMQueueEntry re1 = new RAMQueueEntry(key1, blk1, 1, false);
RAMQueueEntry re2 = new RAMQueueEntry(key1, blk2, 1, false);

assertFalse(cache.containsKey(key1));
assertNull(cache.putIfAbsent(key1, re1));
Expand Down Expand Up @@ -661,11 +679,11 @@ public void testFreeBlockWhenIOEngineWriteFailure() throws IOException {
BucketAllocator allocator = new BucketAllocator(availableSpace, null);

BlockCacheKey key = new BlockCacheKey("dummy", 1L);
RAMQueueEntry re = new RAMQueueEntry(key, block, 1, true, ByteBuffAllocator.NONE);
RAMQueueEntry re = new RAMQueueEntry(key, block, 1, true);

Assert.assertEquals(0, allocator.getUsedSize());
try {
re.writeToCache(ioEngine, allocator, null);
re.writeToCache(ioEngine, allocator, null, null);
Assert.fail();
} catch (Exception e) {
}
Expand Down
Loading