diff options
| -rw-r--r-- | zencore/compactbinarypackage.cpp | 5 | ||||
| -rw-r--r-- | zencore/include/zencore/iobuffer.h | 3 | ||||
| -rw-r--r-- | zencore/include/zencore/sharedbuffer.h | 3 | ||||
| -rw-r--r-- | zencore/iobuffer.cpp | 35 | ||||
| -rw-r--r-- | zenserver/projectstore.cpp | 6 | ||||
| -rw-r--r-- | zenstore/filecas.cpp | 108 |
6 files changed, 155 insertions, 5 deletions
diff --git a/zencore/compactbinarypackage.cpp b/zencore/compactbinarypackage.cpp index f84137ff6..262b6ecba 100644 --- a/zencore/compactbinarypackage.cpp +++ b/zencore/compactbinarypackage.cpp @@ -54,7 +54,10 @@ CbAttachment::CbAttachment(SharedBuffer InBuffer, const IoHash* const InHash) : Hash = *InHash; if (Buffer.GetSize()) { - ZEN_ASSERT_SLOW(Hash == IoHash::HashMemory(Buffer.GetData(), Buffer.GetSize())); + // This is disabled for now as it forces disk-based attachments to get mapped which + // then prevents us from making them delete themselves on close + + // ZEN_ASSERT_SLOW(Hash == IoHash::HashMemory(Buffer.GetData(), Buffer.GetSize())); } else { diff --git a/zencore/include/zencore/iobuffer.h b/zencore/include/zencore/iobuffer.h index f38af3c27..7e7182997 100644 --- a/zencore/include/zencore/iobuffer.h +++ b/zencore/include/zencore/iobuffer.h @@ -269,6 +269,8 @@ public: private: RefPtr<IoBufferCore> m_Core = new IoBufferCore; + IoBuffer(IoBufferCore* Core) : m_Core(Core) {} + friend class SharedBuffer; }; @@ -276,6 +278,7 @@ class IoBufferBuilder { public: ZENCORE_API static IoBuffer MakeFromFile(const wchar_t* FileName, uint64_t Offset = 0, uint64_t Size = ~0ull); + ZENCORE_API static IoBuffer MakeFromTemporaryFile(const wchar_t* FileName, uint64_t Offset = 0, uint64_t Size = ~0ull); ZENCORE_API static IoBuffer MakeFromFileHandle(void* FileHandle, uint64_t Offset = 0, uint64_t Size = ~0ull); inline static IoBuffer MakeCloneFromMemory(const void* Ptr, size_t Sz) { return IoBuffer(IoBuffer::Clone, Ptr, Sz); } diff --git a/zencore/include/zencore/sharedbuffer.h b/zencore/include/zencore/sharedbuffer.h index 7f9c61adb..cdcf66da4 100644 --- a/zencore/include/zencore/sharedbuffer.h +++ b/zencore/include/zencore/sharedbuffer.h @@ -101,7 +101,8 @@ public: } } - operator MemoryView() const { return GetView(); } + operator MemoryView() const { return GetView(); } + inline IoBuffer AsIoBuffer() const { return IoBuffer(m_Buffer); } SharedBuffer& operator=(UniqueBuffer&& Rhs) { diff --git a/zencore/iobuffer.cpp b/zencore/iobuffer.cpp index ec5d599b4..e74287c48 100644 --- a/zencore/iobuffer.cpp +++ b/zencore/iobuffer.cpp @@ -332,6 +332,41 @@ IoBufferBuilder::MakeFromFile(const wchar_t* FileName, uint64_t Offset, uint64_t return {}; } +IoBuffer +IoBufferBuilder::MakeFromTemporaryFile(const wchar_t* FileName, uint64_t Offset, uint64_t Size) +{ + CAtlFile DataFile; + + // We need to open with DELETE since this is used for the case + // when a file has been written to a staging directory, and is going + // to be moved in place. + + HRESULT hRes = DataFile.Create(FileName, GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_DELETE, OPEN_EXISTING); + + if (SUCCEEDED(hRes)) + { + ULONGLONG FileSize; + DataFile.GetSize(FileSize); + + if (Size == ~0ull) + { + Size = FileSize; + } + else + { + // Clamp size + if ((Offset + Size) > FileSize) + { + Size = FileSize - Offset; + } + } + + return IoBuffer(IoBuffer::File, DataFile.Detach(), Offset, Size); + } + + return {}; +} + ////////////////////////////////////////////////////////////////////////// void diff --git a/zenserver/projectstore.cpp b/zenserver/projectstore.cpp index cb6be69e8..dcad483a6 100644 --- a/zenserver/projectstore.cpp +++ b/zenserver/projectstore.cpp @@ -477,8 +477,8 @@ ProjectStore::Oplog::AppendNewOplogEntry(CbPackage OpPackage) for (const auto& Attach : Attachments) { - SharedBuffer BinaryView = Attach.AsBinaryView(); - m_CasStore.InsertChunk(BinaryView.GetData(), BinaryView.GetSize(), Attach.GetHash()); + IoBuffer AttachmentData = Attach.AsBinaryView().AsIoBuffer(); + m_CasStore.InsertChunk(AttachmentData, Attach.GetHash()); } return RegisterOplogEntry(Core, OpEntry, kUpdateNewEntry); @@ -1060,7 +1060,7 @@ HttpProjectService::HttpProjectService(CasStore& Store, ProjectStore* Projects) CbPackage::AttachmentResolver Resolver = [&](const IoHash& Hash) -> SharedBuffer { std::filesystem::path AttachmentPath = Log.TempPath() / Hash.ToHexString(); - if (IoBuffer Data = IoBufferBuilder::MakeFromFile(AttachmentPath.native().c_str())) + if (IoBuffer Data = IoBufferBuilder::MakeFromTemporaryFile(AttachmentPath.native().c_str())) { return SharedBuffer(std::move(Data)); } diff --git a/zenstore/filecas.cpp b/zenstore/filecas.cpp index 84a06c3be..0011ddec6 100644 --- a/zenstore/filecas.cpp +++ b/zenstore/filecas.cpp @@ -3,11 +3,14 @@ #include "FileCas.h" #include <zencore/except.h> +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> #include <zencore/memory.h> #include <zencore/string.h> #include <zencore/thread.h> #include <zencore/uid.h> +#include <spdlog/spdlog.h> #include <gsl/gsl-lite.hpp> #include <functional> @@ -25,6 +28,8 @@ struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax erro namespace zen { +using namespace fmt::literals; + WideStringBuilderBase& FileCasStrategy::MakeShardedPath(WideStringBuilderBase& ShardedPath, const IoHash& ChunkHash, size_t& OutShard2len) { @@ -58,6 +63,109 @@ FileCasStrategy::MakeShardedPath(WideStringBuilderBase& ShardedPath, const IoHas CasStore::InsertResult FileCasStrategy::InsertChunk(IoBuffer Chunk, const IoHash& ChunkHash) { + IoBufferFileReference FileRef; + if (Chunk.GetFileReference(/* out */ FileRef)) + { + size_t Shard2len = 0; + ExtendableWideStringBuilder<128> ShardedPath; + ShardedPath.Append(m_Config.RootDirectory.c_str()); + ShardedPath.Append(std::filesystem::path::preferred_separator); + MakeShardedPath(ShardedPath, ChunkHash, /* out */ Shard2len); + + auto DeletePayloadFileOnClose = [&] { + FILE_DISPOSITION_INFO Fdi{}; + Fdi.DeleteFile = TRUE; + BOOL Success = SetFileInformationByHandle(FileRef.FileHandle, FileDispositionInfo, &Fdi, sizeof Fdi); + + if (!Success) + { + spdlog::warn("Failed to flag temporary payload file for deletion: '{}'", PathFromHandle(FileRef.FileHandle)); + } + }; + + // See if file already exists + // + // Future improvement: maintain Bloom filter to avoid expensive file system probes? + + { + CAtlFile PayloadFile; + + HRESULT hRes = PayloadFile.Create(ShardedPath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); + + if (SUCCEEDED(hRes)) + { + // If we succeeded in opening the file then we don't need to do anything else because it already exists and should contain + // the content we were about to insert + + // We do need to ensure the file goes away on close, however + + DeletePayloadFileOnClose(); + + return CasStore::InsertResult{.New = false}; + } + } + + std::filesystem::path FullPath(ShardedPath.c_str()); + + std::filesystem::path FilePath = FullPath.parent_path(); + std::wstring FileName = FullPath.native(); + + const DWORD BufferSize = sizeof(FILE_RENAME_INFO) + gsl::narrow<DWORD>(FileName.size() * sizeof(WCHAR)); + FILE_RENAME_INFO* RenameInfo = reinterpret_cast<FILE_RENAME_INFO*>(Memory::Alloc(BufferSize)); + memset(RenameInfo, 0, BufferSize); + + RenameInfo->ReplaceIfExists = FALSE; + RenameInfo->FileNameLength = gsl::narrow<DWORD>(FileName.size()); + memcpy(RenameInfo->FileName, FileName.c_str(), FileName.size() * sizeof(WCHAR)); + RenameInfo->FileName[FileName.size()] = 0; + + // Try to move file into place + + BOOL Success = SetFileInformationByHandle(FileRef.FileHandle, FileRenameInfo, RenameInfo, BufferSize); + + if (!Success) + { + CAtlFile DirHandle; + + auto InternalCreateDirectoryHandle = [&] { + return DirHandle.Create(FilePath.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS); + }; + + HRESULT hRes = InternalCreateDirectoryHandle(); + + if (FAILED(hRes)) + { + zen::CreateDirectories(FilePath.c_str()); + + hRes = InternalCreateDirectoryHandle(); + } + + if (FAILED(hRes)) + { + throw WindowsException(hRes, "Failed to open shard directory '{}'"_format(FilePath)); + } + + // Retry + + Success = SetFileInformationByHandle(FileRef.FileHandle, FileRenameInfo, RenameInfo, BufferSize); + } + + Memory::Free(RenameInfo); + + if (Success) + { + return CasStore::InsertResult{.New = true}; + } + + spdlog::warn("rename of CAS payload file failed, falling back to regular write for {}", ChunkHash); + + DeletePayloadFileOnClose(); + } + return InsertChunk(Chunk.Data(), Chunk.Size(), ChunkHash); } |