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}