[ 
https://issues.apache.org/jira/browse/HBASE-26281?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

chenglei updated HBASE-26281:
-----------------------------
    Description: 
{{DBB}} got from BucketCache would be freed unexpectedly before RPC completed 
for following two cases:
# Because of splitting or stream read,two or more read threads may try to cache 
the same {{Block}} concurrently and one thread read all of the next block 
header could replace  the another one didn't(see HBASE-20447).  When replacing 
{{Block}},the following {{BucketCache.WriterThread.putIntoBackingMap}} method 
invokes {{BucketCache.blockEvicted}} in line 916,which free the {{BucketEntry}} 
directly in line 544, not through the {{RefCount}}.
{code:java}
912  private void putIntoBackingMap(BlockCacheKey key, BucketEntry bucketEntry) 
{
913      BucketEntry previousEntry = backingMap.put(key, bucketEntry);
914      if (previousEntry != null && previousEntry != bucketEntry) {
915        previousEntry.withWriteLock(offsetLock, () -> {
916          blockEvicted(key, previousEntry, false);
917          return null;
918        });
919      }
920    }


543  void blockEvicted(BlockCacheKey cacheKey, BucketEntry bucketEntry, boolean 
decrementBlockNumber) {
544     bucketAllocator.freeBlock(bucketEntry.offset());
545     realCacheSize.add(-1 * bucketEntry.getLength());
546     blocksByHFile.remove(cacheKey);
547     if (decrementBlockNumber) {
548         this.blockNumber.decrement();
549      }
550  }
  
{code}
Freeing the {{BucketEntry}} directly may cause {{HFileBlock}} returned from 
{{BucketCache.getBlock}} be freed unexpectedly before RPC completed.HBASE-26155 
fixed this problem to some extent, but not completely. The  
{{BucketCache.getBlock}} might be invoked just before 
{{BucketCache.cacheBlockWithWaitInternal}} and after the 
{{BucketEntry.isRpcRef}} checking, considering the following sequence:
      1. Block1 was cached successfully,the {{RefCnt}} of Block1 is 1.
      2.Thread1 caching the same {{BlockCacheKey}} with Block2 satisfied 
{{BlockCacheUtil.shouldReplaceExistingCacheBlock}}, so Block2 would replace 
Block1, but thread1 stopped before {{BucketCache.cacheBlockWithWaitInternal}}
     3. Thread2 invoking {{BucketCache.getBlock}} with the  same 
{{BlockCacheKey}}, which returned Block1, the {{RefCnt}} of Block1 is 2.
     4.  Thread1 continues caching Block2, in  
{{BucketCache.WriterThread.putIntoBackingMap}} , the old Block1 is freed 
directly which {{RefCnt}}  is 2. The Block1 is still used by Thread2 and the 
content of Block1 would be overwitten  after it is freed, which may cause a 
serious error.

 


  was:
{{DBB}} got from BucketCache would be freed unexpectedly before RPC completed 
for following two cases:
# Because of splitting or stream read,two or more read threads may try to cache 
the same block concurrently and one thread read all of the next block header 
could replace  the another one didn't(see HBASE-20447).  When replacing 
{{Block}},the following {{BucketCache.WriterThread.putIntoBackingMap}} method 
invokes {{BucketCache.blockEvicted}},which free
the {{BucketEntry}} directly in line 544, not through the {{RefCount}}
{code:java}
912  private void putIntoBackingMap(BlockCacheKey key, BucketEntry bucketEntry) 
{
913      BucketEntry previousEntry = backingMap.put(key, bucketEntry);
914      if (previousEntry != null && previousEntry != bucketEntry) {
915        previousEntry.withWriteLock(offsetLock, () -> {
          blockEvicted(key, previousEntry, false);
          return null;
        });
      }
    }


543  void blockEvicted(BlockCacheKey cacheKey, BucketEntry bucketEntry, boolean 
decrementBlockNumber) {
544     bucketAllocator.freeBlock(bucketEntry.offset());
545     realCacheSize.add(-1 * bucketEntry.getLength());
546     blocksByHFile.remove(cacheKey);
547     if (decrementBlockNumber) {
548         this.blockNumber.decrement();
549      }
550  }
  
{code}


> DBB got from BucketCache would be freed unexpectedly before RPC completed
> -------------------------------------------------------------------------
>
>                 Key: HBASE-26281
>                 URL: https://issues.apache.org/jira/browse/HBASE-26281
>             Project: HBase
>          Issue Type: Bug
>          Components: BucketCache
>    Affects Versions: 3.0.0-alpha-1, 2.4.6
>            Reporter: chenglei
>            Priority: Critical
>
> {{DBB}} got from BucketCache would be freed unexpectedly before RPC completed 
> for following two cases:
> # Because of splitting or stream read,two or more read threads may try to 
> cache the same {{Block}} concurrently and one thread read all of the next 
> block header could replace  the another one didn't(see HBASE-20447).  When 
> replacing {{Block}},the following 
> {{BucketCache.WriterThread.putIntoBackingMap}} method invokes 
> {{BucketCache.blockEvicted}} in line 916,which free the {{BucketEntry}} 
> directly in line 544, not through the {{RefCount}}.
> {code:java}
> 912  private void putIntoBackingMap(BlockCacheKey key, BucketEntry 
> bucketEntry) {
> 913      BucketEntry previousEntry = backingMap.put(key, bucketEntry);
> 914      if (previousEntry != null && previousEntry != bucketEntry) {
> 915        previousEntry.withWriteLock(offsetLock, () -> {
> 916          blockEvicted(key, previousEntry, false);
> 917          return null;
> 918        });
> 919      }
> 920    }
> 543  void blockEvicted(BlockCacheKey cacheKey, BucketEntry bucketEntry, 
> boolean decrementBlockNumber) {
> 544     bucketAllocator.freeBlock(bucketEntry.offset());
> 545     realCacheSize.add(-1 * bucketEntry.getLength());
> 546     blocksByHFile.remove(cacheKey);
> 547     if (decrementBlockNumber) {
> 548         this.blockNumber.decrement();
> 549      }
> 550  }
>   
> {code}
> Freeing the {{BucketEntry}} directly may cause {{HFileBlock}} returned from 
> {{BucketCache.getBlock}} be freed unexpectedly before RPC 
> completed.HBASE-26155 fixed this problem to some extent, but not completely. 
> The  {{BucketCache.getBlock}} might be invoked just before 
> {{BucketCache.cacheBlockWithWaitInternal}} and after the 
> {{BucketEntry.isRpcRef}} checking, considering the following sequence:
>       1. Block1 was cached successfully,the {{RefCnt}} of Block1 is 1.
>       2.Thread1 caching the same {{BlockCacheKey}} with Block2 satisfied 
> {{BlockCacheUtil.shouldReplaceExistingCacheBlock}}, so Block2 would replace 
> Block1, but thread1 stopped before {{BucketCache.cacheBlockWithWaitInternal}}
>      3. Thread2 invoking {{BucketCache.getBlock}} with the  same 
> {{BlockCacheKey}}, which returned Block1, the {{RefCnt}} of Block1 is 2.
>      4.  Thread1 continues caching Block2, in  
> {{BucketCache.WriterThread.putIntoBackingMap}} , the old Block1 is freed 
> directly which {{RefCnt}}  is 2. The Block1 is still used by Thread2 and the 
> content of Block1 would be overwitten  after it is freed, which may cause a 
> serious error.
>  



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to