aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2023-05-08 10:53:06 +0200
committerGitHub <[email protected]>2023-05-08 10:53:06 +0200
commit4d63d67beba80811543ad5e4cb5f6c61d5f6b720 (patch)
tree82bf70f030d4e21a873e9064820f8e9886e31b73 /src
parentchangelog (diff)
downloadzen-4d63d67beba80811543ad5e4cb5f6c61d5f6b720.tar.xz
zen-4d63d67beba80811543ad5e4cb5f6c61d5f6b720.zip
project store gc lifetime (#257)
* keep track of last access time for project store projects and oplogs * check size on disk for project store total size * read/write access times to disk * changelog * change some std::filesystem::path -> const std::filesystem::path&
Diffstat (limited to 'src')
-rw-r--r--src/zenserver/projectstore/projectstore.cpp388
-rw-r--r--src/zenserver/projectstore/projectstore.h39
2 files changed, 343 insertions, 84 deletions
diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp
index 847a79a1d..80e3f27f1 100644
--- a/src/zenserver/projectstore/projectstore.cpp
+++ b/src/zenserver/projectstore/projectstore.cpp
@@ -435,18 +435,23 @@ struct ProjectStore::OplogStorage : public RefCounted
Flush();
}
- [[nodiscard]] bool Exists() { return Exists(m_OplogStoragePath); }
- [[nodiscard]] static bool Exists(std::filesystem::path BasePath)
+ [[nodiscard]] bool Exists() const { return Exists(m_OplogStoragePath); }
+ [[nodiscard]] static bool Exists(const std::filesystem::path& BasePath)
{
return std::filesystem::exists(BasePath / "ops.zlog") && std::filesystem::exists(BasePath / "ops.zops");
}
- static bool Delete(std::filesystem::path BasePath) { return DeleteDirectories(BasePath); }
+ static bool Delete(const std::filesystem::path& BasePath) { return DeleteDirectories(BasePath); }
- uint64_t OpBlobsSize() const
+ uint64_t OpBlobsSize() const { return OpBlobsSize(m_OplogStoragePath); }
+ static uint64_t OpBlobsSize(const std::filesystem::path& BasePath)
{
- RwLock::SharedLockScope _(m_RwLock);
- return m_NextOpsOffset;
+ using namespace std::literals;
+ if (Exists(BasePath))
+ {
+ return std::filesystem::file_size(BasePath / "ops.zlog"sv) + std::filesystem::file_size(BasePath / "ops.zops"sv);
+ }
+ return 0;
}
void Open(bool IsCreate)
@@ -668,24 +673,24 @@ ProjectStore::Oplog::GatherReferences(GcContext& GcCtx)
}
uint64_t
-ProjectStore::Oplog::TotalSize() const
+ProjectStore::Oplog::TotalSize(const std::filesystem::path& BasePath)
{
- RwLock::SharedLockScope _(m_OplogLock);
- if (m_Storage)
+ using namespace std::literals;
+
+ uint64_t Size = OplogStorage::OpBlobsSize(BasePath);
+ std::filesystem::path StateFilePath = BasePath / "oplog.zcb"sv;
+ if (std::filesystem::exists(StateFilePath))
{
- return m_Storage->OpBlobsSize();
+ Size += std::filesystem::file_size(StateFilePath);
}
- return 0;
+
+ return Size;
}
-bool
-ProjectStore::Oplog::IsExpired() const
+uint64_t
+ProjectStore::Oplog::TotalSize() const
{
- if (m_MarkerPath.empty())
- {
- return false;
- }
- return !std::filesystem::exists(m_MarkerPath);
+ return TotalSize(m_BasePath);
}
std::filesystem::path
@@ -711,7 +716,7 @@ ProjectStore::Oplog::PrepareForDelete(bool MoveFolder)
}
bool
-ProjectStore::Oplog::ExistsAt(std::filesystem::path BasePath)
+ProjectStore::Oplog::ExistsAt(const std::filesystem::path& BasePath)
{
using namespace std::literals;
@@ -1209,15 +1214,21 @@ ProjectStore::Project::Project(ProjectStore* PrjStore, CidStore& Store, std::fil
: m_ProjectStore(PrjStore)
, m_CidStore(Store)
, m_OplogStoragePath(BasePath)
+, m_LastAccessTimes({std::make_pair(std::string(), GcClock::TickCount())})
{
}
ProjectStore::Project::~Project()
{
+ // Only write access times if we have not been explicitly deleted
+ if (!m_OplogStoragePath.empty())
+ {
+ WriteAccessTimes();
+ }
}
bool
-ProjectStore::Project::Exists(std::filesystem::path BasePath)
+ProjectStore::Project::Exists(const std::filesystem::path& BasePath)
{
return std::filesystem::exists(BasePath / "Project.zcb");
}
@@ -1251,6 +1262,8 @@ ProjectStore::Project::Read()
{
ZEN_ERROR("validation error {} hit for '{}'", int(ValidationError), ProjectStateFilePath);
}
+
+ ReadAccessTimes();
}
void
@@ -1281,6 +1294,84 @@ ProjectStore::Project::Write()
Blob.Flush();
}
+void
+ProjectStore::Project::ReadAccessTimes()
+{
+ using namespace std::literals;
+
+ RwLock::SharedLockScope _(m_ProjectLock);
+
+ std::filesystem::path ProjectAccessTimesFilePath = m_OplogStoragePath / "AccessTimes.zcb"sv;
+ if (!std::filesystem::exists(ProjectAccessTimesFilePath))
+ {
+ return;
+ }
+
+ ZEN_INFO("reading access times for project '{}' from {}", Identifier, ProjectAccessTimesFilePath);
+
+ BasicFile Blob;
+ Blob.Open(ProjectAccessTimesFilePath, BasicFile::Mode::kRead);
+
+ IoBuffer Obj = Blob.ReadAll();
+ CbValidateError ValidationError = ValidateCompactBinary(MemoryView(Obj.Data(), Obj.Size()), CbValidateMode::All);
+
+ if (ValidationError == CbValidateError::None)
+ {
+ CbObject Reader = LoadCompactBinaryObject(Obj);
+ CbArrayView LastAccessTimes = Reader["lastaccess"sv].AsArrayView();
+
+ for (CbFieldView& Entry : LastAccessTimes)
+ {
+ CbObjectView AccessTime = Entry.AsObjectView();
+ std::string_view Id = AccessTime["id"sv].AsString();
+ GcClock::Tick AccessTick = AccessTime["tick"sv].AsUInt64();
+ m_LastAccessTimes.insert_or_assign(std::string(Id), AccessTick);
+ }
+ }
+ else
+ {
+ ZEN_ERROR("validation error {} hit for '{}'", int(ValidationError), ProjectAccessTimesFilePath);
+ }
+}
+
+void
+ProjectStore::Project::WriteAccessTimes()
+{
+ using namespace std::literals;
+
+ RwLock::ExclusiveLockScope _(m_ProjectLock);
+
+ BinaryWriter Mem;
+
+ CbObjectWriter Writer;
+ Writer.BeginArray("lastaccess");
+ {
+ for (const auto& It : m_LastAccessTimes)
+ {
+ Writer.BeginObject();
+ {
+ Writer << "id"sv << It.first;
+ Writer << "tick"sv << gsl::narrow<uint64_t>(It.second);
+ }
+ Writer.EndObject();
+ }
+ }
+ Writer.EndArray();
+
+ Writer.Save(Mem);
+
+ CreateDirectories(m_OplogStoragePath);
+
+ std::filesystem::path ProjectAccessTimesFilePath = m_OplogStoragePath / "AccessTimes.zcb"sv;
+
+ ZEN_INFO("persisting access times for project '{}' to {}", Identifier, ProjectAccessTimesFilePath);
+
+ BasicFile Blob;
+ Blob.Open(ProjectAccessTimesFilePath, BasicFile::Mode::kTruncate);
+ Blob.Write(Mem.Data(), Mem.Size(), 0);
+ Blob.Flush();
+}
+
spdlog::logger&
ProjectStore::Project::Log()
{
@@ -1382,6 +1473,7 @@ ProjectStore::Project::DeleteOplog(std::string_view OplogId)
m_DeletedOplogs.emplace_back(std::move(Oplog));
m_Oplogs.erase(OplogIt);
}
+ m_LastAccessTimes.erase(std::string(OplogId));
}
// Erase content on disk
@@ -1432,6 +1524,7 @@ ProjectStore::Project::Flush()
{
// We only need to flush oplogs that we have already loaded
IterateOplogs([&](Oplog& Ops) { Ops.Flush(); });
+ WriteAccessTimes();
}
void
@@ -1444,7 +1537,7 @@ ProjectStore::Project::Scrub(ScrubContext& Ctx)
OpenOplog(OpLogId);
}
IterateOplogs([&](const Oplog& Ops) {
- if (!Ops.IsExpired())
+ if (!IsExpired(GcClock::TimePoint::max(), Ops))
{
Ops.Scrub(Ctx);
}
@@ -1467,8 +1560,21 @@ ProjectStore::Project::GatherReferences(GcContext& GcCtx)
{
OpenOplog(OpLogId);
}
+
+ {
+ // Make sure any oplog at least have a last access time so they eventually will be GC:d if not touched
+ RwLock::ExclusiveLockScope _(m_ProjectLock);
+ for (const std::string& OpId : OpLogs)
+ {
+ if (auto It = m_LastAccessTimes.find(OpId); It == m_LastAccessTimes.end())
+ {
+ m_LastAccessTimes[OpId] = GcClock::TickCount();
+ }
+ }
+ }
+
IterateOplogs([&](Oplog& Ops) {
- if (!Ops.IsExpired())
+ if (!IsExpired(GcCtx.ExpireTime(), Ops))
{
Ops.GatherReferences(GcCtx);
}
@@ -1480,10 +1586,11 @@ ProjectStore::Project::TotalSize() const
{
uint64_t Result = 0;
{
- RwLock::SharedLockScope _(m_ProjectLock);
- for (const auto& It : m_Oplogs)
+ std::vector<std::string> OpLogs = ScanForOplogs();
+ for (const std::string& OpLogId : OpLogs)
{
- Result += It.second->TotalSize();
+ std::filesystem::path OplogBasePath = m_OplogStoragePath / OpLogId;
+ Result += Oplog::TotalSize(OplogBasePath);
}
}
return Result;
@@ -1513,15 +1620,58 @@ ProjectStore::Project::PrepareForDelete(std::filesystem::path& OutDeletePath)
}
bool
-ProjectStore::Project::IsExpired() const
+ProjectStore::Project::IsExpired(const std::string& EntryName,
+ const std::filesystem::path& MarkerPath,
+ const GcClock::TimePoint ExpireTime) const
{
- if (ProjectFilePath.empty())
+ if (!MarkerPath.empty())
{
- return false;
+ if (std::filesystem::exists(MarkerPath))
+ {
+ return false;
+ }
}
- return !std::filesystem::exists(ProjectFilePath);
+
+ const GcClock::Tick ExpireTicks = ExpireTime.time_since_epoch().count();
+
+ RwLock::SharedLockScope _(m_ProjectLock);
+ if (auto It = m_LastAccessTimes.find(EntryName); It != m_LastAccessTimes.end())
+ {
+ if (It->second <= ExpireTicks)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ProjectStore::Project::IsExpired(const GcClock::TimePoint ExpireTime) const
+{
+ return IsExpired(std::string(), ProjectFilePath, ExpireTime);
}
+bool
+ProjectStore::Project::IsExpired(const GcClock::TimePoint ExpireTime, const ProjectStore::Oplog& Oplog) const
+{
+ return IsExpired(Oplog.OplogId(), Oplog.MarkerPath(), ExpireTime);
+}
+
+void
+ProjectStore::Project::TouchProject() const
+{
+ RwLock::ExclusiveLockScope _(m_ProjectLock);
+ m_LastAccessTimes.insert_or_assign(std::string(), GcClock::TickCount());
+};
+
+void
+ProjectStore::Project::TouchOplog(std::string_view Oplog) const
+{
+ ZEN_ASSERT(!Oplog.empty());
+ RwLock::ExclusiveLockScope _(m_ProjectLock);
+ m_LastAccessTimes.insert_or_assign(std::string(Oplog), GcClock::TickCount());
+};
+
//////////////////////////////////////////////////////////////////////////
ProjectStore::ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc)
@@ -1606,7 +1756,7 @@ ProjectStore::Scrub(ScrubContext& Ctx)
for (auto& Kv : m_Projects)
{
- if (Kv.second->IsExpired())
+ if (Kv.second->IsExpired(GcClock::TimePoint::max()))
{
continue;
}
@@ -1644,7 +1794,7 @@ ProjectStore::GatherReferences(GcContext& GcCtx)
for (auto& Kv : m_Projects)
{
- if (Kv.second->IsExpired())
+ if (Kv.second->IsExpired(GcCtx.ExpireTime()))
{
ExpiredProjectCount++;
continue;
@@ -1682,7 +1832,7 @@ ProjectStore::CollectGarbage(GcContext& GcCtx)
RwLock::SharedLockScope _(m_ProjectsLock);
for (auto& Kv : m_Projects)
{
- if (Kv.second->IsExpired())
+ if (Kv.second->IsExpired(GcCtx.ExpireTime()))
{
ExpiredProjects.push_back(Kv.second);
ExpiredProjectCount++;
@@ -1704,8 +1854,8 @@ ProjectStore::CollectGarbage(GcContext& GcCtx)
std::vector<std::string> ExpiredOplogs;
{
RwLock::ExclusiveLockScope _(m_ProjectsLock);
- Project->IterateOplogs([&ExpiredOplogs](ProjectStore::Oplog& Oplog) {
- if (Oplog.IsExpired())
+ Project->IterateOplogs([&GcCtx, &Project, &ExpiredOplogs](ProjectStore::Oplog& Oplog) {
+ if (Project->IsExpired(GcCtx.ExpireTime(), Oplog))
{
ExpiredOplogs.push_back(Oplog.OplogId());
}
@@ -1718,6 +1868,7 @@ ProjectStore::CollectGarbage(GcContext& GcCtx)
Project->Identifier);
Project->DeleteOplog(OplogId);
}
+ Project->Flush();
}
if (ExpiredProjects.empty())
@@ -1732,7 +1883,7 @@ ProjectStore::CollectGarbage(GcContext& GcCtx)
std::string ProjectId;
{
RwLock::ExclusiveLockScope _(m_ProjectsLock);
- if (!Project->IsExpired())
+ if (!Project->IsExpired(GcCtx.ExpireTime()))
{
ZEN_DEBUG("ProjectStore::CollectGarbage skipped garbage collect of project '{}'. Project no longer expired.", ProjectId);
continue;
@@ -1760,13 +1911,29 @@ ProjectStore::CollectGarbage(GcContext& GcCtx)
GcStorageSize
ProjectStore::StorageSize() const
{
+ using namespace std::literals;
+
GcStorageSize Result;
{
- RwLock::SharedLockScope _(m_ProjectsLock);
- for (auto& Kv : m_Projects)
+ if (std::filesystem::exists(m_ProjectBasePath))
{
- const Ref<Project>& Project = Kv.second;
- Result.DiskSize += Project->TotalSize();
+ DirectoryContent ProjectsFolderContent;
+ GetDirectoryContent(m_ProjectBasePath, DirectoryContent::IncludeDirsFlag, ProjectsFolderContent);
+
+ for (const std::filesystem::path& ProjectBasePath : ProjectsFolderContent.Directories)
+ {
+ std::filesystem::path ProjectStateFilePath = ProjectBasePath / "Project.zcb"sv;
+ if (std::filesystem::exists(ProjectStateFilePath))
+ {
+ Result.DiskSize += std::filesystem::file_size(ProjectStateFilePath);
+ DirectoryContent DirContent;
+ GetDirectoryContent(ProjectBasePath, DirectoryContent::IncludeDirsFlag, DirContent);
+ for (const std::filesystem::path& OplogBasePath : DirContent.Directories)
+ {
+ Result.DiskSize += Oplog::TotalSize(OplogBasePath);
+ }
+ }
+ }
}
}
return Result;
@@ -1815,12 +1982,12 @@ ProjectStore::OpenProject(std::string_view ProjectId)
}
Ref<ProjectStore::Project>
-ProjectStore::NewProject(std::filesystem::path BasePath,
- std::string_view ProjectId,
- std::string_view RootDir,
- std::string_view EngineRootDir,
- std::string_view ProjectRootDir,
- std::string_view ProjectFilePath)
+ProjectStore::NewProject(const std::filesystem::path& BasePath,
+ std::string_view ProjectId,
+ std::string_view RootDir,
+ std::string_view EngineRootDir,
+ std::string_view ProjectRootDir,
+ std::string_view ProjectFilePath)
{
RwLock::ExclusiveLockScope _(m_ProjectsLock);
@@ -1907,13 +2074,14 @@ ProjectStore::GetProjectFiles(const std::string_view ProjectId, const std::strin
{
return {HttpResponseCode::NotFound, fmt::format("Project files request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return {HttpResponseCode::NotFound, fmt::format("Project files for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
CbObjectWriter Response;
Response.BeginArray("files"sv);
@@ -1947,13 +2115,15 @@ ProjectStore::GetChunkInfo(const std::string_view ProjectId,
{
return {HttpResponseCode::NotFound, fmt::format("Chunk info request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return {HttpResponseCode::NotFound, fmt::format("Chunk info request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
+
if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
{
return {HttpResponseCode::BadRequest,
@@ -2000,13 +2170,14 @@ ProjectStore::GetChunkRange(const std::string_view ProjectId,
{
return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
{
@@ -2090,13 +2261,14 @@ ProjectStore::GetChunk(const std::string_view ProjectId,
{
return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
if (Cid.length() != IoHash::StringLength)
{
@@ -2136,13 +2308,14 @@ ProjectStore::PutChunk(const std::string_view ProjectId,
{
return {HttpResponseCode::NotFound, fmt::format("Chunk put request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return {HttpResponseCode::NotFound, fmt::format("Chunk put request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
if (Cid.length() != IoHash::StringLength)
{
@@ -2175,13 +2348,14 @@ ProjectStore::WriteOplog(const std::string_view ProjectId, const std::string_vie
{
return {HttpResponseCode::NotFound, fmt::format("Write oplog request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId);
-
if (!Oplog)
{
return {HttpResponseCode::NotFound, fmt::format("Write oplog request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
CbObject ContainerObject = LoadCompactBinaryObject(Payload);
if (!ContainerObject)
@@ -2243,13 +2417,14 @@ ProjectStore::ReadOplog(const std::string_view ProjectId,
{
return {HttpResponseCode::NotFound, fmt::format("Read oplog request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId);
-
if (!Oplog)
{
return {HttpResponseCode::NotFound, fmt::format("Read oplog request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
size_t MaxBlockSize = 128u * 1024u * 1024u;
if (auto Param = Params.GetValue("maxblocksize"); Param.empty() == false)
@@ -2292,13 +2467,14 @@ ProjectStore::WriteBlock(const std::string_view ProjectId, const std::string_vie
{
return {HttpResponseCode::NotFound, fmt::format("Write block request for unknown project '{}'", ProjectId)};
}
+ Project->TouchProject();
ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId);
-
if (!Oplog)
{
return {HttpResponseCode::NotFound, fmt::format("Write block request for unknown oplog '{}/{}'", ProjectId, OplogId)};
}
+ Project->TouchOplog(OplogId);
if (!IterateBlock(std::move(Payload), [this](CompressedBuffer&& Chunk, const IoHash& AttachmentRawHash) {
IoBuffer Compressed = Chunk.GetCompressed().Flatten().AsIoBuffer();
@@ -2369,15 +2545,16 @@ ProjectStore::Rpc(HttpServerRequest& HttpReq,
HttpContentType::kText,
fmt::format("Rpc oplog request for unknown project '{}'", ProjectId));
}
+ Project->TouchProject();
ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId);
-
if (!Oplog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound,
HttpContentType::kText,
fmt::format("Rpc oplog request for unknown oplog '{}/{}'", ProjectId, OplogId));
}
+ Project->TouchOplog(OplogId);
std::string_view Method = Cb["method"sv].AsString();
@@ -2540,13 +2717,14 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchOplog(OplogId);
// Parse Request
@@ -2879,13 +3057,14 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchOplog(OplogId);
// This operation takes a list of referenced hashes and decides which
// chunks are not present on this server. This list is then returned in
@@ -2948,13 +3127,14 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchOplog(OplogId);
ProjectStore::Oplog& Oplog = *FoundLog;
@@ -3056,13 +3236,14 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchOplog(OplogId);
ProjectStore::Oplog& Oplog = *FoundLog;
@@ -3149,26 +3330,27 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
HttpContentType::kText,
fmt::format("project {} not found", ProjectId));
}
+ Project->TouchProject();
switch (Req.ServerRequest().RequestVerb())
{
case HttpVerb::kGet:
{
ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId);
-
if (!OplogIt)
{
return Req.ServerRequest().WriteResponse(HttpResponseCode::NotFound,
HttpContentType::kText,
fmt::format("oplog {} not found in project {}", OplogId, ProjectId));
}
+ Project->TouchOplog(OplogId);
ProjectStore::Oplog& Log = *OplogIt;
CbObjectWriter Cb;
Cb << "id"sv << Log.OplogId() << "project"sv << Project->Identifier << "tempdir"sv << Log.TempPath().c_str()
<< "markerpath"sv << Log.MarkerPath().c_str() << "totalsize"sv << Log.TotalSize() << "opcount"
- << Log.OplogCount() << "expired"sv << Log.IsExpired();
+ << Log.OplogCount() << "expired"sv << Project->IsExpired(GcClock::TimePoint::max(), Log);
Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Cb.Save());
}
@@ -3183,7 +3365,6 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
}
ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId);
-
if (!OplogIt)
{
if (!Project->NewOplog(OplogId, OplogMarkerPath))
@@ -3191,6 +3372,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
// TODO: indicate why the operation failed!
return Req.ServerRequest().WriteResponse(HttpResponseCode::InternalServerError);
}
+ Project->TouchOplog(OplogId);
ZEN_INFO("established oplog '{}/{}', gc marker file at '{}'", ProjectId, OplogId, OplogMarkerPath);
@@ -3233,13 +3415,14 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchProject();
ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId);
-
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
+ Project->TouchOplog(OplogId);
CbObjectWriter Response;
@@ -3311,13 +3494,13 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
case HttpVerb::kGet:
{
Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
-
if (!Project)
{
return Req.ServerRequest().WriteResponse(HttpResponseCode::NotFound,
HttpContentType::kText,
fmt::format("project {} not found", ProjectId));
}
+ Project->TouchProject();
std::vector<std::string> OpLogs = Project->ScanForOplogs();
@@ -3344,7 +3527,6 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
case HttpVerb::kDelete:
{
Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
-
if (!Project)
{
return Req.ServerRequest().WriteResponse(HttpResponseCode::NotFound,
@@ -3510,6 +3692,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
ProjectStore::Project& Project = *FoundProject.Get();
+
if (CSV)
{
ExtendableStringBuilder<4096> CSVWriter;
@@ -3556,8 +3739,8 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
+ ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -3608,8 +3791,8 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects,
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
+ ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -3885,6 +4068,12 @@ TEST_CASE("project.store.gc")
BasicFile ProjectFile;
ProjectFile.Open(Project1FilePath, BasicFile::Mode::kTruncate);
}
+ std::filesystem::path Project1OplogPath = TempDir.Path() / "game1" / "saves" / "cooked" / ".projectstore";
+ {
+ CreateDirectories(Project1OplogPath.parent_path());
+ BasicFile OplogFile;
+ OplogFile.Open(Project1OplogPath, BasicFile::Mode::kTruncate);
+ }
std::filesystem::path Project2RootDir = TempDir.Path() / "game2";
std::filesystem::path Project2FilePath = TempDir.Path() / "game2" / "game.uproject";
@@ -3893,6 +4082,12 @@ TEST_CASE("project.store.gc")
BasicFile ProjectFile;
ProjectFile.Open(Project2FilePath, BasicFile::Mode::kTruncate);
}
+ std::filesystem::path Project2OplogPath = TempDir.Path() / "game1" / "saves" / "cooked" / ".projectstore";
+ {
+ CreateDirectories(Project2OplogPath.parent_path());
+ BasicFile OplogFile;
+ OplogFile.Open(Project2OplogPath, BasicFile::Mode::kTruncate);
+ }
{
Ref<ProjectStore::Project> Project1(ProjectStore.NewProject(BasePath / "proj1"sv,
@@ -3901,7 +4096,7 @@ TEST_CASE("project.store.gc")
EngineRootDir.string(),
Project1RootDir.string(),
Project1FilePath.string()));
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1", {});
+ ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1", Project1OplogPath);
CHECK(Oplog != nullptr);
Oplog->AppendNewOplogEntry(CreateOplogPackage(Oid::NewOid(), {}));
@@ -3917,7 +4112,7 @@ TEST_CASE("project.store.gc")
EngineRootDir.string(),
Project2RootDir.string(),
Project2FilePath.string()));
- ProjectStore::Oplog* Oplog = Project2->NewOplog("oplog1", {});
+ ProjectStore::Oplog* Oplog = Project2->NewOplog("oplog2", Project2OplogPath);
CHECK(Oplog != nullptr);
Oplog->AppendNewOplogEntry(CreateOplogPackage(Oid::NewOid(), {}));
@@ -3937,6 +4132,17 @@ TEST_CASE("project.store.gc")
CHECK(ProjectStore.OpenProject("proj2"sv));
}
+ {
+ GcContext GcCtx(GcClock::Now() + std::chrono::hours(24));
+ ProjectStore.GatherReferences(GcCtx);
+ size_t RefCount = 0;
+ GcCtx.IterateCids([&RefCount](const IoHash&) { RefCount++; });
+ CHECK(RefCount == 14);
+ ProjectStore.CollectGarbage(GcCtx);
+ CHECK(ProjectStore.OpenProject("proj1"sv));
+ CHECK(ProjectStore.OpenProject("proj2"sv));
+ }
+
std::filesystem::remove(Project1FilePath);
{
@@ -3944,11 +4150,57 @@ TEST_CASE("project.store.gc")
ProjectStore.GatherReferences(GcCtx);
size_t RefCount = 0;
GcCtx.IterateCids([&RefCount](const IoHash&) { RefCount++; });
+ CHECK(RefCount == 14);
+ ProjectStore.CollectGarbage(GcCtx);
+ CHECK(ProjectStore.OpenProject("proj1"sv));
+ CHECK(ProjectStore.OpenProject("proj2"sv));
+ }
+
+ {
+ GcContext GcCtx(GcClock::Now() + std::chrono::hours(24));
+ ProjectStore.GatherReferences(GcCtx);
+ size_t RefCount = 0;
+ GcCtx.IterateCids([&RefCount](const IoHash&) { RefCount++; });
CHECK(RefCount == 7);
ProjectStore.CollectGarbage(GcCtx);
CHECK(!ProjectStore.OpenProject("proj1"sv));
CHECK(ProjectStore.OpenProject("proj2"sv));
}
+
+ std::filesystem::remove(Project2OplogPath);
+ {
+ GcContext GcCtx(GcClock::Now() - std::chrono::hours(24));
+ ProjectStore.GatherReferences(GcCtx);
+ size_t RefCount = 0;
+ GcCtx.IterateCids([&RefCount](const IoHash&) { RefCount++; });
+ CHECK(RefCount == 7);
+ ProjectStore.CollectGarbage(GcCtx);
+ CHECK(!ProjectStore.OpenProject("proj1"sv));
+ CHECK(ProjectStore.OpenProject("proj2"sv));
+ }
+
+ {
+ GcContext GcCtx(GcClock::Now() + std::chrono::hours(24));
+ ProjectStore.GatherReferences(GcCtx);
+ size_t RefCount = 0;
+ GcCtx.IterateCids([&RefCount](const IoHash&) { RefCount++; });
+ CHECK(RefCount == 0);
+ ProjectStore.CollectGarbage(GcCtx);
+ CHECK(!ProjectStore.OpenProject("proj1"sv));
+ CHECK(ProjectStore.OpenProject("proj2"sv));
+ }
+
+ std::filesystem::remove(Project2FilePath);
+ {
+ GcContext GcCtx(GcClock::Now() + std::chrono::hours(24));
+ ProjectStore.GatherReferences(GcCtx);
+ size_t RefCount = 0;
+ GcCtx.IterateCids([&RefCount](const IoHash&) { RefCount++; });
+ CHECK(RefCount == 0);
+ ProjectStore.CollectGarbage(GcCtx);
+ CHECK(!ProjectStore.OpenProject("proj1"sv));
+ CHECK(!ProjectStore.OpenProject("proj2"sv));
+ }
}
TEST_CASE("project.store.partial.read")
diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenserver/projectstore/projectstore.h
index be3ee7075..7b0c96205 100644
--- a/src/zenserver/projectstore/projectstore.h
+++ b/src/zenserver/projectstore/projectstore.h
@@ -74,7 +74,7 @@ public:
const std::filesystem::path& MarkerPath);
~Oplog();
- [[nodiscard]] static bool ExistsAt(std::filesystem::path BasePath);
+ [[nodiscard]] static bool ExistsAt(const std::filesystem::path& BasePath);
void Read();
void Write();
@@ -113,6 +113,7 @@ public:
void Flush();
void Scrub(ScrubContext& Ctx) const;
void GatherReferences(GcContext& GcCtx);
+ static uint64_t TotalSize(const std::filesystem::path& BasePath);
uint64_t TotalSize() const;
std::size_t OplogCount() const
@@ -121,7 +122,6 @@ public:
return m_LatestOpMap.size();
}
- bool IsExpired() const;
std::filesystem::path PrepareForDelete(bool MoveFolder);
private:
@@ -208,14 +208,17 @@ public:
void IterateOplogs(std::function<void(const Oplog&)>&& Fn) const;
void IterateOplogs(std::function<void(Oplog&)>&& Fn);
std::vector<std::string> ScanForOplogs() const;
- bool IsExpired() const;
+ bool IsExpired(const GcClock::TimePoint ExpireTime) const;
+ bool IsExpired(const GcClock::TimePoint ExpireTime, const ProjectStore::Oplog& Oplog) const;
+ void TouchProject() const;
+ void TouchOplog(std::string_view Oplog) const;
Project(ProjectStore* PrjStore, CidStore& Store, std::filesystem::path BasePath);
virtual ~Project();
void Read();
void Write();
- [[nodiscard]] static bool Exists(std::filesystem::path BasePath);
+ [[nodiscard]] static bool Exists(const std::filesystem::path& BasePath);
void Flush();
void Scrub(ScrubContext& Ctx);
spdlog::logger& Log();
@@ -224,25 +227,29 @@ public:
bool PrepareForDelete(std::filesystem::path& OutDeletePath);
private:
- ProjectStore* m_ProjectStore;
- CidStore& m_CidStore;
- mutable RwLock m_ProjectLock;
- std::map<std::string, std::unique_ptr<Oplog>> m_Oplogs;
- std::vector<std::unique_ptr<Oplog>> m_DeletedOplogs;
- std::filesystem::path m_OplogStoragePath;
+ ProjectStore* m_ProjectStore;
+ CidStore& m_CidStore;
+ mutable RwLock m_ProjectLock;
+ std::map<std::string, std::unique_ptr<Oplog>> m_Oplogs;
+ std::vector<std::unique_ptr<Oplog>> m_DeletedOplogs;
+ std::filesystem::path m_OplogStoragePath;
+ mutable tsl::robin_map<std::string, GcClock::Tick> m_LastAccessTimes;
std::filesystem::path BasePathForOplog(std::string_view OplogId);
+ bool IsExpired(const std::string& EntryName, const std::filesystem::path& MarkerPath, const GcClock::TimePoint ExpireTime) const;
+ void WriteAccessTimes();
+ void ReadAccessTimes();
};
// Oplog* OpenProjectOplog(std::string_view ProjectId, std::string_view OplogId);
Ref<Project> OpenProject(std::string_view ProjectId);
- Ref<Project> NewProject(std::filesystem::path BasePath,
- std::string_view ProjectId,
- std::string_view RootDir,
- std::string_view EngineRootDir,
- std::string_view ProjectRootDir,
- std::string_view ProjectFilePath);
+ Ref<Project> NewProject(const std::filesystem::path& BasePath,
+ std::string_view ProjectId,
+ std::string_view RootDir,
+ std::string_view EngineRootDir,
+ std::string_view ProjectRootDir,
+ std::string_view ProjectFilePath);
bool DeleteProject(std::string_view ProjectId);
bool Exists(std::string_view ProjectId);
void Flush();