Minwoo Kang created HBASE-30285:
-----------------------------------
Summary: BucketCache shutdown/disable does not release
backingMap-owned BucketEntry references
Key: HBASE-30285
URL: https://issues.apache.org/jira/browse/HBASE-30285
Project: HBase
Issue Type: Bug
Reporter: Minwoo Kang
Assignee: Minwoo Kang
h2. Description
{{BucketCache}} does not release the reference owned by {{backingMap}} when the
cache is shut down or disabled.
A newly created {{BucketEntry}} has an initial reference count of 1. This
reference is owned by {{BucketCache.backingMap}}.
For shared-memory IO engines, {{BucketCache.getBlock()}} retains the same
{{RefCnt}} before returning a cached block. After the caller releases the
returned block, the backing map reference remains with a count of 1.
Normal eviction removes the map entry under the offset write lock and calls
{{blockEvicted()}}. {{blockEvicted()}} calls {{BucketEntry.markAsEvicted()}},
which releases the backing map reference.
The non-persistent path in {{BucketCache.disableCache()}} instead calls
{{backingMap.clear()}} directly. This removes the map references without
calling {{markAsEvicted()}}. Each affected {{BucketEntry}} therefore retains a
reference count of 1.
The persistent shutdown path needs the backing map for serialization, but it
also does not release the owner references after serialization completes
h2. Reproduction
Add the following deterministic test to
{{hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/bucket/TestBucketCacheRefCnt.java}}.
{code:java}
@Test
public void testShutdownReleasesBackingMapReference() throws Exception {
ByteBuffAllocator alloc =
ByteBuffAllocator.create(HBaseConfiguration.create(), true);
HFileBlock blockToCache = createBlock(200, 1020, alloc);
HFileBlock blockFromCache = null;
try {
cache = create(1, 1000);
BlockCacheKey key = createKey("testShutdownReleasesBackingMapReference",
200);
cache.cacheBlock(key, blockToCache);
waitUntilFlushedToCache(cache, key);
blockFromCache = (HFileBlock) cache.getBlock(key, false, false, false);
assertNotNull(blockFromCache);
assertEquals(2, blockFromCache.refCnt());
blockFromCache.release();
assertEquals(1, blockFromCache.refCnt());
cache.shutdown();
cache = null;
assertEquals(0, blockFromCache.refCnt(),
"BucketCache.shutdown must release the backingMap reference for every
BucketEntry");
} finally {
if (cache != null) {
cache.shutdown();
}
if (blockFromCache != null) {
while (blockFromCache.refCnt() > 0) {
blockFromCache.release();
}
}
while (blockToCache.refCnt() > 0) {
blockToCache.release();
}
alloc.clean();
}
}
{code}
h2. Expected behavior
{{BucketCache}} should release each reference owned by {{backingMap}} exactly
once when the entry is discarded.
References held by in-flight callers must remain valid until those callers
release them.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)