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"
2022-06-02 11:06:47 -04:00
# include "Templates/Tuple.h"
2022-05-31 23:25:37 -04:00
2020-06-23 18:40:00 -04:00
# if WITH_HTTP_DDC_BACKEND
2022-07-22 10:54:37 -04:00
# include "Algo/Transform.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-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"
# 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/WindowsHWrapper.h"
# 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-04-05 16:43:41 -04:00
# define UE_HTTPDDC_NONBLOCKING_REQUEST_POOL_SIZE 128
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
{
FString Host ;
2022-07-18 23:15:54 -04:00
FString HostPinnedPublicKeys ;
2022-05-31 22:01:53 -04:00
FString Namespace ;
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
*/
2022-05-31 22:01:53 -04:00
explicit FHttpCacheStore ( const FHttpCacheStoreParams & Params ) ;
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 ; }
const FString & GetOAuthProvider ( ) const { return OAuthProvider ; }
const FString & GetOAuthClientId ( ) const { return OAuthClientId ; }
const FString & GetOAuthSecret ( ) const { return OAuthSecret ; }
2022-03-23 18:05:53 -04:00
const FString & GetOAuthScope ( ) const { return OAuthScope ; }
2022-07-06 05:59:22 -04:00
const FString & GetOAuthProviderIdentifier ( ) const { return OAuthProviderIdentifier ; }
const FString & GetOAuthAccessToken ( ) const { return OAuthAccessToken ; }
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 ;
2022-07-18 12:23:16 -04:00
FAnsiStringBuilderBase EffectiveDomain ;
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 ] ;
FHttpRequestQueue NonBlockingRequestQueue ;
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-07-12 15:36:38 -04:00
uint32 FailedLoginAttempts = 0 ;
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
THttpUniquePtr < IHttpResponse > BeginIsServiceReady ( IHttpClient & Client , TArray64 < uint8 > & Body ) ;
bool EndIsServiceReady ( THttpUniquePtr < IHttpResponse > & Response , TArray64 < uint8 > & Body ) ;
bool AcquireAccessToken ( IHttpClient * Client = nullptr ) ;
void SetAccessToken ( FStringView Token , double RefreshDelay = 0.0 ) ;
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 ;
TUniquePtr < FHttpOperation > WaitForHttpOperation ( EOperationCategory Category , bool bUnboundedOverflow ) ;
2022-01-11 11:57:38 -05:00
2022-04-05 16:43:41 -04:00
struct FGetCacheRecordOnlyResponse
{
FSharedString Name ;
FCacheKey Key ;
uint64 UserData = 0 ;
uint64 BytesReceived = 0 ;
FOptionalCacheRecord Record ;
EStatus Status = EStatus : : Error ;
} ;
using FOnGetCacheRecordOnlyComplete = TUniqueFunction < void ( FGetCacheRecordOnlyResponse & & Response ) > ;
void GetCacheRecordOnlyAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
2022-01-11 11:57:38 -05:00
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
2022-04-05 16:43:41 -04:00
uint64 UserData ,
FOnGetCacheRecordOnlyComplete & & OnComplete ) ;
2022-02-02 07:35:19 -05:00
2022-04-05 16:43:41 -04:00
void GetCacheRecordAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
uint64 UserData ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & OnComplete ) ;
void PutCacheRecordAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheRecord & Record ,
const FCacheRecordPolicy & Policy ,
uint64 UserData ,
TUniqueFunction < void ( FCachePutResponse & & Response , uint64 BytesSent ) > & & OnComplete ) ;
void PutCacheValueAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
2022-02-02 07:35:19 -05:00
const FCacheKey & Key ,
const FValue & Value ,
ECachePolicy Policy ,
2022-04-05 16:43:41 -04:00
uint64 UserData ,
TUniqueFunction < void ( FCachePutValueResponse & & Response , uint64 BytesSent ) > & & 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 ,
2022-04-05 16:43:41 -04:00
uint64 UserData ,
FOnCacheGetValueComplete & & OnComplete ) ;
2022-02-02 07:35:19 -05:00
2022-04-05 16:43:41 -04:00
void RefCachedDataProbablyExistsBatchAsync (
IRequestOwner & Owner ,
TConstArrayView < FCacheGetValueRequest > ValueRefs ,
FOnCacheGetValueComplete & & OnComplete ) ;
2022-07-18 12:23:16 -04:00
class FHealthCheckOp ;
2022-04-05 16:43:41 -04:00
class FPutPackageOp ;
class FGetRecordOp ;
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 ;
uint64 GetBytesSent ( ) const { return Response - > GetStats ( ) . SendSize ; }
uint64 GetBytesReceived ( ) const { return Response - > GetStats ( ) . RecvSize ; }
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 ( ) ;
const bool bVerbose = ( StatusCode > = 200 & & StatusCode < 300 ) | | Operation - > ExpectedErrorCodes . Contains ( StatusCode ) ;
TStringBuilder < 80 > StatsText ;
if ( ! bVerbose | | UE_LOG_ACTIVE ( LogDerivedDataCache , Verbose ) )
{
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, " ) ;
}
StatsText . Appendf ( TEXT ( " %.3f seconds " ) , Stats . TotalTime ) ;
}
if ( bVerbose )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " HTTP: %s (%s) " ) , * WriteToString < 256 > ( LocalResponse ) , * StatsText ) ;
}
else
{
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 ) ;
}
}
}
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 ( ) ) ;
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 ;
}
//----------------------------------------------------------------------------------------------------------
// 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 :
struct FCachePutPackageResponse
{
FSharedString Name ;
FCacheKey Key ;
uint64 UserData = 0 ;
uint64 BytesSent = 0 ;
EStatus Status = EStatus : : Error ;
} ;
using FOnCachePutPackageComplete = TUniqueFunction < void ( FCachePutPackageResponse & & Response ) > ;
/** Performs a multi-request operation for uploading a package of content. */
static void PutPackage (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
FCacheKey Key ,
FCbPackage & & Package ,
FCacheRecordPolicy Policy ,
uint64 UserData ,
FOnCachePutPackageComplete & & OnComplete ) ;
private :
FHttpCacheStore & CacheStore ;
IRequestOwner & Owner ;
const FSharedString Name ;
const FCacheKey Key ;
const uint64 UserData ;
std : : atomic < uint64 > BytesSent ;
const FCbObject PackageObject ;
const FIoHash PackageObjectHash ;
const uint32 TotalBlobUploads ;
std : : atomic < uint32 > SuccessfulBlobUploads ;
std : : atomic < uint32 > PendingBlobUploads ;
FOnCachePutPackageComplete OnComplete ;
struct FCachePutRefResponse
{
FSharedString Name ;
FCacheKey Key ;
uint64 UserData = 0 ;
uint64 BytesSent = 0 ;
TConstArrayView < FIoHash > NeededBlobHashes ;
EStatus Status = EStatus : : Error ;
} ;
using FOnCachePutRefComplete = TUniqueFunction < void ( FCachePutRefResponse & & Response ) > ;
FPutPackageOp (
FHttpCacheStore & InCacheStore ,
IRequestOwner & InOwner ,
const FSharedString & InName ,
const FCacheKey & InKey ,
uint64 InUserData ,
uint64 InBytesSent ,
const FCbObject & InPackageObject ,
const FIoHash & InPackageObjectHash ,
uint32 InTotalBlobUploads ,
FOnCachePutPackageComplete & & InOnComplete ) ;
static void PutRefAsync (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
FCacheKey Key ,
FCbObject Object ,
FIoHash ObjectHash ,
uint64 UserData ,
bool bFinalize ,
FOnCachePutRefComplete & & OnComplete ) ;
static void OnPackagePutRefComplete (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheKey & Key ,
FCbPackage & & Package ,
FCacheRecordPolicy Policy ,
uint64 UserData ,
FOnCachePutPackageComplete & & OnComplete ,
FCachePutRefResponse & & Response ) ;
2022-07-18 12:23:16 -04:00
void OnCompressedBlobUploadComplete ( FHttpOperation & Operation ) ;
2022-04-05 16:43:41 -04:00
2022-07-18 12:23:16 -04:00
void OnPutRefFinalizationComplete ( FCachePutRefResponse & & Response ) ;
2022-04-05 16:43:41 -04:00
FCachePutPackageResponse MakeResponse ( uint64 InBytesSent , EStatus Status )
{
return FCachePutPackageResponse { Name , Key , UserData , InBytesSent , Status } ;
} ;
} ;
//----------------------------------------------------------------------------------------------------------
// FHttpCacheStore::FGetRecordOp
//----------------------------------------------------------------------------------------------------------
class FHttpCacheStore : : FGetRecordOp final : public FThreadSafeRefCountedObject
{
public :
/** Performs a multi-request operation for downloading a record. */
static void GetRecord (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
uint64 UserData ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & OnComplete ) ;
struct FGetCachedDataBatchResponse
{
FSharedString Name ;
FCacheKey Key ;
int32 ValueIndex ;
uint64 BytesReceived = 0 ;
FCompressedBuffer DataBuffer ;
EStatus Status = EStatus : : Error ;
} ;
using FOnGetCachedDataBatchComplete = TUniqueFunction < void ( FGetCachedDataBatchResponse & & Response ) > ;
/** Utility method for fetching a batch of value data. */
2022-06-29 21:31:09 -04:00
template < typename ValueType , typename ValueIdGetterType >
2022-04-05 16:43:41 -04:00
static void GetDataBatch (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
FSharedString Name ,
const FCacheKey & Key ,
TConstArrayView < ValueType > Values ,
ValueIdGetterType ValueIdGetter ,
FOnGetCachedDataBatchComplete & & OnComplete ) ;
private :
FHttpCacheStore & CacheStore ;
IRequestOwner & Owner ;
const FSharedString Name ;
const FCacheKey Key ;
const uint64 UserData ;
std : : atomic < uint64 > BytesReceived ;
TArray < FCompressedBuffer > FetchedBuffers ;
const TArray < FValueWithId > RequiredGets ;
const TArray < FValueWithId > RequiredHeads ;
FCacheRecordBuilder RecordBuilder ;
const uint32 TotalOperations ;
std : : atomic < uint32 > SuccessfulOperations ;
std : : atomic < uint32 > PendingOperations ;
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > OnComplete ;
FGetRecordOp (
FHttpCacheStore & InCacheStore ,
IRequestOwner & InOwner ,
const FSharedString & InName ,
const FCacheKey & InKey ,
uint64 InUserData ,
uint64 InBytesReceived ,
TArray < FValueWithId > & & InRequiredGets ,
TArray < FValueWithId > & & InRequiredHeads ,
FCacheRecordBuilder & & InRecordBuilder ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & InOnComplete ) ;
static void OnOnlyRecordComplete (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FCacheRecordPolicy & Policy ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & OnComplete ,
FGetCacheRecordOnlyResponse & & Response ) ;
struct FCachedDataProbablyExistsBatchResponse
{
FSharedString Name ;
FCacheKey Key ;
int32 ValueIndex ;
EStatus Status = EStatus : : Error ;
} ;
using FOnCachedDataProbablyExistsBatchComplete = TUniqueFunction < void ( FCachedDataProbablyExistsBatchResponse & & Response ) > ;
void DataProbablyExistsBatch (
TConstArrayView < FValueWithId > Values ,
FOnCachedDataProbablyExistsBatchComplete & & OnComplete ) ;
void FinishDataStep ( bool bSuccess , uint64 InBytesReceived ) ;
} ;
void FHttpCacheStore : : FPutPackageOp : : PutPackage (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
FCacheKey Key ,
FCbPackage & & Package ,
FCacheRecordPolicy Policy ,
uint64 UserData ,
FOnCachePutPackageComplete & & OnComplete )
{
// 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.
// Initial record upload
PutRefAsync ( CacheStore , Owner , Name , Key , Package . GetObject ( ) , Package . GetObjectHash ( ) , UserData , false ,
[ & CacheStore , & Owner , Name = FSharedString ( Name ) , Key , Package = MoveTemp ( Package ) , Policy , UserData , OnComplete = MoveTemp ( OnComplete ) ] ( FCachePutRefResponse & & Response ) mutable
{
return OnPackagePutRefComplete ( CacheStore , Owner , Name , Key , MoveTemp ( Package ) , Policy , UserData , MoveTemp ( OnComplete ) , MoveTemp ( Response ) ) ;
} ) ;
}
FHttpCacheStore : : FPutPackageOp : : FPutPackageOp (
FHttpCacheStore & InCacheStore ,
IRequestOwner & InOwner ,
const FSharedString & InName ,
const FCacheKey & InKey ,
uint64 InUserData ,
uint64 InBytesSent ,
const FCbObject & InPackageObject ,
const FIoHash & InPackageObjectHash ,
uint32 InTotalBlobUploads ,
FOnCachePutPackageComplete & & InOnComplete )
: CacheStore ( InCacheStore )
, Owner ( InOwner )
, Name ( InName )
, Key ( InKey )
, UserData ( InUserData )
, BytesSent ( InBytesSent )
, PackageObject ( InPackageObject )
, PackageObjectHash ( InPackageObjectHash )
, TotalBlobUploads ( InTotalBlobUploads )
, SuccessfulBlobUploads ( 0 )
, PendingBlobUploads ( InTotalBlobUploads )
, OnComplete ( MoveTemp ( InOnComplete ) )
{
}
void FHttpCacheStore : : FPutPackageOp : : PutRefAsync (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
FCacheKey Key ,
FCbObject Object ,
FIoHash ObjectHash ,
uint64 UserData ,
bool bFinalize ,
FOnCachePutRefComplete & & OnComplete )
{
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-07-18 12:23:16 -04:00
TUniquePtr < FHttpOperation > Operation = CacheStore . WaitForHttpOperation ( EOperationCategory : : Put , /*bUnboundedOverflow*/ bFinalize ) ;
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 ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , & CacheStore , Name , Key , Object , UserData , bFinalize , OnComplete = MoveTemp ( OnComplete ) ]
2022-04-05 16:43:41 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_PutRefAsync_OnHttpRequestComplete ) ;
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
{
TArray < FIoHash > NeededBlobHashes ;
// Useful when debugging issues related to compressed/uncompressed blobs being returned from Jupiter
const bool bPutRefBlobsAlways = false ;
if ( bPutRefBlobsAlways & & ! bFinalize )
{
Object . IterateAttachments ( [ & NeededBlobHashes ] ( FCbFieldView AttachmentFieldView )
2022-06-29 21:31:09 -04:00
{
FIoHash AttachmentHash = AttachmentFieldView . AsHash ( ) ;
if ( ! AttachmentHash . IsZero ( ) )
2022-04-05 16:43:41 -04:00
{
2022-06-29 21:31:09 -04:00
NeededBlobHashes . Add ( AttachmentHash ) ;
}
} ) ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
else if ( TSharedPtr < FJsonObject > ResponseObject = Operation - > GetBodyAsJson ( ) )
2022-04-05 16:43:41 -04:00
{
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 ) ;
}
}
}
2022-07-18 12:23:16 -04:00
OnComplete ( { Name , Key , UserData , Operation - > GetBytesSent ( ) , NeededBlobHashes , EStatus : : Ok } ) ;
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
{
2022-07-18 12:23:16 -04:00
const EStatus Status = Operation - > GetErrorCode ( ) = = EHttpErrorCode : : Canceled ? EStatus : : Canceled : EStatus : : Error ;
OnComplete ( { Name , Key , UserData , Operation - > GetBytesSent ( ) , { } , Status } ) ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
} ) ;
2022-04-05 16:43:41 -04:00
}
void FHttpCacheStore : : FPutPackageOp : : OnPackagePutRefComplete (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheKey & Key ,
FCbPackage & & Package ,
FCacheRecordPolicy Policy ,
uint64 UserData ,
FOnCachePutPackageComplete & & OnComplete ,
FCachePutRefResponse & & Response )
{
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' " ) ,
2022-05-31 22:01:53 -04:00
* CacheStore . Domain , * WriteToString < 96 > ( Response . Key ) , * Response . Name ) ;
2022-04-05 16:43:41 -04:00
}
return OnComplete ( FCachePutPackageResponse { Name , Key , UserData , Response . BytesSent , Response . Status } ) ;
}
struct FCompressedBlobUpload
{
FIoHash Hash ;
FSharedBuffer BlobBuffer ;
FCompressedBlobUpload ( const FIoHash & InHash , FSharedBuffer & & InBlobBuffer ) : Hash ( InHash ) , BlobBuffer ( InBlobBuffer )
{
}
} ;
TArray < FCompressedBlobUpload > CompressedBlobUploads ;
// 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)
for ( const FIoHash & NeededBlobHash : Response . NeededBlobHashes )
{
if ( const FCbAttachment * Attachment = Package . FindAttachment ( NeededBlobHash ) )
{
FSharedBuffer TempBuffer ;
if ( Attachment - > IsCompressedBinary ( ) )
{
TempBuffer = Attachment - > AsCompressedBinary ( ) . GetCompressed ( ) . ToShared ( ) ;
}
else if ( Attachment - > IsBinary ( ) )
{
TempBuffer = FValue : : Compress ( Attachment - > AsCompositeBinary ( ) ) . GetData ( ) . GetCompressed ( ) . ToShared ( ) ;
}
else
{
TempBuffer = FValue : : Compress ( Attachment - > AsObject ( ) . GetBuffer ( ) ) . GetData ( ) . GetCompressed ( ) . ToShared ( ) ;
}
CompressedBlobUploads . Emplace ( NeededBlobHash , MoveTemp ( TempBuffer ) ) ;
}
else
{
if ( ! bExpectedHashesSerialized )
{
bool bFirstHash = true ;
for ( const FCbAttachment & PackageAttachment : Package . GetAttachments ( ) )
{
if ( ! bFirstHash )
{
ExpectedHashes < < TEXT ( " , " ) ;
}
ExpectedHashes < < PackageAttachment . GetHash ( ) ;
bFirstHash = false ;
}
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' " ) ,
2022-05-31 22:01:53 -04:00
* CacheStore . Domain , * WriteToString < 96 > ( NeededBlobHash ) , ExpectedHashes . ToString ( ) , * WriteToString < 96 > ( Response . Key ) , * Response . Name ) ;
2022-04-05 16:43:41 -04:00
}
}
if ( CompressedBlobUploads . IsEmpty ( ) )
{
// No blobs need to be uploaded. No finalization necessary.
return OnComplete ( FCachePutPackageResponse { Name , Key , UserData , Response . BytesSent , EStatus : : Ok } ) ;
}
// Having this be a ref ensures we don't have the op reach 0 ref count as we queue up multiple operations which MAY execute synchronously
TRefCountPtr < FPutPackageOp > PutPackageOp = new FPutPackageOp (
CacheStore ,
Owner ,
Response . Name ,
Response . Key ,
Response . UserData ,
Response . BytesSent ,
Package . GetObject ( ) ,
Package . GetObjectHash ( ) ,
( uint32 ) CompressedBlobUploads . Num ( ) ,
MoveTemp ( OnComplete )
) ;
FRequestBarrier Barrier ( Owner ) ;
for ( const FCompressedBlobUpload & CompressedBlobUpload : CompressedBlobUploads )
{
2022-07-18 12:23:16 -04:00
TUniquePtr < FHttpOperation > Operation = CacheStore . WaitForHttpOperation ( EOperationCategory : : Put , /*bUnboundedOverflow*/ true ) ;
FHttpOperation & LocalOperation = * Operation ;
2022-09-14 18:25:46 -04:00
LocalOperation . SetUri ( WriteToAnsiString < 256 > ( CacheStore . EffectiveDomain , ANSITEXTVIEW ( " /api/v1/compressed-blobs/ " ) , CacheStore . Namespace , ' / ' , CompressedBlobUpload . Hash ) ) ;
2022-07-18 12:23:16 -04:00
LocalOperation . SetMethod ( EHttpMethod : : Put ) ;
LocalOperation . SetContentType ( EHttpMediaType : : CompressedBinary ) ;
LocalOperation . SetBody ( FCompositeBuffer ( CompressedBlobUpload . BlobBuffer ) ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , PutPackageOp ]
{
PutPackageOp - > OnCompressedBlobUploadComplete ( * Operation ) ;
} ) ;
2022-04-05 16:43:41 -04:00
}
}
2022-07-18 12:23:16 -04:00
void FHttpCacheStore : : FPutPackageOp : : OnCompressedBlobUploadComplete ( FHttpOperation & Operation )
2022-04-05 16:43:41 -04:00
{
2022-07-18 12:23:16 -04:00
BytesSent . fetch_add ( Operation . GetBytesSent ( ) , std : : memory_order_relaxed ) ;
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 ) ;
}
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 ( ) )
{
OnComplete ( MakeResponse ( BytesSent . load ( std : : memory_order_relaxed ) , EStatus : : Canceled ) ) ;
}
2022-07-18 12:23:16 -04:00
else if ( LocalSuccessfulBlobUploads = = TotalBlobUploads )
2022-04-05 16:43:41 -04:00
{
// Perform finalization
2022-07-18 12:23:16 -04:00
PutRefAsync ( CacheStore , Owner , Name , Key , PackageObject , PackageObjectHash , UserData , /*bFinalize*/ true ,
2022-04-05 16:43:41 -04:00
[ PutPackageOp = TRefCountPtr < FPutPackageOp > ( this ) ] ( FCachePutRefResponse & & Response )
{
return PutPackageOp - > OnPutRefFinalizationComplete ( MoveTemp ( Response ) ) ;
} ) ;
}
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 ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( MakeResponse ( BytesSent . load ( std : : memory_order_relaxed ) , EStatus : : Error ) ) ;
}
}
}
2022-07-18 12:23:16 -04:00
void FHttpCacheStore : : FPutPackageOp : : OnPutRefFinalizationComplete ( FCachePutRefResponse & & Response )
2022-04-05 16:43:41 -04:00
{
BytesSent . fetch_add ( Response . BytesSent , std : : memory_order_relaxed ) ;
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
}
return OnComplete ( MakeResponse ( BytesSent . load ( std : : memory_order_relaxed ) , Response . Status ) ) ;
}
void FHttpCacheStore : : FGetRecordOp : : GetRecord (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
uint64 UserData ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & OnComplete )
{
CacheStore . GetCacheRecordOnlyAsync ( Owner , Name , Key , Policy , UserData , [ & CacheStore , & Owner , Policy = FCacheRecordPolicy ( Policy ) , OnComplete = MoveTemp ( OnComplete ) ] ( FGetCacheRecordOnlyResponse & & Response ) mutable
{
OnOnlyRecordComplete ( CacheStore , Owner , Policy , MoveTemp ( OnComplete ) , MoveTemp ( Response ) ) ;
} ) ;
}
2022-06-29 21:31:09 -04:00
template < typename ValueType , typename ValueIdGetterType >
2022-04-05 16:43:41 -04:00
void FHttpCacheStore : : FGetRecordOp : : GetDataBatch (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
FSharedString Name ,
const FCacheKey & Key ,
TConstArrayView < ValueType > Values ,
ValueIdGetterType ValueIdGetter ,
FOnGetCachedDataBatchComplete & & OnComplete )
{
if ( Values . IsEmpty ( ) )
{
return ;
}
FRequestBarrier Barrier ( Owner ) ;
2022-07-22 10:21:26 -04:00
TSharedRef < FOnGetCachedDataBatchComplete > SharedOnComplete = MakeShared < FOnGetCachedDataBatchComplete > ( MoveTemp ( OnComplete ) ) ;
2022-04-05 16:43:41 -04:00
for ( int32 ValueIndex = 0 ; ValueIndex < Values . Num ( ) ; + + ValueIndex )
{
2022-07-18 12:23:16 -04:00
const ValueType Value = Values [ ValueIndex ] . RemoveData ( ) ;
TUniquePtr < FHttpOperation > Operation = CacheStore . WaitForHttpOperation ( EOperationCategory : : Get , /*bUnboundedOverflow*/ true ) ;
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 } ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , & CacheStore , Name , Key , Value , ValueIndex , ValueIdGetter , SharedOnComplete ]
2022-04-05 16:43:41 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_GetDataBatch_OnHttpRequestComplete ) ;
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 ;
}
}
if ( bHit )
{
if ( CompressedBuffer . GetRawHash ( ) = = Value . GetRawHash ( ) )
{
2022-07-22 10:21:26 -04:00
SharedOnComplete . Get ( ) ( { Name , Key , ValueIndex , Operation - > GetBytesReceived ( ) , 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' " ) ,
* CacheStore . Domain , * ValueIdGetter ( Value ) , * WriteToString < 48 > ( Value . GetRawHash ( ) ) ,
* WriteToString < 96 > ( Key ) , * Name ) ;
2022-07-22 10:21:26 -04:00
SharedOnComplete . Get ( ) ( { Name , Key , ValueIndex , Operation - > GetBytesReceived ( ) , { } , 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
{
2022-07-22 10:21:26 -04:00
SharedOnComplete . Get ( ) ( { Name , Key , ValueIndex , Operation - > GetBytesReceived ( ) , { } , 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' " ) ,
2022-05-31 22:01:53 -04:00
* CacheStore . Domain , * ValueIdGetter ( Value ) , * WriteToString < 48 > ( Value . GetRawHash ( ) ) , * WriteToString < 96 > ( Key ) ,
2022-04-05 16:43:41 -04:00
* Name ) ;
2022-07-22 10:21:26 -04:00
SharedOnComplete . Get ( ) ( { Name , Key , ValueIndex , Operation - > GetBytesReceived ( ) , { } , 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
}
}
FHttpCacheStore : : FGetRecordOp : : FGetRecordOp (
FHttpCacheStore & InCacheStore ,
IRequestOwner & InOwner ,
const FSharedString & InName ,
const FCacheKey & InKey ,
uint64 InUserData ,
uint64 InBytesReceived ,
TArray < FValueWithId > & & InRequiredGets ,
TArray < FValueWithId > & & InRequiredHeads ,
FCacheRecordBuilder & & InRecordBuilder ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & InOnComplete )
: CacheStore ( InCacheStore )
, Owner ( InOwner )
, Name ( InName )
, Key ( InKey )
, UserData ( InUserData )
, BytesReceived ( InBytesReceived )
, RequiredGets ( MoveTemp ( InRequiredGets ) )
, RequiredHeads ( MoveTemp ( InRequiredHeads ) )
, RecordBuilder ( MoveTemp ( InRecordBuilder ) )
, TotalOperations ( RequiredGets . Num ( ) + RequiredHeads . Num ( ) )
, SuccessfulOperations ( 0 )
, PendingOperations ( TotalOperations )
, OnComplete ( MoveTemp ( InOnComplete ) )
{
FetchedBuffers . AddDefaulted ( RequiredGets . Num ( ) ) ;
}
void FHttpCacheStore : : FGetRecordOp : : OnOnlyRecordComplete (
FHttpCacheStore & CacheStore ,
IRequestOwner & Owner ,
const FCacheRecordPolicy & Policy ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & OnComplete ,
FGetCacheRecordOnlyResponse & & Response )
{
FCacheRecordBuilder RecordBuilder ( Response . Key ) ;
if ( Response . Status ! = EStatus : : Ok )
{
return OnComplete ( { Response . Name , RecordBuilder . Build ( ) , Response . UserData , Response . Status } , Response . BytesReceived ) ;
}
if ( ! EnumHasAnyFlags ( Policy . GetRecordPolicy ( ) , ECachePolicy : : SkipMeta ) )
{
RecordBuilder . SetMeta ( FCbObject ( Response . Record . Get ( ) . GetMeta ( ) ) ) ;
}
// TODO: There is not currently a batched GET endpoint for Jupiter. Once there is, all payload data should be fetched in one call.
// In the meantime, we try to keep the code structured in a way that is friendly to future batching of GETs.
TArray < FValueWithId > RequiredGets ;
TArray < FValueWithId > RequiredHeads ;
for ( FValueWithId Value : Response . Record . Get ( ) . GetValues ( ) )
{
const ECachePolicy ValuePolicy = Policy . GetValuePolicy ( Value . GetId ( ) ) ;
if ( IsValueDataReady ( Value , ValuePolicy ) )
{
RecordBuilder . AddValue ( MoveTemp ( Value ) ) ;
}
else
{
if ( EnumHasAnyFlags ( ValuePolicy , ECachePolicy : : SkipData ) )
{
RequiredHeads . Emplace ( Value ) ;
}
else
{
RequiredGets . Emplace ( Value ) ;
}
}
}
if ( RequiredGets . IsEmpty ( ) & & RequiredHeads . IsEmpty ( ) )
{
return OnComplete ( { Response . Name , RecordBuilder . Build ( ) , Response . UserData , Response . Status } , Response . BytesReceived ) ;
}
// Having this be a ref ensures we don't have the op reach 0 ref count in between the start of the exist batch operation and the get batch operation
TRefCountPtr < FGetRecordOp > GetRecordOp = new FGetRecordOp (
CacheStore ,
Owner ,
Response . Name ,
Response . Key ,
Response . UserData ,
Response . BytesReceived ,
MoveTemp ( RequiredGets ) ,
MoveTemp ( RequiredHeads ) ,
MoveTemp ( RecordBuilder ) ,
MoveTemp ( OnComplete )
) ;
auto IdGetter = [ ] ( const FValueWithId & Value )
{
return FString ( WriteToString < 16 > ( Value . GetId ( ) ) ) ;
} ;
{
FRequestBarrier Barrier ( Owner ) ;
GetRecordOp - > DataProbablyExistsBatch ( GetRecordOp - > RequiredHeads , [ GetRecordOp ] ( FCachedDataProbablyExistsBatchResponse & & Response )
{
GetRecordOp - > FinishDataStep ( Response . Status = = EStatus : : Ok , 0 ) ;
} ) ;
GetDataBatch < FValueWithId > ( CacheStore , Owner , Response . Name , Response . Key , GetRecordOp - > RequiredGets , IdGetter , [ GetRecordOp ] ( FGetCachedDataBatchResponse & & Response )
{
GetRecordOp - > FetchedBuffers [ Response . ValueIndex ] = MoveTemp ( Response . DataBuffer ) ;
GetRecordOp - > FinishDataStep ( Response . Status = = EStatus : : Ok , Response . BytesReceived ) ;
} ) ;
}
}
void FHttpCacheStore : : FGetRecordOp : : DataProbablyExistsBatch (
TConstArrayView < FValueWithId > Values ,
FOnCachedDataProbablyExistsBatchComplete & & InOnComplete )
{
if ( Values . IsEmpty ( ) )
{
return ;
}
2022-07-18 12:23:16 -04:00
TAnsiStringBuilder < 256 > CompressedBlobsUri ;
2022-09-14 18:25:46 -04:00
CompressedBlobsUri < < CacheStore . EffectiveDomain < < ANSITEXTVIEW ( " /api/v1/compressed-blobs/ " ) < < CacheStore . Namespace < < ANSITEXTVIEW ( " /exists? " ) ;
2022-04-05 16:43:41 -04:00
bool bFirstItem = true ;
for ( const FValueWithId & Value : Values )
{
if ( ! bFirstItem )
{
2022-07-18 12:23:16 -04:00
CompressedBlobsUri < < ' & ' ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
CompressedBlobsUri < < ANSITEXTVIEW ( " id= " ) < < Value . GetRawHash ( ) ;
2022-04-05 16:43:41 -04:00
bFirstItem = false ;
}
2022-07-18 12:23:16 -04:00
TUniquePtr < FHttpOperation > Operation = CacheStore . WaitForHttpOperation ( EOperationCategory : : Get , /*bUnboundedOverflow*/ true ) ;
FHttpOperation & LocalOperation = * Operation ;
LocalOperation . SetUri ( CompressedBlobsUri ) ;
LocalOperation . SetMethod ( EHttpMethod : : Post ) ;
LocalOperation . SetContentType ( EHttpMediaType : : FormUrlEncoded ) ;
LocalOperation . AddAcceptType ( EHttpMediaType : : Json ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , this , Values = TArray < FValueWithId > ( Values ) , InOnComplete = MoveTemp ( InOnComplete ) ]
2022-04-05 16:43:41 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_DataProbablyExistsBatch_OnHttpRequestComplete ) ;
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
{
2022-07-18 12:23:16 -04:00
if ( TSharedPtr < FJsonObject > ResponseObject = Operation - > GetBodyAsJson ( ) )
2022-04-05 16:43:41 -04:00
{
TArray < FString > NeedsArrayStrings ;
if ( ResponseObject - > TryGetStringArrayField ( TEXT ( " needs " ) , NeedsArrayStrings ) )
{
if ( NeedsArrayStrings . IsEmpty ( ) )
{
for ( int32 ValueIndex = 0 ; ValueIndex < Values . Num ( ) ; + + ValueIndex )
{
const FValueWithId & Value = Values [ ValueIndex ] ;
2022-05-20 11:32:33 -04:00
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
2022-07-18 12:23:16 -04:00
TEXT ( " %s: Cache exists hit for value %s with hash %s for %s from '%s' " ) ,
* CacheStore . Domain , * WriteToString < 16 > ( Value . GetId ( ) ) ,
* WriteToString < 48 > ( Value . GetRawHash ( ) ) , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-05-20 11:32:33 -04:00
InOnComplete ( { Name , Key , ValueIndex , EStatus : : Ok } ) ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
return ;
2022-04-05 16:43:41 -04:00
}
}
TBitArray < > ResultStatus ( true , Values . Num ( ) ) ;
for ( const FString & NeedsString : NeedsArrayStrings )
{
2022-05-20 11:32:33 -04:00
const FIoHash NeedHash ( NeedsString ) ;
2022-04-05 16:43:41 -04:00
for ( int32 ValueIndex = 0 ; ValueIndex < Values . Num ( ) ; + + ValueIndex )
{
const FValueWithId & Value = Values [ ValueIndex ] ;
if ( ResultStatus [ ValueIndex ] & & NeedHash = = Value . GetRawHash ( ) )
{
ResultStatus [ ValueIndex ] = false ;
break ;
}
}
}
for ( int32 ValueIndex = 0 ; ValueIndex < Values . Num ( ) ; + + ValueIndex )
{
const FValueWithId & Value = Values [ ValueIndex ] ;
if ( ResultStatus [ ValueIndex ] )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
2022-07-18 12:23:16 -04:00
TEXT ( " %s: Cache exists hit for value %s with hash %s for %s from '%s' " ) ,
* CacheStore . Domain , * WriteToString < 32 > ( Value . GetId ( ) ) ,
* WriteToString < 48 > ( Value . GetRawHash ( ) ) , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
InOnComplete ( { Name , Key , ValueIndex , EStatus : : Ok } ) ;
}
else
{
UE_LOG ( LogDerivedDataCache , Verbose ,
TEXT ( " %s: Cache exists miss with missing value %s with hash %s for %s from '%s' " ) ,
2022-07-18 12:23:16 -04:00
* CacheStore . Domain , * WriteToString < 32 > ( Value . GetId ( ) ) ,
* WriteToString < 48 > ( Value . GetRawHash ( ) ) , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
InOnComplete ( { Name , Key , ValueIndex , EStatus : : Error } ) ;
}
}
}
else
{
for ( int32 ValueIndex = 0 ; ValueIndex < Values . Num ( ) ; + + ValueIndex )
{
UE_LOG ( LogDerivedDataCache , Log ,
2022-07-18 12:23:16 -04:00
TEXT ( " %s: Cache exists miss with invalid response for value %s for %s from '%s' " ) ,
* CacheStore . Domain , * WriteToString < 32 > ( Values [ ValueIndex ] . GetId ( ) ) ,
* WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
InOnComplete ( { Name , Key , ValueIndex , EStatus : : Error } ) ;
}
}
}
2022-07-18 12:23:16 -04:00
else
2022-04-05 16:43:41 -04:00
{
2022-07-18 12:23:16 -04:00
for ( int32 ValueIndex = 0 ; ValueIndex < Values . Num ( ) ; + + ValueIndex )
{
UE_LOG ( LogDerivedDataCache , Verbose ,
TEXT ( " %s: Cache exists miss with failed response for value %s for %s from '%s' " ) ,
* CacheStore . Domain , * WriteToString < 32 > ( Values [ ValueIndex ] . GetId ( ) ) ,
* WriteToString < 96 > ( Key ) , * Name ) ;
InOnComplete ( { Name , Key , ValueIndex , 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
}
void FHttpCacheStore : : FGetRecordOp : : FinishDataStep ( bool bSuccess , uint64 InBytesReceived )
{
BytesReceived . fetch_add ( InBytesReceived , std : : memory_order_relaxed ) ;
if ( bSuccess )
{
SuccessfulOperations . fetch_add ( 1 , std : : memory_order_relaxed ) ;
}
if ( PendingOperations . fetch_sub ( 1 , std : : memory_order_acq_rel ) = = 1 )
{
EStatus Status = EStatus : : Error ;
uint32 LocalSuccessfulOperations = SuccessfulOperations . load ( std : : memory_order_relaxed ) ;
if ( LocalSuccessfulOperations = = TotalOperations )
{
for ( int32 Index = 0 ; Index < RequiredHeads . Num ( ) ; + + Index )
{
RecordBuilder . AddValue ( RequiredHeads [ Index ] . RemoveData ( ) ) ;
}
for ( int32 Index = 0 ; Index < RequiredGets . Num ( ) ; + + Index )
{
RecordBuilder . AddValue ( FValueWithId ( RequiredGets [ Index ] . GetId ( ) , FetchedBuffers [ Index ] ) ) ;
}
Status = EStatus : : Ok ;
}
OnComplete ( { Name , RecordBuilder . Build ( ) , UserData , Status } , BytesReceived . load ( std : : memory_order_relaxed ) ) ;
}
}
2022-05-31 22:01:53 -04:00
FHttpCacheStore : : FHttpCacheStore ( const FHttpCacheStoreParams & Params )
: 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 )
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 ) ;
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 ) ;
ClientParams . MaxRequests = UE_HTTPDDC_NONBLOCKING_REQUEST_POOL_SIZE * 2 ;
ClientParams . MinRequests = UE_HTTPDDC_NONBLOCKING_REQUEST_POOL_SIZE ;
NonBlockingRequestQueue = FHttpRequestQueue ( * ConnectionPool , ClientParams ) ;
2022-02-08 01:56:17 -05:00
2020-06-23 18:40:00 -04:00
bIsUsable = true ;
}
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 ) ;
}
2021-07-19 13:09:47 -04:00
if ( AnyInstance = = this )
{
AnyInstance = nullptr ;
}
2020-06-23 18:40:00 -04:00
}
2022-07-18 12:23:16 -04:00
FHttpClientParams FHttpCacheStore : : GetDefaultClientParams ( ) const
{
FHttpClientParams ClientParams ;
ClientParams . DnsCacheTimeout = 300 ;
ClientParams . ConnectTimeout = 30 * 1000 ;
ClientParams . LowSpeedLimit = 1024 ;
ClientParams . LowSpeedTime = 30 ;
ClientParams . TlsLevel = EHttpTlsLevel : : All ;
ClientParams . bFollowRedirects = true ;
ClientParams . bFollow302Post = true ;
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 ;
}
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 ( ) )
{
SetAccessToken ( OAuthAccessToken ) ;
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 ) ;
SetAccessToken ( AccessTokenString , ExpiryTimeSeconds ) ;
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 ;
2022-08-11 09:17:03 -04:00
if ( FDesktopPlatformModule : : Get ( ) - > GetOidcAccessToken ( FPaths : : RootDir ( ) , FPaths : : GetProjectFilePath ( ) , OAuthProviderIdentifier , FApp : : IsUnattended ( ) , GWarn , AccessTokenString , TokenExpiresAt ) )
2020-06-23 18:40:00 -04:00
{
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 ) ;
SetAccessToken ( AccessTokenString , ExpiryTimeSeconds ) ;
2020-06-23 18:40:00 -04:00
return true ;
}
2022-07-12 15:36:38 -04:00
UE_LOG ( LogDerivedDataCache , Error , TEXT ( " %s: OidcToken: Failed to log in to HTTP services. " ) , * Domain ) ;
FailedLoginAttempts + + ;
return false ;
}
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 ) ;
2020-06-23 18:40:00 -04:00
return false ;
}
2022-07-12 15:36:38 -04:00
void FHttpCacheStore : : SetAccessToken ( FStringView Token , double RefreshDelay )
{
if ( RefreshAccessTokenHandle . IsValid ( ) )
{
FTSTicker : : GetCoreTicker ( ) . RemoveTicker ( RefreshAccessTokenHandle ) ;
RefreshAccessTokenHandle . Reset ( ) ;
}
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 ;
}
2022-07-18 12:23:16 -04:00
TUniquePtr < FHttpCacheStore : : FHttpOperation > FHttpCacheStore : : WaitForHttpOperation ( EOperationCategory Category , bool bUnboundedOverflow )
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-07-18 12:23:16 -04:00
Params . bIgnoreMaxRequests = bUnboundedOverflow ;
Request = NonBlockingRequestQueue . CreateRequest ( Params ) ;
}
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
}
2022-04-05 16:43:41 -04:00
void FHttpCacheStore : : GetCacheRecordOnlyAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
2022-04-05 15:17:17 -04:00
const FCacheKey & Key ,
2022-04-05 16:43:41 -04:00
const FCacheRecordPolicy & Policy ,
uint64 UserData ,
FOnGetCacheRecordOnlyComplete & & OnComplete )
2022-04-05 15:17:17 -04:00
{
2022-04-05 16:43:41 -04:00
auto MakeResponse = [ Name = FSharedString ( Name ) , Key , UserData ] ( uint64 BytesReceived , EStatus Status )
{
return FGetCacheRecordOnlyResponse { Name , Key , UserData , BytesReceived , { } , Status } ;
} ;
2022-01-10 13:43:40 -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
return OnComplete ( MakeResponse ( 0 , EStatus : : Error ) ) ;
2022-01-10 13:43:40 -05:00
}
// Skip the request if querying the cache is disabled.
2022-05-31 22:01:53 -04:00
if ( ! EnumHasAnyFlags ( Policy . GetRecordPolicy ( ) , ECachePolicy : : QueryRemote ) )
2022-01-10 13:43:40 -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
return OnComplete ( MakeResponse ( 0 , EStatus : : Error ) ) ;
2022-01-10 13:43:40 -05:00
}
2022-03-31 10:06:47 -04:00
if ( DebugOptions . ShouldSimulateGetMiss ( Key ) )
2022-01-10 13:43:40 -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
return OnComplete ( MakeResponse ( 0 , EStatus : : Error ) ) ;
2022-01-10 13:43:40 -05:00
}
2022-07-22 10:54:37 -04:00
TAnsiStringBuilder < 64 > Bucket ;
Algo : : Transform ( Key . Bucket . ToString ( ) , AppendChars ( Bucket ) , FCharAnsi : : ToLower ) ;
2022-02-24 10:24:10 -05:00
2022-07-18 12:23:16 -04:00
TUniquePtr < FHttpOperation > Operation = WaitForHttpOperation ( EOperationCategory : : Get , /*bUnboundedOverflow*/ false ) ;
FHttpOperation & LocalOperation = * Operation ;
2022-09-14 18:25:46 -04:00
LocalOperation . SetUri ( WriteToAnsiString < 256 > ( EffectiveDomain , ANSITEXTVIEW ( " /api/v1/refs/ " ) , Namespace , ' / ' , Bucket , ' / ' , Key . Hash ) ) ;
2022-07-18 12:23:16 -04:00
LocalOperation . SetMethod ( EHttpMethod : : Get ) ;
LocalOperation . AddAcceptType ( EHttpMediaType : : CbObject ) ;
LocalOperation . SetExpectedErrorCodes ( { 404 } ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , this , Name , Key , UserData , OnComplete = MoveTemp ( OnComplete ) ]
2022-01-10 13:43:40 -05:00
{
2022-04-05 16:43:41 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_GetCacheRecordOnlyAsync_OnHttpRequestComplete ) ;
2022-01-10 13:43:40 -05:00
2022-07-18 12:23:16 -04:00
const int32 StatusCode = Operation - > GetStatusCode ( ) ;
if ( StatusCode > = 200 & & StatusCode < = 204 )
2022-01-10 13:43:40 -05:00
{
2022-07-18 12:23:16 -04:00
FSharedBuffer Body = Operation - > GetBody ( ) ;
2022-04-05 16:43:41 -04:00
2022-07-18 12:23:16 -04:00
if ( ValidateCompactBinary ( Body , ECbValidateMode : : Default ) ! = ECbValidateError : : None )
2022-02-24 10:24:10 -05:00
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Cache miss with invalid package for %s from '%s' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-07-18 12:23:16 -04:00
OnComplete ( { Name , Key , UserData , Operation - > GetBytesReceived ( ) , { } , EStatus : : Error } ) ;
2022-02-24 10:24:10 -05:00
}
2022-07-18 12:23:16 -04:00
else if ( FOptionalCacheRecord Record = FCacheRecord : : Load ( FCbPackage ( FCbObject ( Body ) ) ) ; Record . IsNull ( ) )
2022-01-10 13:43:40 -05:00
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Cache miss with record load failure for %s from '%s' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-07-18 12:23:16 -04:00
OnComplete ( { Name , Key , UserData , Operation - > GetBytesReceived ( ) , { } , EStatus : : Error } ) ;
}
else
{
OnComplete ( { Name , Key , UserData , Operation - > GetBytesReceived ( ) , MoveTemp ( Record ) , EStatus : : Ok } ) ;
2022-01-10 13:43:40 -05:00
}
}
2022-07-18 12:23:16 -04:00
else
2022-01-14 10:53:17 -05:00
{
2022-07-18 12:23:16 -04:00
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with missing package for %s from '%s' " ) ,
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
OnComplete ( { Name , Key , UserData , Operation - > GetBytesReceived ( ) , { } , EStatus : : Error } ) ;
2022-01-14 10:53:17 -05:00
}
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 : : PutCacheRecordAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheRecord & Record ,
const FCacheRecordPolicy & Policy ,
uint64 UserData ,
TUniqueFunction < void ( FCachePutResponse & & Response , uint64 BytesSent ) > & & OnComplete )
2022-04-05 14:18:39 -04:00
{
2022-04-05 16:43:41 -04:00
const FCacheKey & Key = Record . GetKey ( ) ;
auto MakeResponse = [ Name = FSharedString ( Name ) , Key = FCacheKey ( Key ) , UserData ] ( EStatus Status )
{
return FCachePutResponse { Name , Key , UserData , Status } ;
} ;
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 " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
return OnComplete ( MakeResponse ( EStatus : : Error ) , 0 ) ;
2022-04-05 14:18:39 -04:00
}
// Skip the request if storing to the cache is disabled.
2022-04-05 16:43:41 -04:00
const ECachePolicy RecordPolicy = 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 " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
return OnComplete ( MakeResponse ( EStatus : : Error ) , 0 ) ;
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' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
return OnComplete ( MakeResponse ( EStatus : : Error ) , 0 ) ;
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.
2022-04-05 16:43:41 -04:00
FCbPackage Package = Record . Save ( ) ;
FPutPackageOp : : PutPackage ( * this , Owner , Name , Key , MoveTemp ( Package ) , Policy , UserData , [ MakeResponse = MoveTemp ( MakeResponse ) , OnComplete = MoveTemp ( OnComplete ) ] ( FPutPackageOp : : FCachePutPackageResponse & & Response )
{
OnComplete ( MakeResponse ( Response . Status ) , Response . BytesSent ) ;
} ) ;
}
void FHttpCacheStore : : PutCacheValueAsync (
IRequestOwner & Owner ,
const FSharedString & Name ,
const FCacheKey & Key ,
const FValue & Value ,
const ECachePolicy Policy ,
uint64 UserData ,
TUniqueFunction < void ( FCachePutValueResponse & & Response , uint64 BytesSent ) > & & OnComplete )
{
auto MakeResponse = [ Name = FSharedString ( Name ) , Key = FCacheKey ( Key ) , UserData ] ( EStatus Status )
{
return FCachePutValueResponse { Name , Key , UserData , Status } ;
} ;
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 " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
return OnComplete ( MakeResponse ( EStatus : : Error ) , 0 ) ;
}
// Skip the request if storing to the cache is disabled.
2022-05-31 22:01:53 -04:00
if ( ! EnumHasAnyFlags ( 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 " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Key ) , * Name ) ;
2022-04-05 16:43:41 -04:00
return OnComplete ( MakeResponse ( EStatus : : Error ) , 0 ) ;
}
if ( DebugOptions . ShouldSimulatePutMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for put 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
return OnComplete ( MakeResponse ( EStatus : : Error ) , 0 ) ;
}
// 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.
2022-02-02 07:35:19 -05:00
FCbWriter Writer ;
Writer . BeginObject ( ) ;
Writer . AddBinaryAttachment ( " RawHash " , Value . GetRawHash ( ) ) ;
Writer . AddInteger ( " RawSize " , Value . GetRawSize ( ) ) ;
Writer . EndObject ( ) ;
FCbPackage Package ( Writer . Save ( ) . AsObject ( ) ) ;
2022-04-05 16:43:41 -04:00
Package . AddAttachment ( FCbAttachment ( Value . GetData ( ) ) ) ;
2022-02-02 07:35:19 -05:00
2022-04-05 16:43:41 -04:00
FPutPackageOp : : PutPackage ( * this , Owner , Name , Key , MoveTemp ( Package ) , Policy , UserData , [ MakeResponse = MoveTemp ( MakeResponse ) , OnComplete = MoveTemp ( OnComplete ) ] ( FPutPackageOp : : FCachePutPackageResponse & & Response )
2022-02-22 13:55:39 -05:00
{
2022-04-05 16:43:41 -04:00
OnComplete ( MakeResponse ( Response . Status ) , Response . BytesSent ) ;
} ) ;
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 ,
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
}
2022-02-24 10:24:10 -05:00
const bool bSkipData = EnumHasAnyFlags ( Policy , ECachePolicy : : SkipData ) ;
2022-02-02 07:35:19 -05:00
2022-07-22 10:54:37 -04:00
TAnsiStringBuilder < 64 > Bucket ;
Algo : : Transform ( Key . Bucket . ToString ( ) , AppendChars ( Bucket ) , FCharAnsi : : ToLower ) ;
2022-02-02 07:35:19 -05:00
2022-07-18 12:23:16 -04:00
TUniquePtr < FHttpOperation > Operation = WaitForHttpOperation ( EOperationCategory : : Get , /*bUnboundedOverflow*/ false ) ;
FHttpOperation & LocalOperation = * Operation ;
2022-09-14 18:25:46 -04:00
LocalOperation . SetUri ( WriteToAnsiString < 256 > ( EffectiveDomain , ANSITEXTVIEW ( " /api/v1/refs/ " ) , Namespace , ' / ' , Bucket , ' / ' , Key . Hash ) ) ;
2022-07-18 12:23:16 -04:00
LocalOperation . SetMethod ( EHttpMethod : : Get ) ;
2022-04-05 15:17:17 -04:00
if ( bSkipData )
{
2022-07-18 12:23:16 -04:00
LocalOperation . AddAcceptType ( EHttpMediaType : : CbObject ) ;
2022-04-05 15:17:17 -04:00
}
else
{
2022-07-18 12:23:16 -04:00
LocalOperation . AddHeader ( ANSITEXTVIEW ( " Accept " ) , ANSITEXTVIEW ( " application/x-jupiter-inline " ) ) ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
LocalOperation . SetExpectedErrorCodes ( { 404 } ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , this , Name , Key , UserData , bSkipData , OnComplete = MoveTemp ( OnComplete ) ]
2022-04-05 16:43:41 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_GetCacheValueAsync_OnHttpRequestComplete ) ;
2022-07-18 12:23:16 -04:00
const int32 StatusCode = Operation - > GetStatusCode ( ) ;
if ( StatusCode > = 200 & & StatusCode < = 204 )
2022-04-05 15:17:17 -04:00
{
2022-04-05 16:43:41 -04:00
FValue ResultValue ;
2022-07-18 12:23:16 -04:00
FSharedBuffer ResponseBuffer = Operation - > GetBody ( ) ;
2022-04-05 16:43:41 -04:00
if ( bSkipData )
2022-03-02 17:30:48 -05:00
{
2022-04-05 16:43:41 -04:00
if ( ValidateCompactBinary ( ResponseBuffer , ECbValidateMode : : Default ) ! = ECbValidateError : : None )
2022-03-02 17:30:48 -05:00
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid package for %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 } ) ;
2022-07-18 12:23:16 -04:00
return ;
2022-03-02 17:30:48 -05:00
}
2022-04-05 16:43:41 -04:00
const FCbObjectView Object = FCbObject ( ResponseBuffer ) ;
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 '%' " ) ,
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 } ) ;
2022-07-18 12:23:16 -04:00
return ;
2022-04-05 16:43:41 -04:00
}
ResultValue = FValue ( RawHash , RawSize ) ;
2022-01-10 13:43:40 -05:00
}
else
{
2022-04-05 16:43:41 -04:00
FCompressedBuffer CompressedBuffer = FCompressedBuffer : : FromCompressed ( ResponseBuffer ) ;
if ( ! CompressedBuffer )
2022-02-24 10:24:10 -05:00
{
2022-07-18 12:23:16 -04:00
if ( FAnsiStringView ReceivedHashStr = Operation - > GetHeader ( " X-Jupiter-InlinePayloadHash " ) ; ! ReceivedHashStr . IsEmpty ( ) )
2022-02-24 10:24:10 -05:00
{
2022-04-05 16:43:41 -04:00
FIoHash ReceivedHash ( ReceivedHashStr ) ;
FIoHash ComputedHash = FIoHash : : HashBuffer ( ResponseBuffer . GetView ( ) ) ;
if ( ReceivedHash = = ComputedHash )
2022-02-24 10:24:10 -05:00
{
2022-04-05 16:43:41 -04:00
CompressedBuffer = FCompressedBuffer : : Compress ( ResponseBuffer ) ;
2022-02-24 10:24:10 -05:00
}
}
}
2022-04-05 16:43:41 -04:00
if ( ! CompressedBuffer )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid package for %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 } ) ;
2022-07-18 12:23:16 -04:00
return ;
2022-04-05 16:43:41 -04:00
}
ResultValue = FValue ( CompressedBuffer ) ;
2022-02-24 10:24:10 -05:00
}
2022-04-05 16:43:41 -04:00
OnComplete ( { Name , Key , ResultValue , UserData , EStatus : : Ok } ) ;
2022-07-18 12:23:16 -04:00
return ;
2022-06-29 21:31:09 -04:00
}
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with failed HTTP request for %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 } ) ;
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 ,
TUniqueFunction < void ( FCacheGetResponse & & Response , uint64 BytesReceived ) > & & OnComplete )
{
FGetRecordOp : : GetRecord ( * this , Owner , Name , Key , Policy , UserData , MoveTemp ( OnComplete ) ) ;
}
void FHttpCacheStore : : RefCachedDataProbablyExistsBatchAsync (
IRequestOwner & Owner ,
TConstArrayView < FCacheGetValueRequest > ValueRefs ,
FOnCacheGetValueComplete & & OnComplete )
2022-03-02 17:30:48 -05:00
{
if ( ValueRefs . IsEmpty ( ) )
{
2022-04-05 16:43:41 -04:00
return ;
2022-03-02 17:30:48 -05:00
}
2022-04-05 16:43:41 -04:00
if ( ! IsUsable ( ) )
2022-03-02 17:30:48 -05:00
{
2022-04-05 16:43:41 -04:00
for ( const FCacheGetValueRequest & ValueRef : ValueRefs )
2022-06-29 21:31:09 -04:00
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped exists check of %s from '%s' because this cache store is not available " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( ValueRef . Key ) , * ValueRef . Name ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
2022-06-29 21:31:09 -04:00
}
2022-04-05 16:43:41 -04:00
return ;
2022-03-02 17:30:48 -05:00
}
FCbWriter RequestWriter ;
RequestWriter . BeginObject ( ) ;
2022-04-25 11:31:36 -04:00
RequestWriter . BeginArray ( ANSITEXTVIEW ( " ops " ) ) ;
2022-03-02 17:30:48 -05:00
uint32 OpIndex = 0 ;
2022-04-05 16:43:41 -04:00
for ( const FCacheGetValueRequest & ValueRef : ValueRefs )
2022-03-02 17:30:48 -05:00
{
RequestWriter . BeginObject ( ) ;
2022-04-25 11:31:36 -04:00
RequestWriter . AddInteger ( ANSITEXTVIEW ( " opId " ) , OpIndex ) ;
RequestWriter . AddString ( ANSITEXTVIEW ( " op " ) , ANSITEXTVIEW ( " GET " ) ) ;
2022-07-22 10:54:37 -04:00
const FCacheKey & Key = ValueRef . Key ;
TAnsiStringBuilder < 64 > Bucket ;
Algo : : Transform ( Key . Bucket . ToString ( ) , AppendChars ( Bucket ) , FCharAnsi : : ToLower ) ;
2022-04-25 11:31:36 -04:00
RequestWriter . AddString ( ANSITEXTVIEW ( " bucket " ) , Bucket ) ;
RequestWriter . AddString ( ANSITEXTVIEW ( " key " ) , LexToString ( Key . Hash ) ) ;
RequestWriter . AddBool ( ANSITEXTVIEW ( " resolveAttachments " ) , true ) ;
2022-03-02 17:30:48 -05:00
RequestWriter . EndObject ( ) ;
+ + OpIndex ;
}
RequestWriter . EndArray ( ) ;
RequestWriter . EndObject ( ) ;
FCbFieldIterator RequestFields = RequestWriter . Save ( ) ;
2022-07-18 12:23:16 -04:00
TUniquePtr < FHttpOperation > Operation = WaitForHttpOperation ( EOperationCategory : : Get , /*bUnboundedOverflow*/ false ) ;
FHttpOperation & LocalOperation = * Operation ;
2022-09-14 18:25:46 -04:00
LocalOperation . SetUri ( WriteToAnsiString < 256 > ( EffectiveDomain , ANSITEXTVIEW ( " /api/v1/refs/ " ) , Namespace ) ) ;
2022-07-18 12:23:16 -04:00
LocalOperation . SetMethod ( EHttpMethod : : Post ) ;
LocalOperation . SetContentType ( EHttpMediaType : : CbObject ) ;
LocalOperation . AddAcceptType ( EHttpMediaType : : CbObject ) ;
LocalOperation . SetBody ( FCompositeBuffer ( RequestFields . GetOuterBuffer ( ) ) ) ;
LocalOperation . SendAsync ( Owner , [ Operation = MoveTemp ( Operation ) , this , ValueRefs = TArray < FCacheGetValueRequest > ( ValueRefs ) , OnComplete = MoveTemp ( OnComplete ) ]
2022-03-02 17:30:48 -05:00
{
2022-04-05 16:43:41 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_RefCachedDataProbablyExistsBatchAsync_OnHttpRequestComplete ) ;
2022-03-02 17:30:48 -05:00
2022-07-18 12:23:16 -04:00
const int32 OverallStatusCode = Operation - > GetStatusCode ( ) ;
if ( OverallStatusCode > = 200 & & OverallStatusCode < = 204 )
2022-03-02 17:30:48 -05:00
{
2022-07-18 12:23:16 -04:00
FMemoryView ResponseView = Operation - > GetBody ( ) ;
2022-03-02 17:30:48 -05:00
if ( ValidateCompactBinary ( ResponseView , ECbValidateMode : : Default ) ! = ECbValidateError : : None )
{
2022-04-05 16:43:41 -04:00
for ( const FCacheGetValueRequest & ValueRef : ValueRefs )
{
2022-07-18 12:23:16 -04:00
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Cache exists returned invalid results. " ) , * Domain ) ;
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
return ;
2022-03-02 17:30:48 -05:00
}
2022-07-18 12:23:16 -04:00
const FCbObjectView ResponseObject ( ResponseView . GetData ( ) ) ;
2022-03-02 17:30:48 -05:00
2022-04-25 11:31:36 -04:00
FCbArrayView ResultsArrayView = ResponseObject [ ANSITEXTVIEW ( " results " ) ] . AsArrayView ( ) ;
2022-03-02 17:30:48 -05:00
if ( ResultsArrayView . Num ( ) ! = ValueRefs . Num ( ) )
{
2022-04-05 16:43:41 -04:00
for ( const FCacheGetValueRequest & ValueRef : ValueRefs )
{
2022-05-11 09:10:24 -04:00
UE_LOG ( LogDerivedDataCache , Log ,
TEXT ( " %s: Cache exists returned unexpected quantity of results (expected %d, got %d). " ) ,
2022-05-31 22:01:53 -04:00
* Domain , ValueRefs . Num ( ) , ResultsArrayView . Num ( ) ) ;
2022-05-11 09:10:24 -04:00
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
2022-04-05 16:43:41 -04:00
}
2022-07-18 12:23:16 -04:00
return ;
2022-03-02 17:30:48 -05:00
}
2022-04-05 16:43:41 -04:00
2022-03-02 17:30:48 -05:00
for ( FCbFieldView ResultFieldView : ResultsArrayView )
{
FCbObjectView ResultObjectView = ResultFieldView . AsObjectView ( ) ;
2022-04-25 11:31:36 -04:00
uint32 OpId = ResultObjectView [ ANSITEXTVIEW ( " opId " ) ] . AsUInt32 ( ) ;
FCbObjectView ResponseObjectView = ResultObjectView [ ANSITEXTVIEW ( " response " ) ] . AsObjectView ( ) ;
int32 StatusCode = ResultObjectView [ ANSITEXTVIEW ( " statusCode " ) ] . AsInt32 ( ) ;
2022-03-02 17:30:48 -05:00
2022-04-05 16:43:41 -04:00
if ( OpId > = ( uint32 ) ValueRefs . Num ( ) )
2022-03-02 17:30:48 -05:00
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Encountered invalid opId %d while querying %d values " ) ,
2022-05-31 22:01:53 -04:00
* Domain , OpId , ValueRefs . Num ( ) ) ;
2022-03-02 17:30:48 -05:00
continue ;
}
2022-04-05 16:43:41 -04:00
const FCacheGetValueRequest & ValueRef = ValueRefs [ OpId ] ;
2022-07-18 12:23:16 -04:00
if ( StatusCode < 200 | | StatusCode > 204 )
2022-03-02 17:30:48 -05:00
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with unsuccessful response code %d for %s from '%s' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , StatusCode , * WriteToString < 96 > ( ValueRef . Key ) , * ValueRef . Name ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
continue ;
}
2022-05-31 22:01:53 -04:00
if ( ! EnumHasAnyFlags ( ValueRef . Policy , ECachePolicy : : QueryRemote ) )
2022-04-05 16:43:41 -04:00
{
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped exists check of %s from '%s' due to cache policy " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( ValueRef . Key ) , * ValueRef . Name ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
2022-03-02 17:30:48 -05:00
continue ;
}
2022-04-25 11:31:36 -04:00
const FIoHash RawHash = ResponseObjectView [ ANSITEXTVIEW ( " RawHash " ) ] . AsHash ( ) ;
const uint64 RawSize = ResponseObjectView [ ANSITEXTVIEW ( " RawSize " ) ] . AsUInt64 ( MAX_uint64 ) ;
2022-03-02 17:30:48 -05:00
if ( RawHash . IsZero ( ) | | RawSize = = MAX_uint64 )
{
2022-04-05 16:43:41 -04:00
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid value for %s from '%s' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( ValueRef . Key ) , * ValueRef . Name ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
2022-03-02 17:30:48 -05:00
continue ;
}
2022-04-05 16:43:41 -04:00
OnComplete ( { ValueRef . Name , ValueRef . Key , FValue ( RawHash , RawSize ) , ValueRef . UserData , EStatus : : Ok } ) ;
2022-03-02 17:30:48 -05:00
}
2022-07-18 12:23:16 -04:00
return ;
2022-04-05 16:43:41 -04:00
}
for ( const FCacheGetValueRequest & ValueRef : ValueRefs )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with failed HTTP request for %s from '%s' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( ValueRef . Key ) , * ValueRef . Name ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( ValueRef . MakeResponse ( EStatus : : Error ) ) ;
}
2022-07-18 12:23:16 -04:00
} ) ;
2022-03-02 17:30:48 -05:00
}
2022-05-31 22:01:53 -04:00
void FHttpCacheStore : : LegacyStats ( FDerivedDataCacheStatsNode & OutNode )
2020-06-23 18:40:00 -04:00
{
2022-10-14 18:39:39 -04:00
OutNode = { TEXT ( " Unreal Cloud DDC " ) , FString : : Printf ( TEXT ( " %s (%s) " ) , * Domain , * Namespace ) , /*bIsLocal*/ false } ;
2022-05-31 22:01:53 -04:00
OutNode . UsageStats . Add ( TEXT ( " " ) , UsageStats ) ;
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 ) ;
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
{
2022-07-22 10:21:26 -04:00
PutCacheRecordAsync ( Owner , Request . Name , Request . Record , Request . Policy , Request . UserData ,
[ COOK_STAT ( Timer = UsageStats . TimePut ( ) , ) SharedOnComplete ] ( FCachePutResponse & & Response , uint64 BytesSent ) mutable
2021-04-28 16:22:18 -04:00
{
2022-04-05 16:43:41 -04:00
TRACE_COUNTER_ADD ( HttpDDC_BytesSent , BytesSent ) ;
if ( Response . Status = = EStatus : : Ok )
{
COOK_STAT ( if ( BytesSent ) { Timer . AddHit ( BytesSent ) ; } ) ;
}
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 ) ;
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 ,
[ COOK_STAT ( Timer = UsageStats . TimePut ( ) , ) SharedOnComplete ] ( FCacheGetResponse & & Response , uint64 BytesReceived ) mutable
2021-04-28 16:22:18 -04:00
{
2022-04-05 16:43:41 -04:00
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , BytesReceived ) ;
if ( Response . Status = = EStatus : : Ok )
{
COOK_STAT ( Timer . AddHit ( BytesReceived ) ; ) ;
}
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 ) ;
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 )
{
2022-07-22 10:21:26 -04:00
PutCacheValueAsync ( Owner , Request . Name , Request . Key , Request . Value , Request . Policy , Request . UserData ,
[ COOK_STAT ( Timer = UsageStats . TimePut ( ) , ) SharedOnComplete ] ( FCachePutValueResponse & & Response , uint64 BytesSent ) mutable
2022-02-02 07:35:19 -05:00
{
2022-04-05 16:43:41 -04:00
TRACE_COUNTER_ADD ( HttpDDC_BytesSent , BytesSent ) ;
if ( Response . Status = = EStatus : : Ok )
{
COOK_STAT ( if ( BytesSent ) { Timer . AddHit ( BytesSent ) ; } ) ;
}
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 ) ;
2022-02-24 10:24:10 -05:00
COOK_STAT ( double StartTime = FPlatformTime : : Seconds ( ) ) ;
COOK_STAT ( bool bIsInGameThread = IsInGameThread ( ) ) ;
2022-03-02 17:30:48 -05:00
bool bBatchExistsCandidate = true ;
2022-02-02 07:35:19 -05:00
for ( const FCacheGetValueRequest & Request : Requests )
{
2022-03-02 17:30:48 -05:00
if ( ! EnumHasAnyFlags ( Request . Policy , ECachePolicy : : SkipData ) )
2022-02-02 07:35:19 -05:00
{
2022-03-02 17:30:48 -05:00
bBatchExistsCandidate = false ;
break ;
2022-02-02 07:35:19 -05:00
}
2022-03-02 17:30:48 -05:00
}
if ( bBatchExistsCandidate )
{
2022-04-05 16:43:41 -04:00
RefCachedDataProbablyExistsBatchAsync ( Owner , Requests ,
[ this , COOK_STAT ( StartTime , bIsInGameThread , ) OnComplete = MoveTemp ( OnComplete ) ] ( FCacheGetValueResponse & & Response )
2022-02-02 07:35:19 -05:00
{
2022-04-05 16:43:41 -04:00
if ( Response . Status ! = EStatus : : Ok )
2022-02-24 10:24:10 -05:00
{
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Miss , FCookStats : : CallStats : : EStatType : : Counter , 1l , bIsInGameThread ) ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( MoveTemp ( Response ) ) ;
2022-02-24 10:24:10 -05:00
}
else
{
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-02-24 10:24:10 -05:00
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Hit , FCookStats : : CallStats : : EStatType : : Counter , 1l , bIsInGameThread ) ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( MoveTemp ( Response ) ) ;
2022-03-02 17:30:48 -05:00
}
2022-04-05 16:43:41 -04:00
COOK_STAT ( const int64 CyclesUsed = int64 ( ( FPlatformTime : : Seconds ( ) - StartTime ) / FPlatformTime : : GetSecondsPerCycle ( ) ) ) ;
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Hit , FCookStats : : CallStats : : EStatType : : Cycles , CyclesUsed , bIsInGameThread ) ) ;
} ) ;
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-04-05 16:43:41 -04:00
int64 HitBytes = 0 ;
2022-03-02 17:30:48 -05:00
for ( const FCacheGetValueRequest & Request : Requests )
{
2022-04-05 16:43:41 -04:00
GetCacheValueAsync ( Owner , Request . Name , Request . Key , Request . Policy , Request . UserData ,
2022-07-22 10:21:26 -04:00
[ this , COOK_STAT ( StartTime , bIsInGameThread , ) Policy = Request . Policy , SharedOnComplete ] ( FCacheGetValueResponse & & Response )
2022-03-02 17:30:48 -05:00
{
2022-07-22 10:21:26 -04:00
const FOnCacheGetValueComplete & OnComplete = SharedOnComplete . Get ( ) ;
2022-04-05 16:43:41 -04:00
check ( OnComplete ) ;
if ( Response . Status ! = EStatus : : Ok )
2022-03-02 17:30:48 -05:00
{
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Miss , FCookStats : : CallStats : : EStatType : : Counter , 1l , bIsInGameThread ) ) ;
2022-04-05 16:43:41 -04:00
OnComplete ( MoveTemp ( Response ) ) ;
2022-03-02 17:30:48 -05:00
}
else
{
2022-04-05 16:43:41 -04:00
if ( ! IsValueDataReady ( Response . Value , Policy ) & & ! EnumHasAnyFlags ( Policy , ECachePolicy : : SkipData ) )
{
// 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' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Response . Key ) , * Response . Name ) ;
2022-04-05 16:43:41 -04:00
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Miss , FCookStats : : CallStats : : EStatType : : Counter , 1l , bIsInGameThread ) ) ;
OnComplete ( MoveTemp ( Response ) ) ;
}
else
{
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-04-05 16:43:41 -04:00
uint64 ValueSize = Response . Value . GetData ( ) . GetCompressedSize ( ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , ValueSize ) ;
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Hit , FCookStats : : CallStats : : EStatType : : Counter , 1l , bIsInGameThread ) ) ;
OnComplete ( { Response . Name , Response . Key , Response . Value , Response . UserData , EStatus : : Ok } ) ;
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Hit , FCookStats : : CallStats : : EStatType : : Bytes , ValueSize , bIsInGameThread ) ) ;
}
2022-03-02 17:30:48 -05:00
}
2022-04-05 16:43:41 -04:00
COOK_STAT ( const int64 CyclesUsed = int64 ( ( FPlatformTime : : Seconds ( ) - StartTime ) / FPlatformTime : : GetSecondsPerCycle ( ) ) ) ;
COOK_STAT ( UsageStats . GetStats . Accumulate ( FCookStats : : CallStats : : EHitOrMiss : : Hit , FCookStats : : CallStats : : EStatType : : Cycles , CyclesUsed , bIsInGameThread ) ) ;
} ) ;
2022-02-02 07:35:19 -05:00
}
}
2022-02-24 10:24:10 -05: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 ) ;
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 ( ) ) ;
bool bHasValue = false ;
FValue Value ;
FValueId ValueId ;
FCacheKey ValueKey ;
FCompressedBuffer ValueBuffer ;
FCompressedBufferReader ValueReader ;
EStatus ValueStatus = EStatus : : Error ;
FOptionalCacheRecord Record ;
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 ) ;
COOK_STAT ( auto Timer = bExistsOnly ? UsageStats . TimeProbablyExists ( ) : UsageStats . TimeGet ( ) ) ;
if ( ! ( bHasValue & & ValueKey = = Request . Key & & ValueId = = Request . Id ) | | ValueReader . HasSource ( ) < ! bExistsOnly )
{
ValueStatus = EStatus : : Error ;
ValueReader . ResetSource ( ) ;
ValueKey = { } ;
ValueId . Reset ( ) ;
Value . Reset ( ) ;
bHasValue = false ;
if ( Request . Id . IsValid ( ) )
{
if ( ! ( Record & & Record . Get ( ) . GetKey ( ) = = Request . Key ) )
{
FCacheRecordPolicyBuilder PolicyBuilder ( ECachePolicy : : None ) ;
PolicyBuilder . AddValuePolicy ( Request . Id , Request . Policy ) ;
Record . Reset ( ) ;
2022-04-05 16:43:41 -04:00
FRequestOwner BlockingOwner ( EPriority : : Blocking ) ;
GetCacheRecordOnlyAsync ( BlockingOwner , Request . Name , Request . Key , PolicyBuilder . Build ( ) , 0 , [ & Record ] ( FGetCacheRecordOnlyResponse & & Response )
2022-06-29 21:31:09 -04:00
{
Record = MoveTemp ( Response . Record ) ;
} ) ;
2022-04-05 16:43:41 -04:00
BlockingOwner . Wait ( ) ;
2022-01-14 10:53:17 -05:00
}
if ( Record )
{
const FValueWithId & ValueWithId = Record . Get ( ) . GetValue ( Request . Id ) ;
bHasValue = ValueWithId . IsValid ( ) ;
Value = ValueWithId ;
ValueId = Request . Id ;
ValueKey = Request . Key ;
if ( IsValueDataReady ( Value , Request . Policy ) )
{
ValueReader . SetSource ( Value . GetData ( ) ) ;
}
else
{
2022-04-05 16:43:41 -04:00
auto IdGetter = [ ] ( const FValueWithId & Value )
2022-02-24 10:24:10 -05:00
{
2022-04-05 16:43:41 -04:00
return FString ( WriteToString < 16 > ( Value . GetId ( ) ) ) ;
2022-02-24 10:24:10 -05:00
} ;
2022-04-05 16:43:41 -04:00
FRequestOwner BlockingOwner ( EPriority : : Blocking ) ;
bool bSucceeded = false ;
FCompressedBuffer NewBuffer ;
FGetRecordOp : : GetDataBatch ( * this , BlockingOwner , Request . Name , Request . Key , : : MakeArrayView ( { ValueWithId } ) , IdGetter , [ & bSucceeded , & NewBuffer ] ( FGetRecordOp : : FGetCachedDataBatchResponse & & Response )
2022-06-29 21:31:09 -04:00
{
if ( Response . Status = = EStatus : : Ok )
2022-04-05 16:43:41 -04:00
{
2022-06-29 21:31:09 -04:00
bSucceeded = true ;
NewBuffer = MoveTemp ( Response . DataBuffer ) ;
}
} ) ;
2022-04-05 16:43:41 -04:00
BlockingOwner . Wait ( ) ;
2022-04-05 15:17:17 -04:00
2022-04-05 16:43:41 -04:00
if ( bSucceeded )
2022-04-05 15:17:17 -04:00
{
2022-04-05 16:43:41 -04:00
ValueBuffer = MoveTemp ( NewBuffer ) ;
2022-01-14 10:53:17 -05:00
ValueReader . SetSource ( ValueBuffer ) ;
}
else
{
ValueBuffer . Reset ( ) ;
ValueReader . ResetSource ( ) ;
}
}
}
}
else
{
2022-02-02 08:18:44 -05:00
ValueKey = Request . Key ;
2022-04-05 16:43:41 -04:00
{
FRequestOwner BlockingOwner ( EPriority : : Blocking ) ;
bool bSucceeded = false ;
GetCacheValueAsync ( BlockingOwner , Request . Name , Request . Key , Request . Policy , 0 , [ & bSucceeded , & Value ] ( FCacheGetValueResponse & & Response )
2022-06-29 21:31:09 -04:00
{
Value = MoveTemp ( Response . Value ) ;
bSucceeded = Response . Status = = EStatus : : Ok ;
} ) ;
2022-04-05 16:43:41 -04:00
BlockingOwner . Wait ( ) ;
bHasValue = bSucceeded ;
}
2022-05-02 11:52:58 -04:00
if ( bHasValue )
2022-02-02 08:18:44 -05:00
{
2022-05-02 11:52:58 -04:00
if ( IsValueDataReady ( Value , Request . Policy ) )
2022-02-24 10:24:10 -05:00
{
2022-05-02 11:52:58 -04:00
ValueReader . SetSource ( Value . GetData ( ) ) ;
2022-02-02 08:18:44 -05:00
}
else
{
2022-05-02 11:52:58 -04:00
auto IdGetter = [ ] ( const FValue & Value )
{
return FString ( TEXT ( " Default " ) ) ;
} ;
FRequestOwner BlockingOwner ( EPriority : : Blocking ) ;
bool bSucceeded = false ;
FCompressedBuffer NewBuffer ;
FGetRecordOp : : GetDataBatch ( * this , BlockingOwner , Request . Name , Request . Key , : : MakeArrayView ( { Value } ) , IdGetter , [ & bSucceeded , & NewBuffer ] ( FGetRecordOp : : FGetCachedDataBatchResponse & & Response )
2022-06-29 21:31:09 -04:00
{
if ( Response . Status = = EStatus : : Ok )
2022-05-02 11:52:58 -04:00
{
2022-06-29 21:31:09 -04:00
bSucceeded = true ;
NewBuffer = MoveTemp ( Response . DataBuffer ) ;
}
} ) ;
2022-05-02 11:52:58 -04:00
BlockingOwner . Wait ( ) ;
if ( bSucceeded )
{
ValueBuffer = MoveTemp ( NewBuffer ) ;
ValueReader . SetSource ( ValueBuffer ) ;
}
else
{
ValueBuffer . Reset ( ) ;
ValueReader . ResetSource ( ) ;
}
2022-02-02 08:18:44 -05:00
}
}
2022-05-02 11:52:58 -04:00
else
{
ValueBuffer . Reset ( ) ;
ValueReader . ResetSource ( ) ;
}
2022-01-14 10:53:17 -05:00
}
}
if ( bHasValue )
{
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' " ) ,
2022-05-31 22:01:53 -04:00
* Domain , * WriteToString < 96 > ( Request . Key , ' / ' , Request . Id ) , * Request . Name ) ;
2022-01-14 10:53:17 -05:00
COOK_STAT ( Timer . AddHit ( ! bExistsOnly ? RawSize : 0 ) ) ;
2022-02-14 14:43:39 -05:00
FSharedBuffer Buffer ;
if ( ! bExistsOnly )
2022-01-14 10:53:17 -05:00
{
2022-02-14 14:43:39 -05:00
Buffer = ValueReader . Decompress ( RawOffset , RawSize ) ;
2022-01-14 10:53:17 -05:00
}
2022-02-14 14:43:39 -05:00
const EStatus ChunkStatus = bExistsOnly | | Buffer . GetSize ( ) = = RawSize ? EStatus : : Ok : EStatus : : Error ;
OnComplete ( { Request . Name , Request . Key , Request . Id , Request . RawOffset ,
RawSize , Value . GetRawHash ( ) , MoveTemp ( Buffer ) , Request . UserData , ChunkStatus } ) ;
2022-01-14 10:53:17 -05:00
continue ;
}
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Error ) ) ;
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 )
{
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 ) ;
// 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 ) ;
FParse : : Value ( Config , TEXT ( " OAuthSecret= " ) , OAuthSecret ) ;
if ( FParse : : Value ( Config , TEXT ( " CommandLineOAuthSecretOverride= " ) , OverrideName ) )
{
if ( FParse : : Value ( FCommandLine : : Get ( ) , * ( OverrideName + TEXT ( " = " ) ) , OAuthSecret ) )
{
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Found command line override for OAuthSecret %s=%s " ) , NodeName , * OverrideName , * OAuthSecret ) ;
}
}
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
{
2022-05-31 22:01:53 -04:00
TTuple < ILegacyCacheStore * , ECacheStoreFlags > CreateHttpCacheStore ( const TCHAR * NodeName , const TCHAR * Config )
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-09-23 11:32:36 -04:00
if ( ! 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
{
2022-09-23 11:32:36 -04:00
if ( TUniquePtr < FHttpCacheStore > Store = MakeUnique < FHttpCacheStore > ( Params ) ; Store - > IsUsable ( ) )
{
const ECacheStoreFlags StoreFlag = ( Params . bReadOnly ? ECacheStoreFlags : : None : ECacheStoreFlags : : Store ) ;
return MakeTuple ( Store . Release ( ) , ECacheStoreFlags : : Remote | ECacheStoreFlags : : Query | StoreFlag ) ;
}
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
return MakeTuple ( nullptr , ECacheStoreFlags : : None ) ;
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 ,
FString & OutOAuthProvider ,
FString & OutOAuthClientId ,
FString & OutOAuthSecret ,
2022-03-23 18:05:53 -04:00
FString & OutOAuthScope ,
2022-07-06 05:59:22 -04:00
FString & OAuthProviderIdentifier ,
FString & OAuthAccessToken ,
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 ( ) ;
OutOAuthProvider = HttpBackend - > GetOAuthProvider ( ) ;
OutOAuthClientId = HttpBackend - > GetOAuthClientId ( ) ;
OutOAuthSecret = HttpBackend - > GetOAuthSecret ( ) ;
2022-03-23 18:05:53 -04:00
OutOAuthScope = HttpBackend - > GetOAuthScope ( ) ;
2022-07-06 05:59:22 -04:00
OAuthProviderIdentifier = HttpBackend - > GetOAuthProviderIdentifier ( ) ;
OAuthAccessToken = HttpBackend - > GetOAuthAccessToken ( ) ;
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