private CompletableFuture<DataBlock> getBlock0(GetOptions options, ObjectReader objectReader,
            DataBlockIndex dataBlockIndex) {
            long objectId = objectReader.metadata().objectId();
            DataBlockGroupKey key = new DataBlockGroupKey(objectId, dataBlockIndex);
            DataBlock dataBlock = blocks.get(key);
            if (dataBlock == null) {
                DataBlock newDataBlock = new DataBlock(objectId, dataBlockIndex, this, time);
                dataBlock = newDataBlock;
                blocks.put(key, newDataBlock);
                read(options, objectReader, newDataBlock, eventLoop);
            }
            lru.touchIfExist(key);
            CompletableFuture<DataBlock> cf = new CompletableFuture<>();
            // if the data is already loaded, the listener will be invoked right now,
            // else the listener will be invoked immediately after data loaded in the same eventLoop.
            if (!dataBlock.dataFuture().isDone()) {
                if (options.isReadahead()) {
                    StorageOperationStats.getInstance().blockCacheReadaheadThroughput.add(MetricsLevel.INFO, dataBlock.dataBlockIndex().size());
                } else {
                    StorageOperationStats.getInstance().blockCacheBlockMissThroughput.add(MetricsLevel.INFO, dataBlock.dataBlockIndex().size());
                }
            }
            // DataBlock#retain should will before the complete the future to avoid the other read use #markRead to really free the data block.
            dataBlock.retain();
            dataBlock.dataFuture().whenComplete((db, ex) -> {
                if (ex != null) {
                    cf.completeExceptionally(ex);
                    return;
                }
                cf.complete(db);
            });
            tryEvictExpired();
            return cf;
        }
Get the data block from cache.
<p>
Note: the data block should invoke the {@link DataBlock#release()} after using.

@param options        the get options
@param objectReader   the object reader could be used read the data block when cache misses
@param dataBlockIndex the required data block's index
@return the future of {@link DataBlock}, the future will be completed in the same stream's eventLoop.
s3stream/src/main/java/com/automq/stream/s3/cache/blockcache/DataBlockCache.java