#include "rediska/cache/lru/LRU.hpp" #include #include #include #include #include "rediska/cache/lru/LRUConfig.hpp" #include "rediska/cache/types.hpp" #include "rediska/common/MessageArguments.hpp" #include "rediska/common/types.hpp" #include "rediska/common/utils.hpp" namespace cache { LRU::LRU(LRUConfig config, CacheOpCallback callback) : config_(std::move(config)), callback_(callback) {} void LRU::get(CacheKey&& key) { std::shared_lock lock(mutex_); auto it = keyToItem_.find(key); if (it == keyToItem_.end()) { return callback_(std::unexpected(RediskaReturnCode::NOT_FOUND)); } auto& metadata = it->second->location.metadata; if (metadata.isExpired()) { evict(it); return callback_(std::unexpected(RediskaReturnCode::KEY_EXPIRED)); } if (config_.resetTTLOnAccess) metadata.resetExpirationTime(); // Move to start lru_list_.splice(lru_list_.begin(), lru_list_, it->second); keyToItem_[key] = lru_list_.begin(); callback_(std::make_optional(it->second->location.value)); } void LRU::set(CacheKey&& key, CacheValue&& value, TTL ttl) { std::unique_lock lock(mutex_); auto it = keyToItem_.find(key); if (it != keyToItem_.end()) { it->second->location.value = std::move(value); it->second->location.metadata.resetExpirationTime(); lru_list_.splice(lru_list_.begin(), lru_list_, it->second); callback_(std::nullopt); return; } if (isFull()) evict(); lru_list_.push_front( CacheNode { .key = std::move(key), .location = ItemHandle{ .value = std::move(value), .metadata = LRU::ItemMetadata(ttl) } } ); keyToItem_[key] = lru_list_.begin(); callback_(std::nullopt); } void LRU::applyTo(CacheKey&& key, OperationId op, MessageArguments&& args) { std::unique_lock lock(mutex_); auto it = keyToItem_.find(key); if (it == keyToItem_.end()) { return callback_(std::unexpected(RediskaReturnCode::NOT_FOUND)); } auto& metadata = it->second->location.metadata; std::expected, RediskaReturnCode> value; std::visit([&value, op](auto&& arg) { using T = std::decay_t; if constexpr (is_any_of_v) { value = std::unexpected(RediskaReturnCode::INCOMPATIBLE_OPERATION); } else if constexpr (std::is_same_v>) { // TODO: Replace with real data argument DSValue dummy_data{}; std::expected, DSReturnCode> res = arg->handle(op, std::move(dummy_data)); if (!res) { value = std::unexpected(DSReturnCodeToRediskaReturnCode(res.error())); return; } if (!res.value().has_value()) { value = std::nullopt; return; } // TODO: Explore implicit conversion // Attempts to covert failed value = std::visit([](auto&& arg) -> CacheValue { return CacheValue{arg}; }, std::move(res->value())); } }, it->second->location.value); lru_list_.splice(lru_list_.begin(), lru_list_, it->second); callback_(std::move(value)); return; } void LRU::evict() { if (lru_list_.empty()) return; keyToItem_.erase(lru_list_.back().key); lru_list_.pop_back(); } void LRU::evict(const std::unordered_map::iterator>::iterator node) { lru_list_.erase(node->second); keyToItem_.erase(node); } inline bool LRU::isFull() const { return lru_list_.size() >= config_.maxCapacity; } }