cachelib/navy/block_cache/Region.h (184 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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
#include <mutex>
#include "cachelib/navy/block_cache/Types.h"
#include "cachelib/navy/common/Types.h"
#include "cachelib/navy/serialization/Serialization.h"
namespace facebook {
namespace cachelib {
namespace navy {
enum class OpenMode {
// Not opened
None,
// Write Mode, caller always uses this mode for writing
Write,
// Read Mode, caller always uses this mode for reading
Read,
};
enum class OpenStatus {
// Caller can proceed
Ready,
// Sequence number mismatch
Retry,
// Error (like too large, out of regions)
Error,
};
class RegionDescriptor;
// Region. Responsible for open/lock synchronization and keeps count of active
// readers/writers.
class Region {
public:
// @param id unique id for the region
// @param regionSize region size
Region(RegionId id, uint64_t regionSize)
: regionId_{id}, regionSize_{regionSize} {}
// @param d previously serialized state of Region.
// @param regionSize size of the region.
Region(const serialization::Region& d, uint64_t regionSize)
: regionId_{static_cast<uint32_t>(*d.regionId_ref())},
regionSize_{regionSize},
priority_{static_cast<uint16_t>(*d.priority_ref())},
lastEntryEndOffset_{static_cast<uint32_t>(*d.lastEntryEndOffset_ref())},
numItems_{static_cast<uint32_t>(*d.numItems_ref())} {}
// Disable copy constructor to avoid mistakes like below:
// auto r = RegionManager.getRegion(rid);
// Did you notice that we forgot reference "&"?
Region(const Region&) = delete;
Region& operator=(const Region&) = delete;
// Immediately block future accesses to this region. Return true if
// there are no pending operation to this region, false otherwise.
// It is safe to repeatedly call this until success. Note that it is
// only safe for one thread to call this, as we assume only a single
// thread can be running region reclaim at a time.
bool readyForReclaim();
// Opens this region for write and allocate a slot of @size.
// Fail if there's insufficient space.
std::tuple<RegionDescriptor, RelAddress> openAndAllocate(uint32_t size);
// Opens this region for reading. Fail if region is blocked.
RegionDescriptor openForRead();
// Resets the region's internal state. This is used to reset state
// after a region has been reclaimed.
void reset();
// Closes the region and consume the region descriptor.
void close(RegionDescriptor&& desc);
// Assigns this region a priority. The meaning of priority
// is dependent on the eviction policy we choose.
void setPriority(uint16_t priority) {
std::lock_guard<std::mutex> l{lock_};
priority_ = priority;
}
// Gets the priority this region is assigned.
uint16_t getPriority() const {
std::lock_guard<std::mutex> l{lock_};
return priority_;
}
// Gets the end offset of last slot added to this region.
uint32_t getLastEntryEndOffset() const {
std::lock_guard<std::mutex> l{lock_};
return lastEntryEndOffset_;
}
// Gets the number of items in this region.
uint32_t getNumItems() const {
std::lock_guard<std::mutex> l{lock_};
return numItems_;
}
// If this region is actively used, then the fragmentation
// is the bytes at the end of the region that's not used.
uint32_t getFragmentationSize() const {
std::lock_guard<std::mutex> l{lock_};
if (numItems_) {
return regionSize_ - lastEntryEndOffset_;
}
return 0;
}
// Writes buf to attached buffer at offset 'offset'.
void writeToBuffer(uint32_t offset, BufferView buf);
// Reads from attached buffer from 'fromOffset' into 'outBuf'.
void readFromBuffer(uint32_t fromOffset, MutableBufferView outBuf) const;
// Attaches buffer 'buf' to the region.
void attachBuffer(std::unique_ptr<Buffer>&& buf) {
std::lock_guard l{lock_};
XDCHECK_EQ(buffer_, nullptr);
buffer_ = std::move(buf);
}
// Checks if the region has buffer attached.
bool hasBuffer() const {
std::lock_guard l{lock_};
return buffer_.get() != nullptr;
}
// Detaches the attached buffer and returns it only if there are no
// active readers, otherwise returns nullptr.
std::unique_ptr<Buffer> detachBuffer() {
std::lock_guard l{lock_};
XDCHECK_NE(buffer_, nullptr);
if (activeInMemReaders_ == 0) {
XDCHECK_EQ(activeWriters_, 0UL);
auto retBuf = std::move(buffer_);
buffer_ = nullptr;
return retBuf;
}
return nullptr;
}
// Flushes the attached buffer by calling the callBack function.
// The callBack function is expected to write to the underlying device.
// The callback function should return true if successfully flushed the
// buffer, otherwise it should return false.
enum FlushRes {
kSuccess,
kRetryDeviceFailure,
kRetryPendingWrites,
};
FlushRes flushBuffer(std::function<bool(RelAddress, BufferView)> callBack);
// Cleans up the attached buffer by calling the callBack function.
bool cleanupBuffer(std::function<void(RegionId, BufferView)> callBack);
// Marks the bit to indicate pending flush status.
void setPendingFlush() {
std::lock_guard l{lock_};
XDCHECK_NE(buffer_, nullptr);
XDCHECK((flags_ & (kFlushPending | kFlushed)) == 0);
flags_ |= kFlushPending;
}
// Checks if the region's buffer is flushed.
bool isFlushedLocked() const { return (flags_ & kFlushed) != 0; }
// Checks whether the region's buffer is cleaned up.
bool isCleanedupLocked() const { return (flags_ & kCleanedup) != 0; }
// Returns the number of active writers using the region.
uint32_t getActiveWriters() const {
std::lock_guard l{lock_};
return activeWriters_;
}
// Returns the number of active readers using the region.
uint32_t getActiveInMemReaders() const {
std::lock_guard l{lock_};
return activeInMemReaders_;
}
// Returns the region id.
RegionId id() const { return regionId_; }
private:
uint32_t activeOpenLocked();
// Checks to see if there is enough space in the region for a new write of
// size 'size'.
bool canAllocateLocked(uint32_t size) const {
// assert that buffer is not flushed and flush is not pending
XDCHECK((flags_ & (kFlushPending | kFlushed)) == 0);
return (lastEntryEndOffset_ + size <= regionSize_);
}
RelAddress allocateLocked(uint32_t size);
static constexpr uint32_t kBlockAccess{1u << 0};
static constexpr uint16_t kPinned{1u << 1};
static constexpr uint16_t kFlushPending{1u << 2};
static constexpr uint16_t kFlushed{1u << 3};
static constexpr uint16_t kCleanedup{1u << 4};
const RegionId regionId_{};
const uint64_t regionSize_{0};
uint16_t priority_{0};
uint16_t flags_{0};
uint32_t activePhysReaders_{0};
uint32_t activeInMemReaders_{0};
uint32_t activeWriters_{0};
// End offset of last slot added to region
uint32_t lastEntryEndOffset_{0};
uint32_t numItems_{0};
std::unique_ptr<Buffer> buffer_{nullptr};
mutable std::mutex lock_;
};
// RegionDescriptor. Contains status of the open, region id and the open mode.
// This is returned when a open is done in read/write mode. close() uses the
// descriptor to properly close and update the internal counters.
class RegionDescriptor {
public:
// @param status status of the open
RegionDescriptor(OpenStatus status) : status_(status) {}
static RegionDescriptor makeWriteDescriptor(OpenStatus status,
RegionId regionId) {
return RegionDescriptor{status, regionId, OpenMode::Write};
}
static RegionDescriptor makeReadDescriptor(OpenStatus status,
RegionId regionId,
bool physReadMode) {
return RegionDescriptor{status, regionId, OpenMode::Read, physReadMode};
}
RegionDescriptor(const RegionDescriptor&) = delete;
RegionDescriptor& operator=(const RegionDescriptor&) = delete;
RegionDescriptor(RegionDescriptor&& o) noexcept
: status_{o.status_},
regionId_{o.regionId_},
mode_{o.mode_},
physReadMode_{o.physReadMode_} {
o.mode_ = OpenMode::None;
o.regionId_ = RegionId{};
o.status_ = OpenStatus::Retry;
o.physReadMode_ = false;
}
RegionDescriptor& operator=(RegionDescriptor&& o) noexcept {
if (this != &o) {
this->~RegionDescriptor();
new (this) RegionDescriptor(std::move(o));
}
return *this;
}
// Checks whether the current open mode is Physical Read Mode.
bool isPhysReadMode() const {
return (mode_ == OpenMode::Read) && physReadMode_;
}
// Checks whether status of the open is Ready.
bool isReady() const { return status_ == OpenStatus::Ready; }
// Returns the current open mode.
OpenMode mode() const { return mode_; }
// Returns the current open status.
OpenStatus status() const { return status_; }
// Returns the unique region ID.
RegionId id() const { return regionId_; }
private:
RegionDescriptor(OpenStatus status,
RegionId regionId,
OpenMode mode,
bool physReadMode = false)
: status_(status),
regionId_(regionId),
mode_(mode),
physReadMode_(physReadMode) {}
OpenStatus status_;
RegionId regionId_{};
OpenMode mode_{OpenMode::None};
// physReadMode_ is applicable only in read mode
bool physReadMode_{false};
};
} // namespace navy
} // namespace cachelib
} // namespace facebook