Files
UnrealEngine/Engine/Source/Runtime/StorageServerClient/Private/Cache/CacheStorageBlocking.cpp
2025-05-18 13:04:45 +08:00

233 lines
6.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CacheStorageBlocking.h"
#if !UE_BUILD_SHIPPING
#include "HAL/PlatformFileManager.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "ProfilingDebugging/CountersTrace.h"
TRACE_DECLARE_MEMORY_COUNTER(FrontBuffer, TEXT("ZenClient/BlockingStorage/FrontBuffer"));
TRACE_DECLARE_MEMORY_COUNTER(BackBuffer, TEXT("ZenClient/BlockingStorage/BackBuffer"));
namespace StorageServer
{
FCacheStorageBlocking::FCacheStorageBlocking(const TCHAR* FileNamePrefix, const uint64 FileSizeTmp)
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
const TArray<TTuple<FString, uint64>> BackingFileNames = GetBackingFileNames(FileNamePrefix, FileSizeTmp);
BackingFiles.SetNum(BackingFileNames.Num());
FileSize = 0;
for (int32 i = 0; i < BackingFiles.Num(); ++i)
{
const FString& FileName = BackingFileNames[i].Key;
const uint64 DesiredFileSize = BackingFileNames[i].Value;
// TODO check if disk size is exhausted?
IFileHandle* BackingFile = PlatformFile.OpenWrite(*FileName, true, true);
if (!ensureAlwaysMsgf(BackingFile, TEXT("Can't open storage server backing file '%s'"), *FileName))
{
return;
}
if (BackingFile->Size() != DesiredFileSize)
{
BackingFile->Truncate(DesiredFileSize);
bNewlyCreatedStorage = true;
}
FileSize += BackingFile->Size();
BackingFiles[i] = TUniquePtr<IFileHandle>(BackingFile);
}
}
FCacheStorageBlocking::~FCacheStorageBlocking()
{
FlushTask.Wait();
}
void FCacheStorageBlocking::Flush()
{
// don't block if there is a pending flush already
Flush(true);
}
uint64 FCacheStorageBlocking::GetSize() const
{
return FileSize;
}
FIoBuffer FCacheStorageBlocking::Read(const uint64 Offset, const uint64 ReadSize, TOptional<FIoBuffer> OptDestination)
{
// TODO potential improvement is to inspect in-flight write queues and read data from them if needed
FIoBuffer Destination = OptDestination.IsSet() && OptDestination->GetSize() >= ReadSize ? OptDestination.GetValue() : FIoBuffer(ReadSize);
Destination.SetSize(ReadSize);
BackingReadAt(Offset, Destination.GetData(), ReadSize);
return Destination;
}
void FCacheStorageBlocking::WriteAsync(const uint64 Offset, const void* Buffer, const uint64 WriteSize)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCacheStorageBlocking::WriteAsync);
// if buffer is too big it's easier to just store it directly
if (WriteSize >= Buffers.GetBack()->MaxDataSize / 2)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCacheStorageBlocking::WriteAsync::SyncWrite);
BackingWriteAt(Offset, Buffer, WriteSize);
}
else
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCacheStorageBlocking::WriteAsync::Write);
bool bNeedsFlush = false;
while (true)
{
if (bNeedsFlush)
{
// wait for buffer swap
Flush(false);
}
FScopeLock ScopeLock(&BackLock);
if (Buffers.GetBack()->Write(Offset, Buffer, WriteSize))
{
return;
}
else
{
bNeedsFlush = true;
}
}
}
}
void FCacheStorageBlocking::Flush(bool bLazy)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCacheStorageBlocking::Flush);
FScopeLock ScopeBackLock(&BackLock);
if (!FlushTask.IsCompleted())
{
if (bLazy)
{
return;
}
else
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCacheStorageBlocking::Flush::FlushTaskWait);
FlushTask.Wait();
}
}
// no data in back buffer, nothing to flush
if (Buffers.GetBack()->Data.Num() == 0)
{
return;
}
Buffers.Flip();
TRACE_COUNTER_SET(FrontBuffer, Buffers.GetFront()->Data.Num());
TRACE_COUNTER_SET(BackBuffer, Buffers.GetBack()->Data.Num());
FlushTask = UE::Tasks::Launch(TEXT("CacheStorageFlush"), [this]()
{
FScopeLock ScopeFrontLock(&FrontLock);
FWriteQueue* Front = Buffers.GetFront();
for (const FWriteOp& Operation : Front->Operations)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCacheStorageBlocking::Flush::Write);
BackingWriteAt(Operation.OffsetInFile, Operation.Data, Operation.Size);
TRACE_COUNTER_SUBTRACT(FrontBuffer, Operation.Size);
}
Front->Empty();
}, LowLevelTasks::ETaskPriority::BackgroundNormal);
}
void FCacheStorageBlocking::BackingReadAt(const uint64 Offset, void* Buffer, const uint64 Size)
{
uint32 IndexA, IndexB;
uint64 OffsetA, SizeA, OffsetB, SizeB;
if (!GetBackingIntervals(Offset, Size, IndexA, OffsetA, SizeA, IndexB, OffsetB, SizeB))
{
return;
}
if (SizeA > 0)
{
BackingFiles[IndexA]->ReadAt(static_cast<uint8*>(Buffer), SizeA, OffsetA);
}
if (SizeB > 0)
{
BackingFiles[IndexB]->ReadAt(static_cast<uint8*>(Buffer) + SizeA, SizeB, OffsetB);
}
}
void FCacheStorageBlocking::BackingWriteAt(const uint64 Offset, const void* Buffer, const uint64 Size)
{
uint32 IndexA, IndexB;
uint64 OffsetA, SizeA, OffsetB, SizeB;
if (!GetBackingIntervals(Offset, Size, IndexA, OffsetA, SizeA, IndexB, OffsetB, SizeB))
{
return;
}
FScopeLock ScopeFileWriteLock(&FileWriteLock);
if (SizeA > 0)
{
BackingFiles[IndexA]->Seek(OffsetA);
BackingFiles[IndexA]->Write(static_cast<const uint8*>(Buffer), SizeA);
}
if (SizeB > 0)
{
BackingFiles[IndexB]->Seek(OffsetB);
BackingFiles[IndexB]->Write(static_cast<const uint8*>(Buffer) + SizeA, SizeB);
}
}
bool FCacheStorageBlocking::FWriteQueue::Write(const uint64 OffsetInFile, const void* Buffer, const uint64 Size)
{
if (!CanStore(Size))
{
return false;
}
TRACE_COUNTER_ADD(BackBuffer, Size);
uint8* Ptr = Data.GetData() + Data.Num();
Data.SetNum(Data.Num() + Size);
FMemory::Memcpy(Ptr, Buffer, Size);
FWriteOp* Last = Operations.Num() > 0 ? &Operations.Last() : nullptr;
// Try to coalesce write operation with last operation
// in hopes that in all linear writes we will only have one write op.
// Another way would be to sort them later and coalesce on sorted data.
if (Last != nullptr && Last->OffsetInFile + Last->Size == OffsetInFile)
{
Last->Size += Size;
}
else
{
FWriteOp& New = Operations.AddDefaulted_GetRef();
New.OffsetInFile = OffsetInFile;
New.Size = Size;
New.Data = Ptr;
}
return true;
}
}
#endif