diff options
| author | Stefan Boberg <[email protected]> | 2021-09-27 12:34:52 +0200 |
|---|---|---|
| committer | Stefan Boberg <[email protected]> | 2021-09-27 12:34:52 +0200 |
| commit | f0036eada7f6bcf6e08afe3ea8517367ed73450e (patch) | |
| tree | b1ce3466bba36175cad369028fad1b410a34b5ec /zenserver/cache | |
| parent | Fixed httpsys Windows compilation error (diff) | |
| parent | GetWindowsErrorAsString() -> GetSystemErrorAsString() (diff) | |
| download | zen-f0036eada7f6bcf6e08afe3ea8517367ed73450e.tar.xz zen-f0036eada7f6bcf6e08afe3ea8517367ed73450e.zip | |
Merged latest from main
Diffstat (limited to 'zenserver/cache')
| -rw-r--r-- | zenserver/cache/cachestore.cpp | 252 | ||||
| -rw-r--r-- | zenserver/cache/cachestore.h | 84 | ||||
| -rw-r--r-- | zenserver/cache/structuredcachestore.cpp | 271 | ||||
| -rw-r--r-- | zenserver/cache/structuredcachestore.h | 1 |
4 files changed, 187 insertions, 421 deletions
diff --git a/zenserver/cache/cachestore.cpp b/zenserver/cache/cachestore.cpp deleted file mode 100644 index 2fc253a07..000000000 --- a/zenserver/cache/cachestore.cpp +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "cachestore.h" - -#include <zencore/crc32.h> -#include <zencore/except.h> -#include <zencore/logging.h> -#include <zencore/windows.h> - -#include <zencore/filesystem.h> -#include <zencore/fmtutils.h> -#include <zencore/iobuffer.h> -#include <zencore/string.h> -#include <zencore/thread.h> -#include <zenstore/basicfile.h> -#include <zenstore/cas.h> -#include <zenstore/caslog.h> - -#include <fmt/core.h> -#include <concepts> -#include <filesystem> -#include <gsl/gsl-lite.hpp> -#include <unordered_map> - -#include <atlfile.h> - -using namespace zen; -using namespace fmt::literals; - -namespace UE { - -struct CorruptionTrailer -{ - enum - { - /** Arbitrary number used to identify corruption **/ - MagicConstant = 0x1e873d89 - }; - - uint32_t Magic = MagicConstant; - uint32_t Version = 1; - uint32_t CRCofPayload = 0; - uint32_t SizeOfPayload = 0; - - void Initialize(const void* Data, size_t Size) - { - CRCofPayload = zen::MemCrc32_Deprecated(Data, Size); - SizeOfPayload = (uint32_t)Size; - } -}; - -std::filesystem::path -GenerateDdcPath(std::string_view Key, std::filesystem::path& rootDir) -{ - std::filesystem::path FilePath = rootDir; - - std::string k8{Key}; - for (auto& c : k8) - c = (char)toupper(c); - - const uint32_t Hash = zen::StrCrc_Deprecated(k8.c_str()); - - std::wstring DirName; - - DirName = u'0' + ((Hash / 100) % 10); - FilePath /= DirName; - DirName = u'0' + ((Hash / 10) % 10); - FilePath /= DirName; - DirName = u'0' + (Hash % 10); - FilePath /= DirName; - - FilePath /= Key; - - auto NativePath = FilePath.native(); - NativePath.append(L".udd"); - - return NativePath; -} - -} // namespace UE - -////////////////////////////////////////////////////////////////////////// - -FileCacheStore::FileCacheStore(const char* RootDir, const char* ReadRootDir) -{ - // Ensure root directory exists - create if it doesn't exist already - - ZEN_INFO("Initializing FileCacheStore at '{}'", std::string_view(RootDir)); - - m_RootDir = RootDir; - - std::error_code ErrorCode; - - std::filesystem::create_directories(m_RootDir, ErrorCode); - - if (ErrorCode) - { - ExtendableStringBuilder<256> Name; - WideToUtf8(m_RootDir.c_str(), Name); - - ZEN_ERROR("Could not open file cache directory '{}' for writing ({})", Name.c_str(), ErrorCode.message()); - - m_IsOk = false; - } - - if (ReadRootDir) - { - m_ReadRootDir = ReadRootDir; - - if (std::filesystem::exists(m_ReadRootDir, ErrorCode)) - { - ZEN_INFO("FileCacheStore will use additional read tree at '{}'", std::string_view(ReadRootDir)); - - m_ReadRootIsValid = true; - } - } -} - -FileCacheStore::~FileCacheStore() -{ -} - -bool -FileCacheStore::Get(std::string_view Key, CacheValue& OutValue) -{ - CAtlFile File; - - std::filesystem::path NativePath; - - HRESULT hRes = E_FAIL; - - if (m_ReadRootDir.empty() == false) - { - NativePath = UE::GenerateDdcPath(Key, m_ReadRootDir); - - hRes = File.Create(NativePath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); - } - - if (FAILED(hRes)) - { - NativePath = UE::GenerateDdcPath(Key, m_RootDir); - - hRes = File.Create(NativePath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); - } - - if (FAILED(hRes)) - { - ZEN_DEBUG("GET MISS {}", Key); - - return false; - } - - ULONGLONG FileSize; - File.GetSize(FileSize); - - if (FileSize <= 16) - { - return false; - } - - FileSize -= 16; // CorruptionWrapper trailer - - OutValue.Value = IoBuffer(IoBuffer::File, File.Detach(), 0, FileSize); - - ZEN_DEBUG("GET HIT {}", Key); - - return true; -} - -void -FileCacheStore::Put(std::string_view Key, const CacheValue& Value) -{ - const void* Data = Value.Value.Data(); - size_t Size = Value.Value.Size(); - - UE::CorruptionTrailer Trailer; - Trailer.Initialize(Data, Size); - - std::filesystem::path NativePath = UE::GenerateDdcPath(Key, m_RootDir); - - CAtlTemporaryFile File; - - ZEN_DEBUG("PUT {}", Key); - - HRESULT hRes = File.Create(m_RootDir.c_str()); - - if (SUCCEEDED(hRes)) - { - const uint8_t* WritePointer = reinterpret_cast<const uint8_t*>(Data); - - while (Size) - { - const int MaxChunkSize = 16 * 1024 * 1024; - const int ChunkSize = (int)((Size > MaxChunkSize) ? MaxChunkSize : Size); - - DWORD BytesWritten = 0; - File.Write(WritePointer, ChunkSize, &BytesWritten); - - Size -= BytesWritten; - WritePointer += BytesWritten; - } - - File.Write(&Trailer, sizeof Trailer); - hRes = File.Close(NativePath.c_str()); // This renames the file to its final name - - if (FAILED(hRes)) - { - ZEN_WARN("Failed to rename temp file for key '{}' - deleting temporary file", Key); - - if (!DeleteFile(File.TempFileName())) - { - ZEN_WARN("Temp file for key '{}' could not be deleted - no value persisted", Key); - } - } - } -} - -////////////////////////////////////////////////////////////////////////// - -MemoryCacheStore::MemoryCacheStore() -{ -} - -MemoryCacheStore::~MemoryCacheStore() -{ -} - -bool -MemoryCacheStore::Get(std::string_view InKey, CacheValue& OutValue) -{ - RwLock::SharedLockScope _(m_Lock); - - auto it = m_CacheMap.find(std::string(InKey)); - - if (it == m_CacheMap.end()) - { - return false; - } - else - { - OutValue.Value = it->second; - - return true; - } -} - -void -MemoryCacheStore::Put(std::string_view Key, const CacheValue& Value) -{ - RwLock::ExclusiveLockScope _(m_Lock); - m_CacheMap[std::string(Key)] = Value.Value; -} diff --git a/zenserver/cache/cachestore.h b/zenserver/cache/cachestore.h deleted file mode 100644 index 89c6396b8..000000000 --- a/zenserver/cache/cachestore.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include <zencore/IoBuffer.h> -#include <zencore/iohash.h> -#include <zencore/thread.h> -#include <zencore/uid.h> -#include <zenstore/cas.h> -#include <compare> -#include <filesystem> -#include <unordered_map> - -namespace zen { - -class WideStringBuilderBase; -class CasStore; - -} // namespace zen - -struct CacheValue -{ - zen::IoBuffer Value; -}; - -/****************************************************************************** - - /$$ /$$/$$ /$$ /$$$$$$ /$$ - | $$ /$$| $$ | $$ /$$__ $$ | $$ - | $$ /$$/| $$ | $$ | $$ \__/ /$$$$$$ /$$$$$$| $$$$$$$ /$$$$$$ - | $$$$$/ | $$ / $$/ | $$ |____ $$/$$_____| $$__ $$/$$__ $$ - | $$ $$ \ $$ $$/ | $$ /$$$$$$| $$ | $$ \ $| $$$$$$$$ - | $$\ $$ \ $$$/ | $$ $$/$$__ $| $$ | $$ | $| $$_____/ - | $$ \ $$ \ $/ | $$$$$$| $$$$$$| $$$$$$| $$ | $| $$$$$$$ - |__/ \__/ \_/ \______/ \_______/\_______|__/ |__/\_______/ - - Basic Key-Value cache. No restrictions on keys, and values are always opaque - binary blobs. - -******************************************************************************/ - -class CacheStore -{ -public: - virtual bool Get(std::string_view Key, CacheValue& OutValue) = 0; - virtual void Put(std::string_view Key, const CacheValue& Value) = 0; -}; - -/** File system based implementation - - Emulates the behaviour of UE4 with regards to file system structure, - and also adds a file corruption trailer to remain compatible with - the file-system based implementation (this should be made configurable) - - */ -class FileCacheStore : public CacheStore -{ -public: - FileCacheStore(const char* RootDir, const char* ReadRootDir = nullptr); - ~FileCacheStore(); - - virtual bool Get(std::string_view Key, CacheValue& OutValue) override; - virtual void Put(std::string_view Key, const CacheValue& Value) override; - -private: - std::filesystem::path m_RootDir; - std::filesystem::path m_ReadRootDir; - bool m_IsOk = true; - bool m_ReadRootIsValid = false; -}; - -class MemoryCacheStore : public CacheStore -{ -public: - MemoryCacheStore(); - ~MemoryCacheStore(); - - virtual bool Get(std::string_view Key, CacheValue& OutValue) override; - virtual void Put(std::string_view Key, const CacheValue& Value) override; - -private: - zen::RwLock m_Lock; - std::unordered_map<std::string, zen::IoBuffer> m_CacheMap; -}; diff --git a/zenserver/cache/structuredcachestore.cpp b/zenserver/cache/structuredcachestore.cpp index 3d80bb14c..5e93ebaa9 100644 --- a/zenserver/cache/structuredcachestore.cpp +++ b/zenserver/cache/structuredcachestore.cpp @@ -22,8 +22,6 @@ #include <gsl/gsl-lite.hpp> #include <unordered_map> -#include <atlfile.h> - ////////////////////////////////////////////////////////////////////////// namespace zen { @@ -131,23 +129,18 @@ ZenCacheMemoryLayer::~ZenCacheMemoryLayer() bool ZenCacheMemoryLayer::Get(std::string_view InBucket, const IoHash& HashKey, ZenCacheValue& OutValue) { - CacheBucket* Bucket = nullptr; - - { - RwLock::SharedLockScope _(m_Lock); + RwLock::SharedLockScope _(m_Lock); - auto it = m_Buckets.find(std::string(InBucket)); + auto it = m_Buckets.find(std::string(InBucket)); - if (it != m_Buckets.end()) - { - Bucket = &it->second; - } + if (it == m_Buckets.end()) + { + return false; } - if (Bucket == nullptr) - return false; + CacheBucket* Bucket = Bucket = &it->second; - ZEN_ASSERT(Bucket != nullptr); + _.ReleaseNow(); return Bucket->Get(HashKey, OutValue); } @@ -177,8 +170,6 @@ ZenCacheMemoryLayer::Put(std::string_view InBucket, const IoHash& HashKey, const Bucket = &m_Buckets[std::string(InBucket)]; } - ZEN_ASSERT(Bucket != nullptr); - // Note that since the underlying IoBuffer is retained, the content type is also Bucket->Put(HashKey, Value); @@ -195,7 +186,31 @@ ZenCacheMemoryLayer::DropBucket(std::string_view Bucket) void ZenCacheMemoryLayer::Scrub(ScrubContext& Ctx) { - ZEN_UNUSED(Ctx); + RwLock::SharedLockScope _(m_Lock); + + for (auto& Kv : m_Buckets) + { + Kv.second.Scrub(Ctx); + } +} + +void +ZenCacheMemoryLayer::CacheBucket::Scrub(ScrubContext& Ctx) +{ + std::vector<IoHash> BadHashes; + + for (auto& Kv : m_cacheMap) + { + if (Kv.first != IoHash::HashBuffer(Kv.second)) + { + BadHashes.push_back(Kv.first); + } + } + + if (!BadHashes.empty()) + { + Ctx.ReportBadChunks(BadHashes); + } } bool @@ -203,16 +218,16 @@ ZenCacheMemoryLayer::CacheBucket::Get(const IoHash& HashKey, ZenCacheValue& OutV { RwLock::SharedLockScope _(m_bucketLock); - auto bucketIt = m_cacheMap.find(HashKey); - - if (bucketIt == m_cacheMap.end()) + if (auto bucketIt = m_cacheMap.find(HashKey); bucketIt == m_cacheMap.end()) { return false; } + else + { + OutValue.Value = bucketIt->second; - OutValue.Value = bucketIt->second; - - return true; + return true; + } } void @@ -241,8 +256,19 @@ struct DiskLocation static uint64_t CombineOffsetAndFlags(uint64_t Offset, uint64_t Flags) { return Offset | Flags; } - inline uint64_t Offset() const { return OffsetAndFlags & kOffsetMask; } - inline uint64_t IsFlagSet(uint64_t Flag) const { return OffsetAndFlags & Flag; } + inline uint64_t Offset() const { return OffsetAndFlags & kOffsetMask; } + inline uint64_t IsFlagSet(uint64_t Flag) const { return OffsetAndFlags & Flag; } + inline ZenContentType GetContentType() const + { + ZenContentType ContentType = ZenContentType::kBinary; + + if (IsFlagSet(DiskLocation::kStructured)) + { + ContentType = ZenContentType::kCbObject; + } + + return ContentType; + } }; struct DiskIndexEntry @@ -267,6 +293,7 @@ struct ZenCacheDiskLayer::CacheBucket void Put(const IoHash& HashKey, const ZenCacheValue& Value); void Drop(); void Flush(); + void Scrub(ScrubContext& Ctx); inline bool IsOk() const { return m_Ok; } @@ -277,15 +304,19 @@ private: bool m_Ok = false; uint64_t m_LargeObjectThreshold = 64 * 1024; + // These files are used to manage storage of small objects for this bucket + BasicFile m_SobsFile; TCasLogFile<DiskIndexEntry> m_SlogFile; - void BuildPath(WideStringBuilderBase& Path, const IoHash& HashKey); - void PutLargeObject(const IoHash& HashKey, const ZenCacheValue& Value); - RwLock m_IndexLock; tsl::robin_map<IoHash, DiskLocation, IoHash::Hasher> m_Index; uint64_t m_WriteCursor = 0; + + void BuildPath(WideStringBuilderBase& Path, const IoHash& HashKey); + void PutLargeObject(const IoHash& HashKey, const ZenCacheValue& Value); + bool GetStandaloneCacheValue(const IoHash& HashKey, ZenCacheValue& OutValue, const DiskLocation& Loc); + bool GetInlineCacheValue(const DiskLocation& Loc, ZenCacheValue& OutValue); }; ZenCacheDiskLayer::CacheBucket::CacheBucket(CasStore& Cas) : m_CasStore(Cas) @@ -320,27 +351,24 @@ ZenCacheDiskLayer::CacheBucket::OpenOrCreate(std::filesystem::path BucketDir) std::filesystem::path SobsPath{m_BucketDir / "zen.sobs"}; std::filesystem::path SlogPath{m_BucketDir / "zen.slog"}; - CAtlFile ManifestFile; + BasicFile ManifestFile; // Try opening existing manifest file first bool IsNew = false; - HRESULT hRes = ManifestFile.Create(ManifestPath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, OPEN_EXISTING); + std::error_code Ec; + ManifestFile.Open(ManifestPath, /* IsCreate */ false, Ec); - if (SUCCEEDED(hRes)) + if (!Ec) { - ULONGLONG FileSize; - ManifestFile.GetSize(FileSize); + uint64_t FileSize = ManifestFile.FileSize(); if (FileSize == sizeof(Oid)) { - hRes = ManifestFile.Read(&m_BucketId, sizeof(Oid)); + ManifestFile.Read(&m_BucketId, sizeof(Oid), 0); - if (SUCCEEDED(hRes)) - { - m_Ok = true; - } + m_Ok = true; } if (!m_Ok) @@ -353,16 +381,16 @@ ZenCacheDiskLayer::CacheBucket::OpenOrCreate(std::filesystem::path BucketDir) { // No manifest file found, this is a new bucket - hRes = ManifestFile.Create(ManifestPath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS); + ManifestFile.Open(ManifestPath, /* IsCreate */ true, Ec); - if (FAILED(hRes)) + if (Ec) { - ThrowLastError("Failed to create bucket manifest '{}'"_format(ManifestPath)); + throw std::system_error(Ec, "Failed to create bucket manifest '{}'"_format(ManifestPath)); } m_BucketId.Generate(); - hRes = ManifestFile.Write(&m_BucketId, sizeof(Oid)); + ManifestFile.Write(&m_BucketId, sizeof(Oid), /* FileOffset */ 0); IsNew = true; } @@ -407,6 +435,37 @@ ZenCacheDiskLayer::CacheBucket::BuildPath(WideStringBuilderBase& Path, const IoH } bool +ZenCacheDiskLayer::CacheBucket::GetInlineCacheValue(const DiskLocation& Loc, ZenCacheValue& OutValue) +{ + if (!Loc.IsFlagSet(DiskLocation::kStandaloneFile)) + { + OutValue.Value = IoBufferBuilder::MakeFromFileHandle(m_SobsFile.Handle(), Loc.Offset(), Loc.Size); + OutValue.Value.SetContentType(Loc.GetContentType()); + + return true; + } + + return false; +} + +bool +ZenCacheDiskLayer::CacheBucket::GetStandaloneCacheValue(const IoHash& HashKey, ZenCacheValue& OutValue, const DiskLocation& Loc) +{ + WideStringBuilder<128> DataFilePath; + BuildPath(DataFilePath, HashKey); + + if (IoBuffer Data = IoBufferBuilder::MakeFromFile(DataFilePath.c_str())) + { + OutValue.Value = Data; + OutValue.Value.SetContentType(Loc.GetContentType()); + + return true; + } + + return false; +} + +bool ZenCacheDiskLayer::CacheBucket::Get(const IoHash& HashKey, ZenCacheValue& OutValue) { if (!m_Ok) @@ -420,35 +479,14 @@ ZenCacheDiskLayer::CacheBucket::Get(const IoHash& HashKey, ZenCacheValue& OutVal { const DiskLocation& Loc = it->second; - ZenContentType ContentType = ZenContentType::kBinary; - - if (Loc.IsFlagSet(DiskLocation::kStructured)) - { - ContentType = ZenContentType::kCbObject; - } - - if (!Loc.IsFlagSet(DiskLocation::kStandaloneFile)) + if (GetInlineCacheValue(Loc, OutValue)) { - OutValue.Value = IoBufferBuilder::MakeFromFileHandle(m_SobsFile.Handle(), Loc.Offset(), Loc.Size); - OutValue.Value.SetContentType(ContentType); - return true; } - else - { - _.ReleaseNow(); - - WideStringBuilder<128> DataFilePath; - BuildPath(DataFilePath, HashKey); - if (IoBuffer Data = IoBufferBuilder::MakeFromFile(DataFilePath.c_str())) - { - OutValue.Value = Data; - OutValue.Value.SetContentType(ContentType); + _.ReleaseNow(); - return true; - } - } + return GetStandaloneCacheValue(HashKey, OutValue, Loc); } return false; @@ -518,9 +556,58 @@ ZenCacheDiskLayer::CacheBucket::Flush() } void -ZenCacheDiskLayer::Scrub(ScrubContext& Ctx) +ZenCacheDiskLayer::CacheBucket::Scrub(ScrubContext& Ctx) { - ZEN_UNUSED(Ctx); + std::vector<DiskIndexEntry> StandaloneFiles; + + std::vector<IoHash> BadChunks; + std::vector<IoBuffer> BadStandaloneChunks; + + { + RwLock::SharedLockScope _(m_IndexLock); + + for (auto& Kv : m_Index) + { + const IoHash& Hash = Kv.first; + const DiskLocation& Loc = Kv.second; + + ZenCacheValue Value; + + if (!GetInlineCacheValue(Loc, Value)) + { + ZEN_ASSERT(Loc.IsFlagSet(DiskLocation::kStandaloneFile)); + StandaloneFiles.push_back({.Key = Hash, .Location = Loc}); + } + else + { + if (GetStandaloneCacheValue(Hash, Value, Loc)) + { + // Hash contents + + const IoHash ComputedHash = HashBuffer(Value.Value); + + if (ComputedHash != Hash) + { + BadChunks.push_back(Hash); + } + } + else + { + // Non-existent + } + } + } + } + + if (Ctx.RunRecovery()) + { + // Clean out bad chunks + } + + if (!BadChunks.empty()) + { + Ctx.ReportBadChunks(BadChunks); + } } void @@ -529,35 +616,38 @@ ZenCacheDiskLayer::CacheBucket::PutLargeObject(const IoHash& HashKey, const ZenC WideStringBuilder<128> DataFilePath; BuildPath(DataFilePath, HashKey); - // TODO: replace this process with a more efficient implementation with proper atomic rename - // and also avoid creating directories if we can - - std::filesystem::path ParentPath = std::filesystem::path(DataFilePath.c_str()).parent_path(); - CreateDirectories(ParentPath); + TemporaryFile DataFile; - CAtlTemporaryFile DataFile; + std::error_code Ec; + DataFile.CreateTemporary(m_BucketDir.c_str(), Ec); - HRESULT hRes = DataFile.Create(m_BucketDir.c_str()); - - if (FAILED(hRes)) + if (Ec) { - ThrowSystemException(hRes, "Failed to open temporary file for put at '{}'"_format(m_BucketDir)); + throw std::system_error(Ec, "Failed to open temporary file for put at '{}'"_format(m_BucketDir)); } - hRes = DataFile.Write(Value.Value.Data(), gsl::narrow<DWORD>(Value.Value.Size())); + DataFile.WriteAll(Value.Value, Ec); - if (FAILED(hRes)) + if (Ec) { - ThrowSystemException(hRes, "Failed to write payload ({} bytes) to file"_format(NiceBytes(Value.Value.Size()))); + throw std::system_error(Ec, "Failed to write payload ({} bytes) to file"_format(NiceBytes(Value.Value.Size()))); } - // Move file into place (note: not fully atomic!) + // Move file into place (atomically) - hRes = DataFile.Close(DataFilePath.c_str()); + DataFile.MoveTemporaryIntoPlace(DataFilePath.c_str(), Ec); - if (FAILED(hRes)) + if (Ec) { - ThrowSystemException(hRes, "Failed to finalize file '{}'"_format(WideToUtf8(DataFilePath))); + std::filesystem::path ParentPath = std::filesystem::path(DataFilePath.c_str()).parent_path(); + CreateDirectories(ParentPath); + + DataFile.MoveTemporaryIntoPlace(DataFilePath.c_str(), Ec); + + if (Ec) + { + throw std::system_error(Ec, "Failed to finalize file '{}'"_format(WideToUtf8(DataFilePath))); + } } // Update index @@ -729,6 +819,17 @@ ZenCacheDiskLayer::Flush() } } +void +ZenCacheDiskLayer::Scrub(ScrubContext& Ctx) +{ + RwLock::SharedLockScope _(m_Lock); + + for (auto& Kv : m_Buckets) + { + Kv.second.Scrub(Ctx); + } +} + ////////////////////////////////////////////////////////////////////////// ZenCacheTracker::ZenCacheTracker(ZenCacheStore& CacheStore) diff --git a/zenserver/cache/structuredcachestore.h b/zenserver/cache/structuredcachestore.h index 2cc3abb53..f96757409 100644 --- a/zenserver/cache/structuredcachestore.h +++ b/zenserver/cache/structuredcachestore.h @@ -65,6 +65,7 @@ private: bool Get(const IoHash& HashKey, ZenCacheValue& OutValue); void Put(const IoHash& HashKey, const ZenCacheValue& Value); + void Scrub(ScrubContext& Ctx); }; RwLock m_Lock; |