207 lines
7.9 KiB
C++
207 lines
7.9 KiB
C++
/*
|
|
* AsyncFileReadAhead.actor.h
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source
|
|
// version.
|
|
#if defined(NO_INTELLISENSE) && !defined(FDBRPC_ASYNCFILEREADAHEAD_ACTOR_G_H)
|
|
#define FDBRPC_ASYNCFILEREADAHEAD_ACTOR_G_H
|
|
#include "fdbrpc/AsyncFileReadAhead.actor.g.h"
|
|
#elif !defined(FDBRPC_ASYNCFILEREADAHEAD_ACTOR_H)
|
|
#define FDBRPC_ASYNCFILEREADAHEAD_ACTOR_H
|
|
|
|
#include "flow/flow.h"
|
|
#include "fdbrpc/IAsyncFile.h"
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
// Read-only file type that wraps another file instance, reads in large blocks, and reads ahead of the actual range
|
|
// requested
|
|
class AsyncFileReadAheadCache final : public IAsyncFile, public ReferenceCounted<AsyncFileReadAheadCache> {
|
|
public:
|
|
void addref() override { ReferenceCounted<AsyncFileReadAheadCache>::addref(); }
|
|
void delref() override { ReferenceCounted<AsyncFileReadAheadCache>::delref(); }
|
|
|
|
struct CacheBlock : ReferenceCounted<CacheBlock> {
|
|
CacheBlock(int size = 0) : data(new uint8_t[size]), len(size) {}
|
|
~CacheBlock() { delete[] data; }
|
|
uint8_t* data;
|
|
int len;
|
|
};
|
|
|
|
// Read from the underlying file to a CacheBlock
|
|
ACTOR static Future<Reference<CacheBlock>> readBlock(AsyncFileReadAheadCache* f, int length, int64_t offset) {
|
|
wait(f->m_max_concurrent_reads.take());
|
|
|
|
state Reference<CacheBlock> block(new CacheBlock(length));
|
|
try {
|
|
int len = wait(f->m_f->read(block->data, length, offset));
|
|
block->len = len;
|
|
} catch (Error& e) {
|
|
f->m_max_concurrent_reads.release(1);
|
|
throw e;
|
|
}
|
|
|
|
f->m_max_concurrent_reads.release(1);
|
|
return block;
|
|
}
|
|
|
|
ACTOR static Future<int> read_impl(Reference<AsyncFileReadAheadCache> f, void* data, int length, int64_t offset) {
|
|
// Make sure range is valid for the file
|
|
int64_t fileSize = wait(f->size());
|
|
if (offset >= fileSize)
|
|
return 0; // TODO: Should this throw since the input isn't really valid?
|
|
|
|
if (length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// If reading past the end then clip length to just read to the end
|
|
if (offset + length > fileSize)
|
|
length = fileSize - offset; // Length is at least 1 since offset < fileSize
|
|
|
|
// Calculate block range for the blocks that contain this data
|
|
state int firstBlockNum = offset / f->m_block_size;
|
|
ASSERT(f->m_block_size > 0);
|
|
state int lastBlockNum = (offset + length - 1) / f->m_block_size;
|
|
|
|
// Start reads (if needed) of the block range required for this read, plus the read ahead blocks
|
|
// The futures for the read started will be stored in the cache but since things can be evicted from
|
|
// the cache while we're wait()ing we also will keep a local cache of futures for the blocks
|
|
// we need (not the read ahead blocks).
|
|
state std::map<int, Future<Reference<CacheBlock>>> localCache;
|
|
|
|
// Start blocks up to the read ahead size beyond the last needed block but don't go past the end of the file
|
|
state int lastBlockNumInFile = ((fileSize + f->m_block_size - 1) / f->m_block_size) - 1;
|
|
ASSERT(lastBlockNum <= lastBlockNumInFile);
|
|
int lastBlockToStart = std::min<int>(lastBlockNum + f->m_read_ahead_blocks, lastBlockNumInFile);
|
|
|
|
state int blockNum;
|
|
for (blockNum = firstBlockNum; blockNum <= lastBlockToStart; ++blockNum) {
|
|
Future<Reference<CacheBlock>> fblock;
|
|
|
|
// Look in the per-file cache for the block's future
|
|
auto i = f->m_blocks.find(blockNum);
|
|
// If not found, start the read.
|
|
if (i == f->m_blocks.end() || (i->second.isValid() && i->second.isError())) {
|
|
// printf("starting read of %s block %d\n", f->getFilename().c_str(), blockNum);
|
|
fblock = readBlock(f.getPtr(), f->m_block_size, f->m_block_size * blockNum);
|
|
f->m_blocks[blockNum] = fblock;
|
|
} else
|
|
fblock = i->second;
|
|
|
|
// Only put blocks we actually need into our local cache
|
|
if (blockNum <= lastBlockNum)
|
|
localCache[blockNum] = fblock;
|
|
}
|
|
|
|
// Read block(s) and copy data
|
|
state int wpos = 0;
|
|
for (blockNum = firstBlockNum; blockNum <= lastBlockNum; ++blockNum) {
|
|
// Wait for block to be ready
|
|
Reference<CacheBlock> block = wait(localCache[blockNum]);
|
|
|
|
// Calculate the block-relative read range. It's a given that the offset / length range touches this block
|
|
// so readStart will never be greater than blocksize (though it could be past the actual end of a short
|
|
// block).
|
|
int64_t blockStart = blockNum * f->m_block_size;
|
|
int64_t readStart = std::max<int64_t>(0, offset - blockStart);
|
|
int64_t readEnd = std::min<int64_t>(f->m_block_size, offset + length - blockStart);
|
|
int rlen = readEnd - readStart;
|
|
memcpy((uint8_t*)data + wpos, block->data + readStart, rlen);
|
|
wpos += rlen;
|
|
}
|
|
|
|
ASSERT(wpos == length);
|
|
localCache.clear();
|
|
|
|
// If the cache is too large then go through the cache in block number order and remove any entries whose future
|
|
// has a reference count of 1, stopping once the cache is no longer too big. There is no point in removing
|
|
// an entry from the cache if it has a reference count of > 1 because it will continue to exist and use memory
|
|
// anyway so it should be left in the cache so that other readers may benefit from it.
|
|
|
|
// printf("cache block limit: %d Cache contents:\n", f->m_cache_block_limit);
|
|
// for(auto &m : f->m_blocks) printf("\tblock %d refcount %d\n", m.first, m.second.getFutureReferenceCount());
|
|
|
|
if (f->m_blocks.size() > f->m_cache_block_limit) {
|
|
auto i = f->m_blocks.begin();
|
|
while (i != f->m_blocks.end()) {
|
|
if (i->second.getFutureReferenceCount() == 1) {
|
|
// printf("evicting block %d\n", i->first);
|
|
i = f->m_blocks.erase(i);
|
|
if (f->m_blocks.size() <= f->m_cache_block_limit)
|
|
break;
|
|
} else
|
|
++i;
|
|
}
|
|
}
|
|
|
|
return wpos;
|
|
}
|
|
|
|
Future<int> read(void* data, int length, int64_t offset) override {
|
|
return read_impl(Reference<AsyncFileReadAheadCache>::addRef(this), data, length, offset);
|
|
}
|
|
|
|
Future<Void> write(void const* data, int length, int64_t offset) override { throw file_not_writable(); }
|
|
Future<Void> truncate(int64_t size) override { throw file_not_writable(); }
|
|
|
|
Future<Void> sync() override { return Void(); }
|
|
Future<Void> flush() override { return Void(); }
|
|
|
|
Future<int64_t> size() const override { return m_f->size(); }
|
|
|
|
Future<Void> readZeroCopy(void** data, int* length, int64_t offset) override {
|
|
TraceEvent(SevError, "ReadZeroCopyNotSupported").detail("FileType", "ReadAheadCache");
|
|
return platform_error();
|
|
}
|
|
void releaseZeroCopy(void* data, int length, int64_t offset) override {}
|
|
|
|
int64_t debugFD() const override { return -1; }
|
|
|
|
std::string getFilename() const override { return m_f->getFilename(); }
|
|
|
|
~AsyncFileReadAheadCache() override {
|
|
for (auto& it : m_blocks) {
|
|
it.second.cancel();
|
|
}
|
|
}
|
|
|
|
Reference<IAsyncFile> m_f;
|
|
int m_block_size;
|
|
int m_read_ahead_blocks;
|
|
int m_cache_block_limit;
|
|
FlowLock m_max_concurrent_reads;
|
|
|
|
// Map block numbers to future
|
|
std::map<int, Future<Reference<CacheBlock>>> m_blocks;
|
|
|
|
AsyncFileReadAheadCache(Reference<IAsyncFile> f,
|
|
int blockSize,
|
|
int readAheadBlocks,
|
|
int maxConcurrentReads,
|
|
int cacheSizeBlocks)
|
|
: m_f(f), m_block_size(blockSize), m_read_ahead_blocks(readAheadBlocks),
|
|
m_cache_block_limit(std::max<int>(1, cacheSizeBlocks)), m_max_concurrent_reads(maxConcurrentReads) {}
|
|
};
|
|
|
|
#include "flow/unactorcompiler.h"
|
|
#endif
|