001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.io.hfile.bucket; 019 020import static org.apache.hadoop.hbase.io.hfile.CacheConfig.BUCKETCACHE_PERSIST_INTERVAL_KEY; 021import static org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.DEFAULT_ERROR_TOLERATION_DURATION; 022import static org.junit.jupiter.api.Assertions.assertArrayEquals; 023import static org.junit.jupiter.api.Assertions.assertEquals; 024import static org.junit.jupiter.api.Assertions.assertFalse; 025import static org.junit.jupiter.api.Assertions.assertNotNull; 026import static org.junit.jupiter.api.Assertions.assertNull; 027import static org.junit.jupiter.api.Assertions.assertTrue; 028 029import java.io.DataInputStream; 030import java.io.File; 031import java.io.FileInputStream; 032import java.io.IOException; 033import java.nio.ByteBuffer; 034import java.nio.channels.FileChannel; 035import java.nio.file.Files; 036import java.util.Arrays; 037import java.util.List; 038import java.util.concurrent.CountDownLatch; 039import java.util.concurrent.CyclicBarrier; 040import java.util.concurrent.ExecutorService; 041import java.util.concurrent.Executors; 042import java.util.concurrent.Future; 043import java.util.concurrent.TimeUnit; 044import java.util.concurrent.atomic.AtomicInteger; 045import java.util.concurrent.atomic.AtomicReference; 046import org.apache.hadoop.conf.Configuration; 047import org.apache.hadoop.fs.Path; 048import org.apache.hadoop.hbase.HBaseConfiguration; 049import org.apache.hadoop.hbase.Waiter; 050import org.apache.hadoop.hbase.io.ByteBuffAllocator; 051import org.apache.hadoop.hbase.io.hfile.BlockCacheKey; 052import org.apache.hadoop.hbase.io.hfile.BlockCacheUtil; 053import org.apache.hadoop.hbase.io.hfile.BlockType; 054import org.apache.hadoop.hbase.io.hfile.Cacheable; 055import org.apache.hadoop.hbase.io.hfile.HFileBlock; 056import org.apache.hadoop.hbase.io.hfile.HFileContext; 057import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder; 058import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.WriterThread; 059import org.apache.hadoop.hbase.nio.ByteBuff; 060import org.apache.hadoop.hbase.nio.RefCnt; 061import org.apache.hadoop.hbase.testclassification.IOTests; 062import org.apache.hadoop.hbase.testclassification.SmallTests; 063import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 064import org.apache.hadoop.hbase.util.ManualEnvironmentEdge; 065import org.junit.jupiter.api.Disabled; 066import org.junit.jupiter.api.Tag; 067import org.junit.jupiter.api.Test; 068import org.junit.jupiter.api.io.TempDir; 069import org.mockito.Mockito; 070 071import org.apache.hadoop.hbase.shaded.protobuf.generated.BucketCacheProtos; 072 073@Tag(IOTests.TAG) 074@Tag(SmallTests.TAG) 075public class TestBucketCacheRefCnt { 076 077 private static final String IO_ENGINE = "offheap"; 078 private static final long CAPACITY_SIZE = 32 * 1024 * 1024; 079 private static final int BLOCK_SIZE = 1024; 080 private static final int[] BLOCK_SIZE_ARRAY = 081 new int[] { 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; 082 private static final String PERSISTENCE_PATH = null; 083 private static final HFileContext CONTEXT = new HFileContextBuilder().build(); 084 085 private BucketCache cache; 086 087 private static BucketCache create(int writerSize, int queueSize) throws IOException { 088 return new BucketCache(IO_ENGINE, CAPACITY_SIZE, BLOCK_SIZE, BLOCK_SIZE_ARRAY, writerSize, 089 queueSize, PERSISTENCE_PATH); 090 } 091 092 private static MyBucketCache createMyBucketCache(int writerSize, int queueSize) 093 throws IOException { 094 return new MyBucketCache(IO_ENGINE, CAPACITY_SIZE, BLOCK_SIZE, BLOCK_SIZE_ARRAY, writerSize, 095 queueSize, PERSISTENCE_PATH); 096 } 097 098 private static MyBucketCache2 createMyBucketCache2(int writerSize, int queueSize) 099 throws IOException { 100 return new MyBucketCache2(IO_ENGINE, CAPACITY_SIZE, BLOCK_SIZE, BLOCK_SIZE_ARRAY, writerSize, 101 queueSize, PERSISTENCE_PATH); 102 } 103 104 private static HFileBlock createBlock(int offset, int size) { 105 return createBlock(offset, size, ByteBuffAllocator.HEAP); 106 } 107 108 private static HFileBlock createBlock(int offset, int size, ByteBuffAllocator alloc) { 109 return new HFileBlock(BlockType.DATA, size, size, -1, ByteBuff.wrap(ByteBuffer.allocate(size)), 110 HFileBlock.FILL_HEADER, offset, 52, size, CONTEXT, alloc); 111 } 112 113 private static BlockCacheKey createKey(String hfileName, long offset) { 114 return new BlockCacheKey(hfileName, offset); 115 } 116 117 private void disableWriter() { 118 if (cache != null) { 119 for (WriterThread wt : cache.writerThreads) { 120 wt.disableWriter(); 121 wt.interrupt(); 122 } 123 } 124 } 125 126 @Disabled 127 @Test // Disabled by HBASE-24079. Reenable issue HBASE-24082 128 // Flakey TestBucketCacheRefCnt.testBlockInRAMCache:121 expected:<3> but was:<2> 129 public void testBlockInRAMCache() throws IOException { 130 cache = create(1, 1000); 131 disableWriter(); 132 final String prefix = "testBlockInRamCache"; 133 try { 134 for (int i = 0; i < 10; i++) { 135 HFileBlock blk = createBlock(i, 1020); 136 BlockCacheKey key = createKey(prefix, i); 137 assertEquals(1, blk.refCnt()); 138 cache.cacheBlock(key, blk); 139 assertEquals(i + 1, cache.getBlockCount()); 140 assertEquals(2, blk.refCnt()); 141 142 Cacheable block = cache.getBlock(key, false, false, false); 143 try { 144 assertEquals(3, blk.refCnt()); 145 assertEquals(3, block.refCnt()); 146 assertEquals(blk, block); 147 } finally { 148 block.release(); 149 } 150 assertEquals(2, blk.refCnt()); 151 assertEquals(2, block.refCnt()); 152 } 153 154 for (int i = 0; i < 10; i++) { 155 BlockCacheKey key = createKey(prefix, i); 156 Cacheable blk = cache.getBlock(key, false, false, false); 157 assertEquals(3, blk.refCnt()); 158 assertFalse(blk.release()); 159 assertEquals(2, blk.refCnt()); 160 161 assertTrue(cache.evictBlock(key)); 162 assertEquals(1, blk.refCnt()); 163 assertTrue(blk.release()); 164 assertEquals(0, blk.refCnt()); 165 } 166 } finally { 167 cache.shutdown(); 168 } 169 } 170 171 private static void waitUntilFlushedToCache(BucketCache bucketCache, BlockCacheKey blockCacheKey) 172 throws InterruptedException { 173 while ( 174 !bucketCache.backingMap.containsKey(blockCacheKey) 175 || bucketCache.ramCache.containsKey(blockCacheKey) 176 ) { 177 Thread.sleep(100); 178 } 179 Thread.sleep(1000); 180 } 181 182 @Test 183 public void testBlockInBackingMap() throws Exception { 184 ByteBuffAllocator alloc = ByteBuffAllocator.create(HBaseConfiguration.create(), true); 185 cache = create(1, 1000); 186 try { 187 HFileBlock blk = createBlock(200, 1020, alloc); 188 BlockCacheKey key = createKey("testHFile-00", 200); 189 cache.cacheBlock(key, blk); 190 waitUntilFlushedToCache(cache, key); 191 assertEquals(1, blk.refCnt()); 192 193 Cacheable block = cache.getBlock(key, false, false, false); 194 assertTrue(block instanceof HFileBlock); 195 assertTrue(((HFileBlock) block).getByteBuffAllocator() == alloc); 196 assertEquals(2, block.refCnt()); 197 198 block.retain(); 199 assertEquals(3, block.refCnt()); 200 201 Cacheable newBlock = cache.getBlock(key, false, false, false); 202 assertTrue(newBlock instanceof HFileBlock); 203 assertTrue(((HFileBlock) newBlock).getByteBuffAllocator() == alloc); 204 assertEquals(4, newBlock.refCnt()); 205 206 // release the newBlock 207 assertFalse(newBlock.release()); 208 assertEquals(3, newBlock.refCnt()); 209 assertEquals(3, block.refCnt()); 210 211 // Evict the key 212 cache.evictBlock(key); 213 assertEquals(2, block.refCnt()); 214 215 // Evict again, shouldn't change the refCnt. 216 cache.evictBlock(key); 217 assertEquals(2, block.refCnt()); 218 219 assertFalse(block.release()); 220 assertEquals(1, block.refCnt()); 221 222 /** 223 * The key was evicted from {@link BucketCache#backingMap} and {@link BucketCache#ramCache}, 224 * so {@link BucketCache#getBlock} return null. 225 */ 226 Cacheable newestBlock = cache.getBlock(key, false, false, false); 227 assertNull(newestBlock); 228 assertEquals(1, block.refCnt()); 229 assertTrue(((HFileBlock) newBlock).getByteBuffAllocator() == alloc); 230 231 // Release the block 232 assertTrue(block.release()); 233 assertEquals(0, block.refCnt()); 234 assertEquals(0, newBlock.refCnt()); 235 } finally { 236 cache.shutdown(); 237 } 238 } 239 240 @Test 241 public void testInBucketCache() throws IOException { 242 ByteBuffAllocator alloc = ByteBuffAllocator.create(HBaseConfiguration.create(), true); 243 cache = create(1, 1000); 244 try { 245 HFileBlock blk = createBlock(200, 1020, alloc); 246 BlockCacheKey key = createKey("testHFile-00", 200); 247 cache.cacheBlock(key, blk); 248 assertTrue(blk.refCnt() == 1 || blk.refCnt() == 2); 249 250 // wait for block to move to backing map because refCnt get refreshed once block moves to 251 // backing map 252 Waiter.waitFor(HBaseConfiguration.create(), 12000, () -> isRamCacheDrained(key, cache)); 253 254 Cacheable block1 = cache.getBlock(key, false, false, false); 255 assertTrue(block1.refCnt() >= 2); 256 assertTrue(((HFileBlock) block1).getByteBuffAllocator() == alloc); 257 258 Cacheable block2 = cache.getBlock(key, false, false, false); 259 assertTrue(((HFileBlock) block2).getByteBuffAllocator() == alloc); 260 assertTrue(block2.refCnt() >= 3); 261 262 cache.evictBlock(key); 263 assertTrue(blk.refCnt() >= 1); 264 assertTrue(block1.refCnt() >= 2); 265 assertTrue(block2.refCnt() >= 2); 266 267 // Get key again 268 Cacheable block3 = cache.getBlock(key, false, false, false); 269 if (block3 != null) { 270 assertTrue(((HFileBlock) block3).getByteBuffAllocator() == alloc); 271 assertTrue(block3.refCnt() >= 3); 272 assertFalse(block3.release()); 273 } 274 275 blk.release(); 276 boolean ret1 = block1.release(); 277 boolean ret2 = block2.release(); 278 assertTrue(ret1 || ret2); 279 assertEquals(0, blk.refCnt()); 280 assertEquals(0, block1.refCnt()); 281 assertEquals(0, block2.refCnt()); 282 } finally { 283 cache.shutdown(); 284 } 285 } 286 287 private boolean isRamCacheDrained(BlockCacheKey key, BucketCache cache) { 288 return cache.backingMap.containsKey(key) && !cache.ramCache.containsKey(key); 289 } 290 291 @Test 292 public void testMarkStaleAsEvicted() throws Exception { 293 cache = create(1, 1000); 294 try { 295 HFileBlock blk = createBlock(200, 1020); 296 BlockCacheKey key = createKey("testMarkStaleAsEvicted", 200); 297 cache.cacheBlock(key, blk); 298 waitUntilFlushedToCache(cache, key); 299 assertEquals(1, blk.refCnt()); 300 assertNotNull(cache.backingMap.get(key)); 301 assertEquals(1, cache.backingMap.get(key).refCnt()); 302 303 // RPC reference this cache. 304 Cacheable block1 = cache.getBlock(key, false, false, false); 305 assertEquals(2, block1.refCnt()); 306 BucketEntry be1 = cache.backingMap.get(key); 307 assertNotNull(be1); 308 assertEquals(2, be1.refCnt()); 309 310 // We've some RPC reference, so it won't have any effect. 311 assertFalse(cache.evictBucketEntryIfNoRpcReferenced(key, be1)); 312 assertEquals(2, block1.refCnt()); 313 assertEquals(2, cache.backingMap.get(key).refCnt()); 314 315 // Release the RPC reference. 316 block1.release(); 317 assertEquals(1, block1.refCnt()); 318 assertEquals(1, cache.backingMap.get(key).refCnt()); 319 320 // Mark the stale as evicted again, it'll do the de-allocation. 321 assertTrue(cache.evictBucketEntryIfNoRpcReferenced(key, be1)); 322 assertEquals(0, block1.refCnt()); 323 assertNull(cache.backingMap.get(key)); 324 assertEquals(0, cache.size()); 325 } finally { 326 cache.shutdown(); 327 } 328 } 329 330 @Test 331 public void testShutdownReleasesOnlyBackingMapReference() throws Exception { 332 ByteBuffAllocator allocator = ByteBuffAllocator.create(HBaseConfiguration.create(), true); 333 HFileBlock blockToCache = createBlock(200, 1020, allocator); 334 HFileBlock retainedBlock = null; 335 try { 336 cache = create(1, 1000); 337 BlockCacheKey key = createKey("testShutdownReleasesOnlyBackingMapReference", 200); 338 cache.cacheBlock(key, blockToCache); 339 waitUntilFlushedToCache(cache, key); 340 341 retainedBlock = (HFileBlock) cache.getBlock(key, false, false, false); 342 assertNotNull(retainedBlock); 343 assertEquals(2, retainedBlock.refCnt()); 344 345 HFileBlock callerReference = retainedBlock; 346 cache.shutdown(); 347 cache.shutdown(); 348 349 Waiter.waitFor(HBaseConfiguration.create(), 10000, 350 () -> cache.backingMap.isEmpty() && callerReference.refCnt() == 1); 351 assertEquals(1, retainedBlock.refCnt()); 352 assertTrue(retainedBlock.release()); 353 assertEquals(0, retainedBlock.refCnt()); 354 } finally { 355 if (cache != null) { 356 cache.shutdown(); 357 cache = null; 358 } 359 if (retainedBlock != null) { 360 while (retainedBlock.refCnt() > 0) { 361 retainedBlock.release(); 362 } 363 } 364 while (blockToCache.refCnt() > 0) { 365 blockToCache.release(); 366 } 367 allocator.clean(); 368 } 369 } 370 371 @Test 372 public void testShutdownPersistsEmptyMapOverPreviousCheckpoint(@TempDir File testDir) 373 throws Exception { 374 HFileBlock blockToCache = createBlock(200, 1020); 375 BucketCache bucketCache = null; 376 BucketCache recoveredCache = null; 377 String cachePath = new File(testDir, "bucket.cache").getAbsolutePath(); 378 String persistencePath = new File(testDir, "bucket.persistence").getAbsolutePath(); 379 BlockCacheKey key = createKey("testShutdownPersistsEmptyMapOverPreviousCheckpoint", 200); 380 Configuration conf = HBaseConfiguration.create(); 381 conf.setLong(BUCKETCACHE_PERSIST_INTERVAL_KEY, Long.MAX_VALUE); 382 try { 383 bucketCache = new BucketCache("file:" + cachePath, CAPACITY_SIZE, BLOCK_SIZE, 384 BLOCK_SIZE_ARRAY, 1, 1000, persistencePath, DEFAULT_ERROR_TOLERATION_DURATION, conf); 385 assertTrue(bucketCache.waitForCacheInitialization(10000)); 386 bucketCache.cacheBlock(key, blockToCache); 387 waitUntilFlushedToCache(bucketCache, key); 388 389 bucketCache.persistToFile(); 390 assertTrue(new File(persistencePath).isFile()); 391 assertTrue(bucketCache.backingMap.containsKey(key)); 392 assertTrue(bucketCache.evictBlock(key)); 393 assertTrue(bucketCache.backingMap.isEmpty()); 394 bucketCache.shutdown(); 395 try ( 396 DataInputStream in = new DataInputStream(new FileInputStream(new File(persistencePath)))) { 397 byte[] magic = new byte[BucketProtoUtils.PB_MAGIC_V2.length]; 398 in.readFully(magic); 399 assertArrayEquals(BucketProtoUtils.PB_MAGIC_V2, magic); 400 assertNotNull(BucketCacheProtos.BucketCacheEntry.parseDelimitedFrom(in)); 401 assertEquals(-1, in.read()); 402 } 403 bucketCache = null; 404 405 recoveredCache = new BucketCache("file:" + cachePath, CAPACITY_SIZE, BLOCK_SIZE, 406 BLOCK_SIZE_ARRAY, 1, 1000, persistencePath, DEFAULT_ERROR_TOLERATION_DURATION, conf); 407 assertTrue(recoveredCache.waitForCacheInitialization(10000)); 408 assertTrue(recoveredCache.backingMap.isEmpty()); 409 assertEquals(0, recoveredCache.getAllocator().getUsedSize()); 410 assertEquals(0, recoveredCache.getBlockCount()); 411 Cacheable staleBlock = recoveredCache.getBlock(key, false, false, false); 412 if (staleBlock != null) { 413 staleBlock.release(); 414 } 415 assertNull(staleBlock); 416 } finally { 417 if (bucketCache != null) { 418 bucketCache.shutdown(); 419 } 420 if (recoveredCache != null) { 421 recoveredCache.shutdown(); 422 } 423 while (blockToCache.refCnt() > 0) { 424 blockToCache.release(); 425 } 426 } 427 } 428 429 @Test 430 public void testFinalPersistFailureReleasesEntriesAndKeepsPreviousCheckpoint( 431 @TempDir File testDir) throws Exception { 432 HFileBlock firstBlock = createBlock(200, 1020); 433 HFileBlock secondBlock = createBlock(400, 1020); 434 BucketCache bucketCache = null; 435 BucketCache recoveredCache = null; 436 String cachePath = new File(testDir, "bucket.cache").getAbsolutePath(); 437 String persistencePath = new File(testDir, "bucket.persistence").getAbsolutePath(); 438 BlockCacheKey firstKey = createKey("first", 200); 439 BlockCacheKey secondKey = createKey("second", 400); 440 Configuration conf = HBaseConfiguration.create(); 441 conf.setLong(BUCKETCACHE_PERSIST_INTERVAL_KEY, Long.MAX_VALUE); 442 try { 443 bucketCache = new BucketCache("file:" + cachePath, CAPACITY_SIZE, BLOCK_SIZE, 444 BLOCK_SIZE_ARRAY, 1, 1000, persistencePath, DEFAULT_ERROR_TOLERATION_DURATION, conf); 445 assertTrue(bucketCache.waitForCacheInitialization(10000)); 446 bucketCache.cacheBlock(firstKey, firstBlock); 447 waitUntilFlushedToCache(bucketCache, firstKey); 448 bucketCache.persistToFile(); 449 byte[] previousCheckpoint = Files.readAllBytes(new File(persistencePath).toPath()); 450 451 bucketCache.cacheBlock(secondKey, secondBlock); 452 waitUntilFlushedToCache(bucketCache, secondKey); 453 BucketEntry firstEntry = bucketCache.backingMap.get(firstKey); 454 BucketEntry secondEntry = bucketCache.backingMap.get(secondKey); 455 assertNotNull(firstEntry); 456 assertNotNull(secondEntry); 457 assertEquals(1, firstEntry.refCnt()); 458 assertEquals(1, secondEntry.refCnt()); 459 460 long failureTime = 123456789L; 461 File tempPersistencePath = new File(persistencePath + failureTime); 462 assertTrue(tempPersistencePath.mkdir()); 463 ManualEnvironmentEdge edge = new ManualEnvironmentEdge(); 464 edge.setValue(failureTime); 465 EnvironmentEdgeManager.injectEdge(edge); 466 try { 467 bucketCache.shutdown(); 468 } finally { 469 EnvironmentEdgeManager.reset(); 470 } 471 472 assertTrue(bucketCache.backingMap.isEmpty()); 473 assertEquals(0, firstEntry.refCnt()); 474 assertEquals(0, secondEntry.refCnt()); 475 assertArrayEquals(previousCheckpoint, Files.readAllBytes(new File(persistencePath).toPath())); 476 bucketCache = null; 477 478 recoveredCache = new BucketCache("file:" + cachePath, CAPACITY_SIZE, BLOCK_SIZE, 479 BLOCK_SIZE_ARRAY, 1, 1000, persistencePath, DEFAULT_ERROR_TOLERATION_DURATION, conf); 480 assertTrue(recoveredCache.waitForCacheInitialization(10000)); 481 BucketCache cacheToValidate = recoveredCache; 482 Waiter.waitFor(HBaseConfiguration.create(), 10000, 483 () -> cacheToValidate.getBackingMapValidated().get()); 484 assertEquals(1, recoveredCache.backingMap.size()); 485 Cacheable recoveredBlock = recoveredCache.getBlock(firstKey, false, false, false); 486 assertNotNull(recoveredBlock); 487 recoveredBlock.release(); 488 Cacheable uncheckpointedBlock = recoveredCache.getBlock(secondKey, false, false, false); 489 if (uncheckpointedBlock != null) { 490 uncheckpointedBlock.release(); 491 } 492 assertNull(uncheckpointedBlock); 493 } finally { 494 EnvironmentEdgeManager.reset(); 495 if (bucketCache != null) { 496 bucketCache.shutdown(); 497 } 498 if (recoveredCache != null) { 499 recoveredCache.shutdown(); 500 } 501 while (firstBlock.refCnt() > 0) { 502 firstBlock.release(); 503 } 504 while (secondBlock.refCnt() > 0) { 505 secondBlock.release(); 506 } 507 } 508 } 509 510 @Test 511 public void testHBaseIOExceptionThroughReferenceEvictsStoredEntry(@TempDir File testDir) 512 throws Exception { 513 HFileBlock blockToCache = createBlock(200, 1020); 514 BucketEntry bucketEntry = null; 515 String cachePath = new File(testDir, "bucket.cache").getAbsolutePath(); 516 String persistencePath = new File(testDir, "bucket.persistence").getAbsolutePath(); 517 BucketCache bucketCache = new BucketCache("file:" + cachePath, CAPACITY_SIZE, BLOCK_SIZE, 518 BLOCK_SIZE_ARRAY, 1, 1000, persistencePath); 519 try { 520 assertTrue(bucketCache.waitForCacheInitialization(10000)); 521 String hfileName = "0123456789abcdef"; 522 String regionName = "region"; 523 BlockCacheKey storedKey = 524 new BlockCacheKey(hfileName, "cf", regionName, 200, true, BlockType.DATA, false); 525 BlockCacheKey referenceKey = createKey(hfileName + ".parent", 200); 526 bucketCache.cacheBlock(storedKey, blockToCache); 527 waitUntilFlushedToCache(bucketCache, storedKey); 528 bucketCache.fileCacheCompleted(new Path("/table/" + regionName + "/cf/" + hfileName), 1020); 529 530 bucketEntry = bucketCache.backingMap.get(storedKey); 531 assertNotNull(bucketEntry); 532 assertTrue(bucketCache.regionCachedSize.containsKey(regionName)); 533 assertTrue(bucketCache.fullyCachedFiles.containsKey(hfileName)); 534 535 ByteBuffer invalidCachedTime = ByteBuffer.allocate(Long.BYTES); 536 invalidCachedTime.putLong(bucketEntry.getCachedTime() + 1).flip(); 537 bucketCache.ioEngine.write(invalidCachedTime, bucketEntry.offset()); 538 bucketCache.ioEngine.sync(); 539 540 assertNull(bucketCache.getBlock(referenceKey, false, false, false)); 541 assertFalse(bucketCache.backingMap.containsKey(storedKey)); 542 assertFalse(bucketCache.blocksByHFile.contains(storedKey)); 543 assertFalse(bucketCache.regionCachedSize.containsKey(regionName)); 544 assertFalse(bucketCache.fullyCachedFiles.containsKey(hfileName)); 545 assertEquals(0, bucketEntry.refCnt()); 546 assertEquals(0, bucketCache.getAllocator().getUsedSize()); 547 } finally { 548 bucketCache.shutdown(); 549 if (bucketEntry != null && bucketEntry.refCnt() > 0) { 550 bucketEntry.markAsEvicted(); 551 } 552 while (blockToCache.refCnt() > 0) { 553 blockToCache.release(); 554 } 555 } 556 } 557 558 @Test 559 public void testPlainIOExceptionKeepsEntryAndCacheMetadata(@TempDir File testDir) 560 throws Exception { 561 HFileBlock blockToCache = createBlock(200, 1020); 562 String cachePath = new File(testDir, "bucket.cache").getAbsolutePath(); 563 String persistencePath = new File(testDir, "bucket.persistence").getAbsolutePath(); 564 BucketCache bucketCache = new BucketCache("file:" + cachePath, CAPACITY_SIZE, BLOCK_SIZE, 565 BLOCK_SIZE_ARRAY, 1, 1000, persistencePath); 566 FileChannel originalChannel = null; 567 try { 568 assertTrue(bucketCache.waitForCacheInitialization(10000)); 569 String hfileName = "testPlainIOExceptionKeepsEntryAndCacheMetadata"; 570 String regionName = "region"; 571 BlockCacheKey key = 572 new BlockCacheKey(hfileName, "cf", regionName, 200, true, BlockType.DATA, false); 573 bucketCache.cacheBlock(key, blockToCache); 574 waitUntilFlushedToCache(bucketCache, key); 575 bucketCache.fileCacheCompleted(new Path("/table/" + regionName + "/cf/" + hfileName), 1020); 576 577 BucketEntry bucketEntry = bucketCache.backingMap.get(key); 578 assertNotNull(bucketEntry); 579 FileIOEngine fileIOEngine = (FileIOEngine) bucketCache.ioEngine; 580 originalChannel = fileIOEngine.getFileChannels()[0]; 581 FileChannel failingChannel = Mockito.mock(FileChannel.class); 582 Mockito.when(failingChannel.read(Mockito.any(ByteBuffer.class), Mockito.anyLong())) 583 .thenThrow(new IOException("Injected read failure")); 584 fileIOEngine.getFileChannels()[0] = failingChannel; 585 586 assertNull(bucketCache.getBlock(key, false, false, false)); 587 assertTrue(bucketCache.isCacheEnabled()); 588 assertEquals(bucketEntry, bucketCache.backingMap.get(key)); 589 assertTrue(bucketCache.blocksByHFile.contains(key)); 590 assertTrue(bucketCache.regionCachedSize.containsKey(regionName)); 591 assertTrue(bucketCache.fullyCachedFiles.containsKey(hfileName)); 592 assertEquals(1, bucketEntry.refCnt()); 593 } finally { 594 if (originalChannel != null) { 595 ((FileIOEngine) bucketCache.ioEngine).getFileChannels()[0] = originalChannel; 596 } 597 bucketCache.shutdown(); 598 while (blockToCache.refCnt() > 0) { 599 blockToCache.release(); 600 } 601 } 602 } 603 604 @Test 605 public void testInitialPublicationDoesNotRestoreIndexAfterConcurrentEviction() throws Exception { 606 BucketCache bucketCache = create(1, 1000); 607 ExecutorService executor = Executors.newSingleThreadExecutor(); 608 CountDownLatch entryPublished = new CountDownLatch(1); 609 CountDownLatch continuePublication = new CountDownLatch(1); 610 BucketEntry publishedEntry = null; 611 try { 612 BlockCacheKey key = 613 createKey("testInitialPublicationDoesNotRestoreIndexAfterConcurrentEviction", 200); 614 publishedEntry = new BucketEntry(8192, 1020, 1020, 0, false, entry -> ByteBuffAllocator.NONE, 615 ByteBuffAllocator.HEAP); 616 BucketEntry entryToPublish = publishedEntry; 617 bucketCache.backingMap = Mockito.spy(bucketCache.backingMap); 618 Mockito.doAnswer(invocation -> { 619 Object previousEntry = invocation.callRealMethod(); 620 entryPublished.countDown(); 621 if (!continuePublication.await(10, TimeUnit.SECONDS)) { 622 throw new AssertionError("Timed out waiting to resume publication"); 623 } 624 return previousEntry; 625 }).when(bucketCache.backingMap).put(key, entryToPublish); 626 Future<?> publication = 627 executor.submit(() -> bucketCache.putIntoBackingMap(key, entryToPublish)); 628 629 assertTrue(entryPublished.await(10, TimeUnit.SECONDS)); 630 assertTrue(entryToPublish.withWriteLock(bucketCache.offsetLock, () -> { 631 if (bucketCache.backingMap.remove(key, entryToPublish)) { 632 bucketCache.blockEvicted(key, entryToPublish, false, false); 633 return true; 634 } 635 return false; 636 })); 637 continuePublication.countDown(); 638 publication.get(10, TimeUnit.SECONDS); 639 640 assertFalse(bucketCache.backingMap.containsKey(key)); 641 assertFalse(bucketCache.blocksByHFile.contains(key)); 642 assertEquals(0, publishedEntry.refCnt()); 643 } finally { 644 continuePublication.countDown(); 645 executor.shutdownNow(); 646 try { 647 assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); 648 } finally { 649 bucketCache.shutdown(); 650 if (publishedEntry != null && publishedEntry.refCnt() > 0) { 651 publishedEntry.markAsEvicted(); 652 } 653 } 654 } 655 } 656 657 /** 658 * <pre> 659 * This test is for HBASE-26281, 660 * test two threads for replacing Block and getting Block execute concurrently. 661 * The threads sequence is: 662 * 1. Block1 was cached successfully,the {@link RefCnt} of Block1 is 1. 663 * 2. Thread1 caching the same {@link BlockCacheKey} with Block2 satisfied 664 * {@link BlockCacheUtil#shouldReplaceExistingCacheBlock}, so Block2 would 665 * replace Block1, but thread1 stopping before {@link BucketCache#cacheBlockWithWaitInternal} 666 * 3. Thread2 invoking {@link BucketCache#getBlock} with the same {@link BlockCacheKey}, 667 * which returned Block1, the {@link RefCnt} of Block1 is 2. 668 * 4. Thread1 continues caching Block2, in {@link BucketCache.WriterThread#putIntoBackingMap}, 669 * the old Block1 is freed directly which {@link RefCnt} is 2, but the Block1 is still used 670 * by Thread2 and the content of Block1 would be overwritten after it is freed, which may 671 * cause a serious error. 672 * </pre> 673 */ 674 @Test 675 public void testReplacingBlockAndGettingBlockConcurrently() throws Exception { 676 ByteBuffAllocator byteBuffAllocator = 677 ByteBuffAllocator.create(HBaseConfiguration.create(), true); 678 final MyBucketCache myBucketCache = createMyBucketCache(1, 1000); 679 try { 680 HFileBlock hfileBlock = createBlock(200, 1020, byteBuffAllocator); 681 final BlockCacheKey blockCacheKey = createKey("testTwoThreadConcurrent", 200); 682 myBucketCache.cacheBlock(blockCacheKey, hfileBlock); 683 waitUntilFlushedToCache(myBucketCache, blockCacheKey); 684 assertEquals(1, hfileBlock.refCnt()); 685 686 assertTrue(!myBucketCache.ramCache.containsKey(blockCacheKey)); 687 final AtomicReference<Throwable> exceptionRef = new AtomicReference<Throwable>(); 688 Thread cacheBlockThread = new Thread(() -> { 689 try { 690 HFileBlock newHFileBlock = createBlock(200, 1020, byteBuffAllocator); 691 myBucketCache.cacheBlock(blockCacheKey, newHFileBlock); 692 waitUntilFlushedToCache(myBucketCache, blockCacheKey); 693 694 } catch (Throwable exception) { 695 exceptionRef.set(exception); 696 } 697 }); 698 cacheBlockThread.setName(MyBucketCache.CACHE_BLOCK_THREAD_NAME); 699 cacheBlockThread.start(); 700 701 String oldThreadName = Thread.currentThread().getName(); 702 HFileBlock gotHFileBlock = null; 703 try { 704 705 Thread.currentThread().setName(MyBucketCache.GET_BLOCK_THREAD_NAME); 706 707 gotHFileBlock = (HFileBlock) (myBucketCache.getBlock(blockCacheKey, false, false, false)); 708 assertTrue(gotHFileBlock.equals(hfileBlock)); 709 assertTrue(gotHFileBlock.getByteBuffAllocator() == byteBuffAllocator); 710 assertEquals(2, gotHFileBlock.refCnt()); 711 /** 712 * Release the second cyclicBarrier.await in 713 * {@link MyBucketCache#cacheBlockWithWaitInternal} 714 */ 715 myBucketCache.cyclicBarrier.await(); 716 717 } finally { 718 Thread.currentThread().setName(oldThreadName); 719 } 720 721 cacheBlockThread.join(); 722 assertTrue(exceptionRef.get() == null); 723 assertTrue(myBucketCache.blocksByHFile.contains(blockCacheKey)); 724 assertEquals(1, gotHFileBlock.refCnt()); 725 assertTrue(gotHFileBlock.equals(hfileBlock)); 726 assertTrue(myBucketCache.overwiteByteBuff == null); 727 assertTrue(myBucketCache.freeBucketEntryCounter.get() == 0); 728 729 gotHFileBlock.release(); 730 assertEquals(0, gotHFileBlock.refCnt()); 731 assertTrue(myBucketCache.overwiteByteBuff != null); 732 assertTrue(myBucketCache.freeBucketEntryCounter.get() == 1); 733 assertTrue(myBucketCache.replaceCounter.get() == 1); 734 assertTrue(myBucketCache.blockEvictCounter.get() == 1); 735 } finally { 736 myBucketCache.shutdown(); 737 } 738 739 } 740 741 /** 742 * <pre> 743 * This test also is for HBASE-26281, 744 * test three threads for evicting Block,caching Block and getting Block 745 * execute concurrently. 746 * 1. Thread1 caching Block1, stopping after {@link BucketCache.WriterThread#putIntoBackingMap}, 747 * the {@link RefCnt} of Block1 is 1. 748 * 2. Thread2 invoking {@link BucketCache#evictBlock} with the same {@link BlockCacheKey}, 749 * but stopping after {@link BucketCache#removeFromRamCache}. 750 * 3. Thread3 invoking {@link BucketCache#getBlock} with the same {@link BlockCacheKey}, 751 * which returned Block1, the {@link RefCnt} of Block1 is 2. 752 * 4. Thread1 continues caching block1,but finding that {@link BucketCache.RAMCache#remove} 753 * returning false, so invoking {@link BucketCache#blockEvicted} to free the the Block1 754 * directly which {@link RefCnt} is 2 and the Block1 is still used by Thread3. 755 * </pre> 756 */ 757 @Test 758 public void testEvictingBlockCachingBlockGettingBlockConcurrently() throws Exception { 759 ByteBuffAllocator byteBuffAllocator = 760 ByteBuffAllocator.create(HBaseConfiguration.create(), true); 761 final MyBucketCache2 myBucketCache2 = createMyBucketCache2(1, 1000); 762 try { 763 final HFileBlock hfileBlock = createBlock(200, 1020, byteBuffAllocator); 764 final BlockCacheKey blockCacheKey = createKey("testThreeThreadConcurrent", 200); 765 final AtomicReference<Throwable> cacheBlockThreadExceptionRef = 766 new AtomicReference<Throwable>(); 767 Thread cacheBlockThread = new Thread(() -> { 768 try { 769 myBucketCache2.cacheBlock(blockCacheKey, hfileBlock); 770 /** 771 * Wait for Caching Block completed. 772 */ 773 myBucketCache2.writeThreadDoneCyclicBarrier.await(); 774 } catch (Throwable exception) { 775 cacheBlockThreadExceptionRef.set(exception); 776 } 777 }); 778 cacheBlockThread.setName(MyBucketCache2.CACHE_BLOCK_THREAD_NAME); 779 cacheBlockThread.start(); 780 781 final AtomicReference<Throwable> evictBlockThreadExceptionRef = 782 new AtomicReference<Throwable>(); 783 Thread evictBlockThread = new Thread(() -> { 784 try { 785 myBucketCache2.evictBlock(blockCacheKey); 786 } catch (Throwable exception) { 787 evictBlockThreadExceptionRef.set(exception); 788 } 789 }); 790 evictBlockThread.setName(MyBucketCache2.EVICT_BLOCK_THREAD_NAME); 791 evictBlockThread.start(); 792 793 String oldThreadName = Thread.currentThread().getName(); 794 HFileBlock gotHFileBlock = null; 795 try { 796 Thread.currentThread().setName(MyBucketCache2.GET_BLOCK_THREAD_NAME); 797 gotHFileBlock = (HFileBlock) (myBucketCache2.getBlock(blockCacheKey, false, false, false)); 798 assertTrue(gotHFileBlock.equals(hfileBlock)); 799 assertTrue(gotHFileBlock.getByteBuffAllocator() == byteBuffAllocator); 800 assertEquals(2, gotHFileBlock.refCnt()); 801 try { 802 /** 803 * Release the second cyclicBarrier.await in {@link MyBucketCache2#putIntoBackingMap} for 804 * {@link BucketCache.WriterThread},getBlock completed,{@link BucketCache.WriterThread} 805 * could continue. 806 */ 807 myBucketCache2.putCyclicBarrier.await(); 808 } catch (Throwable e) { 809 throw new RuntimeException(e); 810 } 811 812 } finally { 813 Thread.currentThread().setName(oldThreadName); 814 } 815 816 cacheBlockThread.join(); 817 evictBlockThread.join(); 818 assertTrue(cacheBlockThreadExceptionRef.get() == null); 819 assertTrue(evictBlockThreadExceptionRef.get() == null); 820 821 assertTrue(gotHFileBlock.equals(hfileBlock)); 822 assertEquals(1, gotHFileBlock.refCnt()); 823 assertTrue(myBucketCache2.overwiteByteBuff == null); 824 assertTrue(myBucketCache2.freeBucketEntryCounter.get() == 0); 825 826 gotHFileBlock.release(); 827 assertEquals(0, gotHFileBlock.refCnt()); 828 assertTrue(myBucketCache2.overwiteByteBuff != null); 829 assertTrue(myBucketCache2.freeBucketEntryCounter.get() == 1); 830 assertTrue(myBucketCache2.blockEvictCounter.get() == 1); 831 } finally { 832 myBucketCache2.shutdown(); 833 } 834 835 } 836 837 static class MyBucketCache extends BucketCache { 838 private static final String GET_BLOCK_THREAD_NAME = "_getBlockThread"; 839 private static final String CACHE_BLOCK_THREAD_NAME = "_cacheBlockThread"; 840 841 private final CyclicBarrier cyclicBarrier = new CyclicBarrier(2); 842 private final AtomicInteger replaceCounter = new AtomicInteger(0); 843 private final AtomicInteger blockEvictCounter = new AtomicInteger(0); 844 private final AtomicInteger freeBucketEntryCounter = new AtomicInteger(0); 845 private ByteBuff overwiteByteBuff = null; 846 847 public MyBucketCache(String ioEngineName, long capacity, int blockSize, int[] bucketSizes, 848 int writerThreadNum, int writerQLen, String persistencePath) throws IOException { 849 super(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, writerQLen, 850 persistencePath); 851 } 852 853 /** 854 * Simulate the Block could be replaced. 855 */ 856 @Override 857 protected boolean shouldReplaceExistingCacheBlock(BlockCacheKey cacheKey, Cacheable newBlock) { 858 replaceCounter.incrementAndGet(); 859 return true; 860 } 861 862 @Override 863 public Cacheable getBlock(BlockCacheKey key, boolean caching, boolean repeat, 864 boolean updateCacheMetrics) { 865 if (Thread.currentThread().getName().equals(GET_BLOCK_THREAD_NAME)) { 866 /** 867 * Wait the first cyclicBarrier.await() in {@link MyBucketCache#cacheBlockWithWaitInternal}, 868 * so the {@link BucketCache#getBlock} is executed after the {@link BucketEntry#isRpcRef} 869 * checking. 870 */ 871 try { 872 cyclicBarrier.await(); 873 } catch (Throwable e) { 874 throw new RuntimeException(e); 875 } 876 } 877 Cacheable result = super.getBlock(key, caching, repeat, updateCacheMetrics); 878 return result; 879 } 880 881 @Override 882 protected void cacheBlockWithWaitInternal(BlockCacheKey cacheKey, Cacheable cachedItem, 883 boolean inMemory, boolean wait) { 884 if (Thread.currentThread().getName().equals(CACHE_BLOCK_THREAD_NAME)) { 885 /** 886 * Wait the cyclicBarrier.await() in {@link MyBucketCache#getBlock} 887 */ 888 try { 889 cyclicBarrier.await(); 890 } catch (Throwable e) { 891 throw new RuntimeException(e); 892 } 893 } 894 if (Thread.currentThread().getName().equals(CACHE_BLOCK_THREAD_NAME)) { 895 /** 896 * Wait the cyclicBarrier.await() in 897 * {@link TestBucketCacheRefCnt#testReplacingBlockAndGettingBlockConcurrently} for 898 * {@link MyBucketCache#getBlock} and Assert completed. 899 */ 900 try { 901 cyclicBarrier.await(); 902 } catch (Throwable e) { 903 throw new RuntimeException(e); 904 } 905 } 906 super.cacheBlockWithWaitInternal(cacheKey, cachedItem, inMemory, wait); 907 } 908 909 @Override 910 void blockEvicted(BlockCacheKey cacheKey, BucketEntry bucketEntry, boolean decrementBlockNumber, 911 boolean evictedByEvictionProcess) { 912 blockEvictCounter.incrementAndGet(); 913 super.blockEvicted(cacheKey, bucketEntry, decrementBlockNumber, evictedByEvictionProcess); 914 } 915 916 /** 917 * Overwrite 0xff to the {@link BucketEntry} content to simulate it would be overwrite after the 918 * {@link BucketEntry} is freed. 919 */ 920 @Override 921 void freeBucketEntry(BucketEntry bucketEntry) { 922 freeBucketEntryCounter.incrementAndGet(); 923 super.freeBucketEntry(bucketEntry); 924 this.overwiteByteBuff = getOverwriteByteBuff(bucketEntry); 925 try { 926 this.ioEngine.write(this.overwiteByteBuff, bucketEntry.offset()); 927 } catch (IOException e) { 928 throw new RuntimeException(e); 929 } 930 } 931 } 932 933 static class MyBucketCache2 extends BucketCache { 934 private static final String GET_BLOCK_THREAD_NAME = "_getBlockThread"; 935 private static final String CACHE_BLOCK_THREAD_NAME = "_cacheBlockThread"; 936 private static final String EVICT_BLOCK_THREAD_NAME = "_evictBlockThread"; 937 938 private final CyclicBarrier getCyclicBarrier = new CyclicBarrier(2); 939 private final CyclicBarrier evictCyclicBarrier = new CyclicBarrier(2); 940 private final CyclicBarrier putCyclicBarrier = new CyclicBarrier(2); 941 /** 942 * This is used for {@link BucketCache.WriterThread},{@link #CACHE_BLOCK_THREAD_NAME} and 943 * {@link #EVICT_BLOCK_THREAD_NAME},waiting for caching block completed. 944 */ 945 private final CyclicBarrier writeThreadDoneCyclicBarrier = new CyclicBarrier(3); 946 private final AtomicInteger blockEvictCounter = new AtomicInteger(0); 947 private final AtomicInteger removeRamCounter = new AtomicInteger(0); 948 private final AtomicInteger freeBucketEntryCounter = new AtomicInteger(0); 949 private ByteBuff overwiteByteBuff = null; 950 951 public MyBucketCache2(String ioEngineName, long capacity, int blockSize, int[] bucketSizes, 952 int writerThreadNum, int writerQLen, String persistencePath) throws IOException { 953 super(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, writerQLen, 954 persistencePath); 955 } 956 957 @Override 958 protected void putIntoBackingMap(BlockCacheKey key, BucketEntry bucketEntry) { 959 super.putIntoBackingMap(key, bucketEntry); 960 /** 961 * The {@link BucketCache.WriterThread} wait for evictCyclicBarrier.await before 962 * {@link MyBucketCache2#removeFromRamCache} for {@link #EVICT_BLOCK_THREAD_NAME} 963 */ 964 try { 965 evictCyclicBarrier.await(); 966 } catch (Throwable e) { 967 throw new RuntimeException(e); 968 } 969 970 /** 971 * Wait the cyclicBarrier.await() in 972 * {@link TestBucketCacheRefCnt#testEvictingBlockCachingBlockGettingBlockConcurrently} for 973 * {@link MyBucketCache#getBlock} and Assert completed. 974 */ 975 try { 976 putCyclicBarrier.await(); 977 } catch (Throwable e) { 978 throw new RuntimeException(e); 979 } 980 } 981 982 @Override 983 void doDrain(List<RAMQueueEntry> entries, ByteBuffer metaBuff) throws InterruptedException { 984 super.doDrain(entries, metaBuff); 985 if (entries.size() > 0) { 986 /** 987 * Caching Block completed,release {@link #GET_BLOCK_THREAD_NAME} and 988 * {@link #EVICT_BLOCK_THREAD_NAME}. 989 */ 990 try { 991 writeThreadDoneCyclicBarrier.await(); 992 } catch (Throwable e) { 993 throw new RuntimeException(e); 994 } 995 } 996 997 } 998 999 @Override 1000 public Cacheable getBlock(BlockCacheKey key, boolean caching, boolean repeat, 1001 boolean updateCacheMetrics) { 1002 if (Thread.currentThread().getName().equals(GET_BLOCK_THREAD_NAME)) { 1003 /** 1004 * Wait for second getCyclicBarrier.await in {@link MyBucketCache2#removeFromRamCache} after 1005 * {@link BucketCache#removeFromRamCache}. 1006 */ 1007 try { 1008 getCyclicBarrier.await(); 1009 } catch (Throwable e) { 1010 throw new RuntimeException(e); 1011 } 1012 } 1013 Cacheable result = super.getBlock(key, caching, repeat, updateCacheMetrics); 1014 return result; 1015 } 1016 1017 @Override 1018 protected boolean removeFromRamCache(BlockCacheKey cacheKey) { 1019 boolean firstTime = false; 1020 if (Thread.currentThread().getName().equals(EVICT_BLOCK_THREAD_NAME)) { 1021 int count = this.removeRamCounter.incrementAndGet(); 1022 firstTime = (count == 1); 1023 if (firstTime) { 1024 /** 1025 * The {@link #EVICT_BLOCK_THREAD_NAME} wait for evictCyclicBarrier.await after 1026 * {@link BucketCache#putIntoBackingMap}. 1027 */ 1028 try { 1029 evictCyclicBarrier.await(); 1030 } catch (Throwable e) { 1031 throw new RuntimeException(e); 1032 } 1033 } 1034 } 1035 boolean result = super.removeFromRamCache(cacheKey); 1036 if (Thread.currentThread().getName().equals(EVICT_BLOCK_THREAD_NAME)) { 1037 if (firstTime) { 1038 /** 1039 * Wait for getCyclicBarrier.await before {@link BucketCache#getBlock}. 1040 */ 1041 try { 1042 getCyclicBarrier.await(); 1043 } catch (Throwable e) { 1044 throw new RuntimeException(e); 1045 } 1046 /** 1047 * Wait for Caching Block completed, after Caching Block completed, evictBlock could 1048 * continue. 1049 */ 1050 try { 1051 writeThreadDoneCyclicBarrier.await(); 1052 } catch (Throwable e) { 1053 throw new RuntimeException(e); 1054 } 1055 } 1056 } 1057 1058 return result; 1059 } 1060 1061 @Override 1062 void blockEvicted(BlockCacheKey cacheKey, BucketEntry bucketEntry, boolean decrementBlockNumber, 1063 boolean evictedByEvictionProcess) { 1064 /** 1065 * This is only invoked by {@link BucketCache.WriterThread}. {@link MyMyBucketCache2} create 1066 * only one {@link BucketCache.WriterThread}. 1067 */ 1068 assertTrue(Thread.currentThread() == this.writerThreads[0]); 1069 1070 blockEvictCounter.incrementAndGet(); 1071 super.blockEvicted(cacheKey, bucketEntry, decrementBlockNumber, evictedByEvictionProcess); 1072 } 1073 1074 /** 1075 * Overwrite 0xff to the {@link BucketEntry} content to simulate it would be overwrite after the 1076 * {@link BucketEntry} is freed. 1077 */ 1078 @Override 1079 void freeBucketEntry(BucketEntry bucketEntry) { 1080 freeBucketEntryCounter.incrementAndGet(); 1081 super.freeBucketEntry(bucketEntry); 1082 this.overwiteByteBuff = getOverwriteByteBuff(bucketEntry); 1083 try { 1084 this.ioEngine.write(this.overwiteByteBuff, bucketEntry.offset()); 1085 } catch (IOException e) { 1086 throw new RuntimeException(e); 1087 } 1088 } 1089 } 1090 1091 private static ByteBuff getOverwriteByteBuff(BucketEntry bucketEntry) { 1092 int byteSize = bucketEntry.getLength(); 1093 byte[] data = new byte[byteSize]; 1094 Arrays.fill(data, (byte) 0xff); 1095 return ByteBuff.wrap(ByteBuffer.wrap(data)); 1096 } 1097}