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)

Reply via email to