2020-06-23 18:40:00 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2022-06-10 17:21:00 -04:00
|
|
|
#include "DerivedDataBackendInterface.h"
|
2022-05-31 23:25:37 -04:00
|
|
|
#include "DerivedDataLegacyCacheStore.h"
|
|
|
|
|
|
2020-06-23 18:40:00 -04:00
|
|
|
#if WITH_HTTP_DDC_BACKEND
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
#include "Algo/AllOf.h"
|
2022-07-22 10:54:37 -04:00
|
|
|
#include "Algo/Transform.h"
|
2023-06-21 13:32:34 -04:00
|
|
|
#include "Async/Mutex.h"
|
|
|
|
|
#include "Async/UniqueLock.h"
|
2022-01-11 11:57:38 -05:00
|
|
|
#include "Compression/CompressedBuffer.h"
|
|
|
|
|
#include "Containers/StringView.h"
|
2020-06-23 18:40:00 -04:00
|
|
|
#include "Containers/Ticker.h"
|
2022-01-11 11:57:38 -05:00
|
|
|
#include "DerivedDataCacheKey.h"
|
2021-04-28 16:22:18 -04:00
|
|
|
#include "DerivedDataCacheRecord.h"
|
2022-01-11 11:57:38 -05:00
|
|
|
#include "DerivedDataCacheUsageStats.h"
|
2022-01-14 10:53:17 -05:00
|
|
|
#include "DerivedDataChunk.h"
|
2022-07-18 12:23:16 -04:00
|
|
|
#include "DerivedDataRequest.h"
|
2022-04-05 16:43:41 -04:00
|
|
|
#include "DerivedDataRequestOwner.h"
|
2022-01-10 13:43:40 -05:00
|
|
|
#include "DerivedDataValue.h"
|
2022-07-06 05:59:22 -04:00
|
|
|
#include "DesktopPlatformModule.h"
|
2020-06-23 18:40:00 -04:00
|
|
|
#include "Dom/JsonObject.h"
|
2022-07-18 12:23:16 -04:00
|
|
|
#include "HAL/CriticalSection.h"
|
2022-04-05 16:43:41 -04:00
|
|
|
#include "HAL/IConsoleManager.h"
|
2022-07-18 12:23:16 -04:00
|
|
|
#include "HAL/PlatformProcess.h"
|
2022-05-31 22:01:53 -04:00
|
|
|
#include "Http/HttpClient.h"
|
2021-02-22 11:11:09 -04:00
|
|
|
#include "IO/IoHash.h"
|
2022-01-06 11:05:57 -05:00
|
|
|
#include "Memory/SharedBuffer.h"
|
2022-05-31 22:01:53 -04:00
|
|
|
#include "Misc/App.h"
|
|
|
|
|
#include "Misc/CommandLine.h"
|
|
|
|
|
#include "Misc/ConfigCacheIni.h"
|
2020-06-23 18:40:00 -04:00
|
|
|
#include "Misc/FileHelper.h"
|
2022-11-16 15:12:51 -05:00
|
|
|
#include "Misc/Optional.h"
|
2022-07-18 12:23:16 -04:00
|
|
|
#include "Misc/ScopeExit.h"
|
2020-06-23 18:40:00 -04:00
|
|
|
#include "Misc/ScopeLock.h"
|
2022-07-18 12:23:16 -04:00
|
|
|
#include "Misc/ScopeRWLock.h"
|
2021-01-11 11:34:27 -04:00
|
|
|
#include "Misc/StringBuilder.h"
|
2020-06-23 18:40:00 -04:00
|
|
|
#include "ProfilingDebugging/CountersTrace.h"
|
2022-01-11 11:57:38 -05:00
|
|
|
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
2022-01-10 13:43:40 -05:00
|
|
|
#include "Serialization/CompactBinary.h"
|
|
|
|
|
#include "Serialization/CompactBinaryPackage.h"
|
2022-12-04 02:26:39 -05:00
|
|
|
#include "Serialization/CompactBinarySerialization.h"
|
2022-01-10 13:43:40 -05:00
|
|
|
#include "Serialization/CompactBinaryValidation.h"
|
2022-02-02 07:35:19 -05:00
|
|
|
#include "Serialization/CompactBinaryWriter.h"
|
2022-07-18 12:23:16 -04:00
|
|
|
#include "Serialization/JsonReader.h"
|
|
|
|
|
#include "Serialization/JsonSerializer.h"
|
|
|
|
|
#include "String/Find.h"
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-27 07:04:22 -04:00
|
|
|
#if PLATFORM_MICROSOFT
|
|
|
|
|
#include "Microsoft/AllowMicrosoftPlatformTypes.h"
|
2022-05-26 18:07:41 -04:00
|
|
|
#include <winsock2.h>
|
|
|
|
|
#include <ws2tcpip.h>
|
2022-05-27 07:04:22 -04:00
|
|
|
#include "Microsoft/HideMicrosoftPlatformTypes.h"
|
2022-05-26 18:07:41 -04:00
|
|
|
#else
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <netdb.h>
|
2021-04-28 16:22:18 -04:00
|
|
|
#endif
|
|
|
|
|
|
2022-07-18 23:15:54 -04:00
|
|
|
#if WITH_SSL
|
|
|
|
|
#include "Ssl.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-02-24 10:24:10 -05:00
|
|
|
#define UE_HTTPDDC_GET_REQUEST_POOL_SIZE 48
|
|
|
|
|
#define UE_HTTPDDC_PUT_REQUEST_POOL_SIZE 16
|
2022-12-04 02:26:39 -05:00
|
|
|
#define UE_HTTPDDC_NONBLOCKING_GET_REQUEST_POOL_SIZE 128
|
|
|
|
|
#define UE_HTTPDDC_NONBLOCKING_PUT_REQUEST_POOL_SIZE 24
|
2020-06-23 18:40:00 -04:00
|
|
|
#define UE_HTTPDDC_MAX_FAILED_LOGIN_ATTEMPTS 16
|
|
|
|
|
#define UE_HTTPDDC_MAX_ATTEMPTS 4
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
namespace UE::DerivedData
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
static bool bHttpEnableAsync = true;
|
|
|
|
|
static FAutoConsoleVariableRef CVarHttpEnableAsync(
|
|
|
|
|
TEXT("DDC.Http.EnableAsync"),
|
|
|
|
|
bHttpEnableAsync,
|
|
|
|
|
TEXT("If true, asynchronous operations are permitted, otherwise all operations are forced to be synchronous."),
|
|
|
|
|
ECVF_Default);
|
|
|
|
|
|
2020-06-23 18:40:00 -04:00
|
|
|
TRACE_DECLARE_INT_COUNTER(HttpDDC_Get, TEXT("HttpDDC Get"));
|
|
|
|
|
TRACE_DECLARE_INT_COUNTER(HttpDDC_GetHit, TEXT("HttpDDC Get Hit"));
|
|
|
|
|
TRACE_DECLARE_INT_COUNTER(HttpDDC_Put, TEXT("HttpDDC Put"));
|
|
|
|
|
TRACE_DECLARE_INT_COUNTER(HttpDDC_PutHit, TEXT("HttpDDC Put Hit"));
|
|
|
|
|
TRACE_DECLARE_INT_COUNTER(HttpDDC_BytesReceived, TEXT("HttpDDC Bytes Received"));
|
|
|
|
|
TRACE_DECLARE_INT_COUNTER(HttpDDC_BytesSent, TEXT("HttpDDC Bytes Sent"));
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
static bool ShouldAbortForShutdown()
|
2021-04-05 14:36:58 -04:00
|
|
|
{
|
|
|
|
|
return !GIsBuildMachine && FDerivedDataBackend::Get().IsShuttingDown();
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-02 07:35:19 -05:00
|
|
|
static bool IsValueDataReady(FValue& Value, const ECachePolicy Policy)
|
|
|
|
|
{
|
|
|
|
|
if (!EnumHasAnyFlags(Policy, ECachePolicy::Query))
|
|
|
|
|
{
|
|
|
|
|
Value = Value.RemoveData();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Value.HasData())
|
|
|
|
|
{
|
|
|
|
|
if (EnumHasAnyFlags(Policy, ECachePolicy::SkipData))
|
|
|
|
|
{
|
|
|
|
|
Value = Value.RemoveData();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-18 23:15:54 -04:00
|
|
|
static FAnsiStringView GetDomainFromUri(const FAnsiStringView Uri)
|
2022-07-18 12:23:16 -04:00
|
|
|
{
|
|
|
|
|
FAnsiStringView Domain = Uri;
|
|
|
|
|
if (const int32 SchemeIndex = String::FindFirst(Domain, ANSITEXTVIEW("://")); SchemeIndex != INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Domain.RightChopInline(SchemeIndex + ANSITEXTVIEW("://").Len());
|
|
|
|
|
}
|
|
|
|
|
if (const int32 SlashIndex = String::FindFirstChar(Domain, '/'); SlashIndex != INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Domain.LeftInline(SlashIndex);
|
|
|
|
|
}
|
|
|
|
|
if (const int32 AtIndex = String::FindFirstChar(Domain, '@'); AtIndex != INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Domain.RightChopInline(AtIndex + 1);
|
|
|
|
|
}
|
|
|
|
|
const auto RemovePort = [](FAnsiStringView& Authority)
|
|
|
|
|
{
|
|
|
|
|
if (const int32 ColonIndex = String::FindLastChar(Authority, ':'); ColonIndex != INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Authority.LeftInline(ColonIndex);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (Domain.StartsWith('['))
|
|
|
|
|
{
|
|
|
|
|
if (const int32 LastBracketIndex = String::FindLastChar(Domain, ']'); LastBracketIndex != INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Domain.MidInline(1, LastBracketIndex - 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RemovePort(Domain);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RemovePort(Domain);
|
|
|
|
|
}
|
2022-07-18 23:15:54 -04:00
|
|
|
return Domain;
|
|
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
|
2022-07-18 23:15:54 -04:00
|
|
|
static bool TryResolveCanonicalHost(const FAnsiStringView Uri, FAnsiStringBuilderBase& OutUri)
|
|
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
// Append the URI until the end of the domain.
|
2022-07-18 23:15:54 -04:00
|
|
|
const FAnsiStringView Domain = GetDomainFromUri(Uri);
|
2022-07-18 12:23:16 -04:00
|
|
|
const int32 OutUriIndex = OutUri.Len();
|
|
|
|
|
const int32 DomainIndex = int32(Domain.GetData() - Uri.GetData());
|
|
|
|
|
const int32 DomainEndIndex = DomainIndex + Domain.Len();
|
|
|
|
|
OutUri.Append(Uri.Left(DomainEndIndex));
|
|
|
|
|
|
|
|
|
|
// Append the URI beyond the end of the domain before returning.
|
|
|
|
|
ON_SCOPE_EXIT { OutUri.Append(Uri.RightChop(DomainEndIndex)); };
|
|
|
|
|
|
|
|
|
|
// Try to resolve the host.
|
|
|
|
|
::addrinfo* Result = nullptr;
|
|
|
|
|
::addrinfo Hints{};
|
|
|
|
|
Hints.ai_flags = AI_CANONNAME;
|
|
|
|
|
Hints.ai_family = AF_UNSPEC;
|
|
|
|
|
if (::getaddrinfo(*OutUri + OutUriIndex + DomainIndex, nullptr, &Hints, &Result) == 0)
|
|
|
|
|
{
|
|
|
|
|
ON_SCOPE_EXIT { ::freeaddrinfo(Result); };
|
|
|
|
|
if (Result->ai_canonname)
|
|
|
|
|
{
|
|
|
|
|
OutUri.RemoveSuffix(Domain.Len());
|
|
|
|
|
OutUri.Append(Result->ai_canonname);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Encapsulation for access token shared by all requests.
|
|
|
|
|
*/
|
|
|
|
|
class FHttpAccessToken
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
void SetToken(FStringView Token);
|
|
|
|
|
inline uint32 GetSerial() const { return Serial.load(std::memory_order_relaxed); }
|
|
|
|
|
friend FAnsiStringBuilderBase& operator<<(FAnsiStringBuilderBase& Builder, const FHttpAccessToken& Token);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
mutable FRWLock Lock;
|
|
|
|
|
TArray<ANSICHAR> Header;
|
|
|
|
|
std::atomic<uint32> Serial;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void FHttpAccessToken::SetToken(const FStringView Token)
|
|
|
|
|
{
|
|
|
|
|
FWriteScopeLock WriteLock(Lock);
|
|
|
|
|
const FAnsiStringView Prefix = ANSITEXTVIEW("Bearer ");
|
|
|
|
|
const int32 TokenLen = FPlatformString::ConvertedLength<ANSICHAR>(Token.GetData(), Token.Len());
|
|
|
|
|
Header.Empty(Prefix.Len() + TokenLen);
|
|
|
|
|
Header.Append(Prefix.GetData(), Prefix.Len());
|
|
|
|
|
const int32 TokenIndex = Header.AddUninitialized(TokenLen);
|
|
|
|
|
FPlatformString::Convert(Header.GetData() + TokenIndex, TokenLen, Token.GetData(), Token.Len());
|
|
|
|
|
Serial.fetch_add(1, std::memory_order_relaxed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FAnsiStringBuilderBase& operator<<(FAnsiStringBuilderBase& Builder, const FHttpAccessToken& Token)
|
|
|
|
|
{
|
|
|
|
|
FReadScopeLock ReadLock(Token.Lock);
|
|
|
|
|
return Builder.Append(Token.Header);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
struct FHttpCacheStoreParams
|
|
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
FString Name;
|
2022-05-31 22:01:53 -04:00
|
|
|
FString Host;
|
2022-07-18 23:15:54 -04:00
|
|
|
FString HostPinnedPublicKeys;
|
2022-05-31 22:01:53 -04:00
|
|
|
FString Namespace;
|
2023-05-04 09:31:50 -04:00
|
|
|
FString HttpVersion;
|
2022-05-31 22:01:53 -04:00
|
|
|
FString OAuthProvider;
|
|
|
|
|
FString OAuthClientId;
|
|
|
|
|
FString OAuthSecret;
|
|
|
|
|
FString OAuthScope;
|
2022-07-06 05:59:22 -04:00
|
|
|
FString OAuthProviderIdentifier;
|
|
|
|
|
FString OAuthAccessToken;
|
2022-07-18 23:15:54 -04:00
|
|
|
FString OAuthPinnedPublicKeys;
|
2022-06-06 13:33:27 -04:00
|
|
|
bool bResolveHostCanonicalName = true;
|
2022-05-31 22:01:53 -04:00
|
|
|
bool bReadOnly = false;
|
|
|
|
|
|
|
|
|
|
void Parse(const TCHAR* NodeName, const TCHAR* Config);
|
|
|
|
|
};
|
|
|
|
|
|
2020-06-23 18:40:00 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
2022-02-14 14:43:39 -05:00
|
|
|
// FHttpCacheStore
|
2020-06-23 18:40:00 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
2022-01-11 11:57:38 -05:00
|
|
|
/**
|
|
|
|
|
* Backend for a HTTP based caching service (Jupiter).
|
2022-05-31 22:01:53 -04:00
|
|
|
*/
|
|
|
|
|
class FHttpCacheStore final : public ILegacyCacheStore
|
2022-01-11 11:57:38 -05:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
/**
|
2022-05-31 22:01:53 -04:00
|
|
|
* Creates the cache store client, checks health status and attempts to acquire an access token.
|
2022-01-11 11:57:38 -05:00
|
|
|
*/
|
2023-06-21 13:32:34 -04:00
|
|
|
FHttpCacheStore(const FHttpCacheStoreParams& Params, ICacheStoreOwner* Owner);
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
~FHttpCacheStore();
|
2022-01-11 11:57:38 -05:00
|
|
|
|
|
|
|
|
/**
|
2022-05-31 22:01:53 -04:00
|
|
|
* Checks is cache service is usable (reachable and accessible).
|
2022-01-11 11:57:38 -05:00
|
|
|
* @return true if usable
|
|
|
|
|
*/
|
2022-05-31 22:01:53 -04:00
|
|
|
inline bool IsUsable() const { return bIsUsable; }
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
void Put(
|
2022-01-11 11:57:38 -05:00
|
|
|
TConstArrayView<FCachePutRequest> Requests,
|
|
|
|
|
IRequestOwner& Owner,
|
2022-05-31 22:01:53 -04:00
|
|
|
FOnCachePutComplete&& OnComplete) final;
|
|
|
|
|
void Get(
|
2022-01-11 11:57:38 -05:00
|
|
|
TConstArrayView<FCacheGetRequest> Requests,
|
|
|
|
|
IRequestOwner& Owner,
|
2022-05-31 22:01:53 -04:00
|
|
|
FOnCacheGetComplete&& OnComplete) final;
|
|
|
|
|
void PutValue(
|
2022-02-02 07:35:19 -05:00
|
|
|
TConstArrayView<FCachePutValueRequest> Requests,
|
|
|
|
|
IRequestOwner& Owner,
|
2022-05-31 22:01:53 -04:00
|
|
|
FOnCachePutValueComplete&& OnComplete) final;
|
|
|
|
|
void GetValue(
|
2022-02-02 07:35:19 -05:00
|
|
|
TConstArrayView<FCacheGetValueRequest> Requests,
|
|
|
|
|
IRequestOwner& Owner,
|
2022-05-31 22:01:53 -04:00
|
|
|
FOnCacheGetValueComplete&& OnComplete) final;
|
|
|
|
|
void GetChunks(
|
2022-01-18 04:47:59 -05:00
|
|
|
TConstArrayView<FCacheGetChunkRequest> Requests,
|
2022-01-11 11:57:38 -05:00
|
|
|
IRequestOwner& Owner,
|
2022-05-31 22:01:53 -04:00
|
|
|
FOnCacheGetChunkComplete&& OnComplete) final;
|
|
|
|
|
|
|
|
|
|
void LegacyStats(FDerivedDataCacheStatsNode& OutNode) final;
|
|
|
|
|
bool LegacyDebugOptions(FBackendDebugOptions& Options) final;
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
static FHttpCacheStore* GetAny()
|
2022-01-11 11:57:38 -05:00
|
|
|
{
|
|
|
|
|
return AnyInstance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FString& GetDomain() const { return Domain; }
|
|
|
|
|
const FString& GetNamespace() const { return Namespace; }
|
2023-03-23 13:05:19 -04:00
|
|
|
const FString GetAccessToken() const
|
|
|
|
|
{
|
|
|
|
|
TAnsiStringBuilder<128> AccessTokenBuilder;
|
|
|
|
|
|
|
|
|
|
if (Access.IsValid())
|
|
|
|
|
{
|
|
|
|
|
AccessTokenBuilder << *Access;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FString(ANSI_TO_TCHAR(AccessTokenBuilder.ToString()));
|
|
|
|
|
}
|
2022-01-11 11:57:38 -05:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
FString Domain;
|
|
|
|
|
FString Namespace;
|
|
|
|
|
FString OAuthProvider;
|
|
|
|
|
FString OAuthClientId;
|
|
|
|
|
FString OAuthSecret;
|
2022-03-23 18:05:53 -04:00
|
|
|
FString OAuthScope;
|
2022-07-06 05:59:22 -04:00
|
|
|
FString OAuthProviderIdentifier;
|
|
|
|
|
FString OAuthAccessToken;
|
2023-05-04 09:31:50 -04:00
|
|
|
FString HttpVersion;
|
2022-07-06 05:59:22 -04:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
FAnsiStringBuilderBase EffectiveDomain;
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
ICacheStoreOwner* StoreOwner = nullptr;
|
2023-06-22 12:55:58 -04:00
|
|
|
ICacheStoreStats* StoreStats = nullptr;
|
2023-06-21 13:32:34 -04:00
|
|
|
|
2022-01-11 11:57:38 -05:00
|
|
|
FDerivedDataCacheUsageStats UsageStats;
|
|
|
|
|
FBackendDebugOptions DebugOptions;
|
2022-07-18 12:23:16 -04:00
|
|
|
THttpUniquePtr<IHttpConnectionPool> ConnectionPool;
|
|
|
|
|
FHttpRequestQueue GetRequestQueues[2];
|
|
|
|
|
FHttpRequestQueue PutRequestQueues[2];
|
2022-12-04 02:26:39 -05:00
|
|
|
FHttpRequestQueue NonBlockingGetRequestQueue;
|
|
|
|
|
FHttpRequestQueue NonBlockingPutRequestQueue;
|
2022-07-12 15:36:38 -04:00
|
|
|
|
|
|
|
|
FCriticalSection AccessCs;
|
2022-05-11 09:10:24 -04:00
|
|
|
TUniquePtr<FHttpAccessToken> Access;
|
2022-07-12 15:36:38 -04:00
|
|
|
FTSTicker::FDelegateHandle RefreshAccessTokenHandle;
|
2022-07-18 12:23:16 -04:00
|
|
|
double RefreshAccessTokenTime = 0.0;
|
2022-11-03 09:39:24 -04:00
|
|
|
uint32 LoginAttempts = 0;
|
2023-06-21 13:32:34 -04:00
|
|
|
uint32 FailedLoginAttempts = 0;
|
2022-11-03 09:39:24 -04:00
|
|
|
uint32 InteractiveLoginAttempts = 0;
|
2022-07-12 15:36:38 -04:00
|
|
|
|
|
|
|
|
bool bIsUsable = false;
|
|
|
|
|
bool bReadOnly = false;
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
static inline FHttpCacheStore* AnyInstance = nullptr;
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpClientParams GetDefaultClientParams() const;
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
bool AcquireAccessToken(IHttpClient* Client = nullptr);
|
2023-01-18 17:59:46 -05:00
|
|
|
void SetAccessTokenAndUnlock(FScopeLock &Lock, FStringView Token, double RefreshDelay = 0.0);
|
2023-01-11 14:28:01 -05:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
enum class EOperationCategory
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
Get,
|
2022-06-29 21:31:09 -04:00
|
|
|
Put,
|
2022-04-05 16:43:41 -04:00
|
|
|
};
|
2022-07-18 12:23:16 -04:00
|
|
|
|
|
|
|
|
class FHttpOperation;
|
|
|
|
|
|
2022-12-04 02:26:39 -05:00
|
|
|
TUniquePtr<FHttpOperation> WaitForHttpOperation(EOperationCategory Category);
|
2022-01-11 11:57:38 -05:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void PutCacheRecordAsync(IRequestOwner& Owner, const FCachePutRequest& Request, FOnCachePutComplete&& OnComplete);
|
|
|
|
|
void PutCacheValueAsync(IRequestOwner& Owner, const FCachePutValueRequest& Request, FOnCachePutValueComplete&& OnComplete);
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
void GetCacheRecordAsync(
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
const FSharedString& Name,
|
|
|
|
|
const FCacheKey& Key,
|
|
|
|
|
const FCacheRecordPolicy& Policy,
|
|
|
|
|
uint64 UserData,
|
2023-06-22 12:55:58 -04:00
|
|
|
FOnCacheGetComplete&& OnComplete);
|
2022-02-02 07:35:19 -05:00
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
void GetCacheValueAsync(
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
FSharedString Name,
|
2022-02-02 07:35:19 -05:00
|
|
|
const FCacheKey& Key,
|
|
|
|
|
ECachePolicy Policy,
|
2023-06-22 12:55:58 -04:00
|
|
|
ERequestOp RequestOp,
|
2022-04-05 16:43:41 -04:00
|
|
|
uint64 UserData,
|
|
|
|
|
FOnCacheGetValueComplete&& OnComplete);
|
2022-02-02 07:35:19 -05:00
|
|
|
|
2023-08-15 11:49:22 -04:00
|
|
|
void FinishChunkRequest(
|
|
|
|
|
const FCacheGetChunkRequest& Request,
|
|
|
|
|
EStatus Status,
|
|
|
|
|
const FValue& Value,
|
|
|
|
|
FCompressedBufferReader& ValueReader,
|
|
|
|
|
const TSharedRef<FOnCacheGetChunkComplete>& SharedOnComplete);
|
|
|
|
|
|
|
|
|
|
void GetChunkGroupAsync(
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
const FCacheGetChunkRequest* StartRequest,
|
|
|
|
|
const FCacheGetChunkRequest* EndRequest,
|
|
|
|
|
TSharedRef<FOnCacheGetChunkComplete>& SharedOnComplete);
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
class FHealthCheckOp;
|
2022-04-05 16:43:41 -04:00
|
|
|
class FPutPackageOp;
|
|
|
|
|
class FGetRecordOp;
|
2023-06-21 13:32:34 -04:00
|
|
|
class FGetValueOp;
|
|
|
|
|
class FExistsBatchOp;
|
2022-04-05 14:18:39 -04:00
|
|
|
};
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
// FHttpCacheStore::FHttpOperation
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
class FHttpCacheStore::FHttpOperation final
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FHttpOperation(const FHttpOperation&) = delete;
|
|
|
|
|
FHttpOperation& operator=(const FHttpOperation&) = delete;
|
|
|
|
|
|
|
|
|
|
explicit FHttpOperation(THttpUniquePtr<IHttpRequest>&& InRequest)
|
|
|
|
|
: Request(MoveTemp(InRequest))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare Request
|
|
|
|
|
|
|
|
|
|
void SetUri(FAnsiStringView Uri) { Request->SetUri(Uri); }
|
|
|
|
|
void SetMethod(EHttpMethod Method) { Request->SetMethod(Method); }
|
|
|
|
|
void AddHeader(FAnsiStringView Name, FAnsiStringView Value) { Request->AddHeader(Name, Value); }
|
|
|
|
|
void SetBody(const FCompositeBuffer& Body) { Request->SetBody(Body); }
|
|
|
|
|
void SetContentType(EHttpMediaType Type) { Request->SetContentType(Type); }
|
|
|
|
|
void AddAcceptType(EHttpMediaType Type) { Request->AddAcceptType(Type); }
|
|
|
|
|
void SetExpectedErrorCodes(TConstArrayView<int32> Codes) { ExpectedErrorCodes = Codes; }
|
|
|
|
|
|
|
|
|
|
// Send Request
|
|
|
|
|
|
|
|
|
|
void Send();
|
|
|
|
|
void SendAsync(IRequestOwner& Owner, TUniqueFunction<void ()>&& OnComplete);
|
|
|
|
|
|
|
|
|
|
// Consume Response
|
|
|
|
|
|
|
|
|
|
int32 GetStatusCode() const { return Response->GetStatusCode(); }
|
|
|
|
|
EHttpErrorCode GetErrorCode() const { return Response->GetErrorCode(); }
|
|
|
|
|
EHttpMediaType GetContentType() const { return Response->GetContentType(); }
|
|
|
|
|
FAnsiStringView GetHeader(FAnsiStringView Name) const { return Response->GetHeader(Name); }
|
|
|
|
|
FSharedBuffer GetBody() const { return ResponseBody; }
|
|
|
|
|
FString GetBodyAsString() const;
|
|
|
|
|
TSharedPtr<FJsonObject> GetBodyAsJson() const;
|
2023-06-22 12:55:58 -04:00
|
|
|
void GetStats(FRequestStats& OutStats) const;
|
2022-07-18 12:23:16 -04:00
|
|
|
|
|
|
|
|
friend FStringBuilderBase& operator<<(FStringBuilderBase& Builder, const FHttpOperation& Operation)
|
|
|
|
|
{
|
|
|
|
|
check(Operation.Response);
|
|
|
|
|
return Builder << *Operation.Response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
class FHttpOperationReceiver;
|
|
|
|
|
class FAsyncHttpOperationReceiver;
|
|
|
|
|
|
|
|
|
|
FSharedBuffer ResponseBody;
|
|
|
|
|
THttpUniquePtr<IHttpRequest> Request;
|
|
|
|
|
THttpUniquePtr<IHttpResponse> Response;
|
|
|
|
|
TArray<int32, TInlineAllocator<4>> ExpectedErrorCodes;
|
|
|
|
|
uint32 AttemptCount = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class FHttpCacheStore::FHttpOperation::FHttpOperationReceiver final : public IHttpReceiver
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FHttpOperationReceiver(const FHttpOperationReceiver&) = delete;
|
|
|
|
|
FHttpOperationReceiver& operator=(const FHttpOperationReceiver&) = delete;
|
|
|
|
|
|
|
|
|
|
explicit FHttpOperationReceiver(FHttpOperation* InOperation, IHttpReceiver* InNext = nullptr)
|
|
|
|
|
: Operation(InOperation)
|
|
|
|
|
, Next(InNext)
|
|
|
|
|
, BodyReceiver(BodyArray, this)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FHttpOperation* GetOperation() const { return Operation; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
IHttpReceiver* OnCreate(IHttpResponse& LocalResponse) final
|
|
|
|
|
{
|
|
|
|
|
++Operation->AttemptCount;
|
|
|
|
|
return &BodyReceiver;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IHttpReceiver* OnComplete(IHttpResponse& LocalResponse) final
|
|
|
|
|
{
|
|
|
|
|
Operation->ResponseBody = MakeSharedBufferFromArray(MoveTemp(BodyArray));
|
|
|
|
|
|
|
|
|
|
LogResponse(LocalResponse);
|
|
|
|
|
|
|
|
|
|
if (!ShouldRetry(LocalResponse))
|
|
|
|
|
{
|
|
|
|
|
Operation->Request.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ShouldRetry(IHttpResponse& LocalResponse) const
|
|
|
|
|
{
|
|
|
|
|
if (Operation->AttemptCount >= UE_HTTPDDC_MAX_ATTEMPTS || ShouldAbortForShutdown())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (LocalResponse.GetErrorCode() == EHttpErrorCode::TimedOut)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Too many requests, make a new attempt.
|
|
|
|
|
if (LocalResponse.GetStatusCode() == 429)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LogResponse(IHttpResponse& LocalResponse) const
|
|
|
|
|
{
|
|
|
|
|
if (UE_LOG_ACTIVE(LogDerivedDataCache, Display))
|
|
|
|
|
{
|
|
|
|
|
const int32 StatusCode = LocalResponse.GetStatusCode();
|
2023-08-23 23:38:40 -04:00
|
|
|
const bool bUnexpectedError = !((StatusCode >= 200 && StatusCode < 300) || Operation->ExpectedErrorCodes.Contains(StatusCode));
|
2022-07-18 12:23:16 -04:00
|
|
|
|
|
|
|
|
TStringBuilder<80> StatsText;
|
2023-08-23 23:38:40 -04:00
|
|
|
if (bUnexpectedError || UE_LOG_ACTIVE(LogDerivedDataCache, Verbose))
|
2022-07-18 12:23:16 -04:00
|
|
|
{
|
|
|
|
|
const FHttpResponseStats& Stats = LocalResponse.GetStats();
|
|
|
|
|
if (Stats.SendSize)
|
|
|
|
|
{
|
|
|
|
|
StatsText << TEXTVIEW("sent ") << Stats.SendSize << TEXTVIEW(" bytes, ");
|
|
|
|
|
}
|
|
|
|
|
if (Stats.RecvSize)
|
|
|
|
|
{
|
|
|
|
|
StatsText << TEXTVIEW("received ") << Stats.RecvSize << TEXTVIEW(" bytes, ");
|
|
|
|
|
}
|
2022-11-08 16:52:32 -05:00
|
|
|
StatsText.Appendf(TEXT("%.3f seconds %.3f|%.3f|%.3f|%.3f"), Stats.TotalTime, Stats.NameResolveTime, Stats.ConnectTime, Stats.TlsConnectTime, Stats.StartTransferTime);
|
2022-07-18 12:23:16 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-23 23:38:40 -04:00
|
|
|
if (bUnexpectedError)
|
2022-07-18 12:23:16 -04:00
|
|
|
{
|
|
|
|
|
FString Body = Operation->GetBodyAsString();
|
|
|
|
|
Body.ReplaceCharInline(TEXT('\r'), TEXT(' '));
|
|
|
|
|
Body.ReplaceCharInline(TEXT('\n'), TEXT(' '));
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display,
|
|
|
|
|
TEXT("HTTP: %s (%s) %s"), *WriteToString<256>(LocalResponse), *StatsText, *Body);
|
|
|
|
|
}
|
2023-08-23 23:38:40 -04:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("HTTP: %s (%s)"), *WriteToString<256>(LocalResponse), *StatsText);
|
|
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
FHttpOperation* Operation;
|
|
|
|
|
IHttpReceiver* Next;
|
|
|
|
|
TArray64<uint8> BodyArray;
|
|
|
|
|
FHttpByteArrayReceiver BodyReceiver{BodyArray, this};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class FHttpCacheStore::FHttpOperation::FAsyncHttpOperationReceiver final : public FRequestBase, public IHttpReceiver
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FAsyncHttpOperationReceiver(const FAsyncHttpOperationReceiver&) = delete;
|
|
|
|
|
FAsyncHttpOperationReceiver& operator=(const FAsyncHttpOperationReceiver&) = delete;
|
|
|
|
|
|
|
|
|
|
FAsyncHttpOperationReceiver(FHttpOperation* InOperation, IRequestOwner* InOwner, TUniqueFunction<void ()>&& InOperationComplete)
|
|
|
|
|
: Owner(InOwner)
|
|
|
|
|
, BaseReceiver(InOperation, this)
|
|
|
|
|
, OperationComplete(MoveTemp(InOperationComplete))
|
2022-10-04 11:22:08 -04:00
|
|
|
{}
|
2022-07-18 12:23:16 -04:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// IRequest Interface
|
|
|
|
|
|
|
|
|
|
void SetPriority(EPriority Priority) final {}
|
|
|
|
|
void Cancel() final { Monitor->Cancel(); }
|
|
|
|
|
void Wait() final { Monitor->Wait(); }
|
|
|
|
|
|
|
|
|
|
// IHttpReceiver Interface
|
|
|
|
|
|
|
|
|
|
IHttpReceiver* OnCreate(IHttpResponse& LocalResponse) final
|
|
|
|
|
{
|
|
|
|
|
Monitor = LocalResponse.GetMonitor();
|
|
|
|
|
Owner->Begin(this);
|
|
|
|
|
return &BaseReceiver;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IHttpReceiver* OnComplete(IHttpResponse& LocalResponse) final
|
|
|
|
|
{
|
|
|
|
|
Owner->End(this, [Self = this]
|
|
|
|
|
{
|
|
|
|
|
FHttpOperation* Operation = Self->BaseReceiver.GetOperation();
|
|
|
|
|
if (IHttpRequest* LocalRequest = Operation->Request.Get())
|
|
|
|
|
{
|
|
|
|
|
// Retry as indicated by the request not being reset.
|
|
|
|
|
TRefCountPtr<FAsyncHttpOperationReceiver> Receiver = new FAsyncHttpOperationReceiver(Operation, Self->Owner, MoveTemp(Self->OperationComplete));
|
|
|
|
|
LocalRequest->SendAsync(Receiver, Operation->Response);
|
|
|
|
|
}
|
|
|
|
|
else if (Self->OperationComplete)
|
|
|
|
|
{
|
|
|
|
|
// Launch a task for the completion function since it can execute arbitrary code.
|
|
|
|
|
Self->Owner->LaunchTask(TEXT("HttpOperationComplete"), [Self = TRefCountPtr(Self)]
|
|
|
|
|
{
|
|
|
|
|
Self->OperationComplete();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
IRequestOwner* Owner;
|
|
|
|
|
FHttpOperationReceiver BaseReceiver;
|
|
|
|
|
TUniqueFunction<void ()> OperationComplete;
|
|
|
|
|
TRefCountPtr<IHttpResponseMonitor> Monitor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FHttpOperation::Send()
|
|
|
|
|
{
|
|
|
|
|
FHttpOperationReceiver Receiver(this);
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
Request->Send(&Receiver, Response);
|
|
|
|
|
}
|
|
|
|
|
while (Request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FHttpOperation::SendAsync(IRequestOwner& Owner, TUniqueFunction<void ()>&& OnComplete)
|
|
|
|
|
{
|
|
|
|
|
TRefCountPtr<FAsyncHttpOperationReceiver> Receiver = new FAsyncHttpOperationReceiver(this, &Owner, MoveTemp(OnComplete));
|
|
|
|
|
Request->SendAsync(Receiver, Response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString FHttpCacheStore::FHttpOperation::GetBodyAsString() const
|
|
|
|
|
{
|
|
|
|
|
static_assert(sizeof(uint8) == sizeof(UTF8CHAR));
|
|
|
|
|
const int32 Len = IntCastChecked<int32>(ResponseBody.GetSize());
|
2022-12-04 02:26:39 -05:00
|
|
|
if (GetContentType() == EHttpMediaType::CbObject)
|
|
|
|
|
{
|
2023-01-05 12:01:37 -05:00
|
|
|
if (ValidateCompactBinary(ResponseBody, ECbValidateMode::Default) == ECbValidateError::None)
|
2022-12-04 02:26:39 -05:00
|
|
|
{
|
|
|
|
|
TUtf8StringBuilder<1024> JsonStringBuilder;
|
|
|
|
|
const FCbObject ResponseObject(ResponseBody);
|
|
|
|
|
CompactBinaryToCompactJson(ResponseObject, JsonStringBuilder);
|
|
|
|
|
return JsonStringBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
return FString(Len, (const UTF8CHAR*)ResponseBody.GetData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TSharedPtr<FJsonObject> FHttpCacheStore::FHttpOperation::GetBodyAsJson() const
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr<FJsonObject> JsonObject;
|
|
|
|
|
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(GetBodyAsString());
|
|
|
|
|
FJsonSerializer::Deserialize(JsonReader, JsonObject);
|
|
|
|
|
return JsonObject;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void FHttpCacheStore::FHttpOperation::GetStats(FRequestStats& OutStats) const
|
|
|
|
|
{
|
|
|
|
|
const FHttpResponseStats& Stats = Response->GetStats();
|
|
|
|
|
TUniqueLock Lock(OutStats.Mutex);
|
|
|
|
|
OutStats.PhysicalReadSize += Stats.RecvSize;
|
|
|
|
|
OutStats.PhysicalWriteSize += Stats.SendSize;
|
|
|
|
|
if (const EHttpMethod Method = Response->GetMethod(); Method == EHttpMethod::Get || Method == EHttpMethod::Head)
|
|
|
|
|
{
|
|
|
|
|
OutStats.AddLatency(FMonotonicTimeSpan::FromSeconds(Stats.StartTransferTime));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
// FHttpCacheStore::FHealthCheckOp
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
class FHttpCacheStore::FHealthCheckOp final
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FHealthCheckOp(FHttpCacheStore& CacheStore, IHttpClient& Client)
|
|
|
|
|
: Operation(Client.TryCreateRequest({}))
|
|
|
|
|
, Owner(EPriority::High)
|
|
|
|
|
, Domain(*CacheStore.Domain)
|
|
|
|
|
{
|
|
|
|
|
Operation.SetUri(WriteToAnsiString<256>(CacheStore.EffectiveDomain, ANSITEXTVIEW("/health/ready")));
|
|
|
|
|
Operation.SendAsync(Owner, []{});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsReady()
|
|
|
|
|
{
|
|
|
|
|
Owner.Wait();
|
|
|
|
|
const FString Body = Operation.GetBodyAsString();
|
|
|
|
|
if (Operation.GetStatusCode() == 200)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: HTTP DDC: %s"), Domain, *Body);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: Unable to reach HTTP DDC at %s. %s"),
|
|
|
|
|
Domain, *WriteToString<256>(Operation), *Body);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
FHttpOperation Operation;
|
|
|
|
|
FRequestOwner Owner;
|
|
|
|
|
const TCHAR* Domain;
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
// FHttpCacheStore::FPutPackageOp
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
class FHttpCacheStore::FPutPackageOp final : public FThreadSafeRefCountedObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
2023-06-21 13:32:34 -04:00
|
|
|
struct FResponse
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
EStatus Status = EStatus::Error;
|
|
|
|
|
};
|
2023-06-21 13:32:34 -04:00
|
|
|
using FOnPackageComplete = TUniqueFunction<void (FResponse&& Response)>;
|
|
|
|
|
|
|
|
|
|
static TRefCountPtr<FPutPackageOp> New(FHttpCacheStore& CacheStore, IRequestOwner& Owner, const FSharedString& Name)
|
|
|
|
|
{
|
|
|
|
|
return new FPutPackageOp(CacheStore, Owner, Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Put(const FCacheKey& Key, FCbPackage&& Package, FOnPackageComplete&& OnComplete);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
const FRequestStats& ReadStats() const { return RequestStats; }
|
|
|
|
|
FRequestStats& EditStats() { return RequestStats; }
|
2022-04-05 16:43:41 -04:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
FHttpCacheStore& CacheStore;
|
|
|
|
|
IRequestOwner& Owner;
|
|
|
|
|
const FSharedString Name;
|
2023-06-21 13:32:34 -04:00
|
|
|
|
|
|
|
|
FCacheKey Key;
|
|
|
|
|
FCbObject Object;
|
|
|
|
|
FIoHash ObjectHash;
|
|
|
|
|
FOnPackageComplete OnPackageComplete;
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestStats RequestStats;
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
std::atomic<uint32> SuccessfulBlobUploads = 0;
|
|
|
|
|
std::atomic<uint32> PendingBlobUploads = 0;
|
|
|
|
|
uint32 TotalBlobUploads = 0;
|
2022-04-05 16:43:41 -04:00
|
|
|
|
|
|
|
|
struct FCachePutRefResponse
|
|
|
|
|
{
|
|
|
|
|
TConstArrayView<FIoHash> NeededBlobHashes;
|
|
|
|
|
EStatus Status = EStatus::Error;
|
|
|
|
|
};
|
|
|
|
|
using FOnCachePutRefComplete = TUniqueFunction<void(FCachePutRefResponse&& Response)>;
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
FPutPackageOp(FHttpCacheStore& CacheStore, IRequestOwner& Owner, const FSharedString& Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void BeginPutRef(bool bFinalize, FOnCachePutRefComplete&& OnComplete);
|
|
|
|
|
void EndPutRef(TUniquePtr<FHttpOperation> Operation, bool bFinalize, FOnCachePutRefComplete&& OnComplete);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void BeginPutBlobs(FCbPackage&& Package, FCachePutRefResponse&& Response);
|
2023-06-22 12:55:58 -04:00
|
|
|
void EndPutBlob(FHttpOperation& Operation, uint64 LogicalSize);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void EndPutRefFinalize(FCachePutRefResponse&& Response);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void EndPut(EStatus Status);
|
|
|
|
|
};
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
FHttpCacheStore::FPutPackageOp::FPutPackageOp(FHttpCacheStore& InCacheStore, IRequestOwner& InOwner, const FSharedString& InName)
|
2022-04-05 16:43:41 -04:00
|
|
|
: CacheStore(InCacheStore)
|
|
|
|
|
, Owner(InOwner)
|
|
|
|
|
, Name(InName)
|
|
|
|
|
{
|
2023-06-23 10:51:52 -04:00
|
|
|
RequestStats.Name = Name;
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void FHttpCacheStore::FPutPackageOp::Put(const FCacheKey& InKey, FCbPackage&& Package, FOnPackageComplete&& OnComplete)
|
|
|
|
|
{
|
|
|
|
|
Key = InKey;
|
|
|
|
|
Object = Package.GetObject();
|
|
|
|
|
ObjectHash = Package.GetObjectHash();
|
|
|
|
|
OnPackageComplete = MoveTemp(OnComplete);
|
|
|
|
|
BeginPutRef(/*bFinalize*/ false, [Self = TRefCountPtr(this), Package = MoveTemp(Package)](FCachePutRefResponse&& Response) mutable
|
|
|
|
|
{
|
|
|
|
|
return Self->BeginPutBlobs(MoveTemp(Package), MoveTemp(Response));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FPutPackageOp::BeginPutRef(bool bFinalize, FOnCachePutRefComplete&& OnComplete)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2022-07-22 10:54:37 -04:00
|
|
|
TAnsiStringBuilder<64> Bucket;
|
|
|
|
|
Algo::Transform(Key.Bucket.ToString(), AppendChars(Bucket), FCharAnsi::ToLower);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
TAnsiStringBuilder<256> RefsUri;
|
2022-09-14 18:25:46 -04:00
|
|
|
RefsUri << CacheStore.EffectiveDomain << ANSITEXTVIEW("/api/v1/refs/") << CacheStore.Namespace << '/' << Bucket << '/' << Key.Hash;
|
2022-04-05 16:43:41 -04:00
|
|
|
if (bFinalize)
|
|
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
RefsUri << ANSITEXTVIEW("/finalize/") << ObjectHash;
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2022-12-04 02:26:39 -05:00
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Put);
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
|
|
|
|
LocalOperation.SetUri(RefsUri);
|
|
|
|
|
if (bFinalize)
|
|
|
|
|
{
|
|
|
|
|
LocalOperation.SetMethod(EHttpMethod::Post);
|
|
|
|
|
LocalOperation.SetContentType(EHttpMediaType::FormUrlEncoded);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LocalOperation.SetMethod(EHttpMethod::Put);
|
|
|
|
|
LocalOperation.SetContentType(EHttpMediaType::CbObject);
|
|
|
|
|
LocalOperation.AddHeader(ANSITEXTVIEW("X-Jupiter-IoHash"), WriteToAnsiString<48>(ObjectHash));
|
|
|
|
|
LocalOperation.SetBody(Object.GetBuffer());
|
|
|
|
|
}
|
|
|
|
|
LocalOperation.AddAcceptType(EHttpMediaType::Json);
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation), bFinalize, OnComplete = MoveTemp(OnComplete)]() mutable
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Operation->GetStats(Self->RequestStats);
|
2023-06-21 13:32:34 -04:00
|
|
|
Self->EndPutRef(MoveTemp(Operation), bFinalize, MoveTemp(OnComplete));
|
2022-07-18 12:23:16 -04:00
|
|
|
});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void FHttpCacheStore::FPutPackageOp::EndPutRef(
|
|
|
|
|
TUniquePtr<FHttpOperation> Operation,
|
|
|
|
|
bool bFinalize,
|
|
|
|
|
FOnCachePutRefComplete&& OnComplete)
|
|
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_PutPackage_EndPutRef);
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
if (const int32 StatusCode = Operation->GetStatusCode(); StatusCode < 200 || StatusCode > 204)
|
2023-06-21 13:32:34 -04:00
|
|
|
{
|
|
|
|
|
const EStatus Status = Operation->GetErrorCode() == EHttpErrorCode::Canceled ? EStatus::Canceled : EStatus::Error;
|
2023-06-22 12:55:58 -04:00
|
|
|
return OnComplete({{}, Status});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
|
|
|
|
TArray<FIoHash> NeededBlobHashes;
|
|
|
|
|
|
|
|
|
|
// Useful when debugging issues related to compressed/uncompressed blobs being returned from Jupiter
|
|
|
|
|
static const bool bHttpCacheAlwaysPut = FParse::Param(FCommandLine::Get(), TEXT("HttpCacheAlwaysPut"));
|
|
|
|
|
|
|
|
|
|
if (bHttpCacheAlwaysPut && !bFinalize)
|
|
|
|
|
{
|
|
|
|
|
Object.IterateAttachments([&NeededBlobHashes](FCbFieldView AttachmentFieldView)
|
|
|
|
|
{
|
|
|
|
|
FIoHash AttachmentHash = AttachmentFieldView.AsHash();
|
|
|
|
|
if (!AttachmentHash.IsZero())
|
|
|
|
|
{
|
|
|
|
|
NeededBlobHashes.Add(AttachmentHash);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else if (TSharedPtr<FJsonObject> ResponseObject = Operation->GetBodyAsJson())
|
|
|
|
|
{
|
|
|
|
|
TArray<FString> NeedsArrayStrings;
|
|
|
|
|
ResponseObject->TryGetStringArrayField(TEXT("needs"), NeedsArrayStrings);
|
|
|
|
|
|
|
|
|
|
NeededBlobHashes.Reserve(NeedsArrayStrings.Num());
|
|
|
|
|
for (const FString& NeededString : NeedsArrayStrings)
|
|
|
|
|
{
|
|
|
|
|
FIoHash BlobHash;
|
|
|
|
|
LexFromString(BlobHash, *NeededString);
|
|
|
|
|
if (!BlobHash.IsZero())
|
|
|
|
|
{
|
|
|
|
|
NeededBlobHashes.Add(BlobHash);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
|
|
|
|
OnComplete({NeededBlobHashes, EStatus::Ok});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FPutPackageOp::BeginPutBlobs(FCbPackage&& Package, FCachePutRefResponse&& Response)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
if (Response.Status != EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
if (Response.Status == EStatus::Error)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Failed to put reference object for put of %s from '%s'"),
|
2023-06-21 13:32:34 -04:00
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
return EndPut(Response.Status);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
|
|
|
|
// TODO: blob uploading and finalization should be replaced with a single batch compressed blob upload endpoint in the future.
|
|
|
|
|
TStringBuilder<128> ExpectedHashes;
|
|
|
|
|
bool bExpectedHashesSerialized = false;
|
|
|
|
|
|
|
|
|
|
// Needed blob upload (if any missing)
|
2023-06-22 12:55:58 -04:00
|
|
|
TArray<FCompressedBuffer> Blobs;
|
2022-04-05 16:43:41 -04:00
|
|
|
for (const FIoHash& NeededBlobHash : Response.NeededBlobHashes)
|
|
|
|
|
{
|
|
|
|
|
if (const FCbAttachment* Attachment = Package.FindAttachment(NeededBlobHash))
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FCompressedBuffer Blob;
|
2022-04-05 16:43:41 -04:00
|
|
|
if (Attachment->IsCompressedBinary())
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Blob = Attachment->AsCompressedBinary();
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
else if (Attachment->IsBinary())
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Blob = FValue::Compress(Attachment->AsCompositeBinary()).GetData();
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Blob = FValue::Compress(Attachment->AsObject().GetBuffer()).GetData();
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
Blobs.Emplace(MoveTemp(Blob));
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!bExpectedHashesSerialized)
|
|
|
|
|
{
|
|
|
|
|
for (const FCbAttachment& PackageAttachment : Package.GetAttachments())
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
ExpectedHashes << PackageAttachment.GetHash() << TEXTVIEW(", ");
|
|
|
|
|
}
|
|
|
|
|
if (ExpectedHashes.Len() >= 2)
|
|
|
|
|
{
|
|
|
|
|
ExpectedHashes.RemoveSuffix(2);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
bExpectedHashesSerialized = true;
|
|
|
|
|
}
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Server reported needed hash '%s' that is outside the set of expected hashes (%s) for put of %s from '%s'"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*CacheStore.Domain, *WriteToString<96>(NeededBlobHash), *ExpectedHashes, *WriteToString<96>(Key), *Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
if (Blobs.IsEmpty())
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
RequestTimer.Stop();
|
|
|
|
|
return EndPut(EStatus::Ok);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
TotalBlobUploads = Blobs.Num();
|
2023-06-21 13:32:34 -04:00
|
|
|
PendingBlobUploads.store(TotalBlobUploads, std::memory_order_relaxed);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
|
|
|
|
FRequestBarrier Barrier(Owner);
|
2023-06-22 12:55:58 -04:00
|
|
|
for (const FCompressedBuffer& Blob : Blobs)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2022-12-04 02:26:39 -05:00
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Put);
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
2023-06-22 12:55:58 -04:00
|
|
|
LocalOperation.SetUri(WriteToAnsiString<256>(CacheStore.EffectiveDomain, ANSITEXTVIEW("/api/v1/compressed-blobs/"), CacheStore.Namespace, '/', Blob.GetRawHash()));
|
2022-07-18 12:23:16 -04:00
|
|
|
LocalOperation.SetMethod(EHttpMethod::Put);
|
|
|
|
|
LocalOperation.SetContentType(EHttpMediaType::CompressedBinary);
|
2023-06-22 12:55:58 -04:00
|
|
|
LocalOperation.SetBody(Blob.GetCompressed());
|
|
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation), LogicalSize = Blob.GetRawSize()]
|
2022-07-18 12:23:16 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Operation->GetStats(Self->RequestStats);
|
|
|
|
|
Self->EndPutBlob(*Operation, LogicalSize);
|
2022-07-18 12:23:16 -04:00
|
|
|
});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void FHttpCacheStore::FPutPackageOp::EndPutBlob(FHttpOperation& Operation, uint64 LogicalSize)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
const int32 StatusCode = Operation.GetStatusCode();
|
|
|
|
|
if (StatusCode >= 200 && StatusCode <= 204)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
SuccessfulBlobUploads.fetch_add(1, std::memory_order_relaxed);
|
2023-06-22 12:55:58 -04:00
|
|
|
TUniqueLock Lock(RequestStats.Mutex);
|
|
|
|
|
RequestStats.LogicalWriteSize += LogicalSize;
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (PendingBlobUploads.fetch_sub(1, std::memory_order_relaxed) == 1)
|
|
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
const uint32 LocalSuccessfulBlobUploads = SuccessfulBlobUploads.load(std::memory_order_relaxed);
|
2022-04-05 16:43:41 -04:00
|
|
|
if (Owner.IsCanceled())
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
EndPut(EStatus::Canceled);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
else if (LocalSuccessfulBlobUploads == TotalBlobUploads)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
BeginPutRef(/*bFinalize*/ true, [Self = TRefCountPtr(this)](FCachePutRefResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
return Self->EndPutRefFinalize(MoveTemp(Response));
|
|
|
|
|
});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
const uint32 FailedBlobUploads = TotalBlobUploads - LocalSuccessfulBlobUploads;
|
2022-04-05 16:43:41 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Failed to put %d/%d blobs for put of %s from '%s'"),
|
2022-05-31 22:01:53 -04:00
|
|
|
*CacheStore.Domain, FailedBlobUploads, TotalBlobUploads, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
EndPut(EStatus::Error);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void FHttpCacheStore::FPutPackageOp::EndPutRefFinalize(FCachePutRefResponse&& Response)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
if (Response.Status == EStatus::Error)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Failed to finalize reference object for put of %s from '%s'"),
|
2022-05-31 22:01:53 -04:00
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
return EndPut(Response.Status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FPutPackageOp::EndPut(EStatus Status)
|
|
|
|
|
{
|
|
|
|
|
RequestStats.EndTime = FMonotonicTimePoint::Now();
|
|
|
|
|
RequestStats.Status = Status;
|
|
|
|
|
OnPackageComplete({Status});
|
|
|
|
|
if (CacheStore.StoreStats)
|
|
|
|
|
{
|
|
|
|
|
CacheStore.StoreStats->AddRequest(RequestStats);
|
|
|
|
|
}
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
// FHttpCacheStore::FGetRecordOp
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
class FHttpCacheStore::FGetRecordOp final : public FThreadSafeRefCountedObject
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
public:
|
|
|
|
|
static TRefCountPtr<FGetRecordOp> New(FHttpCacheStore& CacheStore, IRequestOwner& Owner, const FSharedString& Name)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
return new FGetRecordOp(CacheStore, Owner, Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FRecordResponse
|
|
|
|
|
{
|
|
|
|
|
FCacheRecord Record;
|
|
|
|
|
EStatus Status = EStatus::Error;
|
|
|
|
|
};
|
|
|
|
|
using FOnRecordComplete = TUniqueFunction<void (FRecordResponse&& Response)>;
|
|
|
|
|
|
|
|
|
|
void GetRecordOnly(const FCacheKey& Key, const ECachePolicy RecordPolicy, FOnRecordComplete&& OnComplete);
|
|
|
|
|
void GetRecord(const FCacheKey& Key, const FCacheRecordPolicy& Policy, FOnRecordComplete&& OnComplete);
|
|
|
|
|
|
|
|
|
|
struct FValueResponse
|
|
|
|
|
{
|
|
|
|
|
FValueWithId Value;
|
|
|
|
|
EStatus Status = EStatus::Error;
|
|
|
|
|
};
|
|
|
|
|
using FOnValueComplete = TUniqueFunction<void (FValueResponse&& Response)>;
|
|
|
|
|
|
|
|
|
|
void GetValues(TConstArrayView<FValueWithId> Values, FOnValueComplete&& OnComplete);
|
|
|
|
|
void GetValuesExist(TConstArrayView<FValueWithId> Values, FOnValueComplete&& OnComplete);
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
const FRequestStats& ReadStats() const { return RequestStats; }
|
|
|
|
|
FRequestStats& EditStats() { return RequestStats; }
|
|
|
|
|
void RecordStats(EStatus Status);
|
|
|
|
|
|
2023-08-15 11:49:22 -04:00
|
|
|
int32 GetFailedValues() const { return FailedValues; }
|
|
|
|
|
void PrepareForPendingValues(int32 InPendingValues) { PendingValues = InPendingValues; }
|
|
|
|
|
bool FinishPendingValueFetch(const FValueWithId& Value, bool bAppendToPackage);
|
|
|
|
|
bool FinishPendingValueExists(EStatus Status);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
private:
|
|
|
|
|
FGetRecordOp(FHttpCacheStore& CacheStore, IRequestOwner& Owner, const FSharedString& Name);
|
|
|
|
|
|
|
|
|
|
void EndGetRef(TUniquePtr<FHttpOperation> Operation);
|
|
|
|
|
|
|
|
|
|
void BeginGetValues(const FCacheRecord& Record, const FCacheRecordPolicy& Policy, FOnRecordComplete&& OnComplete);
|
|
|
|
|
void EndGetValues(const FCacheRecordPolicy& Policy, EStatus Status);
|
|
|
|
|
|
|
|
|
|
FHttpCacheStore& CacheStore;
|
|
|
|
|
IRequestOwner& Owner;
|
|
|
|
|
FSharedString Name;
|
|
|
|
|
FCacheKey Key;
|
|
|
|
|
FCbPackage Package;
|
|
|
|
|
FOnRecordComplete OnRecordComplete;
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
FRequestStats RequestStats;
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
int32 PendingValues = 0;
|
|
|
|
|
int32 FailedValues = 0;
|
|
|
|
|
mutable FMutex Mutex;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FHttpCacheStore::FGetRecordOp::FGetRecordOp(FHttpCacheStore& InCacheStore, IRequestOwner& InOwner, const FSharedString& InName)
|
|
|
|
|
: CacheStore(InCacheStore)
|
|
|
|
|
, Owner(InOwner)
|
|
|
|
|
, Name(InName)
|
|
|
|
|
{
|
2023-06-23 10:51:52 -04:00
|
|
|
RequestStats.Name = Name;
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetRecordOp::GetRecordOnly(const FCacheKey& InKey, const ECachePolicy RecordPolicy, FOnRecordComplete&& InOnComplete)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
if (!CacheStore.IsUsable())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose,
|
|
|
|
|
TEXT("%s: Skipped get of %s from '%s' because this cache store is not available"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return InOnComplete({FCacheRecordBuilder(Key).Build(), EStatus::Error});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip the request if querying the cache is disabled.
|
|
|
|
|
if (!EnumHasAnyFlags(RecordPolicy, ECachePolicy::QueryRemote))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: Skipped get of %s from '%s' due to cache policy"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return InOnComplete({FCacheRecordBuilder(Key).Build(), EStatus::Error});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CacheStore.DebugOptions.ShouldSimulateGetMiss(Key))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for get of %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return InOnComplete({FCacheRecordBuilder(Key).Build(), EStatus::Error});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key = InKey;
|
|
|
|
|
OnRecordComplete = MoveTemp(InOnComplete);
|
2023-07-24 16:52:05 -04:00
|
|
|
RequestStats.Bucket = Key.Bucket;
|
2023-06-21 13:32:34 -04:00
|
|
|
|
|
|
|
|
TAnsiStringBuilder<64> Bucket;
|
|
|
|
|
Algo::Transform(Key.Bucket.ToString(), AppendChars(Bucket), FCharAnsi::ToLower);
|
|
|
|
|
|
|
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Get);
|
|
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
|
|
|
|
LocalOperation.SetUri(WriteToAnsiString<256>(CacheStore.EffectiveDomain, ANSITEXTVIEW("/api/v1/refs/"), CacheStore.Namespace, '/', Bucket, '/', Key.Hash));
|
|
|
|
|
LocalOperation.SetMethod(EHttpMethod::Get);
|
|
|
|
|
LocalOperation.AddAcceptType(EHttpMediaType::CbObject);
|
|
|
|
|
LocalOperation.SetExpectedErrorCodes({404});
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation)]() mutable
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Operation->GetStats(Self->RequestStats);
|
2023-06-21 13:32:34 -04:00
|
|
|
Self->EndGetRef(MoveTemp(Operation));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void FHttpCacheStore::FGetRecordOp::EndGetRef(TUniquePtr<FHttpOperation> Operation)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_GetPackage_EndGetRef);
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
FOptionalCacheRecord Record;
|
|
|
|
|
EStatus Status = EStatus::Error;
|
|
|
|
|
ON_SCOPE_EXIT
|
|
|
|
|
{
|
|
|
|
|
Operation.Reset();
|
|
|
|
|
if (Record.IsNull())
|
|
|
|
|
{
|
|
|
|
|
Record = FCacheRecordBuilder(Key).Build();
|
|
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
RequestTimer.Stop();
|
2023-06-21 13:32:34 -04:00
|
|
|
FOnRecordComplete LocalOnComplete = MoveTemp(OnRecordComplete);
|
2023-06-22 12:55:58 -04:00
|
|
|
LocalOnComplete({MoveTemp(Record).Get(), Status});
|
2023-06-21 13:32:34 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const int32 StatusCode = Operation->GetStatusCode();
|
|
|
|
|
if (StatusCode < 200 || StatusCode > 204)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache miss with missing package for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FSharedBuffer Body = Operation->GetBody();
|
|
|
|
|
|
|
|
|
|
if (ValidateCompactBinary(Body, ECbValidateMode::Default) != ECbValidateError::None)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Cache miss with invalid package for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Package = FCbPackage(FCbObject(Body));
|
|
|
|
|
Record = FCacheRecord::Load(Package);
|
|
|
|
|
|
|
|
|
|
if (Record.IsNull())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Cache miss with record load failure for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Status = EStatus::Ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetRecordOp::GetRecord(const FCacheKey& LocalKey, const FCacheRecordPolicy& Policy, FOnRecordComplete&& OnComplete)
|
|
|
|
|
{
|
|
|
|
|
GetRecordOnly(LocalKey, Policy.GetRecordPolicy(), [Self = TRefCountPtr(this), Policy, OnComplete = MoveTemp(OnComplete)](FRecordResponse&& Response) mutable
|
|
|
|
|
{
|
|
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
Self->BeginGetValues(Response.Record, Policy, MoveTemp(OnComplete));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
OnComplete(MoveTemp(Response));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-15 11:49:22 -04:00
|
|
|
bool FHttpCacheStore::FGetRecordOp::FinishPendingValueFetch(const FValueWithId& Value, bool bAppendToPackage)
|
|
|
|
|
{
|
|
|
|
|
TDynamicUniqueLock Lock(Mutex);
|
|
|
|
|
const bool bComplete = --PendingValues == 0;
|
|
|
|
|
if (Value.HasData())
|
|
|
|
|
{
|
|
|
|
|
if (bAppendToPackage)
|
|
|
|
|
{
|
|
|
|
|
Package.AddAttachment(FCbAttachment(Value.GetData()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
++FailedValues;
|
|
|
|
|
}
|
|
|
|
|
return bComplete;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FHttpCacheStore::FGetRecordOp::FinishPendingValueExists(EStatus Status)
|
|
|
|
|
{
|
|
|
|
|
TDynamicUniqueLock Lock(Mutex);
|
|
|
|
|
const bool bComplete = --PendingValues == 0;
|
|
|
|
|
if (Status != EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
++FailedValues;
|
|
|
|
|
}
|
|
|
|
|
return bComplete;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void FHttpCacheStore::FGetRecordOp::BeginGetValues(const FCacheRecord& Record, const FCacheRecordPolicy& Policy, FOnRecordComplete&& OnComplete)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
OnRecordComplete = MoveTemp(OnComplete);
|
|
|
|
|
|
|
|
|
|
TArray<FValueWithId> RequiredGets;
|
|
|
|
|
TArray<FValueWithId> RequiredHeads;
|
|
|
|
|
|
|
|
|
|
for (const FValueWithId& Value : Record.GetValues())
|
|
|
|
|
{
|
|
|
|
|
const ECachePolicy ValuePolicy = Policy.GetValuePolicy(Value.GetId());
|
|
|
|
|
if (EnumHasAnyFlags(ValuePolicy, ECachePolicy::QueryRemote))
|
|
|
|
|
{
|
|
|
|
|
(EnumHasAnyFlags(ValuePolicy, ECachePolicy::SkipData) ? RequiredHeads : RequiredGets).Emplace(Value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-15 11:49:22 -04:00
|
|
|
PrepareForPendingValues(RequiredGets.Num() + RequiredHeads.Num());
|
2023-06-21 13:32:34 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
RequestTimer.Stop();
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
if (PendingValues == 0)
|
|
|
|
|
{
|
|
|
|
|
return EndGetValues(Policy, EStatus::Ok);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GetValues(RequiredGets, [Self = TRefCountPtr(this), Policy](FValueResponse&& Response)
|
|
|
|
|
{
|
2023-08-15 11:49:22 -04:00
|
|
|
if (Self->FinishPendingValueFetch(Response.Value, true))
|
2023-06-21 13:32:34 -04:00
|
|
|
{
|
|
|
|
|
Self->EndGetValues(Policy, Response.Status);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
GetValuesExist(RequiredHeads, [Self = TRefCountPtr(this), Policy](FValueResponse&& Response)
|
|
|
|
|
{
|
2023-08-15 11:49:22 -04:00
|
|
|
if (Self->FinishPendingValueExists(Response.Status))
|
2023-06-21 13:32:34 -04:00
|
|
|
{
|
|
|
|
|
Self->EndGetValues(Policy, Response.Status);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetRecordOp::EndGetValues(const FCacheRecordPolicy& Policy, EStatus Status)
|
|
|
|
|
{
|
|
|
|
|
FCacheRecordBuilder RecordBuilder(Key);
|
|
|
|
|
if (FOptionalCacheRecord Record = FCacheRecord::Load(Package))
|
|
|
|
|
{
|
|
|
|
|
if (!EnumHasAnyFlags(Policy.GetRecordPolicy(), ECachePolicy::SkipMeta))
|
|
|
|
|
{
|
|
|
|
|
RecordBuilder.SetMeta(CopyTemp(Record.Get().GetMeta()));
|
|
|
|
|
}
|
|
|
|
|
for (const FValueWithId& Value : Record.Get().GetValues())
|
|
|
|
|
{
|
|
|
|
|
const ECachePolicy ValuePolicy = Policy.GetValuePolicy(Value.GetId());
|
|
|
|
|
if (EnumHasAnyFlags(ValuePolicy, ECachePolicy::QueryRemote) && !EnumHasAnyFlags(ValuePolicy, ECachePolicy::SkipData))
|
|
|
|
|
{
|
|
|
|
|
if (Status == EStatus::Ok && !Value.HasData())
|
|
|
|
|
{
|
|
|
|
|
Status = EStatus::Error;
|
|
|
|
|
}
|
|
|
|
|
RecordBuilder.AddValue(Value);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RecordBuilder.AddValue(Value.RemoveData());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FailedValues)
|
|
|
|
|
{
|
|
|
|
|
Status = EStatus::Error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FOnRecordComplete LocalOnComplete = MoveTemp(OnRecordComplete);
|
2023-06-22 12:55:58 -04:00
|
|
|
LocalOnComplete({RecordBuilder.Build(), Status});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetRecordOp::GetValues(TConstArrayView<FValueWithId> Values, FOnValueComplete&& OnComplete)
|
|
|
|
|
{
|
|
|
|
|
int32 MissingDataCount = 0;
|
|
|
|
|
for (const FValueWithId& Value : Values)
|
|
|
|
|
{
|
|
|
|
|
if (Value.HasData())
|
|
|
|
|
{
|
|
|
|
|
OnComplete({Value, EStatus::Ok});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
++MissingDataCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (MissingDataCount == 0)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
// TODO: Jupiter does not currently provide a batched GET. Once it does, fetch every blob in one request.
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
FRequestBarrier Barrier(Owner);
|
2023-06-21 13:32:34 -04:00
|
|
|
TSharedRef<FOnValueComplete> SharedOnComplete = MakeShared<FOnValueComplete>(MoveTemp(OnComplete));
|
|
|
|
|
for (const FValueWithId& Value : Values)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
if (Value.HasData())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-04 02:26:39 -05:00
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Get);
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
2022-09-14 18:25:46 -04:00
|
|
|
LocalOperation.SetUri(WriteToAnsiString<256>(CacheStore.EffectiveDomain, ANSITEXTVIEW("/api/v1/compressed-blobs/"), CacheStore.Namespace, '/', Value.GetRawHash()));
|
2022-07-18 12:23:16 -04:00
|
|
|
LocalOperation.SetMethod(EHttpMethod::Get);
|
|
|
|
|
LocalOperation.AddAcceptType(EHttpMediaType::Any);
|
|
|
|
|
LocalOperation.SetExpectedErrorCodes({404});
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation), SharedOnComplete, Id = Value.GetId(), RawHash = Value.GetRawHash(), RawSize = Value.GetRawSize()]
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_GetPackage_GetValues_OnResponse);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(Self->RequestStats);
|
|
|
|
|
Operation->GetStats(Self->RequestStats);
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
bool bHit = false;
|
|
|
|
|
FCompressedBuffer CompressedBuffer;
|
2022-07-18 12:23:16 -04:00
|
|
|
if (Operation->GetStatusCode() == 200)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
switch (Operation->GetContentType())
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
case EHttpMediaType::Any:
|
|
|
|
|
case EHttpMediaType::CompressedBinary:
|
|
|
|
|
CompressedBuffer = FCompressedBuffer::FromCompressed(Operation->GetBody());
|
|
|
|
|
bHit = true;
|
|
|
|
|
break;
|
|
|
|
|
case EHttpMediaType::Binary:
|
|
|
|
|
CompressedBuffer = FValue::Compress(Operation->GetBody()).GetData();
|
|
|
|
|
bHit = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
TUniqueLock Lock(Self->RequestStats.Mutex);
|
|
|
|
|
Self->RequestStats.LogicalReadSize += CompressedBuffer.GetRawSize();
|
2022-07-18 12:23:16 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
RequestTimer.Stop();
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
if (bHit)
|
|
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
if (CompressedBuffer.GetRawHash() == RawHash && CompressedBuffer.GetRawSize() == RawSize)
|
2022-07-18 12:23:16 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
SharedOnComplete.Get()({FValueWithId(Id, MoveTemp(CompressedBuffer)), EStatus::Ok});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Display,
|
|
|
|
|
TEXT("%s: Cache miss with corrupted value %s with hash %s for %s from '%s'"),
|
2023-06-21 13:32:34 -04:00
|
|
|
*Self->CacheStore.Domain, *WriteToString<32>(Id), *WriteToString<48>(RawHash),
|
|
|
|
|
*WriteToString<96>(Self->Key), *Self->Name);
|
|
|
|
|
SharedOnComplete.Get()({FValueWithId(Id, RawHash, RawSize), EStatus::Error});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
else if (Operation->GetErrorCode() == EHttpErrorCode::Canceled)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
SharedOnComplete.Get()({FValueWithId(Id, RawHash, RawSize), EStatus::Canceled});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
else
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose,
|
|
|
|
|
TEXT("%s: Cache miss with missing value %s with hash %s for %s from '%s'"),
|
2023-06-21 13:32:34 -04:00
|
|
|
*Self->CacheStore.Domain, *WriteToString<32>(Id), *WriteToString<48>(RawHash),
|
|
|
|
|
*WriteToString<96>(Self->Key), *Self->Name);
|
|
|
|
|
SharedOnComplete.Get()({FValueWithId(Id, RawHash, RawSize), EStatus::Error});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void FHttpCacheStore::FGetRecordOp::GetValuesExist(TConstArrayView<FValueWithId> Values, FOnValueComplete&& OnComplete)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
TArray<FValueWithId> QueryValues;
|
|
|
|
|
for (const FValueWithId& Value : Values)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
if (Value.HasData())
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
OnComplete({Value, EStatus::Ok});
|
|
|
|
|
continue;
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2023-06-21 13:32:34 -04:00
|
|
|
QueryValues.Emplace(Value);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
if (QueryValues.IsEmpty())
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
TAnsiStringBuilder<256> Uri;
|
|
|
|
|
Uri << CacheStore.EffectiveDomain << ANSITEXTVIEW("/api/v1/compressed-blobs/") << CacheStore.Namespace << ANSITEXTVIEW("/exists?");
|
|
|
|
|
for (const FValueWithId& Value : QueryValues)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
Uri << ANSITEXTVIEW("id=") << Value.GetRawHash() << '&';
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2023-06-21 13:32:34 -04:00
|
|
|
Uri.RemoveSuffix(1);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
FRequestBarrier Barrier(Owner);
|
2022-12-04 02:26:39 -05:00
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Get);
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SetUri(Uri);
|
2022-07-18 12:23:16 -04:00
|
|
|
LocalOperation.SetMethod(EHttpMethod::Post);
|
|
|
|
|
LocalOperation.SetContentType(EHttpMediaType::FormUrlEncoded);
|
|
|
|
|
LocalOperation.AddAcceptType(EHttpMediaType::Json);
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation), Values = MoveTemp(QueryValues), OnComplete = MoveTemp(OnComplete)]() mutable
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_DataProbablyExistsBatch_OnHttpRequestComplete);
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(Self->RequestStats);
|
|
|
|
|
Operation->GetStats(Self->RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
const TCHAR* DefaultMessage = TEXT("Cache exists miss for");
|
|
|
|
|
EStatus DefaultStatus = EStatus::Error;
|
|
|
|
|
|
|
|
|
|
if (Operation->GetErrorCode() == EHttpErrorCode::Canceled)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
DefaultMessage = TEXT("Cache exists miss with canceled request for");
|
|
|
|
|
DefaultStatus = EStatus::Canceled;
|
|
|
|
|
}
|
|
|
|
|
else if (const int32 StatusCode = Operation->GetStatusCode(); StatusCode < 200 || StatusCode > 204)
|
|
|
|
|
{
|
|
|
|
|
DefaultMessage = TEXT("Cache exists miss with failed response for");
|
|
|
|
|
}
|
|
|
|
|
else if (TSharedPtr<FJsonObject> ResponseObject = Operation->GetBodyAsJson(); !ResponseObject)
|
|
|
|
|
{
|
|
|
|
|
DefaultMessage = TEXT("Cache exists miss with invalid response for");
|
|
|
|
|
}
|
|
|
|
|
else if (TArray<FString> NeedsArrayStrings; ResponseObject->TryGetStringArrayField(TEXT("needs"), NeedsArrayStrings))
|
|
|
|
|
{
|
|
|
|
|
DefaultMessage = TEXT("Cache exists hit for");
|
|
|
|
|
DefaultStatus = EStatus::Ok;
|
|
|
|
|
|
|
|
|
|
for (const FString& NeedsString : NeedsArrayStrings)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
const FIoHash NeedHash(NeedsString);
|
|
|
|
|
for (auto It = Values.CreateIterator(); It; ++It)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
const FValueWithId& Value = *It;
|
|
|
|
|
if (Value.GetRawHash() == NeedHash)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose,
|
|
|
|
|
TEXT("%s: Cache exists miss with missing value %s with hash %s for %s from '%s'"),
|
2023-06-21 13:32:34 -04:00
|
|
|
*Self->CacheStore.Domain, *WriteToString<32>(Value.GetId()),
|
|
|
|
|
*WriteToString<48>(Value.GetRawHash()), *WriteToString<96>(Self->Key), *Self->Name);
|
|
|
|
|
OnComplete({Value, EStatus::Error});
|
|
|
|
|
It.RemoveCurrentSwap();
|
|
|
|
|
break;
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-21 13:32:34 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
RequestTimer.Stop();
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
for (const FValueWithId& Value : Values)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Verbose,
|
|
|
|
|
TEXT("%s: %s value %s with hash %s for %s from '%s'"),
|
|
|
|
|
*Self->CacheStore.Domain, DefaultMessage, *WriteToString<32>(Value.GetId()),
|
|
|
|
|
*WriteToString<48>(Value.GetRawHash()), *WriteToString<96>(Self->Key), *Self->Name);
|
|
|
|
|
OnComplete({Value, DefaultStatus});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
});
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void FHttpCacheStore::FGetRecordOp::RecordStats(EStatus Status)
|
|
|
|
|
{
|
|
|
|
|
RequestStats.EndTime = FMonotonicTimePoint::Now();
|
|
|
|
|
RequestStats.Status = Status;
|
|
|
|
|
if (CacheStore.StoreStats)
|
|
|
|
|
{
|
|
|
|
|
CacheStore.StoreStats->AddRequest(RequestStats);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
// FHttpCacheStore::FGetValueOp
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
class FHttpCacheStore::FGetValueOp final : public FThreadSafeRefCountedObject
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
public:
|
|
|
|
|
struct FResponse
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
const FSharedString& Name;
|
|
|
|
|
const FCacheKey& Key;
|
|
|
|
|
FValue Value;
|
|
|
|
|
EStatus Status = EStatus::Error;
|
|
|
|
|
};
|
|
|
|
|
using FOnComplete = TUniqueFunction<void (FResponse&& Response)>;
|
|
|
|
|
|
|
|
|
|
static TRefCountPtr<FGetValueOp> New(FHttpCacheStore& CacheStore, IRequestOwner& Owner, const FSharedString& Name)
|
|
|
|
|
{
|
|
|
|
|
return new FGetValueOp(CacheStore, Owner, Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
void Get(const FCacheKey& Key, ECachePolicy Policy, FOnComplete&& OnComplete);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
const FRequestStats& ReadStats() const { return RequestStats; }
|
|
|
|
|
FRequestStats& EditStats() { return RequestStats; }
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
private:
|
|
|
|
|
FGetValueOp(FHttpCacheStore& CacheStore, IRequestOwner& Owner, const FSharedString& Name);
|
|
|
|
|
|
|
|
|
|
void EndGetRef(TUniquePtr<FHttpOperation> Operation);
|
2023-06-22 12:55:58 -04:00
|
|
|
void EndGet(FResponse&& Response);
|
2023-06-21 13:32:34 -04:00
|
|
|
|
|
|
|
|
FHttpCacheStore& CacheStore;
|
|
|
|
|
IRequestOwner& Owner;
|
|
|
|
|
FSharedString Name;
|
|
|
|
|
FCacheKey Key;
|
|
|
|
|
ECachePolicy Policy = ECachePolicy::None;
|
|
|
|
|
FOnComplete OnComplete;
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestStats RequestStats;
|
2023-06-21 13:32:34 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FHttpCacheStore::FGetValueOp::FGetValueOp(FHttpCacheStore& InCacheStore, IRequestOwner& InOwner, const FSharedString& InName)
|
|
|
|
|
: CacheStore(InCacheStore)
|
|
|
|
|
, Owner(InOwner)
|
|
|
|
|
, Name(InName)
|
|
|
|
|
{
|
2023-06-23 10:51:52 -04:00
|
|
|
RequestStats.Name = Name;
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetValueOp::Get(const FCacheKey& InKey, ECachePolicy InPolicy, FOnComplete&& InOnComplete)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
Key = InKey;
|
|
|
|
|
Policy = InPolicy;
|
|
|
|
|
OnComplete = MoveTemp(InOnComplete);
|
|
|
|
|
|
|
|
|
|
const bool bSkipData = EnumHasAnyFlags(Policy, ECachePolicy::SkipData);
|
|
|
|
|
|
|
|
|
|
TAnsiStringBuilder<64> Bucket;
|
|
|
|
|
Algo::Transform(Key.Bucket.ToString(), AppendChars(Bucket), FCharAnsi::ToLower);
|
|
|
|
|
|
|
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Get);
|
|
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
|
|
|
|
LocalOperation.SetUri(WriteToAnsiString<256>(CacheStore.EffectiveDomain, ANSITEXTVIEW("/api/v1/refs/"), CacheStore.Namespace, '/', Bucket, '/', Key.Hash));
|
|
|
|
|
LocalOperation.SetMethod(EHttpMethod::Get);
|
|
|
|
|
if (bSkipData)
|
|
|
|
|
{
|
|
|
|
|
LocalOperation.AddAcceptType(EHttpMediaType::CbObject);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LocalOperation.AddHeader(ANSITEXTVIEW("Accept"), ANSITEXTVIEW("application/x-jupiter-inline"));
|
|
|
|
|
}
|
|
|
|
|
LocalOperation.SetExpectedErrorCodes({404});
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation)]() mutable
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Operation->GetStats(Self->RequestStats);
|
2023-06-21 13:32:34 -04:00
|
|
|
Self->EndGetRef(MoveTemp(Operation));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetValueOp::EndGetRef(TUniquePtr<FHttpOperation> Operation)
|
|
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_GetValue_EndGetRef);
|
|
|
|
|
|
|
|
|
|
const bool bSkipData = EnumHasAnyFlags(Policy, ECachePolicy::SkipData);
|
|
|
|
|
|
|
|
|
|
const int32 StatusCode = Operation->GetStatusCode();
|
|
|
|
|
if (StatusCode < 200 || StatusCode > 204)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache miss with failed HTTP request for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return EndGet({Name, Key, {}, EStatus::Error});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FSharedBuffer Body = Operation->GetBody();
|
|
|
|
|
|
|
|
|
|
if (bSkipData)
|
|
|
|
|
{
|
|
|
|
|
if (ValidateCompactBinary(Body, ECbValidateMode::Default) != ECbValidateError::None)
|
2022-11-16 15:12:51 -05:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Cache miss with invalid package for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return EndGet({Name, Key, {}, EStatus::Error});
|
2022-11-16 15:12:51 -05:00
|
|
|
}
|
2023-06-21 13:32:34 -04:00
|
|
|
|
|
|
|
|
const FCbObjectView Object = FCbObject(Body);
|
|
|
|
|
const FIoHash RawHash = Object["RawHash"].AsHash();
|
|
|
|
|
const uint64 RawSize = Object["RawSize"].AsUInt64(MAX_uint64);
|
|
|
|
|
if (RawHash.IsZero() || RawSize == MAX_uint64)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Cache miss with invalid value for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return EndGet({Name, Key, {}, EStatus::Error});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
EndGet({Name, Key, FValue(RawHash, RawSize), EStatus::Ok});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FCompressedBuffer CompressedBuffer = FCompressedBuffer::FromCompressed(Body);
|
|
|
|
|
|
|
|
|
|
if (!CompressedBuffer)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
2023-06-21 13:32:34 -04:00
|
|
|
if (FAnsiStringView ReceivedHashStr = Operation->GetHeader("X-Jupiter-InlinePayloadHash"); !ReceivedHashStr.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
FIoHash ReceivedHash(ReceivedHashStr);
|
|
|
|
|
FIoHash ComputedHash = FIoHash::HashBuffer(Body.GetView());
|
|
|
|
|
if (ReceivedHash == ComputedHash)
|
|
|
|
|
{
|
|
|
|
|
CompressedBuffer = FCompressedBuffer::Compress(Body);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!CompressedBuffer)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Cache miss with invalid package for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Key), *Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
return EndGet({Name, Key, {}, EStatus::Error});
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
EndGet({Name, Key, FValue(CompressedBuffer), EStatus::Ok});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FGetValueOp::EndGet(FResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
RequestStats.LogicalReadSize += Response.Value.GetRawSize();
|
|
|
|
|
RequestStats.EndTime = FMonotonicTimePoint::Now();
|
|
|
|
|
RequestStats.Status = Response.Status;
|
|
|
|
|
OnComplete(MoveTemp(Response));
|
|
|
|
|
if (CacheStore.StoreStats)
|
|
|
|
|
{
|
|
|
|
|
CacheStore.StoreStats->AddRequest(RequestStats);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
// FHttpCacheStore::FExistsBatchOp
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
|
|
|
class FHttpCacheStore::FExistsBatchOp final : public FThreadSafeRefCountedObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static TRefCountPtr<FExistsBatchOp> New(FHttpCacheStore& CacheStore, IRequestOwner& Owner)
|
|
|
|
|
{
|
|
|
|
|
return new FExistsBatchOp(CacheStore, Owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Exists(TConstArrayView<FCacheGetValueRequest> Requests, FOnCacheGetValueComplete&& OnComplete);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
FExistsBatchOp(FHttpCacheStore& CacheStore, IRequestOwner& Owner);
|
|
|
|
|
|
|
|
|
|
void EndExists(TUniquePtr<FHttpOperation> Operation);
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void EndRequest(const FCacheGetValueRequest& Request, const FValue& Value, EStatus Status);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
FHttpCacheStore& CacheStore;
|
|
|
|
|
IRequestOwner& Owner;
|
|
|
|
|
TArray<FCacheGetValueRequest> Requests;
|
|
|
|
|
FOnCacheGetValueComplete OnComplete;
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestStats RequestStats;
|
2023-06-21 13:32:34 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FHttpCacheStore::FExistsBatchOp::FExistsBatchOp(FHttpCacheStore& InCacheStore, IRequestOwner& InOwner)
|
|
|
|
|
: CacheStore(InCacheStore)
|
|
|
|
|
, Owner(InOwner)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FExistsBatchOp::Exists(TConstArrayView<FCacheGetValueRequest> InRequests, FOnCacheGetValueComplete&& InOnComplete)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
OnComplete = MoveTemp(InOnComplete);
|
|
|
|
|
|
|
|
|
|
Requests.Empty(InRequests.Num());
|
|
|
|
|
for (const FCacheGetValueRequest& Request : InRequests)
|
|
|
|
|
{
|
|
|
|
|
if (!CacheStore.IsUsable())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose,
|
|
|
|
|
TEXT("%s: Skipped exists check of %s from '%s' because this cache store is not available"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
|
|
|
|
OnComplete(Request.MakeResponse(EStatus::Error));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EnumHasAnyFlags(Request.Policy, ECachePolicy::QueryRemote))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: Skipped exists check of %s from '%s' due to cache policy"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
|
|
|
|
OnComplete(Request.MakeResponse(EStatus::Error));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CacheStore.DebugOptions.ShouldSimulateGetMiss(Request.Key))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for get of %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
|
|
|
|
OnComplete(Request.MakeResponse(EStatus::Error));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Requests.Emplace(Request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Requests.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCbWriter BodyWriter;
|
|
|
|
|
BodyWriter.BeginObject();
|
|
|
|
|
BodyWriter.BeginArray(ANSITEXTVIEW("ops"));
|
|
|
|
|
uint32 OpIndex = 0;
|
|
|
|
|
for (const FCacheGetValueRequest& Request : Requests)
|
|
|
|
|
{
|
|
|
|
|
BodyWriter.BeginObject();
|
|
|
|
|
BodyWriter.AddInteger(ANSITEXTVIEW("opId"), OpIndex);
|
|
|
|
|
BodyWriter.AddString(ANSITEXTVIEW("op"), ANSITEXTVIEW("GET"));
|
|
|
|
|
const FCacheKey& Key = Request.Key;
|
|
|
|
|
TAnsiStringBuilder<64> Bucket;
|
|
|
|
|
Algo::Transform(Key.Bucket.ToString(), AppendChars(Bucket), FCharAnsi::ToLower);
|
|
|
|
|
BodyWriter.AddString(ANSITEXTVIEW("bucket"), Bucket);
|
|
|
|
|
BodyWriter.AddString(ANSITEXTVIEW("key"), LexToString(Key.Hash));
|
|
|
|
|
BodyWriter.AddBool(ANSITEXTVIEW("resolveAttachments"), true);
|
|
|
|
|
BodyWriter.EndObject();
|
|
|
|
|
++OpIndex;
|
|
|
|
|
}
|
|
|
|
|
BodyWriter.EndArray();
|
|
|
|
|
BodyWriter.EndObject();
|
|
|
|
|
FCbFieldIterator Body = BodyWriter.Save();
|
|
|
|
|
|
|
|
|
|
TUniquePtr<FHttpOperation> Operation = CacheStore.WaitForHttpOperation(EOperationCategory::Get);
|
|
|
|
|
FHttpOperation& LocalOperation = *Operation;
|
|
|
|
|
LocalOperation.SetUri(WriteToAnsiString<256>(CacheStore.EffectiveDomain, ANSITEXTVIEW("/api/v1/refs/"), CacheStore.Namespace));
|
|
|
|
|
LocalOperation.SetMethod(EHttpMethod::Post);
|
|
|
|
|
LocalOperation.SetContentType(EHttpMediaType::CbObject);
|
|
|
|
|
LocalOperation.AddAcceptType(EHttpMediaType::CbObject);
|
|
|
|
|
LocalOperation.SetBody(FCompositeBuffer(Body.GetOuterBuffer()));
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
2023-06-21 13:32:34 -04:00
|
|
|
LocalOperation.SendAsync(Owner, [Self = TRefCountPtr(this), Operation = MoveTemp(Operation)]() mutable
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Operation->GetStats(Self->RequestStats);
|
2023-06-21 13:32:34 -04:00
|
|
|
Self->EndExists(MoveTemp(Operation));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FExistsBatchOp::EndExists(TUniquePtr<FHttpOperation> Operation)
|
|
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_ExistsBatch_EndExists);
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
// Divide the stats evenly among the requests.
|
|
|
|
|
RequestStats.PhysicalReadSize /= Requests.Num();
|
|
|
|
|
RequestStats.PhysicalWriteSize /= Requests.Num();
|
|
|
|
|
RequestStats.MainThreadTime = FMonotonicTimeSpan::FromSeconds(RequestStats.MainThreadTime.ToSeconds() / Requests.Num());
|
|
|
|
|
RequestStats.OtherThreadTime = FMonotonicTimeSpan::FromSeconds(RequestStats.OtherThreadTime.ToSeconds() / Requests.Num());
|
|
|
|
|
RequestStats.EndTime = FMonotonicTimePoint::Now();
|
|
|
|
|
RequestStats.Type = ERequestType::Value;
|
|
|
|
|
RequestStats.Op = ERequestOp::Get;
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
const int32 OverallStatusCode = Operation->GetStatusCode();
|
|
|
|
|
if (OverallStatusCode < 200 || OverallStatusCode > 204)
|
|
|
|
|
{
|
|
|
|
|
for (const FCacheGetValueRequest& Request : Requests)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache miss with failed HTTP request for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
2023-07-28 10:37:53 -04:00
|
|
|
RequestStats.Bucket = Request.Key.Bucket;
|
2023-06-22 12:55:58 -04:00
|
|
|
EndRequest(Request, {}, EStatus::Error);
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FMemoryView ResponseView = Operation->GetBody();
|
|
|
|
|
if (ValidateCompactBinary(ResponseView, ECbValidateMode::Default) != ECbValidateError::None)
|
|
|
|
|
{
|
|
|
|
|
for (const FCacheGetValueRequest& Request : Requests)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Cache miss with corrupt response for %s from '%s'."),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
2023-07-24 16:52:05 -04:00
|
|
|
RequestStats.Bucket = Request.Key.Bucket;
|
2023-06-22 12:55:58 -04:00
|
|
|
EndRequest(Request, {}, EStatus::Error);
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FCbObjectView ResponseObject(ResponseView.GetData());
|
|
|
|
|
const FCbArrayView Results = ResponseObject[ANSITEXTVIEW("results")].AsArrayView();
|
|
|
|
|
|
|
|
|
|
if (Results.Num() != Requests.Num())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log,
|
|
|
|
|
TEXT("%s: Cache exists returned unexpected quantity of results (expected %d, got %d)."),
|
|
|
|
|
*CacheStore.Domain, Requests.Num(), Results.Num());
|
|
|
|
|
for (const FCacheGetValueRequest& Request : Requests)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Cache miss with invalid response for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
2023-07-24 16:52:05 -04:00
|
|
|
RequestStats.Bucket = Request.Key.Bucket;
|
2023-06-22 12:55:58 -04:00
|
|
|
EndRequest(Request, {}, EStatus::Error);
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FCbFieldView ResultField : Results)
|
|
|
|
|
{
|
|
|
|
|
const FCbObjectView ResultObject = ResultField.AsObjectView();
|
|
|
|
|
const uint32 OpId = ResultObject[ANSITEXTVIEW("opId")].AsUInt32();
|
|
|
|
|
const int32 StatusCode = ResultObject[ANSITEXTVIEW("statusCode")].AsInt32();
|
|
|
|
|
const FCbObjectView Value = ResultObject[ANSITEXTVIEW("response")].AsObjectView();
|
|
|
|
|
|
|
|
|
|
if (OpId >= (uint32)Requests.Num())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Encountered invalid opId %d while querying %d values"),
|
|
|
|
|
*CacheStore.Domain, OpId, Requests.Num());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FCacheGetValueRequest& Request = Requests[int32(OpId)];
|
2023-07-24 16:52:05 -04:00
|
|
|
RequestStats.Bucket = Request.Key.Bucket;
|
2023-06-21 13:32:34 -04:00
|
|
|
|
|
|
|
|
if (StatusCode < 200 || StatusCode > 204)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache miss with unsuccessful response code %d for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, StatusCode, *WriteToString<96>(Request.Key), *Request.Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
EndRequest(Request, {}, EStatus::Error);
|
2023-06-21 13:32:34 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FIoHash RawHash = Value[ANSITEXTVIEW("RawHash")].AsHash();
|
|
|
|
|
const uint64 RawSize = Value[ANSITEXTVIEW("RawSize")].AsUInt64(MAX_uint64);
|
|
|
|
|
if (RawHash.IsZero() || RawSize == MAX_uint64)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display, TEXT("%s: Cache miss with invalid value for %s from '%s'"),
|
|
|
|
|
*CacheStore.Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
EndRequest(Request, {}, EStatus::Error);
|
2023-06-21 13:32:34 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
EndRequest(Request, FValue(RawHash, RawSize), EStatus::Ok);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::FExistsBatchOp::EndRequest(const FCacheGetValueRequest& Request, const FValue& Value, EStatus Status)
|
|
|
|
|
{
|
|
|
|
|
RequestStats.EndTime = FMonotonicTimePoint::Now();
|
|
|
|
|
RequestStats.Status = Status;
|
|
|
|
|
OnComplete({Request.Name, Request.Key, Value, Request.UserData, Status});
|
|
|
|
|
if (CacheStore.StoreStats)
|
|
|
|
|
{
|
|
|
|
|
CacheStore.StoreStats->AddRequest(RequestStats);
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
FHttpCacheStore::FHttpCacheStore(const FHttpCacheStoreParams& Params, ICacheStoreOwner* Owner)
|
2022-05-31 22:01:53 -04:00
|
|
|
: Domain(Params.Host)
|
|
|
|
|
, Namespace(Params.Namespace)
|
|
|
|
|
, OAuthProvider(Params.OAuthProvider)
|
|
|
|
|
, OAuthClientId(Params.OAuthClientId)
|
|
|
|
|
, OAuthSecret(Params.OAuthSecret)
|
|
|
|
|
, OAuthScope(Params.OAuthScope)
|
2022-07-06 05:59:22 -04:00
|
|
|
, OAuthProviderIdentifier(Params.OAuthProviderIdentifier)
|
|
|
|
|
, OAuthAccessToken(Params.OAuthAccessToken)
|
2023-05-04 09:31:50 -04:00
|
|
|
, HttpVersion(Params.HttpVersion)
|
2023-06-21 13:32:34 -04:00
|
|
|
, StoreOwner(Owner)
|
2022-05-31 22:01:53 -04:00
|
|
|
, bReadOnly(Params.bReadOnly)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_Construct);
|
|
|
|
|
|
2022-11-10 10:57:37 -05:00
|
|
|
// Remove any trailing / because constructing a URI will add one.
|
|
|
|
|
while (Domain.RemoveFromEnd(TEXT("/")));
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
EffectiveDomain.Append(Domain);
|
|
|
|
|
TAnsiStringBuilder<256> ResolvedDomain;
|
|
|
|
|
if (Params.bResolveHostCanonicalName && TryResolveCanonicalHost(EffectiveDomain, ResolvedDomain))
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
// Store the URI with the canonical name to pin to one region when using DNS-based region selection.
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display,
|
|
|
|
|
TEXT("%s: Pinned to %hs based on DNS canonical name."), *Domain, *ResolvedDomain);
|
|
|
|
|
EffectiveDomain.Reset();
|
|
|
|
|
EffectiveDomain.Append(ResolvedDomain);
|
|
|
|
|
}
|
2022-02-08 01:56:17 -05:00
|
|
|
|
2022-07-18 23:15:54 -04:00
|
|
|
#if WITH_SSL
|
|
|
|
|
if (!Params.HostPinnedPublicKeys.IsEmpty() && EffectiveDomain.ToView().StartsWith(ANSITEXTVIEW("https://")))
|
|
|
|
|
{
|
|
|
|
|
FSslModule::Get().GetCertificateManager().SetPinnedPublicKeys(FString(GetDomainFromUri(EffectiveDomain)), Params.HostPinnedPublicKeys);
|
|
|
|
|
}
|
|
|
|
|
if (!Params.OAuthPinnedPublicKeys.IsEmpty() && OAuthProvider.StartsWith(TEXT("https://")))
|
|
|
|
|
{
|
|
|
|
|
FSslModule::Get().GetCertificateManager().SetPinnedPublicKeys(FString(GetDomainFromUri(WriteToAnsiString<256>(OAuthProvider))), Params.OAuthPinnedPublicKeys);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
constexpr uint32 MaxTotalConnections = 8;
|
|
|
|
|
FHttpConnectionPoolParams ConnectionPoolParams;
|
|
|
|
|
ConnectionPoolParams.MaxConnections = MaxTotalConnections;
|
|
|
|
|
ConnectionPoolParams.MinConnections = MaxTotalConnections;
|
|
|
|
|
ConnectionPool = IHttpManager::Get().CreateConnectionPool(ConnectionPoolParams);
|
2022-02-08 01:56:17 -05:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpClientParams ClientParams = GetDefaultClientParams();
|
2022-02-08 01:56:17 -05:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
THttpUniquePtr<IHttpClient> Client = ConnectionPool->CreateClient(ClientParams);
|
|
|
|
|
FHealthCheckOp HealthCheck(*this, *Client);
|
|
|
|
|
if (AcquireAccessToken(Client.Get()) && HealthCheck.IsReady())
|
|
|
|
|
{
|
|
|
|
|
ClientParams.MaxRequests = UE_HTTPDDC_GET_REQUEST_POOL_SIZE;
|
|
|
|
|
ClientParams.MinRequests = UE_HTTPDDC_GET_REQUEST_POOL_SIZE;
|
|
|
|
|
GetRequestQueues[0] = FHttpRequestQueue(*ConnectionPool, ClientParams);
|
|
|
|
|
GetRequestQueues[1] = FHttpRequestQueue(*ConnectionPool, ClientParams);
|
2022-02-08 01:56:17 -05:00
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
ClientParams.MaxRequests = UE_HTTPDDC_PUT_REQUEST_POOL_SIZE;
|
|
|
|
|
ClientParams.MinRequests = UE_HTTPDDC_PUT_REQUEST_POOL_SIZE;
|
|
|
|
|
PutRequestQueues[0] = FHttpRequestQueue(*ConnectionPool, ClientParams);
|
|
|
|
|
PutRequestQueues[1] = FHttpRequestQueue(*ConnectionPool, ClientParams);
|
|
|
|
|
|
2022-12-04 02:26:39 -05:00
|
|
|
ClientParams.MaxRequests = UE_HTTPDDC_NONBLOCKING_GET_REQUEST_POOL_SIZE;
|
|
|
|
|
ClientParams.MinRequests = UE_HTTPDDC_NONBLOCKING_GET_REQUEST_POOL_SIZE;
|
|
|
|
|
NonBlockingGetRequestQueue = FHttpRequestQueue(*ConnectionPool, ClientParams);
|
|
|
|
|
|
|
|
|
|
ClientParams.MaxRequests = UE_HTTPDDC_NONBLOCKING_PUT_REQUEST_POOL_SIZE;
|
|
|
|
|
ClientParams.MinRequests = UE_HTTPDDC_NONBLOCKING_PUT_REQUEST_POOL_SIZE;
|
|
|
|
|
NonBlockingPutRequestQueue = FHttpRequestQueue(*ConnectionPool, ClientParams);
|
2022-02-08 01:56:17 -05:00
|
|
|
|
2020-06-23 18:40:00 -04:00
|
|
|
bIsUsable = true;
|
2023-06-21 13:32:34 -04:00
|
|
|
|
|
|
|
|
if (StoreOwner)
|
|
|
|
|
{
|
|
|
|
|
const ECacheStoreFlags Flags = ECacheStoreFlags::Remote | ECacheStoreFlags::Query |
|
|
|
|
|
(Params.bReadOnly ? ECacheStoreFlags::None : ECacheStoreFlags::Store);
|
|
|
|
|
TStringBuilder<256> Path(InPlace, Domain, TEXTVIEW(" ("), Namespace, TEXTVIEW(")"));
|
|
|
|
|
StoreOwner->Add(this, Flags);
|
2023-06-22 12:55:58 -04:00
|
|
|
StoreStats = StoreOwner->CreateStats(this, Flags, TEXTVIEW("Unreal Cloud DDC"), Params.Name, Path);
|
|
|
|
|
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("Domain"), Domain);
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("EffectiveDomain"), WriteToString<128>(EffectiveDomain));
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("Namespace"), Namespace);
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("LoginAttempts"), WriteToString<16>(LoginAttempts));
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("InteractiveLoginAttempts"), WriteToString<16>(InteractiveLoginAttempts));
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("FailedLoginAttempts"), WriteToString<16>(FailedLoginAttempts));
|
2023-06-21 13:32:34 -04:00
|
|
|
}
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
2021-07-19 13:09:47 -04:00
|
|
|
|
|
|
|
|
AnyInstance = this;
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
FHttpCacheStore::~FHttpCacheStore()
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-07-12 15:36:38 -04:00
|
|
|
if (RefreshAccessTokenHandle.IsValid())
|
|
|
|
|
{
|
|
|
|
|
FTSTicker::GetCoreTicker().RemoveTicker(RefreshAccessTokenHandle);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
if (StoreStats)
|
|
|
|
|
{
|
|
|
|
|
StoreOwner->DestroyStats(StoreStats);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-19 13:09:47 -04:00
|
|
|
if (AnyInstance == this)
|
|
|
|
|
{
|
|
|
|
|
AnyInstance = nullptr;
|
|
|
|
|
}
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-04 09:31:50 -04:00
|
|
|
template <typename CharType>
|
|
|
|
|
static bool HttpVersionFromString(EHttpVersion& OutVersion, const TStringView<CharType> String)
|
|
|
|
|
{
|
|
|
|
|
const auto ConvertedString = StringCast<UTF8CHAR, 16>(String.GetData(), String.Len());
|
|
|
|
|
if (ConvertedString == UTF8TEXTVIEW("none"))
|
|
|
|
|
{
|
|
|
|
|
OutVersion = EHttpVersion::None;
|
|
|
|
|
}
|
|
|
|
|
else if (ConvertedString == UTF8TEXTVIEW("http1.0"))
|
|
|
|
|
{
|
|
|
|
|
OutVersion = EHttpVersion::V1_0;
|
|
|
|
|
}
|
|
|
|
|
else if (ConvertedString == UTF8TEXTVIEW("http1.1"))
|
|
|
|
|
{
|
|
|
|
|
OutVersion = EHttpVersion::V1_1;
|
|
|
|
|
}
|
|
|
|
|
else if (ConvertedString == UTF8TEXTVIEW("http2"))
|
|
|
|
|
{
|
|
|
|
|
OutVersion = EHttpVersion::V2;
|
|
|
|
|
}
|
|
|
|
|
else if (ConvertedString == UTF8TEXTVIEW("http2-only"))
|
|
|
|
|
{
|
|
|
|
|
OutVersion = EHttpVersion::V2Only;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryLexFromString(EHttpVersion& OutVersion, FUtf8StringView String) { return HttpVersionFromString(OutVersion, String); }
|
|
|
|
|
bool TryLexFromString(EHttpVersion& OutVersion, FWideStringView String) { return HttpVersionFromString(OutVersion, String); }
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
FHttpClientParams FHttpCacheStore::GetDefaultClientParams() const
|
|
|
|
|
{
|
|
|
|
|
FHttpClientParams ClientParams;
|
2022-12-04 02:26:39 -05:00
|
|
|
ClientParams.DnsCacheTimeout = 15;
|
|
|
|
|
ClientParams.ConnectTimeout = 3 * 1000;
|
2022-07-18 12:23:16 -04:00
|
|
|
ClientParams.LowSpeedLimit = 1024;
|
2022-12-04 02:26:39 -05:00
|
|
|
ClientParams.LowSpeedTime = 10;
|
2022-07-18 12:23:16 -04:00
|
|
|
ClientParams.TlsLevel = EHttpTlsLevel::All;
|
|
|
|
|
ClientParams.bFollowRedirects = true;
|
|
|
|
|
ClientParams.bFollow302Post = true;
|
2023-05-04 09:31:50 -04:00
|
|
|
|
|
|
|
|
EHttpVersion HttpVersionEnum = EHttpVersion::V2;
|
|
|
|
|
TryLexFromString(HttpVersionEnum, HttpVersion);
|
|
|
|
|
ClientParams.Version = HttpVersionEnum;
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
return ClientParams;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
bool FHttpCacheStore::LegacyDebugOptions(FBackendDebugOptions& InOptions)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2021-11-07 23:43:01 -05:00
|
|
|
DebugOptions = InOptions;
|
|
|
|
|
return true;
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
bool FHttpCacheStore::AcquireAccessToken(IHttpClient* Client)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-03-09 16:48:12 -05:00
|
|
|
if (Domain.StartsWith(TEXT("http://localhost")))
|
|
|
|
|
{
|
2022-07-12 15:36:38 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Skipping authorization for connection to localhost."), *Domain);
|
2022-03-09 16:48:12 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
ON_SCOPE_EXIT
|
|
|
|
|
{
|
|
|
|
|
if (StoreStats)
|
|
|
|
|
{
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("LoginAttempts"), WriteToString<16>(LoginAttempts));
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("InteractiveLoginAttempts"), WriteToString<16>(InteractiveLoginAttempts));
|
|
|
|
|
StoreStats->SetAttribute(TEXTVIEW("FailedLoginAttempts"), WriteToString<16>(FailedLoginAttempts));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-03 09:39:24 -04:00
|
|
|
LoginAttempts++;
|
|
|
|
|
|
2022-07-12 15:36:38 -04:00
|
|
|
// Avoid spamming this if the service is down.
|
2020-06-23 18:40:00 -04:00
|
|
|
if (FailedLoginAttempts > UE_HTTPDDC_MAX_FAILED_LOGIN_ATTEMPTS)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_AcquireAccessToken);
|
|
|
|
|
|
2020-06-23 18:40:00 -04:00
|
|
|
// In case many requests wants to update the token at the same time
|
|
|
|
|
// get the current serial while we wait to take the CS.
|
2022-07-12 15:36:38 -04:00
|
|
|
const uint32 WantsToUpdateTokenSerial = Access ? Access->GetSerial() : 0;
|
|
|
|
|
|
|
|
|
|
FScopeLock Lock(&AccessCs);
|
|
|
|
|
|
|
|
|
|
// If the token was updated while we waited to take the lock, then it should now be valid.
|
|
|
|
|
if (Access && Access->GetSerial() > WantsToUpdateTokenSerial)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!OAuthAccessToken.IsEmpty())
|
|
|
|
|
{
|
2023-01-18 17:59:46 -05:00
|
|
|
SetAccessTokenAndUnlock(Lock, OAuthAccessToken);
|
2022-07-12 15:36:38 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
2020-06-23 18:40:00 -04:00
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
if (!OAuthSecret.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
THttpUniquePtr<IHttpClient> LocalClient;
|
|
|
|
|
if (!Client)
|
|
|
|
|
{
|
|
|
|
|
LocalClient = ConnectionPool->CreateClient(GetDefaultClientParams());
|
|
|
|
|
Client = LocalClient.Get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FHttpRequestParams RequestParams;
|
|
|
|
|
RequestParams.bIgnoreMaxRequests = true;
|
|
|
|
|
FHttpOperation Operation(Client->TryCreateRequest(RequestParams));
|
|
|
|
|
Operation.SetUri(StringCast<ANSICHAR>(*OAuthProvider));
|
|
|
|
|
|
|
|
|
|
if (OAuthProvider.StartsWith(TEXT("http://localhost")))
|
|
|
|
|
{
|
|
|
|
|
// Simple unauthenticated call to a local endpoint that mimics the result from an OIDC provider.
|
|
|
|
|
Operation.Send();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TUtf8StringBuilder<256> OAuthFormData;
|
|
|
|
|
OAuthFormData
|
|
|
|
|
<< ANSITEXTVIEW("client_id=") << OAuthClientId
|
|
|
|
|
<< ANSITEXTVIEW("&scope=") << OAuthScope
|
|
|
|
|
<< ANSITEXTVIEW("&grant_type=client_credentials")
|
|
|
|
|
<< ANSITEXTVIEW("&client_secret=") << OAuthSecret;
|
|
|
|
|
|
|
|
|
|
Operation.SetMethod(EHttpMethod::Post);
|
|
|
|
|
Operation.SetContentType(EHttpMediaType::FormUrlEncoded);
|
|
|
|
|
Operation.SetBody(FCompositeBuffer(FSharedBuffer::MakeView(MakeMemoryView(OAuthFormData))));
|
|
|
|
|
Operation.Send();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Operation.GetStatusCode() == 200)
|
|
|
|
|
{
|
|
|
|
|
if (TSharedPtr<FJsonObject> ResponseObject = Operation.GetBodyAsJson())
|
|
|
|
|
{
|
|
|
|
|
FString AccessTokenString;
|
|
|
|
|
double ExpiryTimeSeconds = 0.0;
|
|
|
|
|
if (ResponseObject->TryGetStringField(TEXT("access_token"), AccessTokenString) &&
|
|
|
|
|
ResponseObject->TryGetNumberField(TEXT("expires_in"), ExpiryTimeSeconds))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display,
|
|
|
|
|
TEXT("%s: Logged in to HTTP DDC services. Expires in %.0f seconds."), *Domain, ExpiryTimeSeconds);
|
2023-01-18 17:59:46 -05:00
|
|
|
SetAccessTokenAndUnlock(Lock, AccessTokenString, ExpiryTimeSeconds);
|
2022-09-23 11:32:36 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: Failed to log in to HTTP services with request %s."), *Domain, *WriteToString<256>(Operation));
|
|
|
|
|
FailedLoginAttempts++;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!OAuthProviderIdentifier.IsEmpty())
|
2022-07-06 05:59:22 -04:00
|
|
|
{
|
|
|
|
|
FString AccessTokenString;
|
|
|
|
|
FDateTime TokenExpiresAt;
|
2023-04-17 15:23:51 -04:00
|
|
|
bool bWasInteractiveLogin = false;
|
2022-11-03 09:39:24 -04:00
|
|
|
|
2023-04-17 15:23:51 -04:00
|
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::TryGet();
|
|
|
|
|
if (DesktopPlatform && DesktopPlatform->GetOidcAccessToken(FPaths::RootDir(), FPaths::GetProjectFilePath(), OAuthProviderIdentifier, FApp::IsUnattended(), GWarn, AccessTokenString, TokenExpiresAt, bWasInteractiveLogin))
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-11-03 18:00:06 -04:00
|
|
|
if (bWasInteractiveLogin)
|
2022-11-03 09:39:24 -04:00
|
|
|
{
|
|
|
|
|
InteractiveLoginAttempts++;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 15:36:38 -04:00
|
|
|
const double ExpiryTimeSeconds = (TokenExpiresAt - FDateTime::UtcNow()).GetTotalSeconds();
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Display,
|
|
|
|
|
TEXT("%s: OidcToken: Logged in to HTTP DDC services. Expires at %s which is in %.0f seconds."),
|
|
|
|
|
*Domain, *TokenExpiresAt.ToString(), ExpiryTimeSeconds);
|
2023-01-18 17:59:46 -05:00
|
|
|
SetAccessTokenAndUnlock(Lock, AccessTokenString, ExpiryTimeSeconds);
|
2020-06-23 18:40:00 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
2023-04-17 15:23:51 -04:00
|
|
|
else if (DesktopPlatform)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: OidcToken: Failed to log in to HTTP services."), *Domain);
|
|
|
|
|
FailedLoginAttempts++;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: OidcToken: Use of OAuthProviderIdentifier requires that the target depend on DesktopPlatform."), *Domain);
|
|
|
|
|
FailedLoginAttempts++;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-07-12 15:36:38 -04:00
|
|
|
}
|
2020-06-23 18:40:00 -04:00
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: No available configuration to acquire an access token."), *Domain);
|
2023-04-17 15:23:51 -04:00
|
|
|
FailedLoginAttempts++;
|
2020-06-23 18:40:00 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 17:59:46 -05:00
|
|
|
void FHttpCacheStore::SetAccessTokenAndUnlock(FScopeLock& Lock, FStringView Token, double RefreshDelay)
|
2022-07-12 15:36:38 -04:00
|
|
|
{
|
2023-01-18 17:59:46 -05:00
|
|
|
// Cache the expired refresh handle.
|
|
|
|
|
FTSTicker::FDelegateHandle ExpiredRefreshAccessTokenHandle = MoveTemp(RefreshAccessTokenHandle);
|
|
|
|
|
RefreshAccessTokenHandle.Reset();
|
2022-07-12 15:36:38 -04:00
|
|
|
|
|
|
|
|
if (!Access)
|
|
|
|
|
{
|
|
|
|
|
Access = MakeUnique<FHttpAccessToken>();
|
|
|
|
|
}
|
|
|
|
|
Access->SetToken(Token);
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
constexpr double RefreshGracePeriod = 20.0f;
|
|
|
|
|
if (RefreshDelay > RefreshGracePeriod)
|
2022-07-12 15:36:38 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
// Schedule a refresh of the token ahead of expiry time (this will not work in commandlets)
|
|
|
|
|
if (!IsRunningCommandlet())
|
|
|
|
|
{
|
|
|
|
|
RefreshAccessTokenHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda(
|
|
|
|
|
[this](float DeltaTime)
|
|
|
|
|
{
|
|
|
|
|
AcquireAccessToken();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-09-07 10:37:16 -04:00
|
|
|
), float(FMath::Min(RefreshDelay - RefreshGracePeriod, MAX_flt)));
|
2022-07-18 12:23:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Schedule a forced refresh of the token when the scheduled refresh is starved or unavailable.
|
|
|
|
|
RefreshAccessTokenTime = FPlatformTime::Seconds() + RefreshDelay - RefreshGracePeriod * 0.5f;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RefreshAccessTokenTime = 0.0;
|
2022-07-12 15:36:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset failed login attempts, the service is indeed alive.
|
|
|
|
|
FailedLoginAttempts = 0;
|
2023-01-18 17:59:46 -05:00
|
|
|
|
|
|
|
|
// Unlock the critical section before attempting to remove the expired refresh handle.
|
|
|
|
|
// The associated ticker delegate could already be executing, which could cause a
|
|
|
|
|
// hang in RemoveTicker when the critical section is locked.
|
|
|
|
|
Lock.Unlock();
|
|
|
|
|
if (ExpiredRefreshAccessTokenHandle.IsValid())
|
|
|
|
|
{
|
|
|
|
|
FTSTicker::GetCoreTicker().RemoveTicker(MoveTemp(ExpiredRefreshAccessTokenHandle));
|
|
|
|
|
}
|
2022-07-12 15:36:38 -04:00
|
|
|
}
|
|
|
|
|
|
2022-12-04 02:26:39 -05:00
|
|
|
TUniquePtr<FHttpCacheStore::FHttpOperation> FHttpCacheStore::WaitForHttpOperation(EOperationCategory Category)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-09-07 10:37:16 -04:00
|
|
|
if (Access && RefreshAccessTokenTime > 0.0 && RefreshAccessTokenTime < FPlatformTime::Seconds())
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
AcquireAccessToken();
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
THttpUniquePtr<IHttpRequest> Request;
|
|
|
|
|
|
|
|
|
|
FHttpRequestParams Params;
|
|
|
|
|
if (FPlatformProcess::SupportsMultithreading() && bHttpEnableAsync)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-12-04 02:26:39 -05:00
|
|
|
if (Category == EOperationCategory::Get)
|
|
|
|
|
{
|
|
|
|
|
Request = NonBlockingGetRequestQueue.CreateRequest(Params);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Request = NonBlockingPutRequestQueue.CreateRequest(Params);
|
|
|
|
|
}
|
2022-07-18 12:23:16 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const bool bIsInGameThread = IsInGameThread();
|
|
|
|
|
if (Category == EOperationCategory::Get)
|
|
|
|
|
{
|
|
|
|
|
Request = GetRequestQueues[bIsInGameThread].CreateRequest(Params);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Request = PutRequestQueues[bIsInGameThread].CreateRequest(Params);
|
|
|
|
|
}
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
if (Access)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2022-07-18 12:23:16 -04:00
|
|
|
Request->AddHeader(ANSITEXTVIEW("Authorization"), WriteToAnsiString<1024>(*Access));
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-18 12:23:16 -04:00
|
|
|
return MakeUnique<FHttpOperation>(MoveTemp(Request));
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void FHttpCacheStore::PutCacheRecordAsync(IRequestOwner& Owner, const FCachePutRequest& Request, FOnCachePutComplete&& OnComplete)
|
2022-04-05 14:18:39 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
const FCacheKey& Key = Request.Record.GetKey();
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
if (bReadOnly)
|
2022-04-05 14:18:39 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose,
|
2022-04-05 16:43:41 -04:00
|
|
|
TEXT("%s: Skipped put of %s from '%s' because this cache store is read-only"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*Domain, *WriteToString<96>(Key), *Request.Name);
|
|
|
|
|
return OnComplete(Request.MakeResponse(EStatus::Error));
|
2022-04-05 14:18:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip the request if storing to the cache is disabled.
|
2023-06-22 12:55:58 -04:00
|
|
|
const ECachePolicy RecordPolicy = Request.Policy.GetRecordPolicy();
|
2022-05-31 22:01:53 -04:00
|
|
|
if (!EnumHasAnyFlags(RecordPolicy, ECachePolicy::StoreRemote))
|
2022-04-05 14:18:39 -04:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: Skipped put of %s from '%s' due to cache policy"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*Domain, *WriteToString<96>(Key), *Request.Name);
|
|
|
|
|
return OnComplete(Request.MakeResponse(EStatus::Error));
|
2022-04-05 14:18:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DebugOptions.ShouldSimulatePutMiss(Key))
|
|
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for put of %s from '%s'"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*Domain, *WriteToString<96>(Key), *Request.Name);
|
|
|
|
|
return OnComplete(Request.MakeResponse(EStatus::Error));
|
2022-04-05 14:18:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Jupiter currently always overwrites. It doesn't have a "write if not present" feature (for records or attachments),
|
|
|
|
|
// but would require one to implement all policy correctly.
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
TRefCountPtr<FPutPackageOp> Op = FPutPackageOp::New(*this, Owner, Request.Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FCbPackage Package;
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestStats& RequestStats = Op->EditStats();
|
|
|
|
|
RequestStats.Bucket = Key.Bucket;
|
|
|
|
|
RequestStats.Type = ERequestType::Record;
|
|
|
|
|
RequestStats.Op = ERequestOp::Put;
|
|
|
|
|
|
|
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
Package = Request.Record.Save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Op->Put(Key, MoveTemp(Package), [Op, Request, OnComplete = MoveTemp(OnComplete)](FPutPackageOp::FResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
if (const FCbObject& Meta = Request.Record.GetMeta())
|
|
|
|
|
{
|
|
|
|
|
Op->EditStats().LogicalWriteSize += Meta.GetSize();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesReceived, Op->ReadStats().PhysicalReadSize);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesSent, Op->ReadStats().PhysicalWriteSize);
|
|
|
|
|
OnComplete(Request.MakeResponse(Response.Status));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
void FHttpCacheStore::PutCacheValueAsync(IRequestOwner& Owner, const FCachePutValueRequest& Request, FOnCachePutValueComplete&& OnComplete)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2022-05-31 22:01:53 -04:00
|
|
|
if (bReadOnly)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose,
|
|
|
|
|
TEXT("%s: Skipped put of %s from '%s' because this cache store is read-only"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
|
|
|
|
return OnComplete(Request.MakeResponse(EStatus::Error));
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip the request if storing to the cache is disabled.
|
2023-06-22 12:55:58 -04:00
|
|
|
if (!EnumHasAnyFlags(Request.Policy, ECachePolicy::StoreRemote))
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: Skipped put of %s from '%s' due to cache policy"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
|
|
|
|
return OnComplete(Request.MakeResponse(EStatus::Error));
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
if (DebugOptions.ShouldSimulatePutMiss(Request.Key))
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for put of %s from '%s'"),
|
2023-06-22 12:55:58 -04:00
|
|
|
*Domain, *WriteToString<96>(Request.Key), *Request.Name);
|
|
|
|
|
return OnComplete(Request.MakeResponse(EStatus::Error));
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Jupiter currently always overwrites. It doesn't have a "write if not present" feature (for records or attachments),
|
|
|
|
|
// but would require one to implement all policy correctly.
|
|
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
TRefCountPtr<FPutPackageOp> Op = FPutPackageOp::New(*this, Owner, Request.Name);
|
2022-02-02 07:35:19 -05:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
FCbPackage Package;
|
2022-02-22 13:55:39 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
FRequestStats& RequestStats = Op->EditStats();
|
|
|
|
|
RequestStats.Bucket = Request.Key.Bucket;
|
|
|
|
|
RequestStats.Type = ERequestType::Value;
|
|
|
|
|
RequestStats.Op = ERequestOp::Put;
|
|
|
|
|
|
|
|
|
|
FRequestTimer RequestTimer(RequestStats);
|
|
|
|
|
|
|
|
|
|
FCbWriter Writer;
|
|
|
|
|
Writer.BeginObject();
|
|
|
|
|
Writer.AddBinaryAttachment("RawHash", Request.Value.GetRawHash());
|
|
|
|
|
Writer.AddInteger("RawSize", Request.Value.GetRawSize());
|
|
|
|
|
Writer.EndObject();
|
|
|
|
|
|
|
|
|
|
Package.SetObject(Writer.Save().AsObject());
|
|
|
|
|
Package.AddAttachment(FCbAttachment(Request.Value.GetData()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Op->Put(Request.Key, MoveTemp(Package), [Op, Request, OnComplete = MoveTemp(OnComplete)](FPutPackageOp::FResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesReceived, Op->ReadStats().PhysicalReadSize);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesSent, Op->ReadStats().PhysicalWriteSize);
|
|
|
|
|
OnComplete(Request.MakeResponse(Response.Status));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
2022-02-02 07:35:19 -05:00
|
|
|
}
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
void FHttpCacheStore::GetCacheValueAsync(
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
FSharedString Name,
|
2022-02-02 07:35:19 -05:00
|
|
|
const FCacheKey& Key,
|
2022-04-05 16:43:41 -04:00
|
|
|
ECachePolicy Policy,
|
2023-06-22 12:55:58 -04:00
|
|
|
ERequestOp RequestOp,
|
2022-04-05 16:43:41 -04:00
|
|
|
uint64 UserData,
|
|
|
|
|
FOnCacheGetValueComplete&& OnComplete)
|
2022-02-02 07:35:19 -05:00
|
|
|
{
|
|
|
|
|
if (!IsUsable())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose,
|
2022-04-05 16:43:41 -04:00
|
|
|
TEXT("%s: Skipped get of %s from '%s' because this cache store is not available"),
|
2022-05-31 22:01:53 -04:00
|
|
|
*Domain, *WriteToString<96>(Key), *Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
OnComplete({Name, Key, {}, UserData, EStatus::Error});
|
|
|
|
|
return;
|
2022-02-02 07:35:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip the request if querying the cache is disabled.
|
2022-05-31 22:01:53 -04:00
|
|
|
if (!EnumHasAnyFlags(Policy, ECachePolicy::QueryRemote))
|
2022-02-02 07:35:19 -05:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: Skipped get of %s from '%s' due to cache policy"),
|
2022-05-31 22:01:53 -04:00
|
|
|
*Domain, *WriteToString<96>(Key), *Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
OnComplete({Name, Key, {}, UserData, EStatus::Error});
|
|
|
|
|
return;
|
2022-02-02 07:35:19 -05:00
|
|
|
}
|
|
|
|
|
|
2022-03-31 10:06:47 -04:00
|
|
|
if (DebugOptions.ShouldSimulateGetMiss(Key))
|
2022-02-02 07:35:19 -05:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Simulated miss for get of %s from '%s'"),
|
2022-05-31 22:01:53 -04:00
|
|
|
*Domain, *WriteToString<96>(Key), *Name);
|
2022-04-05 16:43:41 -04:00
|
|
|
OnComplete({Name, Key, {}, UserData, EStatus::Error});
|
|
|
|
|
return;
|
2022-02-02 07:35:19 -05:00
|
|
|
}
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
TRefCountPtr<FGetValueOp> Op = FGetValueOp::New(*this, Owner, Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
FRequestStats& RequestStats = Op->EditStats();
|
|
|
|
|
RequestStats.Bucket = Key.Bucket;
|
|
|
|
|
RequestStats.Type = ERequestType::Value;
|
|
|
|
|
RequestStats.Op = RequestOp;
|
|
|
|
|
|
|
|
|
|
Op->Get(Key, Policy, [Op, UserData, OnComplete = MoveTemp(OnComplete)](FGetValueOp::FResponse&& Response)
|
2022-04-05 15:17:17 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesReceived, Op->ReadStats().PhysicalReadSize);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesSent, Op->ReadStats().PhysicalWriteSize);
|
2023-06-21 13:32:34 -04:00
|
|
|
OnComplete({Response.Name, Response.Key, MoveTemp(Response.Value), UserData, Response.Status});
|
2022-07-18 12:23:16 -04:00
|
|
|
});
|
2022-01-10 13:43:40 -05:00
|
|
|
}
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
void FHttpCacheStore::GetCacheRecordAsync(
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
const FSharedString& Name,
|
|
|
|
|
const FCacheKey& Key,
|
|
|
|
|
const FCacheRecordPolicy& Policy,
|
|
|
|
|
uint64 UserData,
|
2023-06-22 12:55:58 -04:00
|
|
|
FOnCacheGetComplete&& OnComplete)
|
2022-04-05 16:43:41 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
TRefCountPtr<FGetRecordOp> Op = FGetRecordOp::New(*this, Owner, Name);
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
FRequestStats& RequestStats = Op->EditStats();
|
|
|
|
|
RequestStats.Bucket = Key.Bucket;
|
|
|
|
|
RequestStats.Type = ERequestType::Record;
|
|
|
|
|
RequestStats.Op = ERequestOp::Get;
|
|
|
|
|
|
|
|
|
|
Op->GetRecord(Key, Policy, [Op, Name, UserData, OnComplete = MoveTemp(OnComplete)](FGetRecordOp::FRecordResponse&& Response)
|
2022-03-02 17:30:48 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
if (const FCbObject& Meta = Response.Record.GetMeta())
|
|
|
|
|
{
|
|
|
|
|
Op->EditStats().LogicalReadSize += Meta.GetSize();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Op->RecordStats(Response.Status);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesReceived, Op->ReadStats().PhysicalReadSize);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesSent, Op->ReadStats().PhysicalWriteSize);
|
|
|
|
|
OnComplete({Name, MoveTemp(Response.Record), UserData, Response.Status});
|
2022-07-18 12:23:16 -04:00
|
|
|
});
|
2022-03-02 17:30:48 -05:00
|
|
|
}
|
|
|
|
|
|
2023-08-15 11:49:22 -04:00
|
|
|
void FHttpCacheStore::FinishChunkRequest(
|
|
|
|
|
const FCacheGetChunkRequest& Request,
|
|
|
|
|
EStatus Status,
|
|
|
|
|
const FValue& Value,
|
|
|
|
|
FCompressedBufferReader& ValueReader,
|
|
|
|
|
const TSharedRef<FOnCacheGetChunkComplete>& SharedOnComplete)
|
|
|
|
|
{
|
|
|
|
|
if (Status == EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
const uint64 RawOffset = FMath::Min(Value.GetRawSize(), Request.RawOffset);
|
|
|
|
|
const uint64 RawSize = FMath::Min(Value.GetRawSize() - RawOffset, Request.RawSize);
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache hit for %s from '%s'"),
|
|
|
|
|
*Domain, *WriteToString<96>(Request.Key, '/', Request.Id), *Request.Name);
|
|
|
|
|
FSharedBuffer Buffer;
|
|
|
|
|
const bool bExistsOnly = EnumHasAnyFlags(Request.Policy, ECachePolicy::SkipData);
|
|
|
|
|
if (!bExistsOnly)
|
|
|
|
|
{
|
|
|
|
|
Buffer = ValueReader.Decompress(RawOffset, RawSize);
|
|
|
|
|
}
|
|
|
|
|
const EStatus ChunkStatus = bExistsOnly || Buffer.GetSize() == RawSize ? EStatus::Ok : EStatus::Error;
|
|
|
|
|
if (ChunkStatus == EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
TRACE_COUNTER_INCREMENT(HttpDDC_GetHit);
|
|
|
|
|
}
|
|
|
|
|
SharedOnComplete.Get()({ Request.Name, Request.Key, Request.Id, Request.RawOffset,
|
|
|
|
|
RawSize, Value.GetRawHash(), MoveTemp(Buffer), Request.UserData, ChunkStatus });
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache miss for %s from '%s'"),
|
|
|
|
|
*Domain, *WriteToString<96>(Request.Key, '/', Request.Id), *Request.Name);
|
|
|
|
|
|
|
|
|
|
SharedOnComplete.Get()(Request.MakeResponse(Status));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void AppendGetAndHeadOpsForChunkRequestGroupItem(
|
|
|
|
|
const FCacheGetChunkRequest& Request,
|
|
|
|
|
const FValueWithId& ValueWithId,
|
|
|
|
|
TArray<FValueWithId>& RequiredGets,
|
|
|
|
|
TArray<TArray<FCacheGetChunkRequest>>& RequiredGetRequests,
|
|
|
|
|
TArray<FValueWithId>& RequiredHeads,
|
|
|
|
|
TArray<TArray<FCacheGetChunkRequest>>& RequiredHeadRequests)
|
|
|
|
|
{
|
|
|
|
|
const bool bAlreadyRequiredGet = !RequiredGets.IsEmpty() && RequiredGets.Last() == ValueWithId;
|
|
|
|
|
const bool bAlreadyRequiredHead = !RequiredHeads.IsEmpty() && RequiredHeads.Last() == ValueWithId;
|
|
|
|
|
if (EnumHasAnyFlags(Request.Policy, ECachePolicy::SkipData))
|
|
|
|
|
{
|
|
|
|
|
if (!bAlreadyRequiredHead && !bAlreadyRequiredGet)
|
|
|
|
|
{
|
|
|
|
|
RequiredHeads.Emplace(ValueWithId);
|
|
|
|
|
RequiredHeadRequests.AddDefaulted();
|
|
|
|
|
}
|
|
|
|
|
if (bAlreadyRequiredGet)
|
|
|
|
|
{
|
|
|
|
|
RequiredGetRequests.Last().Add(Request);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RequiredHeadRequests.Last().Add(Request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!bAlreadyRequiredGet)
|
|
|
|
|
{
|
|
|
|
|
RequiredGets.Emplace(ValueWithId);
|
|
|
|
|
if (bAlreadyRequiredHead)
|
|
|
|
|
{
|
|
|
|
|
//Steal existing head contents first
|
|
|
|
|
RequiredGetRequests.Emplace(MoveTemp(RequiredHeadRequests.Last()));
|
|
|
|
|
RequiredHeads.SetNum(RequiredHeads.Num() - 1, false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RequiredGetRequests.AddDefaulted();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequiredGetRequests.Last().Add(Request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpCacheStore::GetChunkGroupAsync(
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
const FCacheGetChunkRequest* StartRequest,
|
|
|
|
|
const FCacheGetChunkRequest* EndRequest,
|
|
|
|
|
TSharedRef<FOnCacheGetChunkComplete>& SharedOnComplete)
|
|
|
|
|
{
|
|
|
|
|
if ((StartRequest == nullptr) || (StartRequest >= EndRequest))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ECachePolicy GroupPolicy(ECachePolicy::None);
|
|
|
|
|
TArray<FCacheGetChunkRequest> RequestGroup;
|
|
|
|
|
RequestGroup.Reserve(static_cast<int>(EndRequest - StartRequest));
|
|
|
|
|
for (const FCacheGetChunkRequest* Request = StartRequest; Request != EndRequest; ++Request)
|
|
|
|
|
{
|
|
|
|
|
RequestGroup.Add(*Request);
|
|
|
|
|
GroupPolicy |= Request->Policy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (StartRequest->Id.IsValid())
|
|
|
|
|
{
|
|
|
|
|
// Get Record and contained Values within the request group
|
|
|
|
|
TRefCountPtr<FGetRecordOp> Op = FGetRecordOp::New(*this, Owner, StartRequest->Name);
|
|
|
|
|
|
|
|
|
|
Op->GetRecordOnly(StartRequest->Key, GroupPolicy, [this, Op = TRefCountPtr(Op), RequestGroup = MoveTemp(RequestGroup), SharedOnComplete](FGetRecordOp::FRecordResponse&& Response) mutable
|
|
|
|
|
{
|
|
|
|
|
auto RecordStats = [](FGetRecordOp& Op, FCacheBucket Bucket, EStatus Status)
|
|
|
|
|
{
|
|
|
|
|
FRequestStats& RequestStats = Op.EditStats();
|
|
|
|
|
RequestStats.Type = ERequestType::Record;
|
|
|
|
|
RequestStats.Bucket = Bucket;
|
|
|
|
|
RequestStats.Op = ERequestOp::GetChunk;
|
|
|
|
|
Op.RecordStats(Status);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesReceived, Op.ReadStats().PhysicalReadSize);
|
|
|
|
|
TRACE_COUNTER_ADD(HttpDDC_BytesSent, Op.ReadStats().PhysicalWriteSize);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
|
|
|
|
// Get Values on the record
|
|
|
|
|
FRequestTimer RequestTimer(Op->EditStats());
|
|
|
|
|
|
|
|
|
|
TArray<FValueWithId> RequiredGets;
|
|
|
|
|
TArray<TArray<FCacheGetChunkRequest>> RequiredGetRequests;
|
|
|
|
|
TArray<FValueWithId> RequiredHeads;
|
|
|
|
|
TArray<TArray<FCacheGetChunkRequest>> RequiredHeadRequests;
|
|
|
|
|
FCompressedBufferReader NullReader;
|
|
|
|
|
for (const FCacheGetChunkRequest& Request : RequestGroup)
|
|
|
|
|
{
|
|
|
|
|
const FValueWithId& ValueWithId = Response.Record.GetValue(Request.Id);
|
|
|
|
|
bool bHasValue = ValueWithId.IsValid();
|
|
|
|
|
FValue Value = ValueWithId;
|
|
|
|
|
|
|
|
|
|
if (!bHasValue || IsValueDataReady(Value, Request.Policy))
|
|
|
|
|
{
|
|
|
|
|
FinishChunkRequest(Request, Response.Status, Value, NullReader, SharedOnComplete);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
AppendGetAndHeadOpsForChunkRequestGroupItem(Request, ValueWithId, RequiredGets,RequiredGetRequests, RequiredHeads, RequiredHeadRequests);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 PendingValues = RequiredGets.Num() + RequiredHeads.Num();
|
|
|
|
|
Op->PrepareForPendingValues(PendingValues);
|
|
|
|
|
|
|
|
|
|
RequestTimer.Stop();
|
|
|
|
|
|
|
|
|
|
if (PendingValues == 0)
|
|
|
|
|
{
|
|
|
|
|
RecordStats(*Op, RequestGroup[0].Key.Bucket, Response.Status);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Op->GetValues(RequiredGets, [this, RecordStats, Op = TRefCountPtr(Op), ChunkRequestsForValues = MoveTemp(RequiredGetRequests), SharedOnComplete](FGetRecordOp::FValueResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
int FoundRequestsIndex = Algo::BinarySearchBy(ChunkRequestsForValues, Response.Value.GetId(), [](const TArray<FCacheGetChunkRequest>& ChunkRequests)
|
|
|
|
|
{
|
|
|
|
|
check(!ChunkRequests.IsEmpty());
|
|
|
|
|
return ChunkRequests[0].Id;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
check(FoundRequestsIndex != INDEX_NONE);
|
|
|
|
|
const TArray<FCacheGetChunkRequest>& ChunkRequests = ChunkRequestsForValues[FoundRequestsIndex];
|
|
|
|
|
FCompressedBufferReader ValueReader(Response.Value.GetData());
|
|
|
|
|
|
|
|
|
|
if (Op->FinishPendingValueFetch(Response.Value, false))
|
|
|
|
|
{
|
|
|
|
|
RecordStats(*Op, ChunkRequests[0].Key.Bucket, Op->GetFailedValues() > 0 ? EStatus::Error : EStatus::Ok);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const FCacheGetChunkRequest& ChunkRequest : ChunkRequests)
|
|
|
|
|
{
|
|
|
|
|
FinishChunkRequest(ChunkRequest, Response.Status, Response.Value, ValueReader, SharedOnComplete);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Op->GetValuesExist(RequiredHeads, [this, RecordStats, Op = TRefCountPtr(Op), ChunkRequestsForValues = MoveTemp(RequiredHeadRequests), SharedOnComplete](FGetRecordOp::FValueResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
int FoundRequestsIndex = Algo::BinarySearchBy(ChunkRequestsForValues, Response.Value.GetId(), [](const TArray<FCacheGetChunkRequest>& ChunkRequests)
|
|
|
|
|
{
|
|
|
|
|
check(!ChunkRequests.IsEmpty());
|
|
|
|
|
return ChunkRequests[0].Id;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
check(FoundRequestsIndex != INDEX_NONE);
|
|
|
|
|
const TArray<FCacheGetChunkRequest>& ChunkRequests = ChunkRequestsForValues[FoundRequestsIndex];
|
|
|
|
|
|
|
|
|
|
if (Op->FinishPendingValueExists(Response.Status))
|
|
|
|
|
{
|
|
|
|
|
RecordStats(*Op, ChunkRequests[0].Key.Bucket, Op->GetFailedValues() > 0 ? EStatus::Error : EStatus::Ok);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCompressedBufferReader NullReader;
|
|
|
|
|
for (const FCacheGetChunkRequest& ChunkRequest : ChunkRequests)
|
|
|
|
|
{
|
|
|
|
|
FinishChunkRequest(ChunkRequest, Response.Status, Response.Value, NullReader, SharedOnComplete);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FCompressedBufferReader NullReader;
|
|
|
|
|
FValue DummyValue;
|
|
|
|
|
for (const FCacheGetChunkRequest& Request : RequestGroup)
|
|
|
|
|
{
|
|
|
|
|
FinishChunkRequest(Request, Response.Status, DummyValue, NullReader, SharedOnComplete);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RecordStats(*Op, RequestGroup[0].Key.Bucket, Response.Status);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Get Value for the request group
|
|
|
|
|
GetCacheValueAsync(Owner, StartRequest->Name, StartRequest->Key, GroupPolicy, ERequestOp::GetChunk, 0, [this, RequestGroup = MoveTemp(RequestGroup), SharedOnComplete](FCacheGetValueResponse&& Response)
|
|
|
|
|
{
|
|
|
|
|
FCompressedBufferReader ValueReader(Response.Value.GetData());
|
|
|
|
|
for (const FCacheGetChunkRequest& Request : RequestGroup)
|
|
|
|
|
{
|
|
|
|
|
FinishChunkRequest(Request, Response.Status, Response.Value, ValueReader, SharedOnComplete);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
void FHttpCacheStore::LegacyStats(FDerivedDataCacheStatsNode& OutNode)
|
2020-06-23 18:40:00 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
checkNoEntry();
|
2020-06-23 18:40:00 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
void FHttpCacheStore::Put(
|
2021-12-13 13:32:28 -05:00
|
|
|
const TConstArrayView<FCachePutRequest> Requests,
|
2021-08-04 18:08:50 -04:00
|
|
|
IRequestOwner& Owner,
|
2021-04-28 16:22:18 -04:00
|
|
|
FOnCachePutComplete&& OnComplete)
|
|
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_Put);
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_ADD(HttpDDC_Put, Requests.Num());
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
FRequestBarrier Barrier(Owner);
|
2022-07-22 10:21:26 -04:00
|
|
|
TSharedRef<FOnCachePutComplete> SharedOnComplete = MakeShared<FOnCachePutComplete>(MoveTemp(OnComplete));
|
2022-01-10 13:43:40 -05:00
|
|
|
for (const FCachePutRequest& Request : Requests)
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
PutCacheRecordAsync(Owner, Request, [SharedOnComplete](FCachePutResponse&& Response)
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_INCREMENT(HttpDDC_PutHit);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-22 10:21:26 -04:00
|
|
|
SharedOnComplete.Get()(MoveTemp(Response));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
2021-04-28 16:22:18 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
void FHttpCacheStore::Get(
|
2021-12-13 13:32:28 -05:00
|
|
|
const TConstArrayView<FCacheGetRequest> Requests,
|
2021-08-04 18:08:50 -04:00
|
|
|
IRequestOwner& Owner,
|
2021-04-28 16:22:18 -04:00
|
|
|
FOnCacheGetComplete&& OnComplete)
|
|
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_Get);
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_ADD(HttpDDC_Get, Requests.Num());
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
FRequestBarrier Barrier(Owner);
|
2022-07-22 10:21:26 -04:00
|
|
|
TSharedRef<FOnCacheGetComplete> SharedOnComplete = MakeShared<FOnCacheGetComplete>(MoveTemp(OnComplete));
|
2022-01-10 13:43:40 -05:00
|
|
|
for (const FCacheGetRequest& Request : Requests)
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
2022-07-22 10:21:26 -04:00
|
|
|
GetCacheRecordAsync(Owner, Request.Name, Request.Key, Request.Policy, Request.UserData,
|
2023-06-22 12:55:58 -04:00
|
|
|
[SharedOnComplete](FCacheGetResponse&& Response)
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_INCREMENT(HttpDDC_GetHit);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-22 10:21:26 -04:00
|
|
|
SharedOnComplete.Get()(MoveTemp(Response));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
2021-04-28 16:22:18 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
void FHttpCacheStore::PutValue(
|
2022-02-02 07:35:19 -05:00
|
|
|
const TConstArrayView<FCachePutValueRequest> Requests,
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
FOnCachePutValueComplete&& OnComplete)
|
|
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_PutValue);
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_ADD(HttpDDC_Put, Requests.Num());
|
|
|
|
|
|
2022-04-05 16:43:41 -04:00
|
|
|
FRequestBarrier Barrier(Owner);
|
2022-07-22 10:21:26 -04:00
|
|
|
TSharedRef<FOnCachePutValueComplete> SharedOnComplete = MakeShared<FOnCachePutValueComplete>(MoveTemp(OnComplete));
|
2022-02-02 07:35:19 -05:00
|
|
|
for (const FCachePutValueRequest& Request : Requests)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
PutCacheValueAsync(Owner, Request, [SharedOnComplete](FCachePutValueResponse&& Response)
|
2022-02-02 07:35:19 -05:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
if (Response.Status == EStatus::Ok)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_INCREMENT(HttpDDC_PutHit);
|
2022-04-05 16:43:41 -04:00
|
|
|
}
|
2022-07-22 10:21:26 -04:00
|
|
|
SharedOnComplete.Get()(MoveTemp(Response));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
2022-02-02 07:35:19 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
void FHttpCacheStore::GetValue(
|
2022-02-02 07:35:19 -05:00
|
|
|
const TConstArrayView<FCacheGetValueRequest> Requests,
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
FOnCacheGetValueComplete&& OnComplete)
|
|
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_GetValue);
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_ADD(HttpDDC_Get, Requests.Num());
|
2022-02-24 10:24:10 -05:00
|
|
|
|
2023-06-22 12:55:58 -04:00
|
|
|
const auto HasSkipData = [](const FCacheGetValueRequest& Request) { return EnumHasAnyFlags(Request.Policy, ECachePolicy::SkipData); };
|
|
|
|
|
if (Algo::AllOf(Requests, HasSkipData))
|
2022-03-02 17:30:48 -05:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
TRefCountPtr<FExistsBatchOp> Op = FExistsBatchOp::New(*this, Owner);
|
2023-06-22 12:55:58 -04:00
|
|
|
Op->Exists(Requests, [this, OnComplete = MoveTemp(OnComplete)](FCacheGetValueResponse&& Response)
|
2022-02-02 07:35:19 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
if (Response.Status == EStatus::Ok)
|
2022-02-24 10:24:10 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_INCREMENT(HttpDDC_GetHit);
|
2022-02-24 10:24:10 -05:00
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache hit for %s from '%s'"),
|
2022-05-31 22:01:53 -04:00
|
|
|
*Domain, *WriteToString<96>(Response.Key), *Response.Name);
|
2022-03-02 17:30:48 -05:00
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
OnComplete(MoveTemp(Response));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
2022-03-02 17:30:48 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
FRequestBarrier Barrier(Owner);
|
2022-07-22 10:21:26 -04:00
|
|
|
TSharedRef<FOnCacheGetValueComplete> SharedOnComplete = MakeShared<FOnCacheGetValueComplete>(MoveTemp(OnComplete));
|
2022-03-02 17:30:48 -05:00
|
|
|
for (const FCacheGetValueRequest& Request : Requests)
|
|
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
GetCacheValueAsync(Owner, Request.Name, Request.Key, Request.Policy, ERequestOp::Get, Request.UserData,
|
|
|
|
|
[this, Policy = Request.Policy, SharedOnComplete](FCacheGetValueResponse&& Response)
|
2022-03-02 17:30:48 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
if (Response.Status == EStatus::Ok && !IsValueDataReady(Response.Value, Policy) && !EnumHasAnyFlags(Policy, ECachePolicy::SkipData))
|
2022-03-02 17:30:48 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
Response.Status = EStatus::Error;
|
|
|
|
|
// With inline fetching, expect we will always have a value we can use.
|
|
|
|
|
// Even SkipData/Exists can rely on the blob existing if the ref is reported to exist.
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Cache miss due to inlining failure for %s from '%s'"),
|
|
|
|
|
*Domain, *WriteToString<96>(Response.Key), *Response.Name);
|
2022-03-02 17:30:48 -05:00
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
if (Response.Status == EStatus::Ok)
|
2022-03-02 17:30:48 -05:00
|
|
|
{
|
2023-06-22 12:55:58 -04:00
|
|
|
TRACE_COUNTER_INCREMENT(HttpDDC_GetHit);
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Verbose, TEXT("%s: Cache hit for %s from '%s'"),
|
|
|
|
|
*Domain, *WriteToString<96>(Response.Key), *Response.Name);
|
2022-03-02 17:30:48 -05:00
|
|
|
}
|
2023-06-22 12:55:58 -04:00
|
|
|
|
|
|
|
|
SharedOnComplete.Get()(MoveTemp(Response));
|
2022-04-05 16:43:41 -04:00
|
|
|
});
|
2022-02-02 07:35:19 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
void FHttpCacheStore::GetChunks(
|
2022-01-18 04:47:59 -05:00
|
|
|
const TConstArrayView<FCacheGetChunkRequest> Requests,
|
2021-08-04 18:08:50 -04:00
|
|
|
IRequestOwner& Owner,
|
2022-01-18 04:47:59 -05:00
|
|
|
FOnCacheGetChunkComplete&& OnComplete)
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
2022-04-05 16:43:41 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_GetChunks);
|
2023-08-15 11:49:22 -04:00
|
|
|
TRACE_COUNTER_ADD(HttpDDC_Get, Requests.Num());
|
|
|
|
|
|
|
|
|
|
if (Requests.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-14 10:53:17 -05:00
|
|
|
// TODO: This is inefficient because Jupiter doesn't allow us to get only part of a compressed blob, so we have to
|
|
|
|
|
// get the whole thing and then decompress only the portion we need. Furthermore, because there is no propagation
|
|
|
|
|
// between cache stores during chunk requests, the fetched result won't end up in the local store.
|
|
|
|
|
// These efficiency issues will be addressed by changes to the Hierarchy that translate chunk requests that
|
|
|
|
|
// are missing in local/fast stores and have to be retrieved from slow stores into record requests instead. That
|
|
|
|
|
// will make this code path unused/uncommon as Jupiter will most always be a slow store with a local/fast store in front of it.
|
|
|
|
|
// Regardless, to adhere to the functional contract, this implementation must exist.
|
2022-01-18 04:47:59 -05:00
|
|
|
TArray<FCacheGetChunkRequest, TInlineAllocator<16>> SortedRequests(Requests);
|
2022-01-14 10:53:17 -05:00
|
|
|
SortedRequests.StableSort(TChunkLess());
|
|
|
|
|
|
2023-08-15 11:49:22 -04:00
|
|
|
const FCacheGetChunkRequest* PendingGroupStartRequest = &SortedRequests[0];
|
|
|
|
|
|
|
|
|
|
FRequestBarrier Barrier(Owner);
|
|
|
|
|
TSharedRef<FOnCacheGetChunkComplete> SharedOnComplete = MakeShared<FOnCacheGetChunkComplete>(MoveTemp(OnComplete));
|
2022-01-18 04:47:59 -05:00
|
|
|
for (const FCacheGetChunkRequest& Request : SortedRequests)
|
2021-04-28 16:22:18 -04:00
|
|
|
{
|
2022-01-14 10:53:17 -05:00
|
|
|
const bool bExistsOnly = EnumHasAnyFlags(Request.Policy, ECachePolicy::SkipData);
|
2023-08-15 11:49:22 -04:00
|
|
|
const bool bMatchesExistingGroup = PendingGroupStartRequest != nullptr && PendingGroupStartRequest->Key == Request.Key && PendingGroupStartRequest->Id.IsValid() == Request.Id.IsValid();
|
|
|
|
|
if (!bMatchesExistingGroup)
|
2022-01-14 10:53:17 -05:00
|
|
|
{
|
2023-08-15 11:49:22 -04:00
|
|
|
GetChunkGroupAsync(Owner, PendingGroupStartRequest, &Request, SharedOnComplete);
|
|
|
|
|
PendingGroupStartRequest = &Request;
|
2022-01-14 10:53:17 -05:00
|
|
|
}
|
2021-04-28 16:22:18 -04:00
|
|
|
}
|
2023-08-15 11:49:22 -04:00
|
|
|
GetChunkGroupAsync(Owner, PendingGroupStartRequest, SortedRequests.GetData() + SortedRequests.Num(), SharedOnComplete);
|
2021-04-28 16:22:18 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
void FHttpCacheStoreParams::Parse(const TCHAR* NodeName, const TCHAR* Config)
|
|
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
Name = NodeName;
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
FString ServerId;
|
|
|
|
|
if (FParse::Value(Config, TEXT("ServerID="), ServerId))
|
|
|
|
|
{
|
|
|
|
|
FString ServerEntry;
|
2022-10-14 18:39:39 -04:00
|
|
|
const TCHAR* ServerSection = TEXT("StorageServers");
|
|
|
|
|
const TCHAR* FallbackServerSection = TEXT("HordeStorageServers");
|
2022-05-31 22:01:53 -04:00
|
|
|
if (GConfig->GetString(ServerSection, *ServerId, ServerEntry, GEngineIni))
|
|
|
|
|
{
|
|
|
|
|
Parse(NodeName, *ServerEntry);
|
|
|
|
|
}
|
2022-10-14 18:39:39 -04:00
|
|
|
else if (GConfig->GetString(FallbackServerSection, *ServerId, ServerEntry, GEngineIni))
|
|
|
|
|
{
|
|
|
|
|
Parse(NodeName, *ServerEntry);
|
|
|
|
|
}
|
2022-05-31 22:01:53 -04:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: Using ServerID=%s which was not found in [%s]"), NodeName, *ServerId, ServerSection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString OverrideName;
|
|
|
|
|
|
|
|
|
|
// Host Params
|
|
|
|
|
|
|
|
|
|
FParse::Value(Config, TEXT("Host="), Host);
|
|
|
|
|
if (FParse::Value(Config, TEXT("EnvHostOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
FString HostEnv = FPlatformMisc::GetEnvironmentVariable(*OverrideName);
|
|
|
|
|
if (!HostEnv.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
Host = HostEnv;
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found environment override for Host %s=%s"), NodeName, *OverrideName, *Host);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (FParse::Value(Config, TEXT("CommandLineHostOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
if (FParse::Value(FCommandLine::Get(), *(OverrideName + TEXT("=")), Host))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found command line override for Host %s=%s"), NodeName, *OverrideName, *Host);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-18 23:15:54 -04:00
|
|
|
FParse::Value(Config, TEXT("HostPinnedPublicKeys="), HostPinnedPublicKeys);
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
FParse::Bool(Config, TEXT("ResolveHostCanonicalName="), bResolveHostCanonicalName);
|
|
|
|
|
|
2023-05-04 09:31:50 -04:00
|
|
|
// Http version Params
|
|
|
|
|
|
|
|
|
|
FParse::Value(Config, TEXT("HttpVersion="), HttpVersion);
|
|
|
|
|
if (FParse::Value(Config, TEXT("EnvHttpVersionOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
FString HttpEnv = FPlatformMisc::GetEnvironmentVariable(*OverrideName);
|
|
|
|
|
if (!HttpEnv.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
HttpVersion = HttpEnv;
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found environment override for HttpVersion %s=%s"), NodeName, *OverrideName, *HttpVersion);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (FParse::Value(Config, TEXT("CommandLineHttpVersionOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
if (FParse::Value(FCommandLine::Get(), *(OverrideName + TEXT("=")), HttpVersion))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found command line override for HttpVersion %s=%s"), NodeName, *OverrideName, *HttpVersion);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
// Namespace Params
|
|
|
|
|
|
2022-09-14 18:25:46 -04:00
|
|
|
if (Namespace.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
FParse::Value(Config, TEXT("Namespace="), Namespace);
|
|
|
|
|
}
|
|
|
|
|
FParse::Value(Config, TEXT("StructuredNamespace="), Namespace);
|
2022-05-31 22:01:53 -04:00
|
|
|
|
|
|
|
|
// OAuth Params
|
|
|
|
|
|
|
|
|
|
FParse::Value(Config, TEXT("OAuthProvider="), OAuthProvider);
|
|
|
|
|
|
|
|
|
|
if (FParse::Value(Config, TEXT("CommandLineOAuthProviderOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
if (FParse::Value(FCommandLine::Get(), *(OverrideName + TEXT("=")), OAuthProvider))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found command line override for OAuthProvider %s=%s"), NodeName, *OverrideName, *OAuthProvider);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FParse::Value(Config, TEXT("OAuthClientId="), OAuthClientId);
|
|
|
|
|
|
2023-05-03 15:37:06 -04:00
|
|
|
FParse::Value(Config, TEXT("OAuthSecret="), OAuthSecret);
|
|
|
|
|
if (FParse::Value(Config, TEXT("EnvOAuthSecretOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
FString OAuthSecretEnv = FPlatformMisc::GetEnvironmentVariable(*OverrideName);
|
|
|
|
|
if (!OAuthSecretEnv.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
OAuthSecret = OAuthSecretEnv;
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found environment override for OAuthSecret %s={SECRET}"), NodeName, *OverrideName);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-31 22:01:53 -04:00
|
|
|
if (FParse::Value(Config, TEXT("CommandLineOAuthSecretOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
if (FParse::Value(FCommandLine::Get(), *(OverrideName + TEXT("=")), OAuthSecret))
|
|
|
|
|
{
|
2023-05-03 15:36:58 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found command line override for OAuthSecret %s={SECRET}"), NodeName, *OverrideName);
|
2022-05-31 22:01:53 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
// If the secret is a file path, read the secret from the file.
|
|
|
|
|
if (OAuthSecret.StartsWith(TEXT("file://")))
|
|
|
|
|
{
|
|
|
|
|
TStringBuilder<256> FilePath;
|
|
|
|
|
FilePath << MakeStringView(OAuthSecret).RightChop(TEXTVIEW("file://").Len());
|
|
|
|
|
if (!FFileHelper::LoadFileToString(OAuthSecret, *FilePath))
|
|
|
|
|
{
|
|
|
|
|
OAuthSecret.Empty();
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: Failed to read OAuth secret file: %s"), NodeName, *FilePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
FParse::Value(Config, TEXT("OAuthScope="), OAuthScope);
|
|
|
|
|
|
2022-07-06 05:59:22 -04:00
|
|
|
FParse::Value(Config, TEXT("OAuthProviderIdentifier="), OAuthProviderIdentifier);
|
|
|
|
|
|
|
|
|
|
if (FParse::Value(Config, TEXT("OAuthAccessTokenEnvOverride="), OverrideName))
|
|
|
|
|
{
|
|
|
|
|
FString AccessToken = FPlatformMisc::GetEnvironmentVariable(*OverrideName);
|
|
|
|
|
if (!AccessToken.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
OAuthAccessToken = AccessToken;
|
2022-07-12 15:36:38 -04:00
|
|
|
// We do not log the access token as it is sensitive information.
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Found OAuth access token in %s."), NodeName, *OverrideName);
|
2022-07-06 05:59:22 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-18 23:15:54 -04:00
|
|
|
FParse::Value(Config, TEXT("OAuthPinnedPublicKeys="), OAuthPinnedPublicKeys);
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
// Cache Params
|
|
|
|
|
|
|
|
|
|
FParse::Bool(Config, TEXT("ReadOnly="), bReadOnly);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
} // UE::DerivedData
|
2021-04-28 16:22:18 -04:00
|
|
|
|
2022-01-11 11:57:38 -05:00
|
|
|
#endif // WITH_HTTP_DDC_BACKEND
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
namespace UE::DerivedData
|
2022-01-11 11:57:38 -05:00
|
|
|
{
|
|
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
ILegacyCacheStore* CreateHttpCacheStore(const TCHAR* NodeName, const TCHAR* Config, ICacheStoreOwner* Owner)
|
2022-01-11 11:57:38 -05:00
|
|
|
{
|
2022-09-23 11:32:36 -04:00
|
|
|
#if !WITH_HTTP_DDC_BACKEND
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: HTTP cache is not yet supported in the current build configuration."), NodeName);
|
|
|
|
|
#else
|
2022-05-31 22:01:53 -04:00
|
|
|
FHttpCacheStoreParams Params;
|
|
|
|
|
Params.Parse(NodeName, Config);
|
|
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
bool bValidParams = true;
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
if (Params.Host.IsEmpty())
|
2022-01-11 11:57:38 -05:00
|
|
|
{
|
2022-05-31 22:01:53 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("%s: Missing required parameter 'Host'"), NodeName);
|
2022-09-23 11:32:36 -04:00
|
|
|
bValidParams = false;
|
2022-01-11 11:57:38 -05:00
|
|
|
}
|
2022-09-23 11:32:36 -04:00
|
|
|
else if (Params.Host == TEXTVIEW("None"))
|
2022-05-31 22:01:53 -04:00
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Log, TEXT("%s: Disabled because Host is set to 'None'"), NodeName);
|
2022-09-23 11:32:36 -04:00
|
|
|
bValidParams = false;
|
2022-05-31 22:01:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Params.Namespace.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
Params.Namespace = FApp::GetProjectName();
|
2022-09-14 18:25:46 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: Missing required parameter 'StructuredNamespace', falling back to '%s'"), NodeName, *Params.Namespace);
|
2022-05-31 22:01:53 -04:00
|
|
|
}
|
|
|
|
|
|
2022-10-28 16:07:04 -04:00
|
|
|
if (bValidParams && !Params.Host.StartsWith(TEXT("http://localhost")))
|
2022-05-31 22:01:53 -04:00
|
|
|
{
|
2022-09-23 11:32:36 -04:00
|
|
|
bool bValidOAuthAccessToken = !Params.OAuthAccessToken.IsEmpty();
|
2022-05-31 22:01:53 -04:00
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
bool bValidOAuthProviderIdentifier = !Params.OAuthProviderIdentifier.IsEmpty();
|
|
|
|
|
|
|
|
|
|
bool bValidOAuthProvider = !Params.OAuthProvider.IsEmpty();
|
|
|
|
|
if (bValidOAuthProvider)
|
2022-05-31 22:01:53 -04:00
|
|
|
{
|
2022-09-23 11:32:36 -04:00
|
|
|
if (!Params.OAuthProvider.StartsWith(TEXT("http://")) &&
|
|
|
|
|
!Params.OAuthProvider.StartsWith(TEXT("https://")))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("%s: OAuth provider '%s' must be a complete URI including the scheme."), NodeName, *Params.OAuthProvider);
|
|
|
|
|
bValidParams = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No need for OAuthClientId and OAuthSecret if using a local provider.
|
|
|
|
|
if (!Params.OAuthProvider.StartsWith(TEXT("http://localhost")))
|
|
|
|
|
{
|
|
|
|
|
if (Params.OAuthClientId.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("%s: Missing required parameter 'OAuthClientId'"), NodeName);
|
|
|
|
|
bValidOAuthProvider = false;
|
|
|
|
|
bValidParams = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Params.OAuthSecret.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
UE_CLOG(!bValidOAuthAccessToken && !bValidOAuthProviderIdentifier,
|
|
|
|
|
LogDerivedDataCache, Error, TEXT("%s: Missing required parameter 'OAuthSecret'"), NodeName);
|
|
|
|
|
bValidOAuthProvider = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-31 22:01:53 -04:00
|
|
|
}
|
|
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
if (!bValidOAuthAccessToken && !bValidOAuthProviderIdentifier && !bValidOAuthProvider)
|
2022-05-31 22:01:53 -04:00
|
|
|
{
|
2022-09-23 11:32:36 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Error, TEXT("%s: At least one OAuth configuration must be provided and valid. "
|
|
|
|
|
"Options are 'OAuthProvider', 'OAuthProviderIdentifier', and 'OAuthAccessTokenEnvOverride'"), NodeName);
|
|
|
|
|
bValidParams = false;
|
2022-05-31 22:01:53 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Params.OAuthScope.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
Params.OAuthScope = TEXTVIEW("cache_access");
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 11:32:36 -04:00
|
|
|
if (bValidParams)
|
2022-05-31 22:01:53 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
if (TUniquePtr<FHttpCacheStore> Store = MakeUnique<FHttpCacheStore>(Params, Owner); Store->IsUsable())
|
2022-09-23 11:32:36 -04:00
|
|
|
{
|
2023-06-21 13:32:34 -04:00
|
|
|
return Store.Release();
|
2022-09-23 11:32:36 -04:00
|
|
|
}
|
2022-05-31 22:01:53 -04:00
|
|
|
UE_LOG(LogDerivedDataCache, Warning, TEXT("%s: Failed to contact the service (%s), will not use it."), NodeName, *Params.Host);
|
|
|
|
|
}
|
2022-01-11 11:57:38 -05:00
|
|
|
#endif
|
2022-09-23 11:32:36 -04:00
|
|
|
|
2023-06-21 13:32:34 -04:00
|
|
|
return nullptr;
|
2022-01-11 11:57:38 -05:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 22:01:53 -04:00
|
|
|
ILegacyCacheStore* GetAnyHttpCacheStore(
|
2022-01-11 11:57:38 -05:00
|
|
|
FString& OutDomain,
|
2023-03-23 13:05:19 -04:00
|
|
|
FString& OutAccessToken,
|
2022-09-14 18:25:46 -04:00
|
|
|
FString& OutNamespace)
|
2022-01-11 11:57:38 -05:00
|
|
|
{
|
|
|
|
|
#if WITH_HTTP_DDC_BACKEND
|
2022-02-14 14:43:39 -05:00
|
|
|
if (FHttpCacheStore* HttpBackend = FHttpCacheStore::GetAny())
|
2022-01-14 10:53:17 -05:00
|
|
|
{
|
|
|
|
|
OutDomain = HttpBackend->GetDomain();
|
2023-03-23 13:05:19 -04:00
|
|
|
OutAccessToken = HttpBackend->GetAccessToken();
|
2022-01-14 10:53:17 -05:00
|
|
|
OutNamespace = HttpBackend->GetNamespace();
|
|
|
|
|
return HttpBackend;
|
|
|
|
|
}
|
2022-01-11 11:57:38 -05:00
|
|
|
#endif
|
2022-09-23 11:32:36 -04:00
|
|
|
return nullptr;
|
2022-01-11 11:57:38 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:39 -05:00
|
|
|
} // UE::DerivedData
|