diff options
| author | Dan Engelbrecht <[email protected]> | 2025-10-15 13:11:00 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-10-15 13:11:00 +0200 |
| commit | f34d6b40abcf748b0cbba9584f3b85dd164d0c79 (patch) | |
| tree | 3ab53eefac47d4dda00875ffc056b933f6da9420 /src | |
| parent | restructured zenserver configuration (#575) (diff) | |
| download | zen-f34d6b40abcf748b0cbba9584f3b85dd164d0c79.tar.xz zen-f34d6b40abcf748b0cbba9584f3b85dd164d0c79.zip | |
move file i/o related files to separate file and remove duplicated code (#576)
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/builds_cmd.cpp | 295 | ||||
| -rw-r--r-- | src/zenremotestore/builds/buildstorageoperations.cpp | 695 | ||||
| -rw-r--r-- | src/zenremotestore/filesystemutils.cpp | 595 | ||||
| -rw-r--r-- | src/zenremotestore/filesystemutils.h | 114 | ||||
| -rw-r--r-- | src/zenutil/bufferedopenfile.cpp | 75 | ||||
| -rw-r--r-- | src/zenutil/include/zenutil/bufferedopenfile.h | 43 |
6 files changed, 908 insertions, 909 deletions
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp index 6a0a1a0da..a911d1e44 100644 --- a/src/zen/cmds/builds_cmd.cpp +++ b/src/zen/cmds/builds_cmd.cpp @@ -32,8 +32,6 @@ #include <zenremotestore/chunking/chunkedfile.h> #include <zenremotestore/chunking/chunkingcontroller.h> #include <zenremotestore/jupiter/jupiterhost.h> -#include <zenutil/bufferedopenfile.h> -#include <zenutil/bufferedwritefilecache.h> #include <zenutil/wildcard.h> #include <zenutil/workerpools.h> #include <zenutil/zenserverprocess.h> @@ -385,288 +383,6 @@ namespace { return true; } - bool IsFileWithRetry(const std::filesystem::path& Path) - { - std::error_code Ec; - bool Result = IsFile(Path, Ec); - for (size_t Retries = 0; Ec && Retries < 3; Retries++) - { - Sleep(100 + int(Retries * 50)); - Ec.clear(); - Result = IsFile(Path, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Check path '{}' is file failed with: {} ({})", Path, Ec.message(), Ec.value())); - } - return Result; - } - - bool SetFileReadOnlyWithRetry(const std::filesystem::path& Path, bool ReadOnly) - { - std::error_code Ec; - bool Result = SetFileReadOnly(Path, ReadOnly, Ec); - for (size_t Retries = 0; Ec && Retries < 3; Retries++) - { - Sleep(100 + int(Retries * 50)); - if (!IsFileWithRetry(Path)) - { - return false; - } - Ec.clear(); - Result = SetFileReadOnly(Path, ReadOnly, Ec); - } - if (Ec) - { - throw std::system_error( - std::error_code(Ec.value(), std::system_category()), - fmt::format("Failed {} read only flag for '{}' failed with: {}", ReadOnly ? "setting" : "clearing", Path, Ec.message())); - } - return Result; - } - - void RemoveFileWithRetry(const std::filesystem::path& Path) - { - std::error_code Ec; - RemoveFile(Path, Ec); - for (size_t Retries = 0; Ec && Retries < 6; Retries++) - { - Sleep(100 + int(Retries * 50)); - if (!IsFileWithRetry(Path)) - { - return; - } - Ec.clear(); - RemoveFile(Path, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Removing file '{}' failed with: {}", Path, Ec.message())); - } - } - - void RemoveDirWithRetry(const std::filesystem::path& Path) - { - std::error_code Ec; - RemoveDir(Path, Ec); - for (size_t Retries = 0; Ec && Retries < 3; Retries++) - { - Sleep(100 + int(Retries * 50)); - if (!IsDir(Path)) - { - return; - } - Ec.clear(); - RemoveDir(Path, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Removing directory '{}' failed with: {}", Path, Ec.message())); - } - } - - bool CleanDirectory(const std::filesystem::path& Path, std::span<const std::string> ExcludeDirectories) - { - ZEN_TRACE_CPU("CleanDirectory"); - Stopwatch Timer; - - ProgressBar Progress(ProgressMode, "Clean Folder"); - - std::atomic<bool> CleanWipe = true; - std::atomic<uint64_t> DiscoveredItemCount = 0; - std::atomic<uint64_t> DeletedItemCount = 0; - std::atomic<uint64_t> DeletedByteCount = 0; - ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog); - - struct AsyncVisitor : public GetDirectoryContentVisitor - { - AsyncVisitor(const std::filesystem::path& InPath, - std::atomic<bool>& InCleanWipe, - std::atomic<uint64_t>& InDiscoveredItemCount, - std::atomic<uint64_t>& InDeletedItemCount, - std::atomic<uint64_t>& InDeletedByteCount, - std::span<const std::string> InExcludeDirectories) - : Path(InPath) - , CleanWipe(InCleanWipe) - , DiscoveredItemCount(InDiscoveredItemCount) - , DeletedItemCount(InDeletedItemCount) - , DeletedByteCount(InDeletedByteCount) - , ExcludeDirectories(InExcludeDirectories) - { - } - virtual void AsyncVisitDirectory(const std::filesystem::path& RelativeRoot, DirectoryContent&& Content) override - { - ZEN_TRACE_CPU("CleanDirectory_AsyncVisitDirectory"); - if (!AbortFlag) - { - if (!Content.FileNames.empty()) - { - DiscoveredItemCount += Content.FileNames.size(); - - const std::string RelativeRootString = RelativeRoot.generic_string(); - bool RemoveContent = true; - for (const std::string_view ExcludeDirectory : ExcludeDirectories) - { - if (RelativeRootString.starts_with(ExcludeDirectory)) - { - if (RelativeRootString.length() > ExcludeDirectory.length()) - { - const char MaybePathDelimiter = RelativeRootString[ExcludeDirectory.length()]; - if (MaybePathDelimiter == '/' || MaybePathDelimiter == '\\' || - MaybePathDelimiter == std::filesystem::path::preferred_separator) - { - RemoveContent = false; - break; - } - } - else - { - RemoveContent = false; - break; - } - } - } - if (RemoveContent) - { - ZEN_TRACE_CPU("DeleteFiles"); - for (size_t FileIndex = 0; FileIndex < Content.FileNames.size(); FileIndex++) - { - const std::filesystem::path& FileName = Content.FileNames[FileIndex]; - const std::filesystem::path FilePath = (Path / RelativeRoot / FileName).make_preferred(); - try - { - SetFileReadOnlyWithRetry(FilePath, false); - RemoveFileWithRetry(FilePath); - DeletedItemCount++; - DeletedByteCount += Content.FileSizes[FileIndex]; - } - catch (const std::exception& Ex) - { - ZEN_CONSOLE_WARN("Failed removing file {}. Reason: {}", FilePath, Ex.what()); - CleanWipe = false; - } - } - } - } - } - } - const std::filesystem::path& Path; - std::atomic<bool>& CleanWipe; - std::atomic<uint64_t>& DiscoveredItemCount; - std::atomic<uint64_t>& DeletedItemCount; - std::atomic<uint64_t>& DeletedByteCount; - std::span<const std::string> ExcludeDirectories; - } Visitor(Path, CleanWipe, DiscoveredItemCount, DeletedItemCount, DeletedByteCount, ExcludeDirectories); - - GetDirectoryContent( - Path, - DirectoryContentFlags::IncludeFiles | DirectoryContentFlags::Recursive | DirectoryContentFlags::IncludeFileSizes, - Visitor, - GetIOWorkerPool(), - Work.PendingWork()); - - DirectoryContent LocalDirectoryContent; - GetDirectoryContent(Path, DirectoryContentFlags::IncludeDirs | DirectoryContentFlags::IncludeFiles, LocalDirectoryContent); - DiscoveredItemCount += LocalDirectoryContent.Directories.size(); - std::vector<std::filesystem::path> DirectoriesToDelete; - DirectoriesToDelete.reserve(LocalDirectoryContent.Directories.size()); - for (std::filesystem::path& LocalDirPath : LocalDirectoryContent.Directories) - { - bool Leave = false; - for (const std::string& ExcludeDirectory : ExcludeDirectories) - { - if (LocalDirPath == (Path / ExcludeDirectory)) - { - Leave = true; - break; - } - } - if (!Leave) - { - DirectoriesToDelete.emplace_back(std::move(LocalDirPath)); - DiscoveredItemCount++; - } - } - - uint64_t LastUpdateTimeMs = Timer.GetElapsedTimeMs(); - - Work.Wait(GetUpdateDelayMS(ProgressMode), [&](bool IsAborted, bool IsPaused, ptrdiff_t PendingWork) { - ZEN_UNUSED(PendingWork); - LastUpdateTimeMs = Timer.GetElapsedTimeMs(); - - uint64_t Deleted = DeletedItemCount.load(); - uint64_t DeletedBytes = DeletedByteCount.load(); - uint64_t Discovered = DiscoveredItemCount.load(); - Progress.UpdateState({.Task = "Cleaning folder ", - .Details = fmt::format("Found {}, Deleted {} ({})", Discovered, Deleted, NiceBytes(DeletedBytes)), - .TotalCount = Discovered, - .RemainingCount = Discovered - Deleted, - .Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)}, - false); - }); - - for (const std::filesystem::path& DirectoryToDelete : DirectoriesToDelete) - { - ZEN_TRACE_CPU("DeleteDirs"); - try - { - std::error_code Ec; - zen::CleanDirectory(DirectoryToDelete, true, Ec); - if (Ec) - { - Sleep(200); - zen::CleanDirectory(DirectoryToDelete, true); - Ec.clear(); - } - - RemoveDirWithRetry(DirectoryToDelete); - - DeletedItemCount++; - } - catch (const std::exception& Ex) - { - ZEN_CONSOLE_WARN("Failed removing directory {}. Reason: {}", DirectoryToDelete, Ex.what()); - CleanWipe = false; - } - - uint64_t NowMs = Timer.GetElapsedTimeMs(); - if ((NowMs - LastUpdateTimeMs) >= GetUpdateDelayMS(ProgressMode)) - { - LastUpdateTimeMs = NowMs; - - uint64_t Deleted = DeletedItemCount.load(); - uint64_t DeletedBytes = DeletedByteCount.load(); - uint64_t Discovered = DiscoveredItemCount.load(); - Progress.UpdateState({.Task = "Cleaning folder ", - .Details = fmt::format("Found {}, Deleted {} ({})", Discovered, Deleted, NiceBytes(DeletedBytes)), - .TotalCount = Discovered, - .RemainingCount = Discovered - Deleted, - .Status = ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)}, - false); - } - } - - Progress.Finish(); - if (AbortFlag) - { - return false; - } - - uint64_t ElapsedTimeMs = Timer.GetElapsedTimeMs(); - if (ElapsedTimeMs >= 200 && !IsQuiet) - { - ZEN_CONSOLE("Wiped folder '{}' {} ({}) in {}", - Path, - DiscoveredItemCount.load(), - NiceBytes(DeletedByteCount.load()), - NiceTimeSpanMs(ElapsedTimeMs)); - } - return CleanWipe; - } - class FilteredRate { public: @@ -2934,7 +2650,8 @@ namespace { if (CleanDirectory(ZenTempFolder, {})) { - RemoveDirWithRetry(ZenTempFolder); + std::error_code DummyEc; + RemoveDir(ZenTempFolder, DummyEc); } } @@ -5132,7 +4849,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) [SourceSize, FilePath = std::filesystem::path(FilePath)](std::atomic<bool>&) { if (!AbortFlag) { - bool IsReadOnly = SetFileReadOnlyWithRetry(FilePath, false); + bool WasReadOnly = SetFileReadOnly(FilePath, false); { BasicFile Source(FilePath, BasicFile::Mode::kWrite); uint64_t RangeSize = Min(SourceSize / 3, 512u * 1024u); @@ -5146,7 +4863,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) Source.Write(TempBuffer2, SourceSize - RangeSize); Source.Write(TempBuffer3, SourceSize - 0); } - if (IsReadOnly) + if (WasReadOnly) { SetFileReadOnly(FilePath, true); } @@ -5157,8 +4874,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) break; case 1: { - (void)SetFileReadOnlyWithRetry(FilePath, false); - RemoveFileWithRetry(FilePath); + (void)SetFileReadOnly(FilePath, false); + (void)RemoveFile(FilePath); } break; default: diff --git a/src/zenremotestore/builds/buildstorageoperations.cpp b/src/zenremotestore/builds/buildstorageoperations.cpp index 60058c4a4..ca927e643 100644 --- a/src/zenremotestore/builds/buildstorageoperations.cpp +++ b/src/zenremotestore/builds/buildstorageoperations.cpp @@ -17,7 +17,7 @@ #include <zencore/string.h> #include <zencore/timer.h> #include <zencore/trace.h> -#include <zenutil/bufferedopenfile.h> +#include "../filesystemutils.h" #include <numeric> @@ -77,181 +77,10 @@ namespace { return CacheFolderPath / RawHash.ToHexString(); } - uint32_t SetNativeFileAttributes(const std::filesystem::path FilePath, SourcePlatform SourcePlatform, uint32_t Attributes) - { -#if ZEN_PLATFORM_WINDOWS - if (SourcePlatform == SourcePlatform::Windows) - { - SetFileAttributesToPath(FilePath, Attributes); - return Attributes; - } - else - { - uint32_t CurrentAttributes = GetFileAttributesFromPath(FilePath); - uint32_t NewAttributes = zen::MakeFileAttributeReadOnly(CurrentAttributes, zen::IsFileModeReadOnly(Attributes)); - if (CurrentAttributes != NewAttributes) - { - SetFileAttributesToPath(FilePath, NewAttributes); - } - return NewAttributes; - } -#endif // ZEN_PLATFORM_WINDOWS -#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - if (SourcePlatform != SourcePlatform::Windows) - { - zen::SetFileMode(FilePath, Attributes); - return Attributes; - } - else - { - uint32_t CurrentMode = zen::GetFileMode(FilePath); - uint32_t NewMode = zen::MakeFileModeReadOnly(CurrentMode, zen::IsFileAttributeReadOnly(Attributes)); - if (CurrentMode != NewMode) - { - zen::SetFileMode(FilePath, NewMode); - } - return NewMode; - } -#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - }; - - uint32_t GetNativeFileAttributes(const std::filesystem::path FilePath) - { -#if ZEN_PLATFORM_WINDOWS - return GetFileAttributesFromPath(FilePath); -#endif // ZEN_PLATFORM_WINDOWS -#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - return GetFileMode(FilePath); -#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - } - - bool IsFileWithRetry(const std::filesystem::path& Path) - { - std::error_code Ec; - bool Result = IsFile(Path, Ec); - for (size_t Retries = 0; Ec && Retries < 3; Retries++) - { - Sleep(100 + int(Retries * 50)); - Ec.clear(); - Result = IsFile(Path, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Check path '{}' is file failed with: {} ({})", Path, Ec.message(), Ec.value())); - } - return Result; - } - - bool SetFileReadOnlyWithRetry(const std::filesystem::path& Path, bool ReadOnly) - { - std::error_code Ec; - bool Result = SetFileReadOnly(Path, ReadOnly, Ec); - for (size_t Retries = 0; Ec && Retries < 3; Retries++) - { - Sleep(100 + int(Retries * 50)); - if (!IsFileWithRetry(Path)) - { - return false; - } - Ec.clear(); - Result = SetFileReadOnly(Path, ReadOnly, Ec); - } - if (Ec) - { - throw std::system_error( - std::error_code(Ec.value(), std::system_category()), - fmt::format("Failed {} read only flag for '{}' failed with: {}", ReadOnly ? "setting" : "clearing", Path, Ec.message())); - } - return Result; - } - - void RenameFileWithRetry(BuildOpLogOutput& LogOutput, const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath) - { - std::error_code Ec; - RenameFile(SourcePath, TargetPath, Ec); - for (size_t Retries = 0; Ec && Retries < 10; Retries++) - { - ZEN_ASSERT_SLOW(IsFile(SourcePath)); - if (Retries > 5) - { - LOG_OUTPUT_WARN(LogOutput, "Unable to overwrite file {} ({}: {}), retrying...", TargetPath, Ec.value(), Ec.message()); - } - Sleep(50 + int(Retries * 150)); - Ec.clear(); - RenameFile(SourcePath, TargetPath, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Rename from '{}' to '{}' failed with: {}", SourcePath, TargetPath, Ec.message())); - } - } - - void TryRemoveFile(BuildOpLogOutput& LogOutput, const std::filesystem::path& Path) - { - std::error_code Ec; - RemoveFile(Path, Ec); - if (Ec) - { - if (IsFile(Path, Ec)) - { - Ec.clear(); - RemoveFile(Path, Ec); - if (Ec) - { - LOG_OUTPUT_DEBUG(LogOutput, "Failed removing file '{}', reason: {}", Path, Ec.message()); - } - } - } - } - - void RemoveFileWithRetry(const std::filesystem::path& Path) - { - std::error_code Ec; - RemoveFile(Path, Ec); - for (size_t Retries = 0; Ec && Retries < 6; Retries++) - { - Sleep(100 + int(Retries * 50)); - if (!IsFileWithRetry(Path)) - { - return; - } - Ec.clear(); - RemoveFile(Path, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Removing file '{}' failed with: {}", Path, Ec.message())); - } - } - - void RemoveDirWithRetry(const std::filesystem::path& Path) - { - std::error_code Ec; - RemoveDir(Path, Ec); - for (size_t Retries = 0; Ec && Retries < 3; Retries++) - { - Sleep(100 + int(Retries * 50)); - if (!IsDir(Path)) - { - return; - } - Ec.clear(); - RemoveDir(Path, Ec); - } - if (Ec) - { - throw std::system_error(std::error_code(Ec.value(), std::system_category()), - fmt::format("Removing directory '{}' failed with: {}", Path, Ec.message())); - } - } - - bool CleanDirectory(WorkerThreadPool& IOWorkerPool, + bool CleanDirectory(BuildOpLogOutput& LogOutput, + WorkerThreadPool& IOWorkerPool, std::atomic<bool>& AbortFlag, std::atomic<bool>& PauseFlag, - BuildOpLogOutput& LogOutput, bool IsQuiet, const std::filesystem::path& Path, std::span<const std::string> ExcludeDirectories) @@ -262,241 +91,55 @@ namespace { std::unique_ptr<BuildOpLogOutput::ProgressBar> ProgressBarPtr(LogOutput.CreateProgressBar("Clean Folder")); BuildOpLogOutput::ProgressBar& Progress(*ProgressBarPtr); - std::atomic<bool> CleanWipe = true; - std::atomic<uint64_t> DiscoveredItemCount = 0; - std::atomic<uint64_t> DeletedItemCount = 0; - std::atomic<uint64_t> DeletedByteCount = 0; - ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog); - - struct AsyncVisitor : public GetDirectoryContentVisitor - { - AsyncVisitor(const std::filesystem::path& InPath, - std::atomic<bool>& InAbortFlag, - BuildOpLogOutput& InLogOutput, - std::atomic<bool>& InCleanWipe, - std::atomic<uint64_t>& InDiscoveredItemCount, - std::atomic<uint64_t>& InDeletedItemCount, - std::atomic<uint64_t>& InDeletedByteCount, - std::span<const std::string> InExcludeDirectories) - : Path(InPath) - , AbortFlag(InAbortFlag) - , LogOutput(InLogOutput) - , CleanWipe(InCleanWipe) - , DiscoveredItemCount(InDiscoveredItemCount) - , DeletedItemCount(InDeletedItemCount) - , DeletedByteCount(InDeletedByteCount) - , ExcludeDirectories(InExcludeDirectories) - { - } - virtual void AsyncVisitDirectory(const std::filesystem::path& RelativeRoot, DirectoryContent&& Content) override - { - ZEN_TRACE_CPU("CleanDirectory_AsyncVisitDirectory"); - if (!AbortFlag) - { - if (!Content.FileNames.empty()) - { - DiscoveredItemCount += Content.FileNames.size(); - - const std::string RelativeRootString = RelativeRoot.generic_string(); - bool RemoveContent = true; - for (const std::string_view ExcludeDirectory : ExcludeDirectories) - { - if (RelativeRootString.starts_with(ExcludeDirectory)) - { - if (RelativeRootString.length() > ExcludeDirectory.length()) - { - const char MaybePathDelimiter = RelativeRootString[ExcludeDirectory.length()]; - if (MaybePathDelimiter == '/' || MaybePathDelimiter == '\\' || - MaybePathDelimiter == std::filesystem::path::preferred_separator) - { - RemoveContent = false; - break; - } - } - else - { - RemoveContent = false; - break; - } - } - } - if (RemoveContent) - { - ZEN_TRACE_CPU("DeleteFiles"); - for (size_t FileIndex = 0; FileIndex < Content.FileNames.size(); FileIndex++) - { - const std::filesystem::path& FileName = Content.FileNames[FileIndex]; - const std::filesystem::path FilePath = (Path / RelativeRoot / FileName).make_preferred(); - try - { - SetFileReadOnlyWithRetry(FilePath, false); - RemoveFileWithRetry(FilePath); - DeletedItemCount++; - DeletedByteCount += Content.FileSizes[FileIndex]; - } - catch (const std::exception& Ex) - { - LOG_OUTPUT_WARN(LogOutput, "Failed removing file {}. Reason: {}", FilePath, Ex.what()); - CleanWipe = false; - } - } - } - } - } - } - const std::filesystem::path& Path; - std::atomic<bool>& AbortFlag; - BuildOpLogOutput& LogOutput; - std::atomic<bool>& CleanWipe; - std::atomic<uint64_t>& DiscoveredItemCount; - std::atomic<uint64_t>& DeletedItemCount; - std::atomic<uint64_t>& DeletedByteCount; - std::span<const std::string> ExcludeDirectories; - } Visitor(Path, AbortFlag, LogOutput, CleanWipe, DiscoveredItemCount, DeletedItemCount, DeletedByteCount, ExcludeDirectories); - - GetDirectoryContent( - Path, - DirectoryContentFlags::IncludeFiles | DirectoryContentFlags::Recursive | DirectoryContentFlags::IncludeFileSizes, - Visitor, + CleanDirectoryResult Result = CleanDirectory( IOWorkerPool, - Work.PendingWork()); + AbortFlag, + PauseFlag, + Path, + ExcludeDirectories, + [&](const std::string_view Details, uint64_t TotalCount, uint64_t RemainingCount, bool IsPaused, bool IsAborted) { + Progress.UpdateState({.Task = "Cleaning folder ", + .Details = std::string(Details), + .TotalCount = TotalCount, + .RemainingCount = RemainingCount, + .Status = BuildOpLogOutput::ProgressBar::State::CalculateStatus(IsAborted, IsPaused)}, + false); + }, + LogOutput.GetProgressUpdateDelayMS()); + + Progress.Finish(); - DirectoryContent LocalDirectoryContent; - GetDirectoryContent(Path, DirectoryContentFlags::IncludeDirs | DirectoryContentFlags::IncludeFiles, LocalDirectoryContent); - DiscoveredItemCount += LocalDirectoryContent.Directories.size(); - std::vector<std::filesystem::path> DirectoriesToDelete; - DirectoriesToDelete.reserve(LocalDirectoryContent.Directories.size()); - for (std::filesystem::path& LocalDirPath : LocalDirectoryContent.Directories) + if (AbortFlag) { - bool Leave = false; - for (const std::string_view ExcludeDirectory : ExcludeDirectories) - { - if (LocalDirPath == (Path / ExcludeDirectory)) - { - Leave = true; - break; - } - } - if (!Leave) - { - DirectoriesToDelete.emplace_back(std::move(LocalDirPath)); - DiscoveredItemCount++; - } + return false; } - uint64_t LastUpdateTimeMs = Timer.GetElapsedTimeMs(); - - Work.Wait(LogOutput.GetProgressUpdateDelayMS(), [&](bool IsAborted, bool IsPaused, ptrdiff_t PendingWork) { - ZEN_UNUSED(PendingWork); - LastUpdateTimeMs = Timer.GetElapsedTimeMs(); - - uint64_t Deleted = DeletedItemCount.load(); - uint64_t DeletedBytes = DeletedByteCount.load(); - uint64_t Discovered = DiscoveredItemCount.load(); - Progress.UpdateState({.Task = "Cleaning folder ", - .Details = fmt::format("Found {}, Deleted {} ({})", Discovered, Deleted, NiceBytes(DeletedBytes)), - .TotalCount = Discovered, - .RemainingCount = Discovered - Deleted, - .Status = BuildOpLogOutput::ProgressBar::State::CalculateStatus(IsAborted, IsPaused)}, - false); - }); + uint64_t ElapsedTimeMs = Timer.GetElapsedTimeMs(); - for (const std::filesystem::path& DirectoryToDelete : DirectoriesToDelete) + if (!Result.FailedRemovePaths.empty()) { - ZEN_TRACE_CPU("DeleteDirs"); - try - { - std::error_code Ec; - zen::CleanDirectory(DirectoryToDelete, true, Ec); - if (Ec) - { - Sleep(200); - zen::CleanDirectory(DirectoryToDelete, true); - Ec.clear(); - } - - RemoveDirWithRetry(DirectoryToDelete); - - DeletedItemCount++; - } - catch (const std::exception& Ex) + ExtendableStringBuilder<512> SB; + for (size_t FailedPathIndex = 0; FailedPathIndex < Result.FailedRemovePaths.size(); FailedPathIndex++) { - LOG_OUTPUT_WARN(LogOutput, "Failed removing directory {}. Reason: {}", DirectoryToDelete, Ex.what()); - CleanWipe = false; + SB << fmt::format("\n '{}': ({}) {}", + Result.FailedRemovePaths[FailedPathIndex].first, + Result.FailedRemovePaths[FailedPathIndex].second.value(), + Result.FailedRemovePaths[FailedPathIndex].second.message()); } - - uint64_t NowMs = Timer.GetElapsedTimeMs(); - if ((NowMs - LastUpdateTimeMs) >= LogOutput.GetProgressUpdateDelayMS()) - { - LastUpdateTimeMs = NowMs; - - uint64_t Deleted = DeletedItemCount.load(); - uint64_t DeletedBytes = DeletedByteCount.load(); - uint64_t Discovered = DiscoveredItemCount.load(); - Progress.UpdateState({.Task = "Cleaning folder ", - .Details = fmt::format("Found {}, Deleted {} ({})", Discovered, Deleted, NiceBytes(DeletedBytes)), - .TotalCount = Discovered, - .RemainingCount = Discovered - Deleted, - .Status = BuildOpLogOutput::ProgressBar::State::CalculateStatus(AbortFlag, PauseFlag)}, - false); - } - } - - Progress.Finish(); - if (AbortFlag) - { - return false; + LOG_OUTPUT_WARN(LogOutput, "Clean failed to remove files from '{}': {}", Path, SB.ToView()); } - uint64_t ElapsedTimeMs = Timer.GetElapsedTimeMs(); if (ElapsedTimeMs >= 200 && !IsQuiet) { LOG_OUTPUT(LogOutput, "Wiped folder '{}' {} ({}) in {}", Path, - DiscoveredItemCount.load(), - NiceBytes(DeletedByteCount.load()), + Result.FoundCount, + NiceBytes(Result.DeletedByteCount), NiceTimeSpanMs(ElapsedTimeMs)); } - return CleanWipe; - } - void CopyFile(bool AllowFileClone, - bool UseSparseFiles, - const std::filesystem::path& SourceFilePath, - const std::filesystem::path& TargetFilePath, - uint64_t RawSize, - std::atomic<uint64_t>& WriteCount, - std::atomic<uint64_t>& WriteByteCount, - std::atomic<uint64_t>& CloneCount, - std::atomic<uint64_t>& CloneByteCount) - { - ZEN_TRACE_CPU("CopyFile"); - if (AllowFileClone && TryCloneFile(SourceFilePath, TargetFilePath)) - { - WriteCount += 1; - WriteByteCount += RawSize; - CloneCount += 1; - CloneByteCount += RawSize; - } - else - { - BasicFile TargetFile(TargetFilePath, BasicFile::Mode::kTruncate); - if (UseSparseFiles) - { - PrepareFileForScatteredWrite(TargetFile.Handle(), RawSize); - } - uint64_t Offset = 0; - if (!ScanFile(SourceFilePath, 512u * 1024u, [&](const void* Data, size_t Size) { - TargetFile.Write(Data, Size, Offset); - Offset += Size; - WriteCount++; - WriteByteCount += Size; - })) - { - throw std::runtime_error(fmt::format("Failed to copy scavenged file '{}' to '{}'", SourceFilePath, TargetFilePath)); - } - } + return Result.FailedRemovePaths.empty(); } const std::vector<uint32_t> NonCompressableExtensions({HashStringDjb2(".mp4"sv), @@ -567,7 +210,7 @@ namespace { if (Ec) { throw std::runtime_error( - fmt::format("Failed opening temporary file '{}': {} ({})", Workload->TempFile.GetPath(), Ec.message(), Ec.value())); + fmt::format("Failed opening temporary file '{}', reason: ({}) {}", Workload->TempFile.GetPath(), Ec.message(), Ec.value())); } std::vector<std::function<void()>> WorkItems = Storage.GetLargeBuildBlob( BuildId, @@ -700,71 +343,6 @@ namespace { } // namespace -class ReadFileCache -{ -public: - // A buffered file reader that provides CompositeBuffer where the buffers are owned and the memory never overwritten - ReadFileCache(DiskStatistics& DiskStats, - const std::filesystem::path& Path, - const ChunkedFolderContent& LocalContent, - const ChunkedContentLookup& LocalLookup, - size_t MaxOpenFileCount) - : m_Path(Path) - , m_LocalContent(LocalContent) - , m_LocalLookup(LocalLookup) - , m_DiskStats(DiskStats) - { - m_OpenFiles.reserve(MaxOpenFileCount); - } - ~ReadFileCache() { m_OpenFiles.clear(); } - - CompositeBuffer GetRange(uint32_t SequenceIndex, uint64_t Offset, uint64_t Size) - { - ZEN_TRACE_CPU("ReadFileCache::GetRange"); - - auto CacheIt = - std::find_if(m_OpenFiles.begin(), m_OpenFiles.end(), [SequenceIndex](const auto& Lhs) { return Lhs.first == SequenceIndex; }); - if (CacheIt != m_OpenFiles.end()) - { - if (CacheIt != m_OpenFiles.begin()) - { - auto CachedFile(std::move(CacheIt->second)); - m_OpenFiles.erase(CacheIt); - m_OpenFiles.insert(m_OpenFiles.begin(), std::make_pair(SequenceIndex, std::move(CachedFile))); - } - CompositeBuffer Result = m_OpenFiles.front().second->GetRange(Offset, Size); - return Result; - } - const uint32_t LocalPathIndex = m_LocalLookup.SequenceIndexFirstPathIndex[SequenceIndex]; - const std::filesystem::path LocalFilePath = (m_Path / m_LocalContent.Paths[LocalPathIndex]).make_preferred(); - if (Size == m_LocalContent.RawSizes[LocalPathIndex]) - { - IoBuffer Result = IoBufferBuilder::MakeFromFile(LocalFilePath); - return CompositeBuffer(SharedBuffer(Result)); - } - if (m_OpenFiles.size() == m_OpenFiles.capacity()) - { - m_OpenFiles.pop_back(); - } - m_OpenFiles.insert(m_OpenFiles.begin(), - std::make_pair(SequenceIndex, - std::make_unique<BufferedOpenFile>(LocalFilePath, - m_DiskStats.OpenReadCount, - m_DiskStats.CurrentOpenFileCount, - m_DiskStats.ReadCount, - m_DiskStats.ReadByteCount))); - CompositeBuffer Result = m_OpenFiles.front().second->GetRange(Offset, Size); - return Result; - } - -private: - const std::filesystem::path m_Path; - const ChunkedFolderContent& m_LocalContent; - const ChunkedContentLookup& m_LocalLookup; - std::vector<std::pair<uint32_t, std::unique_ptr<BufferedOpenFile>>> m_OpenFiles; - DiskStatistics& m_DiskStats; -}; - bool ReadStateObject(BuildOpLogOutput& LogOutput, CbObjectView StateView, @@ -1210,8 +788,10 @@ StreamDecompress(std::atomic<bool>& AbortFlag, DecompressedTemp.CreateTemporary(TempChunkSequenceFileName.parent_path(), Ec); if (Ec) { - throw std::runtime_error( - fmt::format("Failed creating temporary file for decompressing large blob {}. Reason: {}", SequenceRawHash, Ec.message())); + throw std::runtime_error(fmt::format("Failed creating temporary file for decompressing large blob {}, reason: ({}) {}", + SequenceRawHash, + Ec.value(), + Ec.message())); } IoHash RawHash; uint64_t RawSize; @@ -1267,8 +847,10 @@ StreamDecompress(std::atomic<bool>& AbortFlag, DecompressedTemp.MoveTemporaryIntoPlace(TempChunkSequenceFileName, Ec); if (Ec) { - throw std::runtime_error( - fmt::format("Failed moving temporary file for decompressing large blob {}. Reason: {}", SequenceRawHash, Ec.message())); + throw std::runtime_error(fmt::format("Failed moving temporary file for decompressing large blob {}, reason: ({}) {}", + SequenceRawHash, + Ec.value(), + Ec.message())); } // WriteChunkStats.ChunkCountWritten++; } @@ -2365,7 +1947,15 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) FilteredWrittenBytesPerSecond.Stop(); } - TryRemoveFile(m_LogOutput, CompressedChunkPath); + std::error_code Ec = TryRemoveFile(CompressedChunkPath); + if (Ec) + { + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + CompressedChunkPath, + Ec.value(), + Ec.message()); + } std::vector<uint32_t> CompletedSequences = CompleteChunkTargets(ChunkTargetPtrs, SequenceIndexChunksLeftToWriteCounters); @@ -2650,7 +2240,15 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) throw std::runtime_error(fmt::format("Block {} is malformed", BlockDescription.BlockHash)); } - TryRemoveFile(m_LogOutput, BlockChunkPath); + std::error_code Ec = TryRemoveFile(BlockChunkPath); + if (Ec) + { + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + BlockChunkPath, + Ec.value(), + Ec.message()); + } WritePartsComplete++; @@ -2829,9 +2427,14 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) fmt::format("Partial block {} is malformed", BlockDescription.BlockHash)); } - if (!BlockChunkPath.empty()) + std::error_code Ec = TryRemoveFile(BlockChunkPath); + if (Ec) { - TryRemoveFile(m_LogOutput, BlockChunkPath); + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + BlockChunkPath, + Ec.value(), + Ec.message()); } WritePartsComplete++; @@ -3009,7 +2612,15 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) if (!BlockChunkPath.empty()) { - TryRemoveFile(m_LogOutput, BlockChunkPath); + std::error_code Ec = TryRemoveFile(BlockChunkPath); + if (Ec) + { + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + BlockChunkPath, + Ec.value(), + Ec.message()); + } } WritePartsComplete++; @@ -3267,7 +2878,28 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) const std::filesystem::path CacheFilePath = GetFinalChunkedSequenceFileName(m_CacheFolderPath, RawHash); ZEN_ASSERT_SLOW(!IsFileWithRetry(CacheFilePath)); const std::filesystem::path LocalFilePath = (m_Path / LocalPath).make_preferred(); - RenameFileWithRetry(m_LogOutput, LocalFilePath, CacheFilePath); + + std::error_code Ec = RenameFileWithRetry(LocalFilePath, CacheFilePath); + if (Ec) + { + LOG_OUTPUT_WARN(m_LogOutput, + "Failed to move file from '{}' to '{}', reason: ({}) {}, retrying...", + LocalFilePath, + CacheFilePath, + Ec.value(), + Ec.message()); + Ec = RenameFileWithRetry(LocalFilePath, CacheFilePath); + if (Ec) + { + throw std::system_error(std::error_code(Ec.value(), std::system_category()), + fmt::format("Failed to file from '{}' to '{}', reason: ({}) {}", + LocalFilePath, + CacheFilePath, + Ec.value(), + Ec.message())); + } + } + CachedCount++; CachedByteCount += m_LocalContent.RawSizes[LocalPathIndex]; } @@ -3317,7 +2949,7 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) Stopwatch Timer; // Clean target folder - if (!CleanDirectory(m_IOWorkerPool, m_AbortFlag, m_PauseFlag, m_LogOutput, m_Options.IsQuiet, m_Path, m_Options.ExcludeFolders)) + if (!CleanDirectory(m_LogOutput, m_IOWorkerPool, m_AbortFlag, m_PauseFlag, m_Options.IsQuiet, m_Path, m_Options.ExcludeFolders)) { LOG_OUTPUT_WARN(m_LogOutput, "Some files in {} could not be removed", m_Path); } @@ -3500,15 +3132,15 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) std::atomic<uint64_t> WriteByteCount; std::atomic<uint64_t> CloneCount; std::atomic<uint64_t> CloneByteCount; - CopyFile(m_Options.AllowFileClone, - m_Options.UseSparseFiles, - SourceFilePath, - FirstTargetFilePath, - RawSize, - WriteCount, - WriteByteCount, - CloneCount, - CloneByteCount); + FastCopyFile(m_Options.AllowFileClone, + m_Options.UseSparseFiles, + SourceFilePath, + FirstTargetFilePath, + RawSize, + WriteCount, + WriteByteCount, + CloneCount, + CloneByteCount); m_RebuildFolderStateStats.FinalizeTreeFilesCopiedCount++; } else @@ -3517,7 +3149,26 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) const std::filesystem::path CacheFilePath = GetFinalChunkedSequenceFileName(m_CacheFolderPath, RawHash); ZEN_ASSERT_SLOW(IsFileWithRetry(CacheFilePath)); - RenameFileWithRetry(m_LogOutput, CacheFilePath, FirstTargetFilePath); + std::error_code Ec = RenameFileWithRetry(CacheFilePath, FirstTargetFilePath); + if (Ec) + { + LOG_OUTPUT_WARN(m_LogOutput, + "Failed to move file from '{}' to '{}', reason: ({}) {}, retrying...", + CacheFilePath, + FirstTargetFilePath, + Ec.value(), + Ec.message()); + Ec = RenameFileWithRetry(CacheFilePath, FirstTargetFilePath); + if (Ec) + { + throw std::system_error(std::error_code(Ec.value(), std::system_category()), + fmt::format("Failed to move file from '{}' to '{}', reason: ({}) {}", + CacheFilePath, + FirstTargetFilePath, + Ec.value(), + Ec.message())); + } + } m_RebuildFolderStateStats.FinalizeTreeFilesMovedCount++; } @@ -3568,15 +3219,15 @@ BuildsOperationUpdateFolder::Execute(FolderContent& OutLocalFolderState) std::atomic<uint64_t> WriteByteCount; std::atomic<uint64_t> CloneCount; std::atomic<uint64_t> CloneByteCount; - CopyFile(m_Options.AllowFileClone, - m_Options.UseSparseFiles, - FirstTargetFilePath, - TargetFilePath, - RawSize, - WriteCount, - WriteByteCount, - CloneCount, - CloneByteCount); + FastCopyFile(m_Options.AllowFileClone, + m_Options.UseSparseFiles, + FirstTargetFilePath, + TargetFilePath, + RawSize, + WriteCount, + WriteByteCount, + CloneCount, + CloneByteCount); m_RebuildFolderStateStats.FinalizeTreeFilesCopiedCount++; } @@ -3675,7 +3326,15 @@ BuildsOperationUpdateFolder::ScanCacheFolder(tsl::robin_map<IoHash, uint32_t, Io } } } - TryRemoveFile(m_LogOutput, CacheDirContent.Files[Index]); + std::error_code Ec = TryRemoveFile(CacheDirContent.Files[Index]); + if (Ec) + { + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + CacheDirContent.Files[Index], + Ec.value(), + Ec.message()); + } } m_CacheMappingStats.CacheScanElapsedWallTimeUs += CacheTimer.GetElapsedTimeUs(); } @@ -3727,7 +3386,15 @@ BuildsOperationUpdateFolder::ScanTempBlocksFolder(tsl::robin_map<IoHash, uint32_ } } } - TryRemoveFile(m_LogOutput, BlockDirContent.Files[Index]); + std::error_code Ec = TryRemoveFile(BlockDirContent.Files[Index]); + if (Ec) + { + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + BlockDirContent.Files[Index], + Ec.value(), + Ec.message()); + } } m_CacheMappingStats.CacheScanElapsedWallTimeUs += CacheTimer.GetElapsedTimeUs(); @@ -4114,15 +3781,15 @@ BuildsOperationUpdateFolder::WriteScavengedSequenceToCache(const std::filesystem const std::filesystem::path TempFilePath = GetTempChunkedSequenceFileName(m_CacheFolderPath, RemoteSequenceRawHash); const uint64_t RawSize = ScavengedContent.RawSizes[ScavengeOp.ScavengedPathIndex]; - CopyFile(m_Options.AllowFileClone, - m_Options.UseSparseFiles, - ScavengedFilePath, - TempFilePath, - RawSize, - m_DiskStats.WriteCount, - m_DiskStats.WriteByteCount, - m_DiskStats.CloneCount, - m_DiskStats.CloneByteCount); + FastCopyFile(m_Options.AllowFileClone, + m_Options.UseSparseFiles, + ScavengedFilePath, + TempFilePath, + RawSize, + m_DiskStats.WriteCount, + m_DiskStats.WriteByteCount, + m_DiskStats.CloneCount, + m_DiskStats.CloneByteCount); const std::filesystem::path CacheFilePath = GetFinalChunkedSequenceFileName(m_CacheFolderPath, RemoteSequenceRawHash); RenameFile(TempFilePath, CacheFilePath); @@ -4817,7 +4484,15 @@ BuildsOperationUpdateFolder::AsyncWriteDownloadedChunk(const std::filesystem::pa if (!CompressedChunkPath.empty()) { - TryRemoveFile(m_LogOutput, CompressedChunkPath); + std::error_code Ec = TryRemoveFile(CompressedChunkPath); + if (Ec) + { + LOG_OUTPUT_DEBUG(m_LogOutput, + "Failed removing file '{}', reason: ({}) {}", + CompressedChunkPath, + Ec.value(), + Ec.message()); + } } std::vector<uint32_t> CompletedSequences = @@ -6567,7 +6242,14 @@ BuildsOperationUploadFolder::GenerateBlock(const ChunkedFolderContent& Content, ChunkBlockDescription& OutBlockDescription) { ZEN_TRACE_CPU("GenerateBlock"); - ReadFileCache OpenFileCache(m_DiskStats, m_Path, Content, Lookup, 4); + ReadFileCache OpenFileCache(m_DiskStats.OpenReadCount, + m_DiskStats.CurrentOpenFileCount, + m_DiskStats.ReadCount, + m_DiskStats.ReadByteCount, + m_Path, + Content, + Lookup, + 4); std::vector<std::pair<IoHash, FetchChunkFunc>> BlockContent; BlockContent.reserve(ChunksInBlock.size()); @@ -6601,7 +6283,14 @@ BuildsOperationUploadFolder::RebuildBlock(const ChunkedFolderContent& Content, const std::vector<uint32_t>& ChunksInBlock) { ZEN_TRACE_CPU("RebuildBlock"); - ReadFileCache OpenFileCache(m_DiskStats, m_Path, Content, Lookup, 4); + ReadFileCache OpenFileCache(m_DiskStats.OpenReadCount, + m_DiskStats.CurrentOpenFileCount, + m_DiskStats.ReadCount, + m_DiskStats.ReadByteCount, + m_Path, + Content, + Lookup, + 4); std::vector<SharedBuffer> ResultBuffers; ResultBuffers.reserve(HeaderBuffer.GetSegments().size() + ChunksInBlock.size()); @@ -7126,8 +6815,10 @@ BuildsOperationUploadFolder::CompressChunk(const ChunkedFolderContent& Content, CompressedFile.Open(TempFilePath, BasicFile::Mode::kTruncateDelete, Ec); if (Ec) { - throw std::runtime_error( - fmt::format("Failed creating temporary file for compressing blob {}. Reason: {}", ChunkHash, Ec.message())); + throw std::runtime_error(fmt::format("Failed creating temporary file for compressing blob {}, reason: ({}) {}", + ChunkHash, + Ec.value(), + Ec.message())); } uint64_t StreamRawBytes = 0; diff --git a/src/zenremotestore/filesystemutils.cpp b/src/zenremotestore/filesystemutils.cpp new file mode 100644 index 000000000..20ab3faea --- /dev/null +++ b/src/zenremotestore/filesystemutils.cpp @@ -0,0 +1,595 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "filesystemutils.h" + +#include <zenremotestore/chunking/chunkedcontent.h> + +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> +#include <zencore/parallelwork.h> +#include <zencore/scopeguard.h> +#include <zencore/timer.h> +#include <zencore/trace.h> + +namespace zen { + +BufferedOpenFile::BufferedOpenFile(const std::filesystem::path Path, + std::atomic<uint64_t>& OpenReadCount, + std::atomic<uint64_t>& CurrentOpenFileCount, + std::atomic<uint64_t>& ReadCount, + std::atomic<uint64_t>& ReadByteCount) +: m_Source(Path, BasicFile::Mode::kRead) +, m_SourceSize(m_Source.FileSize()) +, m_OpenReadCount(OpenReadCount) +, m_CurrentOpenFileCount(CurrentOpenFileCount) +, m_ReadCount(ReadCount) +, m_ReadByteCount(ReadByteCount) + +{ + m_OpenReadCount++; + m_CurrentOpenFileCount++; +} + +BufferedOpenFile::~BufferedOpenFile() +{ + m_CurrentOpenFileCount--; +} + +CompositeBuffer +BufferedOpenFile::GetRange(uint64_t Offset, uint64_t Size) +{ + ZEN_TRACE_CPU("BufferedOpenFile::GetRange"); + + ZEN_ASSERT((m_CacheBlockIndex == (uint64_t)-1) || m_Cache); + auto _ = MakeGuard([&]() { ZEN_ASSERT((m_CacheBlockIndex == (uint64_t)-1) || m_Cache); }); + + ZEN_ASSERT((Offset + Size) <= m_SourceSize); + const uint64_t BlockIndexStart = Offset / BlockSize; + const uint64_t BlockIndexEnd = (Offset + Size - 1) / BlockSize; + + std::vector<SharedBuffer> BufferRanges; + BufferRanges.reserve(BlockIndexEnd - BlockIndexStart + 1); + + uint64_t ReadOffset = Offset; + for (uint64_t BlockIndex = BlockIndexStart; BlockIndex <= BlockIndexEnd; BlockIndex++) + { + const uint64_t BlockStartOffset = BlockIndex * BlockSize; + if (m_CacheBlockIndex != BlockIndex) + { + uint64_t CacheSize = Min(BlockSize, m_SourceSize - BlockStartOffset); + ZEN_ASSERT(CacheSize > 0); + m_Cache = IoBuffer(CacheSize); + m_Source.Read(m_Cache.GetMutableView().GetData(), CacheSize, BlockStartOffset); + m_ReadCount++; + m_ReadByteCount += CacheSize; + m_CacheBlockIndex = BlockIndex; + } + + const uint64_t BytesRead = ReadOffset - Offset; + ZEN_ASSERT(BlockStartOffset <= ReadOffset); + const uint64_t OffsetIntoBlock = ReadOffset - BlockStartOffset; + ZEN_ASSERT(OffsetIntoBlock < m_Cache.GetSize()); + const uint64_t BlockBytes = Min(m_Cache.GetSize() - OffsetIntoBlock, Size - BytesRead); + BufferRanges.emplace_back(SharedBuffer(IoBuffer(m_Cache, OffsetIntoBlock, BlockBytes))); + ReadOffset += BlockBytes; + } + CompositeBuffer Result(std::move(BufferRanges)); + ZEN_ASSERT(Result.GetSize() == Size); + return Result; +} + +ReadFileCache::ReadFileCache(std::atomic<uint64_t>& OpenReadCount, + std::atomic<uint64_t>& CurrentOpenFileCount, + std::atomic<uint64_t>& ReadCount, + std::atomic<uint64_t>& ReadByteCount, + const std::filesystem::path& Path, + const ChunkedFolderContent& LocalContent, + const ChunkedContentLookup& LocalLookup, + size_t MaxOpenFileCount) +: m_Path(Path) +, m_LocalContent(LocalContent) +, m_LocalLookup(LocalLookup) +, m_OpenReadCount(OpenReadCount) +, m_CurrentOpenFileCount(CurrentOpenFileCount) +, m_ReadCount(ReadCount) +, m_ReadByteCount(ReadByteCount) +{ + m_OpenFiles.reserve(MaxOpenFileCount); +} +ReadFileCache::~ReadFileCache() +{ + m_OpenFiles.clear(); +} + +CompositeBuffer +ReadFileCache::GetRange(uint32_t SequenceIndex, uint64_t Offset, uint64_t Size) +{ + ZEN_TRACE_CPU("ReadFileCache::GetRange"); + + auto CacheIt = + std::find_if(m_OpenFiles.begin(), m_OpenFiles.end(), [SequenceIndex](const auto& Lhs) { return Lhs.first == SequenceIndex; }); + if (CacheIt != m_OpenFiles.end()) + { + if (CacheIt != m_OpenFiles.begin()) + { + auto CachedFile(std::move(CacheIt->second)); + m_OpenFiles.erase(CacheIt); + m_OpenFiles.insert(m_OpenFiles.begin(), std::make_pair(SequenceIndex, std::move(CachedFile))); + } + CompositeBuffer Result = m_OpenFiles.front().second->GetRange(Offset, Size); + return Result; + } + const uint32_t LocalPathIndex = m_LocalLookup.SequenceIndexFirstPathIndex[SequenceIndex]; + const std::filesystem::path LocalFilePath = (m_Path / m_LocalContent.Paths[LocalPathIndex]).make_preferred(); + if (Size == m_LocalContent.RawSizes[LocalPathIndex]) + { + IoBuffer Result = IoBufferBuilder::MakeFromFile(LocalFilePath); + return CompositeBuffer(SharedBuffer(Result)); + } + if (m_OpenFiles.size() == m_OpenFiles.capacity()) + { + m_OpenFiles.pop_back(); + } + m_OpenFiles.insert( + m_OpenFiles.begin(), + std::make_pair( + SequenceIndex, + std::make_unique<BufferedOpenFile>(LocalFilePath, m_OpenReadCount, m_CurrentOpenFileCount, m_ReadCount, m_ReadByteCount))); + CompositeBuffer Result = m_OpenFiles.front().second->GetRange(Offset, Size); + return Result; +} + +uint32_t +SetNativeFileAttributes(const std::filesystem::path FilePath, SourcePlatform SourcePlatform, uint32_t Attributes) +{ +#if ZEN_PLATFORM_WINDOWS + if (SourcePlatform == SourcePlatform::Windows) + { + SetFileAttributesToPath(FilePath, Attributes); + return Attributes; + } + else + { + uint32_t CurrentAttributes = GetFileAttributesFromPath(FilePath); + uint32_t NewAttributes = zen::MakeFileAttributeReadOnly(CurrentAttributes, zen::IsFileModeReadOnly(Attributes)); + if (CurrentAttributes != NewAttributes) + { + SetFileAttributesToPath(FilePath, NewAttributes); + } + return NewAttributes; + } +#endif // ZEN_PLATFORM_WINDOWS +#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + if (SourcePlatform != SourcePlatform::Windows) + { + zen::SetFileMode(FilePath, Attributes); + return Attributes; + } + else + { + uint32_t CurrentMode = zen::GetFileMode(FilePath); + uint32_t NewMode = zen::MakeFileModeReadOnly(CurrentMode, zen::IsFileAttributeReadOnly(Attributes)); + if (CurrentMode != NewMode) + { + zen::SetFileMode(FilePath, NewMode); + } + return NewMode; + } +#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC +}; + +uint32_t +GetNativeFileAttributes(const std::filesystem::path FilePath) +{ +#if ZEN_PLATFORM_WINDOWS + return GetFileAttributesFromPath(FilePath); +#endif // ZEN_PLATFORM_WINDOWS +#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + return GetFileMode(FilePath); +#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC +} + +bool +IsFileWithRetry(const std::filesystem::path& Path) +{ + std::error_code Ec; + bool Result = IsFile(Path, Ec); + for (size_t Retries = 0; Ec && Retries < 3; Retries++) + { + Sleep(100 + int(Retries * 50)); + Ec.clear(); + Result = IsFile(Path, Ec); + } + if (Ec) + { + throw std::system_error(std::error_code(Ec.value(), std::system_category()), + fmt::format("Failed to check path '{}' is file, reason: ({}) {}", Path, Ec.value(), Ec.message())); + } + return Result; +} + +bool +SetFileReadOnlyWithRetry(const std::filesystem::path& Path, bool ReadOnly) +{ + std::error_code Ec; + bool Result = SetFileReadOnly(Path, ReadOnly, Ec); + for (size_t Retries = 0; Ec && Retries < 3; Retries++) + { + Sleep(100 + int(Retries * 50)); + if (!IsFileWithRetry(Path)) + { + return false; + } + Ec.clear(); + Result = SetFileReadOnly(Path, ReadOnly, Ec); + } + if (Ec) + { + throw std::system_error(std::error_code(Ec.value(), std::system_category()), + fmt::format("Failed {} read only flag for file '{}', reason: ({}) {}", + ReadOnly ? "setting" : "clearing", + Path, + Ec.value(), + Ec.message())); + } + return Result; +} + +std::error_code +RenameFileWithRetry(const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath) +{ + std::error_code Ec; + RenameFile(SourcePath, TargetPath, Ec); + for (size_t Retries = 0; Ec && Retries < 5; Retries++) + { + ZEN_ASSERT_SLOW(IsFile(SourcePath)); + Sleep(50 + int(Retries * 150)); + Ec.clear(); + RenameFile(SourcePath, TargetPath, Ec); + } + return Ec; +} + +std::error_code +TryRemoveFile(const std::filesystem::path& Path) +{ + std::error_code Ec; + RemoveFile(Path, Ec); + if (Ec) + { + if (IsFile(Path, Ec)) + { + Ec.clear(); + RemoveFile(Path, Ec); + if (Ec) + { + return Ec; + } + } + } + return {}; +} + +void +RemoveFileWithRetry(const std::filesystem::path& Path) +{ + std::error_code Ec; + RemoveFile(Path, Ec); + for (size_t Retries = 0; Ec && Retries < 6; Retries++) + { + Sleep(100 + int(Retries * 50)); + if (!IsFileWithRetry(Path)) + { + return; + } + Ec.clear(); + RemoveFile(Path, Ec); + } + if (Ec) + { + throw std::system_error(std::error_code(Ec.value(), std::system_category()), + fmt::format("Failed removing file '{}', reason: ({}) {}", Path, Ec.value(), Ec.message())); + } +} + +void +FastCopyFile(bool AllowFileClone, + bool UseSparseFiles, + const std::filesystem::path& SourceFilePath, + const std::filesystem::path& TargetFilePath, + uint64_t RawSize, + std::atomic<uint64_t>& WriteCount, + std::atomic<uint64_t>& WriteByteCount, + std::atomic<uint64_t>& CloneCount, + std::atomic<uint64_t>& CloneByteCount) +{ + ZEN_TRACE_CPU("CopyFile"); + if (AllowFileClone && TryCloneFile(SourceFilePath, TargetFilePath)) + { + WriteCount += 1; + WriteByteCount += RawSize; + CloneCount += 1; + CloneByteCount += RawSize; + } + else + { + BasicFile TargetFile(TargetFilePath, BasicFile::Mode::kTruncate); + if (UseSparseFiles) + { + PrepareFileForScatteredWrite(TargetFile.Handle(), RawSize); + } + uint64_t Offset = 0; + if (!ScanFile(SourceFilePath, 512u * 1024u, [&](const void* Data, size_t Size) { + TargetFile.Write(Data, Size, Offset); + Offset += Size; + WriteCount++; + WriteByteCount += Size; + })) + { + throw std::runtime_error(fmt::format("Failed to copy file '{}' to '{}'", SourceFilePath, TargetFilePath)); + } + } +} + +CleanDirectoryResult +CleanDirectory( + WorkerThreadPool& IOWorkerPool, + std::atomic<bool>& AbortFlag, + std::atomic<bool>& PauseFlag, + const std::filesystem::path& Path, + std::span<const std::string> ExcludeDirectories, + std::function<void(const std::string_view Details, uint64_t TotalCount, uint64_t RemainingCount, bool IsPaused, bool IsAborted)>&& + ProgressFunc, + uint32_t ProgressUpdateDelayMS) +{ + ZEN_TRACE_CPU("CleanDirectory"); + Stopwatch Timer; + + std::atomic<uint64_t> DiscoveredItemCount = 0; + std::atomic<uint64_t> DeletedItemCount = 0; + std::atomic<uint64_t> DeletedByteCount = 0; + + CleanDirectoryResult Result; + RwLock ResultLock; + auto _ = MakeGuard([&]() { + Result.DeletedCount = DeletedItemCount.load(); + Result.DeletedByteCount = DeletedByteCount.load(); + Result.FoundCount = DiscoveredItemCount.load(); + }); + + ParallelWork Work(AbortFlag, + PauseFlag, + ProgressFunc ? WorkerThreadPool::EMode::DisableBacklog : WorkerThreadPool::EMode::EnableBacklog); + + struct AsyncVisitor : public GetDirectoryContentVisitor + { + AsyncVisitor(const std::filesystem::path& InPath, + std::atomic<bool>& InAbortFlag, + std::atomic<uint64_t>& InDiscoveredItemCount, + std::atomic<uint64_t>& InDeletedItemCount, + std::atomic<uint64_t>& InDeletedByteCount, + std::span<const std::string> InExcludeDirectories, + CleanDirectoryResult& InResult, + RwLock& InResultLock) + : Path(InPath) + , AbortFlag(InAbortFlag) + , DiscoveredItemCount(InDiscoveredItemCount) + , DeletedItemCount(InDeletedItemCount) + , DeletedByteCount(InDeletedByteCount) + , ExcludeDirectories(InExcludeDirectories) + , Result(InResult) + , ResultLock(InResultLock) + { + } + virtual void AsyncVisitDirectory(const std::filesystem::path& RelativeRoot, DirectoryContent&& Content) override + { + ZEN_TRACE_CPU("CleanDirectory_AsyncVisitDirectory"); + if (!AbortFlag) + { + if (!Content.FileNames.empty()) + { + DiscoveredItemCount += Content.FileNames.size(); + + const std::string RelativeRootString = RelativeRoot.generic_string(); + bool RemoveContent = true; + for (const std::string_view ExcludeDirectory : ExcludeDirectories) + { + if (RelativeRootString.starts_with(ExcludeDirectory)) + { + if (RelativeRootString.length() > ExcludeDirectory.length()) + { + const char MaybePathDelimiter = RelativeRootString[ExcludeDirectory.length()]; + if (MaybePathDelimiter == '/' || MaybePathDelimiter == '\\' || + MaybePathDelimiter == std::filesystem::path::preferred_separator) + { + RemoveContent = false; + break; + } + } + else + { + RemoveContent = false; + break; + } + } + } + if (RemoveContent) + { + ZEN_TRACE_CPU("DeleteFiles"); + for (size_t FileIndex = 0; FileIndex < Content.FileNames.size(); FileIndex++) + { + const std::filesystem::path& FileName = Content.FileNames[FileIndex]; + const std::filesystem::path FilePath = (Path / RelativeRoot / FileName).make_preferred(); + + bool IsRemoved = false; + std::error_code Ec; + (void)SetFileReadOnly(FilePath, false, Ec); + for (size_t Retries = 0; Ec && Retries < 3; Retries++) + { + Sleep(100 + int(Retries * 50)); + if (!IsFileWithRetry(FilePath)) + { + IsRemoved = true; + break; + } + Ec.clear(); + (void)SetFileReadOnly(FilePath, false, Ec); + } + if (!IsRemoved && !Ec) + { + (void)RemoveFile(FilePath, Ec); + for (size_t Retries = 0; Ec && Retries < 6; Retries++) + { + Sleep(100 + int(Retries * 50)); + if (!IsFileWithRetry(FilePath)) + { + IsRemoved = true; + return; + } + Ec.clear(); + (void)RemoveFile(FilePath, Ec); + } + } + if (!IsRemoved && Ec) + { + RwLock::ExclusiveLockScope _(ResultLock); + Result.FailedRemovePaths.push_back(std::make_pair(FilePath, Ec)); + } + else + { + DeletedItemCount++; + DeletedByteCount += Content.FileSizes[FileIndex]; + } + } + } + } + } + } + const std::filesystem::path& Path; + std::atomic<bool>& AbortFlag; + std::atomic<uint64_t>& DiscoveredItemCount; + std::atomic<uint64_t>& DeletedItemCount; + std::atomic<uint64_t>& DeletedByteCount; + std::span<const std::string> ExcludeDirectories; + CleanDirectoryResult& Result; + RwLock& ResultLock; + } Visitor(Path, AbortFlag, DiscoveredItemCount, DeletedItemCount, DeletedByteCount, ExcludeDirectories, Result, ResultLock); + + GetDirectoryContent(Path, + DirectoryContentFlags::IncludeFiles | DirectoryContentFlags::Recursive | DirectoryContentFlags::IncludeFileSizes, + Visitor, + IOWorkerPool, + Work.PendingWork()); + + DirectoryContent LocalDirectoryContent; + GetDirectoryContent(Path, DirectoryContentFlags::IncludeDirs | DirectoryContentFlags::IncludeFiles, LocalDirectoryContent); + DiscoveredItemCount += LocalDirectoryContent.Directories.size(); + std::vector<std::filesystem::path> DirectoriesToDelete; + DirectoriesToDelete.reserve(LocalDirectoryContent.Directories.size()); + for (std::filesystem::path& LocalDirPath : LocalDirectoryContent.Directories) + { + bool Leave = false; + for (const std::string_view ExcludeDirectory : ExcludeDirectories) + { + if (LocalDirPath == (Path / ExcludeDirectory)) + { + Leave = true; + break; + } + } + if (!Leave) + { + DirectoriesToDelete.emplace_back(std::move(LocalDirPath)); + DiscoveredItemCount++; + } + } + + uint64_t LastUpdateTimeMs = Timer.GetElapsedTimeMs(); + + if (ProgressFunc && ProgressUpdateDelayMS != 0) + { + Work.Wait(ProgressUpdateDelayMS, [&](bool IsAborted, bool IsPaused, ptrdiff_t PendingWork) { + ZEN_UNUSED(PendingWork); + LastUpdateTimeMs = Timer.GetElapsedTimeMs(); + + uint64_t Deleted = DeletedItemCount.load(); + uint64_t DeletedBytes = DeletedByteCount.load(); + uint64_t Discovered = DiscoveredItemCount.load(); + std::string Details = fmt::format("Found {}, Deleted {} ({})", Discovered, Deleted, NiceBytes(DeletedBytes)); + ProgressFunc(Details, Discovered, Discovered - Deleted, IsPaused, IsAborted); + }); + } + else + { + Work.Wait(); + } + + { + ZEN_TRACE_CPU("DeleteDirs"); + for (const std::filesystem::path& DirectoryToDelete : DirectoriesToDelete) + { + if (AbortFlag) + { + break; + } + else + { + while (PauseFlag && !AbortFlag) + { + Sleep(2000); + } + } + + { + std::error_code Ec; + zen::CleanDirectory(DirectoryToDelete, true, Ec); + if (Ec) + { + Sleep(200); + zen::CleanDirectory(DirectoryToDelete, true, Ec); + } + + if (!Ec) + { + RemoveDir(Path, Ec); + for (size_t Retries = 0; Ec && Retries < 3; Retries++) + { + Sleep(100 + int(Retries * 50)); + if (!IsDir(Path)) + { + break; + } + Ec.clear(); + RemoveDir(Path, Ec); + } + } + if (Ec) + { + RwLock::ExclusiveLockScope __(ResultLock); + Result.FailedRemovePaths.push_back(std::make_pair(DirectoryToDelete, Ec)); + } + else + { + DeletedItemCount++; + } + } + + uint64_t NowMs = Timer.GetElapsedTimeMs(); + + if ((NowMs - LastUpdateTimeMs) >= ProgressUpdateDelayMS) + { + LastUpdateTimeMs = NowMs; + + uint64_t Deleted = DeletedItemCount.load(); + uint64_t DeletedBytes = DeletedByteCount.load(); + uint64_t Discovered = DiscoveredItemCount.load(); + std::string Details = fmt::format("Found {}, Deleted {} ({})", Discovered, Deleted, NiceBytes(DeletedBytes)); + ProgressFunc(Details, Discovered, Discovered - Deleted, PauseFlag, AbortFlag); + } + } + } + + return Result; +} + +} // namespace zen diff --git a/src/zenremotestore/filesystemutils.h b/src/zenremotestore/filesystemutils.h new file mode 100644 index 000000000..cfe6adc6c --- /dev/null +++ b/src/zenremotestore/filesystemutils.h @@ -0,0 +1,114 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/basicfile.h> +#include <zenremotestore/chunking/chunkedcontent.h> + +namespace zen { + +class CompositeBuffer; + +class BufferedOpenFile +{ +public: + BufferedOpenFile(const std::filesystem::path Path, + std::atomic<uint64_t>& OpenReadCount, + std::atomic<uint64_t>& CurrentOpenFileCount, + std::atomic<uint64_t>& ReadCount, + std::atomic<uint64_t>& ReadByteCount); + ~BufferedOpenFile(); + BufferedOpenFile() = delete; + BufferedOpenFile(const BufferedOpenFile&) = delete; + BufferedOpenFile(BufferedOpenFile&&) = delete; + BufferedOpenFile& operator=(BufferedOpenFile&&) = delete; + BufferedOpenFile& operator=(const BufferedOpenFile&) = delete; + + CompositeBuffer GetRange(uint64_t Offset, uint64_t Size); + +public: + void* Handle() { return m_Source.Handle(); } + +private: + const uint64_t BlockSize = 256u * 1024u; + + BasicFile m_Source; + const uint64_t m_SourceSize; + std::atomic<uint64_t>& m_OpenReadCount; + std::atomic<uint64_t>& m_CurrentOpenFileCount; + std::atomic<uint64_t>& m_ReadCount; + std::atomic<uint64_t>& m_ReadByteCount; + uint64_t m_CacheBlockIndex = (uint64_t)-1; + IoBuffer m_Cache; +}; + +class ReadFileCache +{ +public: + // A buffered file reader that provides CompositeBuffer where the buffers are owned and the memory never overwritten + ReadFileCache(std::atomic<uint64_t>& OpenReadCount, + std::atomic<uint64_t>& CurrentOpenFileCount, + std::atomic<uint64_t>& ReadCount, + std::atomic<uint64_t>& ReadByteCount, + const std::filesystem::path& Path, + const ChunkedFolderContent& LocalContent, + const ChunkedContentLookup& LocalLookup, + size_t MaxOpenFileCount); + ~ReadFileCache(); + + CompositeBuffer GetRange(uint32_t SequenceIndex, uint64_t Offset, uint64_t Size); + +private: + const std::filesystem::path m_Path; + const ChunkedFolderContent& m_LocalContent; + const ChunkedContentLookup& m_LocalLookup; + std::vector<std::pair<uint32_t, std::unique_ptr<BufferedOpenFile>>> m_OpenFiles; + std::atomic<uint64_t>& m_OpenReadCount; + std::atomic<uint64_t>& m_CurrentOpenFileCount; + std::atomic<uint64_t>& m_ReadCount; + std::atomic<uint64_t>& m_ReadByteCount; +}; + +uint32_t SetNativeFileAttributes(const std::filesystem::path FilePath, SourcePlatform SourcePlatform, uint32_t Attributes); + +uint32_t GetNativeFileAttributes(const std::filesystem::path FilePath); + +bool IsFileWithRetry(const std::filesystem::path& Path); + +bool SetFileReadOnlyWithRetry(const std::filesystem::path& Path, bool ReadOnly); + +std::error_code RenameFileWithRetry(const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath); + +std::error_code TryRemoveFile(const std::filesystem::path& Path); + +void RemoveFileWithRetry(const std::filesystem::path& Path); + +void FastCopyFile(bool AllowFileClone, + bool UseSparseFiles, + const std::filesystem::path& SourceFilePath, + const std::filesystem::path& TargetFilePath, + uint64_t RawSize, + std::atomic<uint64_t>& WriteCount, + std::atomic<uint64_t>& WriteByteCount, + std::atomic<uint64_t>& CloneCount, + std::atomic<uint64_t>& CloneByteCount); + +struct CleanDirectoryResult +{ + uint64_t FoundCount = 0; + uint64_t DeletedCount = 0; + uint64_t DeletedByteCount = 0; + std::vector<std::pair<std::filesystem::path, std::error_code>> FailedRemovePaths; +}; + +CleanDirectoryResult CleanDirectory( + WorkerThreadPool& IOWorkerPool, + std::atomic<bool>& AbortFlag, + std::atomic<bool>& PauseFlag, + const std::filesystem::path& Path, + std::span<const std::string> ExcludeDirectories, + std::function<void(const std::string_view Details, uint64_t TotalCount, uint64_t RemainingCount, bool IsPaused, bool IsAborted)>&& + ProgressFunc, + uint32_t ProgressUpdateDelayMS); + +} // namespace zen diff --git a/src/zenutil/bufferedopenfile.cpp b/src/zenutil/bufferedopenfile.cpp deleted file mode 100644 index 360011302..000000000 --- a/src/zenutil/bufferedopenfile.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include <zenutil/bufferedopenfile.h> - -#include <zencore/scopeguard.h> -#include <zencore/trace.h> - -namespace zen { - -BufferedOpenFile::BufferedOpenFile(const std::filesystem::path Path, - std::atomic<uint64_t>& OpenReadCount, - std::atomic<uint64_t>& CurrentOpenFileCount, - std::atomic<uint64_t>& ReadCount, - std::atomic<uint64_t>& ReadByteCount) -: m_Source(Path, BasicFile::Mode::kRead) -, m_SourceSize(m_Source.FileSize()) -, m_OpenReadCount(OpenReadCount) -, m_CurrentOpenFileCount(CurrentOpenFileCount) -, m_ReadCount(ReadCount) -, m_ReadByteCount(ReadByteCount) - -{ - m_OpenReadCount++; - m_CurrentOpenFileCount++; -} - -BufferedOpenFile::~BufferedOpenFile() -{ - m_CurrentOpenFileCount--; -} - -CompositeBuffer -BufferedOpenFile::GetRange(uint64_t Offset, uint64_t Size) -{ - ZEN_TRACE_CPU("BufferedOpenFile::GetRange"); - - ZEN_ASSERT((m_CacheBlockIndex == (uint64_t)-1) || m_Cache); - auto _ = MakeGuard([&]() { ZEN_ASSERT((m_CacheBlockIndex == (uint64_t)-1) || m_Cache); }); - - ZEN_ASSERT((Offset + Size) <= m_SourceSize); - const uint64_t BlockIndexStart = Offset / BlockSize; - const uint64_t BlockIndexEnd = (Offset + Size - 1) / BlockSize; - - std::vector<SharedBuffer> BufferRanges; - BufferRanges.reserve(BlockIndexEnd - BlockIndexStart + 1); - - uint64_t ReadOffset = Offset; - for (uint64_t BlockIndex = BlockIndexStart; BlockIndex <= BlockIndexEnd; BlockIndex++) - { - const uint64_t BlockStartOffset = BlockIndex * BlockSize; - if (m_CacheBlockIndex != BlockIndex) - { - uint64_t CacheSize = Min(BlockSize, m_SourceSize - BlockStartOffset); - ZEN_ASSERT(CacheSize > 0); - m_Cache = IoBuffer(CacheSize); - m_Source.Read(m_Cache.GetMutableView().GetData(), CacheSize, BlockStartOffset); - m_ReadCount++; - m_ReadByteCount += CacheSize; - m_CacheBlockIndex = BlockIndex; - } - - const uint64_t BytesRead = ReadOffset - Offset; - ZEN_ASSERT(BlockStartOffset <= ReadOffset); - const uint64_t OffsetIntoBlock = ReadOffset - BlockStartOffset; - ZEN_ASSERT(OffsetIntoBlock < m_Cache.GetSize()); - const uint64_t BlockBytes = Min(m_Cache.GetSize() - OffsetIntoBlock, Size - BytesRead); - BufferRanges.emplace_back(SharedBuffer(IoBuffer(m_Cache, OffsetIntoBlock, BlockBytes))); - ReadOffset += BlockBytes; - } - CompositeBuffer Result(std::move(BufferRanges)); - ZEN_ASSERT(Result.GetSize() == Size); - return Result; -} - -} // namespace zen diff --git a/src/zenutil/include/zenutil/bufferedopenfile.h b/src/zenutil/include/zenutil/bufferedopenfile.h deleted file mode 100644 index 3af7c9991..000000000 --- a/src/zenutil/include/zenutil/bufferedopenfile.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include <zencore/basicfile.h> - -namespace zen { - -class CompositeBuffer; - -class BufferedOpenFile -{ -public: - BufferedOpenFile(const std::filesystem::path Path, - std::atomic<uint64_t>& OpenReadCount, - std::atomic<uint64_t>& CurrentOpenFileCount, - std::atomic<uint64_t>& ReadCount, - std::atomic<uint64_t>& ReadByteCount); - ~BufferedOpenFile(); - BufferedOpenFile() = delete; - BufferedOpenFile(const BufferedOpenFile&) = delete; - BufferedOpenFile(BufferedOpenFile&&) = delete; - BufferedOpenFile& operator=(BufferedOpenFile&&) = delete; - BufferedOpenFile& operator=(const BufferedOpenFile&) = delete; - - const uint64_t BlockSize = 256u * 1024u; - CompositeBuffer GetRange(uint64_t Offset, uint64_t Size); - -public: - void* Handle() { return m_Source.Handle(); } - -private: - BasicFile m_Source; - const uint64_t m_SourceSize; - std::atomic<uint64_t>& m_OpenReadCount; - std::atomic<uint64_t>& m_CurrentOpenFileCount; - std::atomic<uint64_t>& m_ReadCount; - std::atomic<uint64_t>& m_ReadByteCount; - uint64_t m_CacheBlockIndex = (uint64_t)-1; - IoBuffer m_Cache; -}; - -} // namespace zen |