aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2024-08-30 11:26:42 +0200
committerGitHub Enterprise <[email protected]>2024-08-30 11:26:42 +0200
commitcbb9ed149517cf781bf21bff4650d7d01bd6d567 (patch)
treea185fe2a84cd6681caae7a71bb72d398421f97b6 /src
parentzenserver process launch/termination improvements (#138) (diff)
downloadzen-cbb9ed149517cf781bf21bff4650d7d01bd6d567.tar.xz
zen-cbb9ed149517cf781bf21bff4650d7d01bd6d567.zip
meta info store (#75)
- Feature: Added option `--gc-cache-attachment-store` which caches referenced attachments in cache records on disk for faster GC - default is `false` - Feature: Added option `--gc-projectstore-attachment-store` which caches referenced attachments in project store oplogs on disk for faster GC - default is `false`
Diffstat (limited to 'src')
-rw-r--r--src/zenserver/config.cpp20
-rw-r--r--src/zenserver/config.h7
-rw-r--r--src/zenserver/projectstore/projectstore.cpp146
-rw-r--r--src/zenserver/projectstore/projectstore.h12
-rw-r--r--src/zenserver/zenserver.cpp9
-rw-r--r--src/zenstore/blockstore.cpp154
-rw-r--r--src/zenstore/cache/cachedisklayer.cpp129
-rw-r--r--src/zenstore/include/zenstore/blockstore.h17
-rw-r--r--src/zenstore/include/zenstore/cache/cachedisklayer.h14
-rw-r--r--src/zenutil/basicfile.cpp25
-rw-r--r--src/zenutil/include/zenutil/basicfile.h7
-rw-r--r--src/zenutil/include/zenutil/referencemetadata.h109
-rw-r--r--src/zenutil/referencemetadata.cpp32
13 files changed, 618 insertions, 63 deletions
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp
index b430db629..6c2bf40d8 100644
--- a/src/zenserver/config.cpp
+++ b/src/zenserver/config.cpp
@@ -518,6 +518,12 @@ ParseConfigFile(const std::filesystem::path& Path,
"gc-compactblock-threshold"sv);
LuaOptions.AddOption("gc.verbose"sv, ServerOptions.GcConfig.Verbose, "gc-verbose"sv);
LuaOptions.AddOption("gc.single-threaded"sv, ServerOptions.GcConfig.SingleThreaded, "gc-single-threaded"sv);
+ LuaOptions.AddOption("gc.cache.attachment.store"sv,
+ ServerOptions.StructuredCacheConfig.StoreAttachmentMetaData,
+ "gc-cache-attachment-store");
+ LuaOptions.AddOption("gc.projectstore.attachment.store"sv,
+ ServerOptions.ProjectStoreConfig.StoreAttachmentMetaData,
+ "gc-projectstore-attachment-store");
////// gc
LuaOptions.AddOption("gc.cache.maxdurationseconds"sv, ServerOptions.GcConfig.Cache.MaxDurationSeconds, "gc-cache-duration-seconds"sv);
@@ -898,6 +904,20 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
options.add_option("gc",
"",
+ "gc-cache-attachment-store",
+ "Enable storing attachments referenced by a cache record in block store meta data.",
+ cxxopts::value<bool>(ServerOptions.StructuredCacheConfig.StoreAttachmentMetaData)->default_value("false"),
+ "");
+
+ options.add_option("gc",
+ "",
+ "gc-projectstore-attachment-store",
+ "Enable storing attachments referenced by project oplogs in meta data.",
+ cxxopts::value<bool>(ServerOptions.ProjectStoreConfig.StoreAttachmentMetaData)->default_value("false"),
+ "");
+
+ options.add_option("gc",
+ "",
"gc-enabled",
"Whether garbage collection is enabled or not.",
cxxopts::value<bool>(ServerOptions.GcConfig.Enabled)->default_value("true"),
diff --git a/src/zenserver/config.h b/src/zenserver/config.h
index 4c9b9361c..6e45e7230 100644
--- a/src/zenserver/config.h
+++ b/src/zenserver/config.h
@@ -117,6 +117,12 @@ struct ZenStructuredCacheConfig
uint64_t MemTargetFootprintBytes = 512 * 1024 * 1024;
uint64_t MemTrimIntervalSeconds = 60;
uint64_t MemMaxAgeSeconds = gsl::narrow<uint64_t>(std::chrono::seconds(std::chrono::days(1)).count());
+ bool StoreAttachmentMetaData = false;
+};
+
+struct ZenProjectStoreConfig
+{
+ bool StoreAttachmentMetaData = false;
};
struct ZenWorkspacesConfig
@@ -132,6 +138,7 @@ struct ZenServerOptions
ZenObjectStoreConfig ObjectStoreConfig;
zen::HttpServerConfig HttpServerConfig;
ZenStructuredCacheConfig StructuredCacheConfig;
+ ZenProjectStoreConfig ProjectStoreConfig;
ZenStatsConfig StatsConfig;
ZenWorkspacesConfig WorksSpacesConfig;
std::filesystem::path SystemRootDir; // System root directory (used for machine level config)
diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp
index bc1020b58..378423100 100644
--- a/src/zenserver/projectstore/projectstore.cpp
+++ b/src/zenserver/projectstore/projectstore.cpp
@@ -20,6 +20,7 @@
#include <zenstore/scrubcontext.h>
#include <zenutil/cache/rpcrecording.h>
#include <zenutil/packageformat.h>
+#include <zenutil/referencemetadata.h>
#include <zenutil/workerpools.h>
#include "fileremoteprojectstore.h"
@@ -111,6 +112,22 @@ namespace {
return false;
}
+ bool IsFileOlderThan(const std::filesystem::path& CheckPath, const std::filesystem::path& ReferencePath)
+ {
+ std::error_code Ec;
+ std::filesystem::file_time_type CheckWriteTime = std::filesystem::last_write_time(CheckPath, Ec);
+ if (Ec)
+ {
+ return true;
+ }
+ std::filesystem::file_time_type ReferenceWriteTime = std::filesystem::last_write_time(ReferencePath, Ec);
+ if (Ec)
+ {
+ return true;
+ }
+ return CheckWriteTime < ReferenceWriteTime;
+ }
+
struct CreateRemoteStoreResult
{
std::shared_ptr<RemoteProjectStore> Store;
@@ -308,6 +325,7 @@ struct ProjectStore::OplogStorage : public RefCounted
{
if (Exists(BasePath))
{
+ std::error_code DummyEc;
return std::filesystem::file_size(GetLogPath(BasePath)) + std::filesystem::file_size(GetBlobsPath(BasePath));
}
return 0;
@@ -845,6 +863,7 @@ ProjectStore::Oplog::Oplog(std::string_view Id,
, m_CidStore(Store)
, m_BasePath(BasePath)
, m_MarkerPath(MarkerPath)
+, m_MetaValid(false)
, m_OplogId(Id)
{
using namespace std::literals;
@@ -852,8 +871,9 @@ ProjectStore::Oplog::Oplog(std::string_view Id,
m_Storage = new OplogStorage(this, m_BasePath);
const bool StoreExists = m_Storage->Exists();
m_Storage->Open(/* IsCreate */ !StoreExists);
-
- m_TempPath = m_BasePath / "temp"sv;
+ m_TempPath = m_BasePath / "temp"sv;
+ m_MetaPath = m_BasePath / "ops.meta"sv;
+ m_MetaValid = !IsFileOlderThan(m_MetaPath, m_Storage->GetBlobsPath());
CleanDirectory(m_TempPath);
}
@@ -871,6 +891,11 @@ ProjectStore::Oplog::Flush()
{
ZEN_ASSERT(m_Storage);
m_Storage->Flush();
+ if (!m_MetaValid)
+ {
+ std::error_code DummyEc;
+ std::filesystem::remove(m_MetaPath, DummyEc);
+ }
}
void
@@ -923,12 +948,15 @@ ProjectStore::Oplog::ScrubStorage(ScrubContext& Ctx)
// Actually perform some clean-up
RwLock::ExclusiveLockScope _(m_OplogLock);
-
for (const auto& Key : BadEntryKeys)
{
m_LatestOpMap.erase(Key);
m_Storage->AppendTombstone(Key);
}
+ if (!BadEntryKeys.empty())
+ {
+ m_MetaValid = false;
+ }
}
else
{
@@ -970,6 +998,11 @@ ProjectStore::Oplog::TotalSize(const std::filesystem::path& BasePath)
{
Size += std::filesystem::file_size(StateFilePath);
}
+ std::filesystem::path MetaFilePath = BasePath / "ops.meta"sv;
+ if (std::filesystem::exists(MetaFilePath))
+ {
+ Size += std::filesystem::file_size(MetaFilePath);
+ }
return Size;
}
@@ -1097,6 +1130,7 @@ ProjectStore::Oplog::Reset()
m_Storage = new OplogStorage(this, m_BasePath);
const bool StoreExists = m_Storage->Exists();
m_Storage->Open(/* IsCreate */ !StoreExists);
+ m_MetaValid = !IsFileOlderThan(m_MetaPath, m_Storage->GetBlobsPath());
return false;
}
m_ChunkMap.clear();
@@ -1106,6 +1140,7 @@ ProjectStore::Oplog::Reset()
m_LatestOpMap.clear();
m_Storage = new OplogStorage(this, m_BasePath);
m_Storage->Open(true);
+ m_MetaValid = false;
CleanDirectory(m_TempPath);
Write();
}
@@ -1491,6 +1526,80 @@ ProjectStore::Oplog::IterateOplogLocked(std::function<void(CbObjectView)>&& Hand
m_Storage->ReplayLogEntries(Entries, [&](CbObjectView Op) { Handler(Op); });
}
+static constexpr uint32_t OplogMetaDataExpectedMagic = 0x6f'74'6d'62; // 'omta';
+
+void
+ProjectStore::Oplog::GetAttachmentsLocked(std::vector<IoHash>& OutAttachments, bool StoreMetaDataOnDisk)
+{
+ if (!m_Storage)
+ {
+ return;
+ }
+
+ if (StoreMetaDataOnDisk && m_MetaValid)
+ {
+ IoBuffer MetadataPayload = IoBufferBuilder::MakeFromFile(m_MetaPath);
+ if (MetadataPayload)
+ {
+ if (GetAttachmentsFromMetaData<Oid, IoHash>(
+ MetadataPayload,
+ OplogMetaDataExpectedMagic,
+ [&](std::span<const Oid> Keys, std::span<const uint32_t> AttachmentCounts, std::span<const IoHash> Attachments) {
+ ZEN_UNUSED(Keys);
+ ZEN_UNUSED(AttachmentCounts);
+ OutAttachments.insert(OutAttachments.end(), Attachments.begin(), Attachments.end());
+ }))
+ {
+ return;
+ }
+ }
+ }
+
+ std::vector<Oid> Keys;
+ std::vector<uint32_t> AttachmentCounts;
+ size_t AttachmentOffset = OutAttachments.size();
+
+ IterateOplogLocked([&](CbObjectView Op) {
+ using namespace std::literals;
+ size_t CurrentAttachmentCount = OutAttachments.size();
+ Op.IterateAttachments([&](CbFieldView Visitor) { OutAttachments.emplace_back(Visitor.AsAttachment()); });
+ if (StoreMetaDataOnDisk)
+ {
+ XXH3_128Stream KeyHasher;
+ Op["key"sv].WriteToStream([&](const void* Data, size_t Size) { KeyHasher.Append(Data, Size); });
+ XXH3_128 KeyHash128 = KeyHasher.GetHash();
+ Oid KeyHash;
+ memcpy(&KeyHash, KeyHash128.Hash, sizeof KeyHash);
+ Keys.push_back(KeyHash);
+ AttachmentCounts.push_back(gsl::narrow<uint32_t>(OutAttachments.size() - CurrentAttachmentCount));
+ }
+ });
+ if (StoreMetaDataOnDisk)
+ {
+ const IoHash* FirstAttachment = OutAttachments.data() + AttachmentOffset;
+ size_t AttachmentCount = OutAttachments.size() - AttachmentOffset;
+ IoBuffer MetaPayload = BuildReferenceMetaData<Oid>(OplogMetaDataExpectedMagic,
+ Keys,
+ AttachmentCounts,
+ std::span<const IoHash>(FirstAttachment, AttachmentCount))
+ .Flatten()
+ .AsIoBuffer();
+
+ const std::filesystem::path MetaPath = m_MetaPath;
+ std::error_code Ec;
+ TemporaryFile::SafeWriteFile(MetaPath, MetaPayload.GetView(), Ec);
+ if (Ec)
+ {
+ m_MetaValid = false;
+ ZEN_WARN("Unable to set meta data for oplog '{}' at meta path: '{}'. Reason: '{}'", OplogId(), MetaPath, Ec.message());
+ }
+ else
+ {
+ m_MetaValid = true;
+ }
+ }
+}
+
size_t
ProjectStore::Oplog::GetOplogEntryCount() const
{
@@ -1954,7 +2063,7 @@ ProjectStore::Oplog::AppendNewOplogEntry(CbObjectView Core)
using namespace std::literals;
RefPtr<OplogStorage> Storage = GetStorage();
- if (!m_Storage)
+ if (!Storage)
{
return 0xffffffffu;
}
@@ -1962,11 +2071,12 @@ ProjectStore::Oplog::AppendNewOplogEntry(CbObjectView Core)
OplogEntryMapping Mapping = GetMapping(Core);
OplogStorage::AppendOpData OpData = OplogStorage::GetAppendOpData(Core);
- const OplogEntry OpEntry = m_Storage->AppendOp(OpData);
+ const OplogEntry OpEntry = Storage->AppendOp(OpData);
RwLock::ExclusiveLockScope OplogLock(m_OplogLock);
const uint32_t EntryId = RegisterOplogEntry(OplogLock, Mapping, OpEntry);
CaptureUpdatedLSNs(std::array<uint32_t, 1u>({EntryId}));
+ m_MetaValid = false;
return EntryId;
}
@@ -1979,7 +2089,7 @@ ProjectStore::Oplog::AppendNewOplogEntries(std::span<CbObjectView> Cores)
using namespace std::literals;
RefPtr<OplogStorage> Storage = GetStorage();
- if (!m_Storage)
+ if (!Storage)
{
return std::vector<uint32_t>(Cores.size(), 0xffffffffu);
}
@@ -2008,6 +2118,7 @@ ProjectStore::Oplog::AppendNewOplogEntries(std::span<CbObjectView> Cores)
EntryIds[OpIndex] = RegisterOplogEntry(OplogLock, Mappings[OpIndex], OpEntries[OpIndex]);
}
CaptureUpdatedLSNs(EntryIds);
+ m_MetaValid = false;
}
return EntryIds;
}
@@ -2664,12 +2775,13 @@ ProjectStore::Project::LastOplogAccessTime(std::string_view Oplog) const
//////////////////////////////////////////////////////////////////////////
-ProjectStore::ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, JobQueue& JobQueue)
+ProjectStore::ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, JobQueue& JobQueue, const Configuration& Config)
: m_Log(logging::Get("project"))
, m_Gc(Gc)
, m_CidStore(Store)
, m_JobQueue(JobQueue)
, m_ProjectBasePath(BasePath)
+, m_Config(Config)
, m_DiskWriteBlocker(Gc.GetDiskWriteBlocker())
{
ZEN_INFO("initializing project store at '{}'", m_ProjectBasePath);
@@ -4675,10 +4787,7 @@ public:
Oplog->OplogId());
});
- Oplog->IterateOplogLocked([&](const CbObjectView& UpdateOp) -> bool {
- UpdateOp.IterateAttachments([&](CbFieldView Visitor) { m_References.emplace_back(Visitor.AsAttachment()); });
- return true;
- });
+ Oplog->GetAttachmentsLocked(m_References, m_ProjectStore.m_Config.StoreAttachmentMetaData);
}
}
@@ -4779,9 +4888,8 @@ public:
{
return;
}
- m_Oplog->IterateOplog([&](CbObjectView Op) {
- Op.IterateAttachments([&](CbFieldView Visitor) { m_References.emplace_back(Visitor.AsAttachment()); });
- });
+
+ m_Oplog->GetAttachmentsLocked(m_References, m_ProjectStore.m_Config.StoreAttachmentMetaData);
}
}
@@ -5021,7 +5129,7 @@ TEST_CASE("project.store.create")
std::string_view ProjectName("proj1"sv);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue);
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProjectStore::Configuration{.StoreAttachmentMetaData = true});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
@@ -5050,7 +5158,7 @@ TEST_CASE("project.store.lifetimes")
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue);
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProjectStore::Configuration{.StoreAttachmentMetaData = true});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
@@ -5112,7 +5220,7 @@ TEST_CASE_TEMPLATE("project.store.export",
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue);
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProjectStore::Configuration{.StoreAttachmentMetaData = true});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
@@ -5193,7 +5301,7 @@ TEST_CASE("project.store.gc")
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue);
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProjectStore::Configuration{.StoreAttachmentMetaData = true});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
@@ -5477,7 +5585,7 @@ TEST_CASE("project.store.partial.read")
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore"sv;
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue);
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProjectStore::Configuration{.StoreAttachmentMetaData = true});
std::filesystem::path RootDir = TempDir.Path() / "root"sv;
std::filesystem::path EngineRootDir = TempDir.Path() / "engine"sv;
diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenserver/projectstore/projectstore.h
index bb042e331..bed3c83b7 100644
--- a/src/zenserver/projectstore/projectstore.h
+++ b/src/zenserver/projectstore/projectstore.h
@@ -63,7 +63,12 @@ class ProjectStore : public RefCounted, public GcStorage, public GcContributor,
struct OplogStorage;
public:
- ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, JobQueue& JobQueue);
+ struct Configuration
+ {
+ bool StoreAttachmentMetaData = false;
+ };
+
+ ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, JobQueue& JobQueue, const Configuration& Config);
~ProjectStore();
struct Project;
@@ -162,6 +167,8 @@ public:
uint32_t GetUnusedSpacePercent() const;
void Compact(bool DryRun, bool RetainLSNs, std::string_view LogPrefix);
+ void GetAttachmentsLocked(std::vector<IoHash>& OutAttachments, bool StoreMetaDataOnDisk);
+
private:
struct FileMapEntry
{
@@ -177,6 +184,7 @@ public:
std::filesystem::path m_BasePath;
std::filesystem::path m_MarkerPath;
std::filesystem::path m_TempPath;
+ std::filesystem::path m_MetaPath;
mutable RwLock m_OplogLock;
OidMap<IoHash> m_ChunkMap; // output data chunk id -> CAS address
@@ -185,6 +193,7 @@ public:
int32_t m_ManifestVersion; // File system manifest version
tsl::robin_map<uint32_t, OplogEntryAddress> m_OpAddressMap; // Index LSN -> op data in ops blob file
OidMap<uint32_t> m_LatestOpMap; // op key -> latest op LSN for key
+ std::atomic<bool> m_MetaValid = false;
mutable RwLock m_UpdateCaptureLock;
uint32_t m_UpdateCaptureRefCounter = 0;
@@ -429,6 +438,7 @@ private:
CidStore& m_CidStore;
JobQueue& m_JobQueue;
std::filesystem::path m_ProjectBasePath;
+ const Configuration m_Config;
mutable RwLock m_ProjectsLock;
std::map<std::string, Ref<Project>> m_Projects;
const DiskWriteBlocker* m_DiskWriteBlocker = nullptr;
diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp
index dcfef7530..ebc56d7d9 100644
--- a/src/zenserver/zenserver.cpp
+++ b/src/zenserver/zenserver.cpp
@@ -232,7 +232,12 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
ZEN_INFO("instantiating project service");
- m_ProjectStore = new ProjectStore(*m_CidStore, m_DataRoot / "projects", m_GcManager, *m_JobQueue);
+ m_ProjectStore =
+ new ProjectStore(*m_CidStore,
+ m_DataRoot / "projects",
+ m_GcManager,
+ *m_JobQueue,
+ ProjectStore::Configuration{.StoreAttachmentMetaData = ServerOptions.ProjectStoreConfig.StoreAttachmentMetaData});
m_HttpProjectService.reset(new HttpProjectService{*m_CidStore, m_ProjectStore, m_StatsService, *m_AuthMgr});
if (ServerOptions.WorksSpacesConfig.Enabled)
@@ -513,6 +518,8 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions)
Config.NamespaceConfig.DiskLayerConfig.MemCacheTargetFootprintBytes = ServerOptions.StructuredCacheConfig.MemTargetFootprintBytes;
Config.NamespaceConfig.DiskLayerConfig.MemCacheTrimIntervalSeconds = ServerOptions.StructuredCacheConfig.MemTrimIntervalSeconds;
Config.NamespaceConfig.DiskLayerConfig.MemCacheMaxAgeSeconds = ServerOptions.StructuredCacheConfig.MemMaxAgeSeconds;
+ Config.NamespaceConfig.DiskLayerConfig.BucketConfig.StoreAttachmentMetaData =
+ ServerOptions.StructuredCacheConfig.StoreAttachmentMetaData;
if (ServerOptions.IsDedicated)
{
diff --git a/src/zenstore/blockstore.cpp b/src/zenstore/blockstore.cpp
index 70ddcedaf..287a3f7fa 100644
--- a/src/zenstore/blockstore.cpp
+++ b/src/zenstore/blockstore.cpp
@@ -90,6 +90,7 @@ BlockStoreFile::Create(uint64_t InitialSize)
RetriesLeft--;
return true;
});
+ RemoveMeta();
void* FileHandle = m_File.Handle();
@@ -99,15 +100,26 @@ BlockStoreFile::Create(uint64_t InitialSize)
}
uint64_t
-BlockStoreFile::FileSize()
+BlockStoreFile::FileSize() const
{
- return m_CachedFileSize == 0 ? m_File.FileSize() : m_CachedFileSize;
+ if (m_CachedFileSize == 0)
+ {
+ std::error_code Ec;
+ uint64_t Size = m_File.FileSize(Ec);
+ if (Ec)
+ {
+ return 0;
+ }
+ return Size;
+ }
+ return m_CachedFileSize;
}
void
BlockStoreFile::MarkAsDeleteOnClose()
{
m_IoBuffer.SetDeleteOnClose(true);
+ RemoveMeta();
}
IoBuffer
@@ -157,6 +169,90 @@ BlockStoreFile::IsOpen() const
return !!m_IoBuffer;
}
+bool
+BlockStoreFile::SetMetaData(const IoBuffer& Payload)
+{
+ if (!Payload)
+ {
+ RemoveMeta();
+ return true;
+ }
+ const std::filesystem::path MetaPath = GetMetaPath();
+ std::error_code Ec;
+ TemporaryFile::SafeWriteFile(MetaPath, Payload.GetView(), Ec);
+ if (Ec)
+ {
+ ZEN_WARN("Unable to set meta data for block '{}' at meta path: '{}'. Reason: '{}'", m_Path, MetaPath, Ec.message());
+ return false;
+ }
+ return true;
+}
+
+static bool
+IsMetaDataValid(const std::filesystem::path& BlockPath, const std::filesystem::path& MetaPath)
+{
+ std::error_code Ec;
+ std::filesystem::file_time_type MetaWriteTime = std::filesystem::last_write_time(MetaPath, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ std::filesystem::file_time_type BlockWriteTime = std::filesystem::last_write_time(BlockPath, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ if (MetaWriteTime < BlockWriteTime)
+ {
+ std::filesystem::remove(MetaPath, Ec);
+ return false;
+ }
+ return true;
+}
+
+IoBuffer
+BlockStoreFile::GetMetaData() const
+{
+ const std::filesystem::path MetaPath = GetMetaPath();
+ if (IsMetaDataValid(m_Path, MetaPath))
+ {
+ return IoBufferBuilder::MakeFromFile(MetaPath);
+ }
+ return {};
+}
+
+uint64_t
+BlockStoreFile::MetaSize() const
+{
+ const std::filesystem::path MetaPath = GetMetaPath();
+ if (IsMetaDataValid(m_Path, MetaPath))
+ {
+ std::error_code DummyEc;
+ if (uint64_t Size = std::filesystem::file_size(MetaPath, DummyEc); !DummyEc)
+ {
+ return Size;
+ }
+ }
+ return 0;
+}
+
+void
+BlockStoreFile::RemoveMeta()
+{
+ std::filesystem::path MetaPath = GetMetaPath();
+ std::error_code DummyEc;
+ std::filesystem::remove(MetaPath, DummyEc);
+}
+
+std::filesystem::path
+BlockStoreFile::GetMetaPath() const
+{
+ std::filesystem::path MetaPath(m_Path);
+ return MetaPath.replace_extension(".meta");
+}
+
+////////////////////////////////////////////////////////
+
constexpr uint64_t DefaultIterateSmallChunkWindowSize = 2 * 1024 * 1024;
constexpr uint64_t IterateSmallChunkMaxGapSize = 4 * 1024;
@@ -215,7 +311,7 @@ BlockStore::Initialize(const std::filesystem::path& BlocksBasePath, uint64_t Max
}
Ref<BlockStoreFile> BlockFile{new BlockStoreFile(Path)};
BlockFile->Open();
- m_TotalSize.fetch_add(BlockFile->FileSize(), std::memory_order::relaxed);
+ m_TotalSize.fetch_add(BlockFile->TotalSize(), std::memory_order::relaxed);
m_ChunkBlocks[BlockIndex] = BlockFile;
FoundBlocks[BlockIndex] = BlockFile->FileSize();
if (BlockIndex >= NextBlockIndex)
@@ -283,7 +379,7 @@ BlockStore::SyncExistingBlocksOnDisk(const BlockIndexSet& KnownLocations)
std::filesystem::path BlockPath = GetBlockPath(m_BlocksBasePath, BlockIndex);
if (m_ChunkBlocks[BlockIndex])
{
- m_TotalSize.fetch_sub(m_ChunkBlocks[BlockIndex]->FileSize(), std::memory_order::relaxed);
+ m_TotalSize.fetch_sub(m_ChunkBlocks[BlockIndex]->TotalSize(), std::memory_order::relaxed);
m_ChunkBlocks[BlockIndex]->MarkAsDeleteOnClose();
}
m_ChunkBlocks.erase(BlockIndex);
@@ -781,6 +877,7 @@ BlockStore::ReclaimSpace(const ReclaimSnapshotState& Snapshot,
{
ZEN_DEBUG("dropping incomplete cas block store file '{}'", NewBlockFile->GetPath());
m_TotalSize.fetch_sub(NewBlockFile->FileSize(), std::memory_order::relaxed);
+ ZEN_ASSERT_SLOW(NewBlockFile->MetaSize() == 0);
NewBlockFile->MarkAsDeleteOnClose();
}
});
@@ -833,7 +930,7 @@ BlockStore::ReclaimSpace(const ReclaimSnapshotState& Snapshot,
ZEN_DEBUG("marking cas block store file '{}' for delete, block #{}", OldBlockFile->GetPath(), BlockIndex);
ZEN_ASSERT(m_ChunkBlocks[BlockIndex] == OldBlockFile);
m_ChunkBlocks.erase(BlockIndex);
- m_TotalSize.fetch_sub(OldBlockFile->FileSize(), std::memory_order::relaxed);
+ m_TotalSize.fetch_sub(OldBlockFile->TotalSize(), std::memory_order::relaxed);
OldBlockFile->MarkAsDeleteOnClose();
}
continue;
@@ -1003,7 +1100,7 @@ BlockStore::ReclaimSpace(const ReclaimSnapshotState& Snapshot,
ZEN_DEBUG("marking cas block store file '{}' for delete, block #{}", OldBlockFile->GetPath(), BlockIndex);
ZEN_ASSERT(m_ChunkBlocks[BlockIndex] == OldBlockFile);
m_ChunkBlocks.erase(BlockIndex);
- m_TotalSize.fetch_sub(OldBlockFile->FileSize(), std::memory_order::relaxed);
+ m_TotalSize.fetch_sub(OldBlockFile->TotalSize(), std::memory_order::relaxed);
OldBlockFile->MarkAsDeleteOnClose();
}
}
@@ -1500,9 +1597,10 @@ BlockStore::CompactBlocks(const BlockStoreCompactState& CompactState,
LogPrefix,
OldBlockFile->GetPath().filename(),
NiceBytes(OldBlockSize));
+ m_TotalSize.fetch_sub(OldBlockSize);
+ m_TotalSize.fetch_sub(OldBlockFile->MetaSize());
OldBlockFile->MarkAsDeleteOnClose();
m_ChunkBlocks.erase(BlockIndex);
- m_TotalSize.fetch_sub(OldBlockSize);
RemovedSize += OldBlockSize;
}
return true;
@@ -1544,6 +1642,48 @@ BlockStore::GetBlockPath(const std::filesystem::path& BlocksBasePath, const uint
return Path.ToPath();
}
+bool
+BlockStore::IsWriting(uint32_t BlockIndex) const
+{
+ RwLock::SharedLockScope _(m_InsertLock);
+ if (std::find(m_ActiveWriteBlocks.begin(), m_ActiveWriteBlocks.end(), BlockIndex) != m_ActiveWriteBlocks.end())
+ {
+ return true;
+ }
+ if (BlockIndex == m_WriteBlockIndex.load() && m_WriteBlock)
+ {
+ return true;
+ }
+ return false;
+}
+
+void
+BlockStore::SetMetaData(uint32_t BlockIndex, const IoBuffer& Payload)
+{
+ RwLock::ExclusiveLockScope _(m_InsertLock);
+ if (auto It = m_ChunkBlocks.find(BlockIndex); It != m_ChunkBlocks.end() && It->second)
+ {
+ uint64_t OldMetaSize = It->second->MetaSize();
+ if (It->second->SetMetaData(Payload))
+ {
+ uint64_t NewMetaSize = It->second->MetaSize();
+ m_TotalSize += NewMetaSize;
+ m_TotalSize -= OldMetaSize;
+ }
+ }
+}
+
+IoBuffer
+BlockStore::GetMetaData(uint32_t BlockIndex) const
+{
+ RwLock::SharedLockScope _(m_InsertLock);
+ if (auto It = m_ChunkBlocks.find(BlockIndex); It != m_ChunkBlocks.end() && It->second)
+ {
+ return It->second->GetMetaData();
+ }
+ return {};
+}
+
#if ZEN_WITH_TESTS
TEST_CASE("blockstore.blockstoredisklocation")
diff --git a/src/zenstore/cache/cachedisklayer.cpp b/src/zenstore/cache/cachedisklayer.cpp
index f85d05dce..940f78c30 100644
--- a/src/zenstore/cache/cachedisklayer.cpp
+++ b/src/zenstore/cache/cachedisklayer.cpp
@@ -14,6 +14,7 @@
#include <zencore/trace.h>
#include <zencore/workthreadpool.h>
#include <zencore/xxhash.h>
+#include <zenutil/referencemetadata.h>
#include <zenutil/workerpools.h>
#include <future>
@@ -109,6 +110,8 @@ namespace {
static_assert(sizeof(BucketMetaHeader) == 32);
+ static constexpr uint32_t BlockMetaDataExpectedMagic = 0x61'74'6d'62; // 'bmta';
+
#pragma pack(pop)
//////////////////////////////////////////////////////////////////////////
@@ -3410,14 +3413,58 @@ ZenCacheDiskLayer::CacheBucket::RemoveExpiredData(GcCtx& Ctx, GcStats& Stats)
}
bool
+ZenCacheDiskLayer::CacheBucket::ReadAttachmentsFromMetaData(uint32_t BlockIndex,
+ std::span<const IoHash> InlineKeys,
+ std::span<const std::size_t> ChunkIndexes,
+ std::vector<IoHash>& OutReferences) const
+{
+ ZEN_TRACE_CPU("Z$::Bucket::ReadAttachmentsFromMetaData");
+ IoBuffer MetaDataPayload = m_BlockStore.GetMetaData(BlockIndex);
+ if (MetaDataPayload)
+ {
+ std::unordered_set<IoHash, IoHash::Hasher> WantedKeys;
+ WantedKeys.reserve(ChunkIndexes.size());
+ for (const size_t ChunkIndex : ChunkIndexes)
+ {
+ WantedKeys.insert(InlineKeys[ChunkIndex]);
+ }
+ ZEN_TRACE_CPU("Z$::Bucket::GetAttachmentsFromMetaData");
+ return GetAttachmentsFromMetaData<IoHash, IoHash>(
+ MetaDataPayload,
+ BlockMetaDataExpectedMagic,
+ [&](std::span<const IoHash> Keys, std::span<const uint32_t> AttachmentCounts, std::span<const IoHash> Attachments) {
+ OutReferences.reserve(OutReferences.capacity() + Attachments.size());
+ auto AttachmentStart = Attachments.begin();
+ for (uint32_t Index = 0; Index < Keys.size(); Index++)
+ {
+ uint32_t AttachmentCount = AttachmentCounts[Index];
+ if (AttachmentCount > 0)
+ {
+ if (WantedKeys.contains(Keys[Index]))
+ {
+ OutReferences.insert(OutReferences.end(), AttachmentStart, AttachmentStart + AttachmentCount);
+ }
+ AttachmentStart += AttachmentCount;
+ }
+ }
+ });
+ }
+ return false;
+}
+
+bool
ZenCacheDiskLayer::CacheBucket::GetReferencesLocked(GcCtx& Ctx, std::vector<IoHash>& OutReferences)
{
- auto GetAttachments = [&](MemoryView Data) {
+ ZEN_TRACE_CPU("Z$::Bucket::GetReferencesLocked");
+
+ auto GetAttachments = [&](MemoryView Data) -> bool {
if (ValidateCompactBinary(Data, CbValidateMode::Default) == CbValidateError::None)
{
CbObjectView Obj(Data.GetData());
Obj.IterateAttachments([&](CbFieldView Field) { OutReferences.emplace_back(Field.AsAttachment()); });
+ return true;
}
+ return false;
};
std::vector<std::pair<IoHash, DiskLocation>> StandaloneKeys;
@@ -3471,26 +3518,77 @@ ZenCacheDiskLayer::CacheBucket::GetReferencesLocked(GcCtx& Ctx, std::vector<IoHa
{
ZEN_ASSERT(!ChunkIndexes.empty());
- bool Continue = m_BlockStore.IterateBlock(
- InlineLocations,
- ChunkIndexes,
- [&](size_t ChunkIndex, const void* Data, uint64_t Size) {
- ZEN_UNUSED(ChunkIndex);
- GetAttachments(MemoryView(Data, Size));
- return !Ctx.IsCancelledFlag.load();
- },
- [&](size_t ChunkIndex, BlockStoreFile& File, uint64_t Offset, uint64_t Size) {
- ZEN_UNUSED(ChunkIndex);
- GetAttachments(File.GetChunk(Offset, Size).GetView());
- return !Ctx.IsCancelledFlag.load();
- });
+ uint32_t BlockIndex = InlineLocations[ChunkIndexes[0]].BlockIndex;
+ if (!m_Configuration.StoreAttachmentMetaData ||
+ !ReadAttachmentsFromMetaData(BlockIndex, InlineKeys, ChunkIndexes, OutReferences))
+ {
+ std::vector<IoHash> Keys;
+ std::vector<uint32_t> AttachmentCounts;
+ size_t PrecachedReferencesStart = OutReferences.size();
+ size_t NextPrecachedReferencesStart = PrecachedReferencesStart;
- if (!Continue && Ctx.IsCancelledFlag.load())
+ bool WriteMetaData = m_Configuration.StoreAttachmentMetaData && !m_BlockStore.IsWriting(BlockIndex);
+ if (WriteMetaData)
+ {
+ Keys.reserve(InlineLocations.size());
+ }
+
+ auto CaptureAttachments = [&](size_t ChunkIndex, MemoryView Data) {
+ if (GetAttachments(Data))
+ {
+ size_t AttachmentCount = OutReferences.size() - NextPrecachedReferencesStart;
+ if (WriteMetaData && AttachmentCount > 0)
+ {
+ Keys.push_back(InlineKeys[ChunkIndex]);
+ AttachmentCounts.push_back(gsl::narrow<uint32_t>(AttachmentCount));
+ NextPrecachedReferencesStart += AttachmentCount;
+ }
+ }
+ };
+
+ bool Continue = m_BlockStore.IterateBlock(
+ InlineLocations,
+ ChunkIndexes,
+ [&](size_t ChunkIndex, const void* Data, uint64_t Size) {
+ ZEN_UNUSED(ChunkIndex);
+ CaptureAttachments(ChunkIndex, MemoryView(Data, Size));
+ return !Ctx.IsCancelledFlag.load();
+ },
+ [&](size_t ChunkIndex, BlockStoreFile& File, uint64_t Offset, uint64_t Size) {
+ ZEN_UNUSED(ChunkIndex);
+ CaptureAttachments(ChunkIndex, File.GetChunk(Offset, Size).GetView());
+ return !Ctx.IsCancelledFlag.load();
+ });
+
+ if (Continue)
+ {
+ if (WriteMetaData)
+ {
+ ZEN_ASSERT(Keys.size() == AttachmentCounts.size());
+ IoBuffer MetaDataPayload =
+ BuildReferenceMetaData<IoHash>(
+ BlockMetaDataExpectedMagic,
+ Keys,
+ AttachmentCounts,
+ std::span<const IoHash>(OutReferences)
+ .subspan(PrecachedReferencesStart, OutReferences.size() - PrecachedReferencesStart))
+ .Flatten()
+ .AsIoBuffer();
+ m_BlockStore.SetMetaData(BlockIndex, MetaDataPayload);
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ if (Ctx.IsCancelledFlag.load())
{
return false;
}
}
}
+
for (const auto& It : StandaloneKeys)
{
if (Ctx.IsCancelledFlag.load())
@@ -3551,7 +3649,6 @@ public:
RwLock::SharedLockScope IndexLock(m_CacheBucket.m_IndexLock);
bool Continue = m_CacheBucket.GetReferencesLocked(Ctx, m_References);
IndexLock.ReleaseNow();
-
if (!Continue)
{
m_CacheBucket.m_IndexLock.WithExclusiveLock([&]() { m_CacheBucket.m_TrackedReferences.reset(); });
diff --git a/src/zenstore/include/zenstore/blockstore.h b/src/zenstore/include/zenstore/blockstore.h
index ba8259c82..8f67c1c0f 100644
--- a/src/zenstore/include/zenstore/blockstore.h
+++ b/src/zenstore/include/zenstore/blockstore.h
@@ -94,16 +94,22 @@ struct BlockStoreFile : public RefCounted
void Open();
void Create(uint64_t InitialSize);
void MarkAsDeleteOnClose();
- uint64_t FileSize();
+ uint64_t FileSize() const;
+ uint64_t MetaSize() const;
+ uint64_t TotalSize() const { return FileSize() + MetaSize(); }
IoBuffer GetChunk(uint64_t Offset, uint64_t Size);
void Read(void* Data, uint64_t Size, uint64_t FileOffset);
void Write(const void* Data, uint64_t Size, uint64_t FileOffset);
void Flush();
BasicFile& GetBasicFile();
- void StreamByteRange(uint64_t FileOffset, uint64_t Size, std::function<void(const void* Data, uint64_t Size)>&& ChunkFun);
- bool IsOpen() const;
+ void StreamByteRange(uint64_t FileOffset, uint64_t Size, std::function<void(const void* Data, uint64_t Size)>&& ChunkFun);
+ bool IsOpen() const;
+ bool SetMetaData(const IoBuffer& Payload);
+ IoBuffer GetMetaData() const;
private:
+ std::filesystem::path GetMetaPath() const;
+ void RemoveMeta();
const std::filesystem::path m_Path;
IoBuffer m_IoBuffer;
BasicFile m_File;
@@ -196,6 +202,11 @@ public:
inline uint64_t TotalSize() const { return m_TotalSize.load(std::memory_order::relaxed); }
+ bool IsWriting(uint32_t BlockIndex) const;
+
+ void SetMetaData(uint32_t BlockIndex, const IoBuffer& Payload);
+ IoBuffer GetMetaData(uint32_t BlockIndex) const;
+
private:
static const char* GetBlockFileExtension();
static std::filesystem::path GetBlockPath(const std::filesystem::path& BlocksBasePath, const uint32_t BlockIndex);
diff --git a/src/zenstore/include/zenstore/cache/cachedisklayer.h b/src/zenstore/include/zenstore/cache/cachedisklayer.h
index 537f4396a..e7c995081 100644
--- a/src/zenstore/include/zenstore/cache/cachedisklayer.h
+++ b/src/zenstore/include/zenstore/cache/cachedisklayer.h
@@ -105,10 +105,11 @@ class ZenCacheDiskLayer
public:
struct BucketConfiguration
{
- uint64_t MaxBlockSize = 1ull << 30;
- uint32_t PayloadAlignment = 1u << 4;
- uint64_t MemCacheSizeThreshold = 1 * 1024;
- uint64_t LargeObjectThreshold = 128 * 1024;
+ uint64_t MaxBlockSize = 1ull << 30;
+ uint32_t PayloadAlignment = 1u << 4;
+ uint64_t MemCacheSizeThreshold = 1 * 1024;
+ uint64_t LargeObjectThreshold = 128 * 1024;
+ bool StoreAttachmentMetaData = false;
};
struct Configuration
@@ -236,6 +237,11 @@ public:
RwLock::SharedLockScope GetGcReferencerLock();
bool GetReferencesLocked(GcCtx& Ctx, std::vector<IoHash>& OutReferences);
+ bool ReadAttachmentsFromMetaData(uint32_t BlockIndex,
+ std::span<const IoHash> InlineKeys,
+ std::span<const std::size_t> ChunkIndexes,
+ std::vector<IoHash>& OutReferences) const;
+
inline GcStorageSize StorageSize() const
{
return {.DiskSize = m_StandaloneSize.load(std::memory_order::relaxed) + m_BlockStore.TotalSize(),
diff --git a/src/zenutil/basicfile.cpp b/src/zenutil/basicfile.cpp
index 266146ca1..174125069 100644
--- a/src/zenutil/basicfile.cpp
+++ b/src/zenutil/basicfile.cpp
@@ -372,7 +372,7 @@ BasicFile::Flush()
}
uint64_t
-BasicFile::FileSize()
+BasicFile::FileSize() const
{
#if ZEN_PLATFORM_WINDOWS
ULARGE_INTEGER liFileSize;
@@ -401,7 +401,7 @@ BasicFile::FileSize()
}
uint64_t
-BasicFile::FileSize(std::error_code& Ec)
+BasicFile::FileSize(std::error_code& Ec) const
{
#if ZEN_PLATFORM_WINDOWS
ULARGE_INTEGER liFileSize;
@@ -565,20 +565,27 @@ TemporaryFile::MoveTemporaryIntoPlace(std::filesystem::path FinalFileName, std::
//////////////////////////////////////////////////////////////////////////
void
-TemporaryFile::SafeWriteFile(std::filesystem::path Path, MemoryView Data)
+TemporaryFile::SafeWriteFile(const std::filesystem::path& Path, MemoryView Data)
{
TemporaryFile TempFile;
std::error_code Ec;
- TempFile.CreateTemporary(Path.parent_path(), Ec);
+ SafeWriteFile(Path, Data, Ec);
if (Ec)
{
- throw std::system_error(Ec, fmt::format("Failed to create temp file for file at '{}'", Path));
+ throw std::system_error(Ec, fmt::format("Failed to safely write file '{}'", Path));
}
- TempFile.Write(Data, 0);
- TempFile.MoveTemporaryIntoPlace(Path, Ec);
- if (Ec)
+}
+
+void
+TemporaryFile::SafeWriteFile(const std::filesystem::path& Path, MemoryView Data, std::error_code& OutEc)
+{
+ TemporaryFile TempFile;
+ if (TempFile.CreateTemporary(Path.parent_path(), OutEc); !OutEc)
{
- throw std::system_error(Ec, fmt::format("Failed to move temp file '{}' to '{}'", TempFile.GetPath(), Path));
+ if (TempFile.Write(Data, 0, OutEc); !OutEc)
+ {
+ TempFile.MoveTemporaryIntoPlace(Path, OutEc);
+ }
}
}
diff --git a/src/zenutil/include/zenutil/basicfile.h b/src/zenutil/include/zenutil/basicfile.h
index 375da20c3..674457196 100644
--- a/src/zenutil/include/zenutil/basicfile.h
+++ b/src/zenutil/include/zenutil/basicfile.h
@@ -60,8 +60,8 @@ public:
void Write(const void* Data, uint64_t Size, uint64_t FileOffset);
void Write(const void* Data, uint64_t Size, uint64_t FileOffset, std::error_code& Ec);
void Flush();
- [[nodiscard]] uint64_t FileSize();
- [[nodiscard]] uint64_t FileSize(std::error_code& Ec);
+ [[nodiscard]] uint64_t FileSize() const;
+ [[nodiscard]] uint64_t FileSize(std::error_code& Ec) const;
void SetFileSize(uint64_t FileSize);
IoBuffer ReadAll();
void WriteAll(IoBuffer Data, std::error_code& Ec);
@@ -101,7 +101,8 @@ public:
void MoveTemporaryIntoPlace(std::filesystem::path FinalFileName, std::error_code& Ec);
const std::filesystem::path& GetPath() const { return m_TempPath; }
- static void SafeWriteFile(std::filesystem::path Path, MemoryView Data);
+ static void SafeWriteFile(const std::filesystem::path& Path, MemoryView Data);
+ static void SafeWriteFile(const std::filesystem::path& Path, MemoryView Data, std::error_code& OutEc);
private:
std::filesystem::path m_TempPath;
diff --git a/src/zenutil/include/zenutil/referencemetadata.h b/src/zenutil/include/zenutil/referencemetadata.h
new file mode 100644
index 000000000..5160bfb8b
--- /dev/null
+++ b/src/zenutil/include/zenutil/referencemetadata.h
@@ -0,0 +1,109 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/compositebuffer.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <gsl/gsl-lite.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+#pragma pack(push)
+#pragma pack(1)
+
+struct ReferenceMetaDataHeader
+{
+ static constexpr uint32_t Version1 = 1;
+ static constexpr uint32_t CurrentVersion = Version1;
+
+ ReferenceMetaDataHeader(uint32_t InMagic, uint32_t InEntryCount, uint64_t InAttachmentCount)
+ : Magic(InMagic)
+ , EntryCount(InEntryCount)
+ , AttachmentCount(InAttachmentCount)
+ , Checksum(ComputeChecksum())
+ {
+ }
+
+ uint32_t Magic = 0xffffffffu;
+ uint32_t Version = CurrentVersion;
+
+ uint32_t EntryCount = 0;
+ uint64_t AttachmentCount = 0;
+ uint64_t Padding = 0;
+
+ uint32_t Checksum = 0;
+
+ bool IsValid(uint32_t ExpectedMagic) const;
+
+ template<typename KeyType, typename AttachmentType>
+ static uint64_t ExpectedSize(uint32_t EntryCount, uint64_t AttachmentCount)
+ {
+ return sizeof(ReferenceMetaDataHeader) + sizeof(KeyType) * EntryCount + sizeof(uint32_t) * EntryCount +
+ sizeof(AttachmentType) * AttachmentCount;
+ }
+
+ ReferenceMetaDataHeader() = delete;
+
+private:
+ uint32_t ComputeChecksum() const;
+};
+
+static_assert(sizeof(ReferenceMetaDataHeader) == 32);
+
+#pragma pack(pop)
+
+template<typename KeyType, typename AttachmentType>
+CompositeBuffer
+BuildReferenceMetaData(uint32_t HeaderMagic,
+ std::span<const KeyType> Keys,
+ std::span<const uint32_t> AttachmentCounts,
+ std::span<const AttachmentType> Attachments)
+{
+ uint32_t KeyCount = gsl::narrow<uint32_t>(Keys.size());
+
+ ReferenceMetaDataHeader Header(HeaderMagic, KeyCount, Attachments.size());
+ return CompositeBuffer(std::vector<SharedBuffer>{
+ SharedBuffer(IoBuffer(IoBuffer::Clone, &Header, sizeof(ReferenceMetaDataHeader))),
+ SharedBuffer(IoBuffer(IoBuffer::Wrap, Keys.data(), sizeof(KeyType) * Keys.size())),
+ SharedBuffer(IoBuffer(IoBuffer::Wrap, AttachmentCounts.data(), sizeof(uint32_t) * AttachmentCounts.size())),
+ SharedBuffer(IoBuffer(IoBuffer::Wrap, Attachments.data(), sizeof(AttachmentType) * Attachments.size()))});
+}
+
+template<typename KeyType, typename AttachmentType>
+bool
+GetAttachmentsFromMetaData(const IoBuffer& MetaData,
+ const uint32_t ExpectedHeaderMagic,
+ std::function<void(std::span<const KeyType> Keys,
+ std::span<const uint32_t> AttachmentCounts,
+ std::span<const AttachmentType> Attachments)>&& Parser)
+{
+ MemoryView PayloadView = MetaData.GetView();
+ if (PayloadView.GetSize() >= sizeof(ReferenceMetaDataHeader))
+ {
+ const ReferenceMetaDataHeader* Header = (const ReferenceMetaDataHeader*)PayloadView.GetData();
+ if (Header->IsValid(ExpectedHeaderMagic))
+ {
+ if (MetaData.GetSize() ==
+ ReferenceMetaDataHeader::ExpectedSize<KeyType, AttachmentType>(Header->EntryCount, Header->AttachmentCount))
+ {
+ PayloadView.MidInline(sizeof(ReferenceMetaDataHeader));
+ std::span<const KeyType> PayloadKeys = {(const KeyType*)PayloadView.GetData(), Header->EntryCount};
+ PayloadView.MidInline(sizeof(KeyType) * Header->EntryCount);
+ std::span<const uint32_t> PayloadAttachmentCounts = {(const uint32_t*)PayloadView.GetData(), Header->EntryCount};
+ PayloadView.MidInline(sizeof(uint32_t) * Header->EntryCount);
+ std::span<const AttachmentType> PayloadAttachments = {(const AttachmentType*)PayloadView.GetData(),
+ Header->AttachmentCount};
+ PayloadView.MidInline(sizeof(AttachmentType) * Header->AttachmentCount);
+ ZEN_ASSERT(PayloadView.GetSize() == 0);
+
+ Parser(PayloadKeys, PayloadAttachmentCounts, PayloadAttachments);
+
+ return true;
+ }
+ }
+ }
+ return false;
+}
+} // namespace zen
diff --git a/src/zenutil/referencemetadata.cpp b/src/zenutil/referencemetadata.cpp
new file mode 100644
index 000000000..bbcafcfba
--- /dev/null
+++ b/src/zenutil/referencemetadata.cpp
@@ -0,0 +1,32 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenutil/referencemetadata.h>
+//////////////////////////////////////////////////////////////////////////
+
+#include <zencore/xxhash.h>
+
+namespace zen {
+
+uint32_t
+ReferenceMetaDataHeader::ComputeChecksum() const
+{
+ return XXH32(&Magic, sizeof(ReferenceMetaDataHeader) - sizeof(uint32_t), 0xC0C0'BABA);
+}
+
+bool
+ReferenceMetaDataHeader::IsValid(uint32_t ExpectedMagic) const
+{
+ if (Magic != ExpectedMagic)
+ {
+ return false;
+ }
+
+ if (Checksum != ComputeChecksum())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace zen