diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java
index c98dd2275d85..a4de6035728e 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServices.java
@@ -18,7 +18,9 @@
package org.apache.hadoop.hbase.io.hfile.cache;
import java.util.Objects;
+import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.io.hfile.BlockCache;
+import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory;
import org.apache.yetus.audience.InterfaceAudience;
/**
@@ -56,6 +58,35 @@ public static CacheAccessService fromBlockCache(BlockCache blockCache) {
Objects.requireNonNull(blockCache, "blockCache must not be null"));
}
+ /**
+ * Creates a {@link CacheAccessService} from the block cache configuration.
+ *
+ * This method is a compatibility factory for tests and transitional code paths that want to
+ * obtain a {@link CacheAccessService} directly from {@link Configuration}, while still using the
+ * existing {@link BlockCacheFactory} and legacy {@link BlockCache} implementations underneath.
+ *
+ *
+ * The method delegates block-cache construction to
+ * {@link BlockCacheFactory#createBlockCache(Configuration)}. If the legacy factory creates a
+ * {@link BlockCache}, the returned service is backed by that cache through
+ * {@link BlockCacheBackedCacheAccessService}. If the legacy factory does not create a cache, this
+ * method returns the disabled/no-op cache access service.
+ *
+ *
+ * This method does not introduce new cache-engine or topology-based runtime wiring. It is
+ * intended only as a bridge while existing HBase tests and integration paths migrate from direct
+ * {@link BlockCache} usage to {@link CacheAccessService}.
+ *
+ * @param conf configuration used by {@link BlockCacheFactory}
+ * @return cache access service created from the configured legacy block cache, or disabled when
+ * no block cache is configured
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessService fromConfiguration(Configuration conf) {
+ Objects.requireNonNull(conf, "conf must not be null");
+ return fromBlockCache(BlockCacheFactory.createBlockCache(conf));
+ }
+
/**
* Creates a {@link CacheAccessService} backed by a {@link CacheTopology} and placement/admission
* policy.
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java
new file mode 100644
index 000000000000..069fc6fb6bc1
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestFactory.java
@@ -0,0 +1,643 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.io.hfile.cache;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.io.hfile.BlockCache;
+import org.apache.hadoop.hbase.io.hfile.LruAdaptiveBlockCache;
+import org.apache.hadoop.hbase.io.hfile.LruBlockCache;
+import org.apache.hadoop.hbase.io.hfile.TinyLfuBlockCache;
+import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
+import org.apache.hadoop.hbase.regionserver.HRegion;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * Test-only factory helpers for creating {@link CacheAccessService} instances backed by legacy
+ * {@link BlockCache} implementations.
+ *
+ * These helpers are intended to keep integration-style tests concise while the block cache
+ * architecture migrates toward {@link CacheAccessService}. They deliberately do not replace
+ * implementation-specific tests for {@link LruBlockCache}, {@link LruAdaptiveBlockCache},
+ * {@link TinyLfuBlockCache}, {@link BucketCache}, or other concrete cache implementations.
+ *
+ *
+ * Each helper mirrors a public constructor on one of the legacy cache implementations and returns
+ * either:
+ *
+ *
+ * - a {@link CacheAccessService} facade backed by the constructed cache, or
+ * - a {@link CacheAccessServiceTestInstance} containing both the concrete cache and the
+ * facade.
+ *
+ *
+ * The factory currently creates legacy {@link BlockCache} implementations and wraps them using
+ * {@link CacheAccessServices#fromBlockCache(BlockCache)}. Later, when cache engines and topologies
+ * are wired directly, the internals of this factory can change without forcing integration-style
+ * tests to be rewritten again.
+ *
+ */
+@InterfaceAudience.Private
+public final class CacheAccessServiceTestFactory {
+
+ private CacheAccessServiceTestFactory() {
+ }
+
+ /**
+ * Wraps an existing {@link BlockCache} in a {@link CacheAccessService}.
+ * @param blockCache block cache to wrap
+ * @return cache access service backed by the supplied block cache
+ * @throws NullPointerException if {@code blockCache} is {@code null}
+ */
+ public static CacheAccessService fromBlockCache(BlockCache blockCache) {
+ return CacheAccessServices.fromBlockCache(blockCache);
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} from the block cache configuration.
+ *
+ * This method delegates to {@link CacheAccessServices#fromConfiguration(Configuration)} and is
+ * useful for tests that want the normal legacy
+ * {@link org.apache.hadoop.hbase.io.hfile.BlockCacheFactory} construction path while receiving a
+ * {@link CacheAccessService} facade.
+ *
+ * @param conf configuration used to create the legacy block cache
+ * @return cache access service created from configuration, or disabled when no cache is
+ * configured
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessService fromConfiguration(Configuration conf) {
+ return CacheAccessServices.fromConfiguration(conf);
+ }
+
+ /**
+ * Returns a disabled/no-op cache access service.
+ * @return disabled cache access service
+ */
+ public static CacheAccessService disabled() {
+ return CacheAccessServices.disabled();
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @return cache access service backed by {@link LruBlockCache}
+ */
+ public static CacheAccessService lru(long maxSize, long blockSize) {
+ return lruInstance(maxSize, blockSize).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService}
+ * facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @return test instance
+ */
+ public static CacheAccessServiceTestInstance lruInstance(long maxSize,
+ long blockSize) {
+ return new CacheAccessServiceTestInstance<>(new LruBlockCache(maxSize, blockSize));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @return cache access service backed by {@link LruBlockCache}
+ */
+ public static CacheAccessService lru(long maxSize, long blockSize, boolean evictionThread) {
+ return lruInstance(maxSize, blockSize, evictionThread).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService}
+ * facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @return test instance
+ */
+ public static CacheAccessServiceTestInstance lruInstance(long maxSize,
+ long blockSize, boolean evictionThread) {
+ return new CacheAccessServiceTestInstance<>(
+ new LruBlockCache(maxSize, blockSize, evictionThread));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @param conf configuration used by the cache constructor
+ * @return cache access service backed by {@link LruBlockCache}
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessService lru(long maxSize, long blockSize, boolean evictionThread,
+ Configuration conf) {
+ return lruInstance(maxSize, blockSize, evictionThread, conf).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService}
+ * facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @param conf configuration used by the cache constructor
+ * @return test instance
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance lruInstance(long maxSize,
+ long blockSize, boolean evictionThread, Configuration conf) {
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(
+ new LruBlockCache(maxSize, blockSize, evictionThread, conf));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param conf configuration used by the cache constructor
+ * @return cache access service backed by {@link LruBlockCache}
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessService lru(long maxSize, long blockSize, Configuration conf) {
+ return lruInstance(maxSize, blockSize, conf).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruBlockCache} and its {@link CacheAccessService}
+ * facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param conf configuration used by the cache constructor
+ * @return test instance
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance lruInstance(long maxSize,
+ long blockSize, Configuration conf) {
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(new LruBlockCache(maxSize, blockSize, conf));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by a fully configured {@link LruBlockCache}.
+ * @param maxSize maximum size of this cache, in bytes
+ * @param blockSize expected average size of blocks, in bytes
+ * @param evictionThread whether to run evictions in a background thread
+ * @param mapInitialSize initial size of the backing concurrent map
+ * @param mapLoadFactor initial load factor of the backing concurrent map
+ * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map
+ * @param minFactor percentage of total size to evict down to
+ * @param acceptableFactor percentage of total size that triggers eviction
+ * @param singleFactor percentage of total size for single-access blocks
+ * @param multiFactor percentage of total size for multi-access blocks
+ * @param memoryFactor percentage of total size for in-memory blocks
+ * @param hardLimitFactor hard capacity limit factor
+ * @param forceInMemory whether in-memory HFile data blocks have higher priority
+ * @param maxBlockSize maximum block size accepted by the cache
+ * @return cache access service backed by {@link LruBlockCache}
+ */
+ public static CacheAccessService lru(long maxSize, long blockSize, boolean evictionThread,
+ int mapInitialSize, float mapLoadFactor, int mapConcurrencyLevel, float minFactor,
+ float acceptableFactor, float singleFactor, float multiFactor, float memoryFactor,
+ float hardLimitFactor, boolean forceInMemory, long maxBlockSize) {
+ return lruInstance(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor,
+ mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor,
+ hardLimitFactor, forceInMemory, maxBlockSize).service();
+ }
+
+ /**
+ * Creates a test instance containing a fully configured {@link LruBlockCache} and its
+ * {@link CacheAccessService} facade.
+ * @param maxSize maximum size of this cache, in bytes
+ * @param blockSize expected average size of blocks, in bytes
+ * @param evictionThread whether to run evictions in a background thread
+ * @param mapInitialSize initial size of the backing concurrent map
+ * @param mapLoadFactor initial load factor of the backing concurrent map
+ * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map
+ * @param minFactor percentage of total size to evict down to
+ * @param acceptableFactor percentage of total size that triggers eviction
+ * @param singleFactor percentage of total size for single-access blocks
+ * @param multiFactor percentage of total size for multi-access blocks
+ * @param memoryFactor percentage of total size for in-memory blocks
+ * @param hardLimitFactor hard capacity limit factor
+ * @param forceInMemory whether in-memory HFile data blocks have higher priority
+ * @param maxBlockSize maximum block size accepted by the cache
+ * @return test instance
+ */
+ public static CacheAccessServiceTestInstance lruInstance(long maxSize,
+ long blockSize, boolean evictionThread, int mapInitialSize, float mapLoadFactor,
+ int mapConcurrencyLevel, float minFactor, float acceptableFactor, float singleFactor,
+ float multiFactor, float memoryFactor, float hardLimitFactor, boolean forceInMemory,
+ long maxBlockSize) {
+ return new CacheAccessServiceTestInstance<>(
+ new LruBlockCache(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor,
+ mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor,
+ hardLimitFactor, forceInMemory, maxBlockSize));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @return cache access service backed by {@link LruAdaptiveBlockCache}
+ */
+ public static CacheAccessService lruAdaptive(long maxSize, long blockSize) {
+ return lruAdaptiveInstance(maxSize, blockSize).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruAdaptiveBlockCache} and its
+ * {@link CacheAccessService} facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @return test instance
+ */
+ public static CacheAccessServiceTestInstance
+ lruAdaptiveInstance(long maxSize, long blockSize) {
+ return new CacheAccessServiceTestInstance<>(new LruAdaptiveBlockCache(maxSize, blockSize));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @return cache access service backed by {@link LruAdaptiveBlockCache}
+ */
+ public static CacheAccessService lruAdaptive(long maxSize, long blockSize,
+ boolean evictionThread) {
+ return lruAdaptiveInstance(maxSize, blockSize, evictionThread).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruAdaptiveBlockCache} and its
+ * {@link CacheAccessService} facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @return test instance
+ */
+ public static CacheAccessServiceTestInstance
+ lruAdaptiveInstance(long maxSize, long blockSize, boolean evictionThread) {
+ return new CacheAccessServiceTestInstance<>(
+ new LruAdaptiveBlockCache(maxSize, blockSize, evictionThread));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @param conf configuration used by the cache constructor
+ * @return cache access service backed by {@link LruAdaptiveBlockCache}
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessService lruAdaptive(long maxSize, long blockSize, boolean evictionThread,
+ Configuration conf) {
+ return lruAdaptiveInstance(maxSize, blockSize, evictionThread, conf).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruAdaptiveBlockCache} and its
+ * {@link CacheAccessService} facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param evictionThread whether to start the eviction thread
+ * @param conf configuration used by the cache constructor
+ * @return test instance
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance
+ lruAdaptiveInstance(long maxSize, long blockSize, boolean evictionThread, Configuration conf) {
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(
+ new LruAdaptiveBlockCache(maxSize, blockSize, evictionThread, conf));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link LruAdaptiveBlockCache}.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param conf configuration used by the cache constructor
+ * @return cache access service backed by {@link LruAdaptiveBlockCache}
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessService lruAdaptive(long maxSize, long blockSize, Configuration conf) {
+ return lruAdaptiveInstance(maxSize, blockSize, conf).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link LruAdaptiveBlockCache} and its
+ * {@link CacheAccessService} facade.
+ * @param maxSize maximum size of cache, in bytes
+ * @param blockSize approximate size of each block, in bytes
+ * @param conf configuration used by the cache constructor
+ * @return test instance
+ * @throws NullPointerException if {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance
+ lruAdaptiveInstance(long maxSize, long blockSize, Configuration conf) {
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(
+ new LruAdaptiveBlockCache(maxSize, blockSize, conf));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by a fully configured
+ * {@link LruAdaptiveBlockCache}.
+ * @param maxSize maximum size of this cache, in bytes
+ * @param blockSize expected average size of blocks, in bytes
+ * @param evictionThread whether to run evictions in a background thread
+ * @param mapInitialSize initial size of the backing concurrent map
+ * @param mapLoadFactor initial load factor of the backing concurrent map
+ * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map
+ * @param minFactor percentage of total size to evict down to
+ * @param acceptableFactor percentage of total size that triggers eviction
+ * @param singleFactor percentage of total size for single-access blocks
+ * @param multiFactor percentage of total size for multi-access blocks
+ * @param memoryFactor percentage of total size for in-memory blocks
+ * @param hardLimitFactor hard capacity limit factor
+ * @param forceInMemory whether in-memory HFile data blocks have higher
+ * priority
+ * @param maxBlockSize maximum block size accepted by the cache
+ * @param heavyEvictionCountLimit threshold count for heavy-eviction adaptive behavior
+ * @param heavyEvictionMbSizeLimit threshold size for heavy-eviction adaptive behavior
+ * @param heavyEvictionOverheadCoefficient coefficient controlling adaptive reduction
+ * aggressiveness
+ * @return cache access service backed by {@link LruAdaptiveBlockCache}
+ */
+ public static CacheAccessService lruAdaptive(long maxSize, long blockSize, boolean evictionThread,
+ int mapInitialSize, float mapLoadFactor, int mapConcurrencyLevel, float minFactor,
+ float acceptableFactor, float singleFactor, float multiFactor, float memoryFactor,
+ float hardLimitFactor, boolean forceInMemory, long maxBlockSize, int heavyEvictionCountLimit,
+ long heavyEvictionMbSizeLimit, float heavyEvictionOverheadCoefficient) {
+ return lruAdaptiveInstance(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor,
+ mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor,
+ hardLimitFactor, forceInMemory, maxBlockSize, heavyEvictionCountLimit,
+ heavyEvictionMbSizeLimit, heavyEvictionOverheadCoefficient).service();
+ }
+
+ /**
+ * Creates a test instance containing a fully configured {@link LruAdaptiveBlockCache} and its
+ * {@link CacheAccessService} facade.
+ * @param maxSize maximum size of this cache, in bytes
+ * @param blockSize expected average size of blocks, in bytes
+ * @param evictionThread whether to run evictions in a background thread
+ * @param mapInitialSize initial size of the backing concurrent map
+ * @param mapLoadFactor initial load factor of the backing concurrent map
+ * @param mapConcurrencyLevel initial concurrency level of the backing concurrent map
+ * @param minFactor percentage of total size to evict down to
+ * @param acceptableFactor percentage of total size that triggers eviction
+ * @param singleFactor percentage of total size for single-access blocks
+ * @param multiFactor percentage of total size for multi-access blocks
+ * @param memoryFactor percentage of total size for in-memory blocks
+ * @param hardLimitFactor hard capacity limit factor
+ * @param forceInMemory whether in-memory HFile data blocks have higher
+ * priority
+ * @param maxBlockSize maximum block size accepted by the cache
+ * @param heavyEvictionCountLimit threshold count for heavy-eviction adaptive behavior
+ * @param heavyEvictionMbSizeLimit threshold size for heavy-eviction adaptive behavior
+ * @param heavyEvictionOverheadCoefficient coefficient controlling adaptive reduction
+ * aggressiveness
+ * @return test instance
+ */
+ public static CacheAccessServiceTestInstance lruAdaptiveInstance(
+ long maxSize, long blockSize, boolean evictionThread, int mapInitialSize, float mapLoadFactor,
+ int mapConcurrencyLevel, float minFactor, float acceptableFactor, float singleFactor,
+ float multiFactor, float memoryFactor, float hardLimitFactor, boolean forceInMemory,
+ long maxBlockSize, int heavyEvictionCountLimit, long heavyEvictionMbSizeLimit,
+ float heavyEvictionOverheadCoefficient) {
+ return new CacheAccessServiceTestInstance<>(
+ new LruAdaptiveBlockCache(maxSize, blockSize, evictionThread, mapInitialSize, mapLoadFactor,
+ mapConcurrencyLevel, minFactor, acceptableFactor, singleFactor, multiFactor, memoryFactor,
+ hardLimitFactor, forceInMemory, maxBlockSize, heavyEvictionCountLimit,
+ heavyEvictionMbSizeLimit, heavyEvictionOverheadCoefficient));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link TinyLfuBlockCache}.
+ * @param maximumSizeInBytes maximum size of this cache, in bytes
+ * @param avgBlockSize expected average size of blocks, in bytes
+ * @param executor executor used by the underlying Caffeine cache
+ * @param conf configuration used by the cache constructor
+ * @return cache access service backed by {@link TinyLfuBlockCache}
+ * @throws NullPointerException if {@code executor} or {@code conf} is {@code null}
+ */
+ public static CacheAccessService tinyLfu(long maximumSizeInBytes, long avgBlockSize,
+ Executor executor, Configuration conf) {
+ return tinyLfuInstance(maximumSizeInBytes, avgBlockSize, executor, conf).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link TinyLfuBlockCache} and its {@link CacheAccessService}
+ * facade.
+ * @param maximumSizeInBytes maximum size of this cache, in bytes
+ * @param avgBlockSize expected average size of blocks, in bytes
+ * @param executor executor used by the underlying Caffeine cache
+ * @param conf configuration used by the cache constructor
+ * @return test instance
+ * @throws NullPointerException if {@code executor} or {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance tinyLfuInstance(
+ long maximumSizeInBytes, long avgBlockSize, Executor executor, Configuration conf) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(
+ new TinyLfuBlockCache(maximumSizeInBytes, avgBlockSize, executor, conf));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link TinyLfuBlockCache}.
+ * @param maximumSizeInBytes maximum size of this cache, in bytes
+ * @param avgBlockSize expected average size of blocks, in bytes
+ * @param maxBlockSize maximum size of a block, in bytes
+ * @param executor executor used by the underlying Caffeine cache
+ * @return cache access service backed by {@link TinyLfuBlockCache}
+ * @throws NullPointerException if {@code executor} is {@code null}
+ */
+ public static CacheAccessService tinyLfu(long maximumSizeInBytes, long avgBlockSize,
+ long maxBlockSize, Executor executor) {
+ return tinyLfuInstance(maximumSizeInBytes, avgBlockSize, maxBlockSize, executor).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link TinyLfuBlockCache} and its {@link CacheAccessService}
+ * facade.
+ * @param maximumSizeInBytes maximum size of this cache, in bytes
+ * @param avgBlockSize expected average size of blocks, in bytes
+ * @param maxBlockSize maximum size of a block, in bytes
+ * @param executor executor used by the underlying Caffeine cache
+ * @return test instance
+ * @throws NullPointerException if {@code executor} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance tinyLfuInstance(
+ long maximumSizeInBytes, long avgBlockSize, long maxBlockSize, Executor executor) {
+ Objects.requireNonNull(executor, "executor must not be null");
+ return new CacheAccessServiceTestInstance<>(
+ new TinyLfuBlockCache(maximumSizeInBytes, avgBlockSize, maxBlockSize, executor));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link BucketCache}.
+ * @param ioEngineName IO engine name
+ * @param capacity cache capacity, in bytes
+ * @param blockSize expected block size, in bytes
+ * @param bucketSizes configured bucket sizes, or {@code null} to use defaults
+ * @param writerThreadNum number of writer threads
+ * @param writerQLen writer queue length
+ * @param persistencePath persistence path, or {@code null} for non-persistent cache
+ * @return cache access service backed by {@link BucketCache}
+ * @throws IOException if the bucket cache cannot be created
+ * @throws NullPointerException if {@code ioEngineName} is {@code null}
+ */
+ public static CacheAccessService bucket(String ioEngineName, long capacity, int blockSize,
+ int[] bucketSizes, int writerThreadNum, int writerQLen, String persistencePath)
+ throws IOException {
+ return bucketInstance(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum,
+ writerQLen, persistencePath).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link BucketCache} and its {@link CacheAccessService}
+ * facade.
+ * @param ioEngineName IO engine name
+ * @param capacity cache capacity, in bytes
+ * @param blockSize expected block size, in bytes
+ * @param bucketSizes configured bucket sizes, or {@code null} to use defaults
+ * @param writerThreadNum number of writer threads
+ * @param writerQLen writer queue length
+ * @param persistencePath persistence path, or {@code null} for non-persistent cache
+ * @return test instance
+ * @throws IOException if the bucket cache cannot be created
+ * @throws NullPointerException if {@code ioEngineName} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance bucketInstance(String ioEngineName,
+ long capacity, int blockSize, int[] bucketSizes, int writerThreadNum, int writerQLen,
+ String persistencePath) throws IOException {
+ Objects.requireNonNull(ioEngineName, "ioEngineName must not be null");
+ return new CacheAccessServiceTestInstance<>(new BucketCache(ioEngineName, capacity, blockSize,
+ bucketSizes, writerThreadNum, writerQLen, persistencePath));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link BucketCache}.
+ * @param ioEngineName IO engine name
+ * @param capacity cache capacity, in bytes
+ * @param blockSize expected block size, in bytes
+ * @param bucketSizes configured bucket sizes, or {@code null} to use defaults
+ * @param writerThreadNum number of writer threads
+ * @param writerQLen writer queue length
+ * @param persistencePath persistence path, or {@code null} for non-persistent cache
+ * @param ioErrorsTolerationDuration duration for tolerating IO engine errors
+ * @param conf configuration used by the bucket cache
+ * @return cache access service backed by {@link BucketCache}
+ * @throws IOException if the bucket cache cannot be created
+ * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null}
+ */
+ public static CacheAccessService bucket(String ioEngineName, long capacity, int blockSize,
+ int[] bucketSizes, int writerThreadNum, int writerQLen, String persistencePath,
+ int ioErrorsTolerationDuration, Configuration conf) throws IOException {
+ return bucketInstance(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum,
+ writerQLen, persistencePath, ioErrorsTolerationDuration, conf).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link BucketCache} and its {@link CacheAccessService}
+ * facade.
+ * @param ioEngineName IO engine name
+ * @param capacity cache capacity, in bytes
+ * @param blockSize expected block size, in bytes
+ * @param bucketSizes configured bucket sizes, or {@code null} to use defaults
+ * @param writerThreadNum number of writer threads
+ * @param writerQLen writer queue length
+ * @param persistencePath persistence path, or {@code null} for non-persistent cache
+ * @param ioErrorsTolerationDuration duration for tolerating IO engine errors
+ * @param conf configuration used by the bucket cache
+ * @return test instance
+ * @throws IOException if the bucket cache cannot be created
+ * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance bucketInstance(String ioEngineName,
+ long capacity, int blockSize, int[] bucketSizes, int writerThreadNum, int writerQLen,
+ String persistencePath, int ioErrorsTolerationDuration, Configuration conf) throws IOException {
+ Objects.requireNonNull(ioEngineName, "ioEngineName must not be null");
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(new BucketCache(ioEngineName, capacity, blockSize,
+ bucketSizes, writerThreadNum, writerQLen, persistencePath, ioErrorsTolerationDuration, conf));
+ }
+
+ /**
+ * Creates a {@link CacheAccessService} backed by {@link BucketCache}.
+ * @param ioEngineName IO engine name
+ * @param capacity cache capacity, in bytes
+ * @param blockSize expected block size, in bytes
+ * @param bucketSizes configured bucket sizes, or {@code null} to use defaults
+ * @param writerThreadNum number of writer threads
+ * @param writerQLen writer queue length
+ * @param persistencePath persistence path, or {@code null} for non-persistent cache
+ * @param ioErrorsTolerationDuration duration for tolerating IO engine errors
+ * @param conf configuration used by the bucket cache
+ * @param onlineRegions online regions map used by the bucket cache, or {@code null}
+ * @return cache access service backed by {@link BucketCache}
+ * @throws IOException if the bucket cache cannot be created
+ * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null}
+ */
+ public static CacheAccessService bucket(String ioEngineName, long capacity, int blockSize,
+ int[] bucketSizes, int writerThreadNum, int writerQLen, String persistencePath,
+ int ioErrorsTolerationDuration, Configuration conf, Map onlineRegions)
+ throws IOException {
+ return bucketInstance(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum,
+ writerQLen, persistencePath, ioErrorsTolerationDuration, conf, onlineRegions).service();
+ }
+
+ /**
+ * Creates a test instance containing {@link BucketCache} and its {@link CacheAccessService}
+ * facade.
+ * @param ioEngineName IO engine name
+ * @param capacity cache capacity, in bytes
+ * @param blockSize expected block size, in bytes
+ * @param bucketSizes configured bucket sizes, or {@code null} to use defaults
+ * @param writerThreadNum number of writer threads
+ * @param writerQLen writer queue length
+ * @param persistencePath persistence path, or {@code null} for non-persistent cache
+ * @param ioErrorsTolerationDuration duration for tolerating IO engine errors
+ * @param conf configuration used by the bucket cache
+ * @param onlineRegions online regions map used by the bucket cache, or {@code null}
+ * @return test instance
+ * @throws IOException if the bucket cache cannot be created
+ * @throws NullPointerException if {@code ioEngineName} or {@code conf} is {@code null}
+ */
+ public static CacheAccessServiceTestInstance bucketInstance(String ioEngineName,
+ long capacity, int blockSize, int[] bucketSizes, int writerThreadNum, int writerQLen,
+ String persistencePath, int ioErrorsTolerationDuration, Configuration conf,
+ Map onlineRegions) throws IOException {
+ Objects.requireNonNull(ioEngineName, "ioEngineName must not be null");
+ Objects.requireNonNull(conf, "conf must not be null");
+ return new CacheAccessServiceTestInstance<>(
+ new BucketCache(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, writerQLen,
+ persistencePath, ioErrorsTolerationDuration, conf, onlineRegions));
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java
new file mode 100644
index 000000000000..b9d8789f3c1d
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/CacheAccessServiceTestInstance.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.io.hfile.cache;
+
+import java.util.Objects;
+import org.apache.hadoop.hbase.io.hfile.BlockCache;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * Test helper that keeps a concrete {@link BlockCache} together with the {@link CacheAccessService}
+ * facade backed by that cache.
+ *
+ * This is useful for tests that need to exercise production code through
+ * {@link CacheAccessService}, while still inspecting implementation-specific state through the
+ * concrete cache instance.
+ *
+ * @param concrete block cache type
+ */
+@InterfaceAudience.Private
+public final class CacheAccessServiceTestInstance {
+
+ private final T blockCache;
+ private final CacheAccessService cacheAccessService;
+
+ CacheAccessServiceTestInstance(T blockCache) {
+ this.blockCache = Objects.requireNonNull(blockCache, "blockCache must not be null");
+ this.cacheAccessService = CacheAccessServices.fromBlockCache(blockCache);
+ }
+
+ /**
+ * Returns the concrete block cache instance.
+ *
+ * Tests should use this only when they need implementation-specific inspection or assertions.
+ * Normal cache access should go through {@link #service()}.
+ *
+ * @return concrete block cache
+ */
+ public T blockCache() {
+ return blockCache;
+ }
+
+ /**
+ * Returns the {@link CacheAccessService} facade backed by the concrete block cache.
+ * @return cache access service
+ */
+ public CacheAccessService service() {
+ return cacheAccessService;
+ }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java
new file mode 100644
index 000000000000..ac0b09bee21b
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/cache/TestCacheAccessServiceTestFactory.java
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.io.hfile.cache;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.io.hfile.LruAdaptiveBlockCache;
+import org.apache.hadoop.hbase.io.hfile.LruBlockCache;
+import org.apache.hadoop.hbase.io.hfile.TinyLfuBlockCache;
+import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
+import org.apache.hadoop.hbase.testclassification.IOTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link CacheAccessServiceTestFactory}.
+ */
+@Tag(IOTests.TAG)
+@Tag(SmallTests.TAG)
+public class TestCacheAccessServiceTestFactory {
+
+ private static final long MAX_SIZE = 1024L * 1024L;
+ private static final long BUCKET_CACHE_SIZE = 64L * 1024L * 1024L;
+ private static final long BLOCK_SIZE = 4096L;
+ private static final Executor DIRECT_EXECUTOR = Runnable::run;
+
+ @Test
+ void testDisabled() {
+ CacheAccessService service = CacheAccessServiceTestFactory.disabled();
+
+ assertNotNull(service);
+ assertFalse(service.isCacheEnabled());
+ }
+
+ @Test
+ void testFromBlockCacheRejectsNull() {
+ assertThrows(NullPointerException.class,
+ () -> CacheAccessServiceTestFactory.fromBlockCache(null));
+ }
+
+ @Test
+ void testFromConfigurationRejectsNull() {
+ assertThrows(NullPointerException.class,
+ () -> CacheAccessServiceTestFactory.fromConfiguration(null));
+ }
+
+ @Test
+ void testLruServiceOnlyFactory() {
+ Configuration conf = HBaseConfiguration.create();
+ CacheAccessService service =
+ CacheAccessServiceTestFactory.lru(MAX_SIZE, BLOCK_SIZE, true, conf);
+
+ try {
+ assertNotNull(service);
+ assertTrue(service.isCacheEnabled());
+ } finally {
+ service.shutdown();
+ }
+ }
+
+ @Test
+ void testLruInstanceFactory() {
+ Configuration conf = HBaseConfiguration.create();
+ CacheAccessServiceTestInstance instance =
+ CacheAccessServiceTestFactory.lruInstance(MAX_SIZE, BLOCK_SIZE, true, conf);
+
+ try {
+ assertNotNull(instance);
+ assertNotNull(instance.blockCache());
+ assertNotNull(instance.service());
+ assertInstanceOf(LruBlockCache.class, instance.blockCache());
+ assertTrue(instance.service().isCacheEnabled());
+ } finally {
+ instance.service().shutdown();
+ }
+ }
+
+ @Test
+ void testLruAdaptiveServiceOnlyFactory() {
+ Configuration conf = HBaseConfiguration.create();
+ CacheAccessService service =
+ CacheAccessServiceTestFactory.lruAdaptive(MAX_SIZE, BLOCK_SIZE, true, conf);
+
+ try {
+ assertNotNull(service);
+ assertTrue(service.isCacheEnabled());
+ } finally {
+ service.shutdown();
+ }
+ }
+
+ @Test
+ void testLruAdaptiveInstanceFactory() {
+ Configuration conf = HBaseConfiguration.create();
+ CacheAccessServiceTestInstance instance =
+ CacheAccessServiceTestFactory.lruAdaptiveInstance(MAX_SIZE, BLOCK_SIZE, true, conf);
+
+ try {
+ assertNotNull(instance);
+ assertNotNull(instance.blockCache());
+ assertNotNull(instance.service());
+ assertInstanceOf(LruAdaptiveBlockCache.class, instance.blockCache());
+ assertTrue(instance.service().isCacheEnabled());
+ } finally {
+ instance.service().shutdown();
+ }
+ }
+
+ @Test
+ void testTinyLfuServiceOnlyFactory() {
+ Configuration conf = HBaseConfiguration.create();
+ CacheAccessService service =
+ CacheAccessServiceTestFactory.tinyLfu(MAX_SIZE, BLOCK_SIZE, DIRECT_EXECUTOR, conf);
+
+ try {
+ assertNotNull(service);
+ assertTrue(service.isCacheEnabled());
+ } finally {
+ service.shutdown();
+ }
+ }
+
+ @Test
+ void testTinyLfuInstanceFactory() {
+ Configuration conf = HBaseConfiguration.create();
+ CacheAccessServiceTestInstance instance =
+ CacheAccessServiceTestFactory.tinyLfuInstance(MAX_SIZE, BLOCK_SIZE, DIRECT_EXECUTOR, conf);
+
+ try {
+ assertNotNull(instance);
+ assertNotNull(instance.blockCache());
+ assertNotNull(instance.service());
+ assertInstanceOf(TinyLfuBlockCache.class, instance.blockCache());
+ assertTrue(instance.service().isCacheEnabled());
+ } finally {
+ instance.service().shutdown();
+ }
+ }
+
+ @Test
+ void testTinyLfuInstanceFactoryRejectsNullExecutor() {
+ assertThrows(NullPointerException.class, () -> CacheAccessServiceTestFactory
+ .tinyLfuInstance(MAX_SIZE, BLOCK_SIZE, null, HBaseConfiguration.create()));
+ }
+
+ @Test
+ void testTinyLfuInstanceFactoryRejectsNullConfiguration() {
+ assertThrows(NullPointerException.class, () -> CacheAccessServiceTestFactory
+ .tinyLfuInstance(MAX_SIZE, BLOCK_SIZE, DIRECT_EXECUTOR, null));
+ }
+
+ @Test
+ void testBucketServiceOnlyFactory() throws IOException {
+ CacheAccessService service = CacheAccessServiceTestFactory.bucket("offheap", BUCKET_CACHE_SIZE,
+ (int) BLOCK_SIZE, null, 1, 1, null);
+
+ try {
+ assertNotNull(service);
+ assertTrue(service.isCacheEnabled());
+ } finally {
+ service.shutdown();
+ }
+ }
+
+ @Test
+ void testBucketInstanceFactory() throws IOException {
+ CacheAccessServiceTestInstance instance = CacheAccessServiceTestFactory
+ .bucketInstance("offheap", BUCKET_CACHE_SIZE, (int) BLOCK_SIZE, null, 1, 1, null);
+
+ try {
+ assertNotNull(instance);
+ assertNotNull(instance.blockCache());
+ assertNotNull(instance.service());
+ assertInstanceOf(BucketCache.class, instance.blockCache());
+ assertTrue(instance.service().isCacheEnabled());
+ } finally {
+ instance.service().shutdown();
+ }
+ }
+
+ @Test
+ void testBucketInstanceFactoryRejectsNullIoEngineName() {
+ assertThrows(NullPointerException.class, () -> CacheAccessServiceTestFactory
+ .bucketInstance(null, MAX_SIZE, (int) BLOCK_SIZE, null, 1, 1, null));
+ }
+
+ @Test
+ void testInstanceRejectsNullBlockCache() {
+ assertThrows(NullPointerException.class, () -> new CacheAccessServiceTestInstance<>(null));
+ }
+}