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