2020-06-23 18:40:00 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2022-01-11 11:57:38 -05:00
# include "DerivedDataBackendInterface.h"
2020-06-23 18:40:00 -04:00
# if WITH_HTTP_DDC_BACKEND
# if PLATFORM_WINDOWS || PLATFORM_HOLOLENS
# include "Windows/WindowsHWrapper.h"
# include "Windows/AllowWindowsPlatformTypes.h"
# endif
# include "curl/curl.h"
# if PLATFORM_WINDOWS || PLATFORM_HOLOLENS
# include "Windows/HideWindowsPlatformTypes.h"
# endif
2022-01-10 13:43:40 -05:00
# include "Algo/Accumulate.h"
2021-03-04 09:35:03 -04:00
# include "Algo/Find.h"
2022-01-11 11:57:38 -05:00
# include "Algo/Transform.h"
# include "Compression/CompressedBuffer.h"
2020-06-23 18:40:00 -04:00
# include "Containers/StaticArray.h"
2022-01-11 11:57:38 -05:00
# 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"
2022-01-10 13:43:40 -05:00
# include "DerivedDataCachePrivate.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-01-10 13:43:40 -05:00
# include "DerivedDataValue.h"
2020-06-23 18:40:00 -04:00
# include "Dom/JsonObject.h"
2022-01-11 11:57:38 -05:00
# include "Experimental/Containers/FAAArrayQueue.h"
2020-06-23 18:40:00 -04:00
# include "GenericPlatform/GenericPlatformFile.h"
# include "HAL/PlatformFileManager.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"
2020-06-23 18:40:00 -04:00
# include "Misc/FileHelper.h"
# include "Misc/ScopeLock.h"
# include "Misc/SecureHash.h"
2021-01-11 11:34:27 -04:00
# include "Misc/StringBuilder.h"
2022-01-11 11:57:38 -05:00
# include "Policies/CondensedJsonPrintPolicy.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"
2021-03-04 09:35:03 -04:00
# include "Serialization/BufferArchive.h"
2022-01-10 13:43:40 -05:00
# include "Serialization/CompactBinary.h"
# include "Serialization/CompactBinaryPackage.h"
# include "Serialization/CompactBinaryValidation.h"
2022-02-02 07:35:19 -05:00
# include "Serialization/CompactBinaryWriter.h"
2020-06-23 18:40:00 -04:00
# include "Serialization/JsonReader.h"
# include "Serialization/JsonSerializer.h"
2022-01-11 11:57:38 -05:00
# include "Serialization/JsonWriter.h"
2021-03-04 09:35:03 -04:00
2021-04-28 16:22:18 -04:00
# if WITH_SSL
# include "Ssl.h"
# include <openssl/ssl.h>
# endif
2021-03-04 09:35:03 -04:00
// Enables data request helpers that internally
// batch requests to reduce the number of concurrent
// connections.
# ifndef WITH_DATAREQUEST_HELPER
2021-03-17 04:27:28 -04:00
# define WITH_DATAREQUEST_HELPER 1
2021-03-04 09:35:03 -04:00
# endif
2020-06-23 18:40:00 -04:00
# define UE_HTTPDDC_BACKEND_WAIT_INTERVAL 0.01f
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
# define UE_HTTPDDC_BACKEND_WAIT_INTERVAL_MS ((uint32)(UE_HTTPDDC_BACKEND_WAIT_INTERVAL*1000))
2020-06-23 18:40:00 -04:00
# define UE_HTTPDDC_HTTP_REQUEST_TIMEOUT_SECONDS 30L
# define UE_HTTPDDC_HTTP_REQUEST_TIMOUT_ENABLED 1
# define UE_HTTPDDC_HTTP_DEBUG 0
2021-03-04 09:35:03 -04:00
# if WITH_DATAREQUEST_HELPER
2021-03-17 04:27:28 -04:00
# define UE_HTTPDDC_GET_REQUEST_POOL_SIZE 16
2022-02-08 01:56:17 -05:00
# define UE_HTTPDDC_PUT_REQUEST_POOL_SIZE 4
2021-03-04 09:35:03 -04:00
# else
# define UE_HTTPDDC_GET_REQUEST_POOL_SIZE 48
# define UE_HTTPDDC_PUT_REQUEST_POOL_SIZE 16
# endif
2020-06-23 18:40:00 -04:00
# define UE_HTTPDDC_MAX_FAILED_LOGIN_ATTEMPTS 16
# define UE_HTTPDDC_MAX_ATTEMPTS 4
# define UE_HTTPDDC_MAX_BUFFER_RESERVE 104857600u
2021-03-04 09:35:03 -04:00
# define UE_HTTPDDC_BATCH_SIZE 12
# define UE_HTTPDDC_BATCH_NUM 64
# define UE_HTTPDDC_BATCH_GET_WEIGHT 4
# define UE_HTTPDDC_BATCH_HEAD_WEIGHT 1
# define UE_HTTPDDC_BATCH_WEIGHT_HINT 12
2020-06-23 18:40:00 -04:00
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_Exist , TEXT ( " HttpDDC Exist " ) ) ;
TRACE_DECLARE_INT_COUNTER ( HttpDDC_ExistHit , TEXT ( " HttpDDC Exist Hit " ) ) ;
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 " ) ) ;
static CURLcode sslctx_function ( CURL * curl , void * sslctx , void * parm ) ;
2022-01-11 11:57:38 -05:00
typedef TSharedPtr < class IHttpRequest > FHttpRequestPtr ;
typedef TSharedPtr < class IHttpResponse , ESPMode : : ThreadSafe > FHttpResponsePtr ;
2020-06-23 18:40:00 -04:00
/**
* Encapsulation for access token shared by all requests .
*/
struct FHttpAccessToken
{
public :
FHttpAccessToken ( ) = default ;
FString GetHeader ( ) ;
void SetHeader ( const TCHAR * ) ;
uint32 GetSerial ( ) const ;
private :
FRWLock Lock ;
FString Token ;
uint32 Serial ;
} ;
/**
* Minimal HTTP request type wrapping CURL without the need for managers . This request
* is written to allow reuse of request objects , in order to allow connections to be reused .
*
* CURL has a global library initialization ( curl_global_init ) . We rely on this happening in
* the Online / HTTP library which is a dependency on this module .
*/
2021-04-28 16:22:18 -04:00
class FHttpRequest
2020-06-23 18:40:00 -04:00
{
public :
/**
* Supported request verbs
*/
enum RequestVerb
{
Get ,
Put ,
2022-01-10 13:43:40 -05:00
PutCompactBinary ,
PutCompressedBlob ,
2020-06-23 18:40:00 -04:00
Post ,
PostJson ,
Delete ,
Head
} ;
/**
* Convenience result type interpreted from HTTP response code .
*/
enum Result
{
Success ,
Failed ,
FailedTimeout
} ;
2022-02-08 01:56:17 -05:00
FHttpRequest ( const TCHAR * InDomain , const TCHAR * InEffectiveDomain , FHttpAccessToken * InAuthorizationToken , bool bInLogErrors )
2020-06-23 18:40:00 -04:00
: bLogErrors ( bInLogErrors )
, Domain ( InDomain )
2022-02-08 01:56:17 -05:00
, EffectiveDomain ( InEffectiveDomain )
2020-06-23 18:40:00 -04:00
, AuthorizationToken ( InAuthorizationToken )
{
Curl = curl_easy_init ( ) ;
Reset ( ) ;
}
2021-04-28 16:22:18 -04:00
~ FHttpRequest ( )
2020-06-23 18:40:00 -04:00
{
curl_easy_cleanup ( Curl ) ;
}
/**
* Resets all options on the request except those that should always be set .
*/
void Reset ( )
{
Headers . Reset ( ) ;
ResponseHeader . Reset ( ) ;
ResponseBuffer . Reset ( ) ;
ResponseCode = 0 ;
ReadDataView = TArrayView < const uint8 > ( ) ;
WriteDataBufferPtr = nullptr ;
WriteHeaderBufferPtr = nullptr ;
BytesSent = 0 ;
BytesReceived = 0 ;
CurlResult = CURL_LAST ;
curl_easy_reset ( Curl ) ;
// Options that are always set for all connections.
# if UE_HTTPDDC_HTTP_REQUEST_TIMOUT_ENABLED
curl_easy_setopt ( Curl , CURLOPT_CONNECTTIMEOUT , UE_HTTPDDC_HTTP_REQUEST_TIMEOUT_SECONDS ) ;
# endif
curl_easy_setopt ( Curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( Curl , CURLOPT_NOSIGNAL , 1L ) ;
// SSL options
curl_easy_setopt ( Curl , CURLOPT_USE_SSL , CURLUSESSL_ALL ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_VERIFYPEER , 1 ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_VERIFYHOST , 1 ) ;
curl_easy_setopt ( Curl , CURLOPT_SSLCERTTYPE , " PEM " ) ;
// Response functions
curl_easy_setopt ( Curl , CURLOPT_HEADERDATA , this ) ;
2021-04-28 16:22:18 -04:00
curl_easy_setopt ( Curl , CURLOPT_HEADERFUNCTION , & FHttpRequest : : StaticWriteHeaderFn ) ;
2020-06-23 18:40:00 -04:00
curl_easy_setopt ( Curl , CURLOPT_WRITEDATA , this ) ;
curl_easy_setopt ( Curl , CURLOPT_WRITEFUNCTION , StaticWriteBodyFn ) ;
// SSL certification verification
curl_easy_setopt ( Curl , CURLOPT_CAINFO , nullptr ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_CTX_FUNCTION , * sslctx_function ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_CTX_DATA , this ) ;
// Allow compressed data
curl_easy_setopt ( Curl , CURLOPT_ACCEPT_ENCODING , " gzip " ) ;
// Rewind method, handle special error case where request need to rewind data stream
curl_easy_setopt ( Curl , CURLOPT_SEEKFUNCTION , StaticSeekFn ) ;
curl_easy_setopt ( Curl , CURLOPT_SEEKDATA , this ) ;
2021-04-21 17:14:57 -04:00
// Set minimum speed behavior to allow operations to abort if the transfer speed is poor for the given duration (1kbps over a 30 second span)
curl_easy_setopt ( Curl , CURLOPT_LOW_SPEED_TIME , 30L ) ;
curl_easy_setopt ( Curl , CURLOPT_LOW_SPEED_LIMIT , 1024L ) ;
2020-06-23 18:40:00 -04:00
// Debug hooks
# if UE_HTTPDDC_HTTP_DEBUG
curl_easy_setopt ( Curl , CURLOPT_DEBUGDATA , this ) ;
curl_easy_setopt ( Curl , CURLOPT_DEBUGFUNCTION , StaticDebugCallback ) ;
curl_easy_setopt ( Curl , CURLOPT_VERBOSE , 1L ) ;
# endif
}
2022-02-08 01:56:17 -05:00
/** Gets the domain name for this request */
const FString & GetName ( ) const
{
return Domain ;
}
2020-06-23 18:40:00 -04:00
/** Gets the domain name for this request */
const FString & GetDomain ( ) const
{
return Domain ;
}
2022-02-08 01:56:17 -05:00
/** Gets the effective domain name for this request */
const FString & GetEffectiveDomain ( ) const
{
return EffectiveDomain ;
}
2020-06-23 18:40:00 -04:00
/** Returns the HTTP response code.*/
const int64 GetResponseCode ( ) const
{
return ResponseCode ;
}
/** Returns the number of bytes received this request (headers withstanding). */
const size_t GetBytesReceived ( ) const
{
return BytesReceived ;
}
/** Returns the number of bytes sent during this request (headers withstanding). */
const size_t GetBytesSent ( ) const
{
return BytesSent ;
}
/**
* Upload buffer using the request , using either " Put " or " Post " verbs .
* @ param Uri Url to use .
* @ param Buffer Data to upload
* @ return Result of the request
*/
template < RequestVerb V >
2022-01-26 00:46:18 -05:00
Result PerformBlockingUpload ( const TCHAR * Uri , TArrayView < const uint8 > Buffer , TConstArrayView < long > ExpectedErrorCodes = { } )
2020-06-23 18:40:00 -04:00
{
2022-01-10 13:43:40 -05:00
static_assert ( V = = Put | | V = = PutCompactBinary | | V = = PutCompressedBlob | | V = = Post | | V = = PostJson , " Upload should use either Put or Post verbs. " ) ;
2020-06-23 18:40:00 -04:00
uint32 ContentLength = 0u ;
2022-01-10 13:43:40 -05:00
if constexpr ( V = = Put | | V = = PutCompactBinary | | V = = PutCompressedBlob )
2020-06-23 18:40:00 -04:00
{
curl_easy_setopt ( Curl , CURLOPT_UPLOAD , 1L ) ;
curl_easy_setopt ( Curl , CURLOPT_INFILESIZE , Buffer . Num ( ) ) ;
curl_easy_setopt ( Curl , CURLOPT_READDATA , this ) ;
curl_easy_setopt ( Curl , CURLOPT_READFUNCTION , StaticReadFn ) ;
2022-01-10 13:43:40 -05:00
if constexpr ( V = = PutCompactBinary )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-cb " ) ) ) ;
}
else if constexpr ( V = = PutCompressedBlob )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-comp " ) ) ) ;
}
else
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/octet-stream " ) ) ) ;
}
2020-06-23 18:40:00 -04:00
ContentLength = Buffer . Num ( ) ;
ReadDataView = Buffer ;
}
else if ( V = = Post | | V = = PostJson )
{
curl_easy_setopt ( Curl , CURLOPT_POST , 1L ) ;
curl_easy_setopt ( Curl , CURLOPT_INFILESIZE , Buffer . Num ( ) ) ;
curl_easy_setopt ( Curl , CURLOPT_READDATA , this ) ;
curl_easy_setopt ( Curl , CURLOPT_READFUNCTION , StaticReadFn ) ;
Headers . Add ( V = = Post ? FString ( TEXT ( " Content-Type: application/x-www-form-urlencoded " ) ) : FString ( TEXT ( " Content-Type: application/json " ) ) ) ;
ContentLength = Buffer . Num ( ) ;
ReadDataView = Buffer ;
}
2022-01-26 00:46:18 -05:00
return PerformBlocking ( Uri , V , ContentLength , ExpectedErrorCodes ) ;
2020-06-23 18:40:00 -04:00
}
/**
* Download an url into a buffer using the request .
* @ param Uri Url to use .
* @ param Buffer Optional buffer where data should be downloaded to . If empty downloaded data will
* be stored in an internal buffer and accessed GetResponse * methods .
* @ return Result of the request
*/
2022-01-26 00:46:18 -05:00
Result PerformBlockingDownload ( const TCHAR * Uri , TArray < uint8 > * Buffer , TConstArrayView < long > ExpectedErrorCodes = { 400 } )
2020-06-23 18:40:00 -04:00
{
curl_easy_setopt ( Curl , CURLOPT_HTTPGET , 1L ) ;
WriteDataBufferPtr = Buffer ;
2022-01-26 00:46:18 -05:00
return PerformBlocking ( Uri , Get , 0u , ExpectedErrorCodes ) ;
2020-06-23 18:40:00 -04:00
}
/**
* Query an url using the request . Queries can use either " Head " or " Delete " verbs .
* @ param Uri Url to use .
* @ return Result of the request
*/
template < RequestVerb V >
2022-01-26 00:46:18 -05:00
Result PerformBlockingQuery ( const TCHAR * Uri , TConstArrayView < long > ExpectedErrorCodes = { 400 } )
2020-06-23 18:40:00 -04:00
{
static_assert ( V = = Head | | V = = Delete , " Queries should use either Head or Delete verbs. " ) ;
if ( V = = Delete )
{
curl_easy_setopt ( Curl , CURLOPT_CUSTOMREQUEST , " DELETE " ) ;
}
else if ( V = = Head )
{
curl_easy_setopt ( Curl , CURLOPT_NOBODY , 1L ) ;
}
2022-01-26 00:46:18 -05:00
return PerformBlocking ( Uri , V , 0u , ExpectedErrorCodes ) ;
2020-06-23 18:40:00 -04:00
}
/**
* Set a header to send with the request .
*/
void SetHeader ( const TCHAR * Header , const TCHAR * Value )
{
check ( CurlResult = = CURL_LAST ) ; // Cannot set header after request is sent
Headers . Add ( FString : : Printf ( TEXT ( " %s: %s " ) , Header , Value ) ) ;
}
/**
* Attempts to find the header from the response . Returns false if header is not present .
*/
bool GetHeader ( const ANSICHAR * Header , FString & OutValue ) const
{
check ( CurlResult ! = CURL_LAST ) ; // Cannot query headers before request is sent
const ANSICHAR * HeadersBuffer = ( const ANSICHAR * ) ResponseHeader . GetData ( ) ;
size_t HeaderLen = strlen ( Header ) ;
// Find the header key in the (ANSI) response buffer. If not found we can exist immediately
if ( const ANSICHAR * Found = strstr ( HeadersBuffer , Header ) )
{
const ANSICHAR * Linebreak = strchr ( Found , ' \r ' ) ;
const ANSICHAR * ValueStart = Found + HeaderLen + 2 ; //colon and space
const size_t ValueSize = Linebreak - ValueStart ;
FUTF8ToTCHAR TCHARData ( ValueStart , ValueSize ) ;
OutValue = FString ( TCHARData . Length ( ) , TCHARData . Get ( ) ) ;
return true ;
}
return false ;
}
/**
* Returns the response buffer . Note that is the request is performed
* with an external buffer as target buffer this string will be empty .
*/
const TArray < uint8 > & GetResponseBuffer ( ) const
{
return ResponseBuffer ;
}
/**
* Returns the response buffer as a string . Note that is the request is performed
* with an external buffer as target buffer this string will be empty .
*/
FString GetResponseAsString ( ) const
{
return GetAnsiBufferAsString ( ResponseBuffer ) ;
}
/**
* Returns the response header as a string .
*/
FString GetResponseHeaderAsString ( )
{
return GetAnsiBufferAsString ( ResponseHeader ) ;
}
/**
* Tries to parse the response buffer as a JsonObject . Return empty pointer if
* parse error occurs .
*/
TSharedPtr < FJsonObject > GetResponseAsJsonObject ( ) const
{
FString Response = GetAnsiBufferAsString ( ResponseBuffer ) ;
TSharedPtr < FJsonObject > JsonObject ;
TSharedRef < TJsonReader < > > JsonReader = TJsonReaderFactory < > : : Create ( Response ) ;
if ( ! FJsonSerializer : : Deserialize ( JsonReader , JsonObject ) | | ! JsonObject . IsValid ( ) )
{
return TSharedPtr < FJsonObject > ( nullptr ) ;
}
return JsonObject ;
}
2021-01-11 11:34:27 -04:00
/**
* Tries to parse the response buffer as a JsonArray . Return empty array if
* parse error occurs .
*/
TArray < TSharedPtr < FJsonValue > > GetResponseAsJsonArray ( ) const
{
FString Response = GetAnsiBufferAsString ( ResponseBuffer ) ;
TArray < TSharedPtr < FJsonValue > > JsonArray ;
TSharedRef < TJsonReader < > > JsonReader = TJsonReaderFactory < > : : Create ( Response ) ;
FJsonSerializer : : Deserialize ( JsonReader , JsonArray ) ;
return JsonArray ;
}
2020-12-16 14:06:26 -04:00
/** Will return true if the response code is considered a success */
static bool IsSuccessResponse ( long ResponseCode )
{
// We consider anything in the 1XX or 2XX range a success
return ResponseCode > = 100 & & ResponseCode < 300 ;
}
2020-06-23 18:40:00 -04:00
private :
CURL * Curl ;
CURLcode CurlResult ;
long ResponseCode ;
size_t BytesSent ;
size_t BytesReceived ;
bool bLogErrors ;
TArrayView < const uint8 > ReadDataView ;
TArray < uint8 > * WriteDataBufferPtr ;
TArray < uint8 > * WriteHeaderBufferPtr ;
TArray < uint8 > ResponseHeader ;
TArray < uint8 > ResponseBuffer ;
TArray < FString > Headers ;
FString Domain ;
2022-02-08 01:56:17 -05:00
FString EffectiveDomain ;
2020-06-23 18:40:00 -04:00
FHttpAccessToken * AuthorizationToken ;
/**
* Performs the request , blocking until finished .
* @ param Uri Address on the domain to query
* @ param Verb HTTP verb to use
* @ param Buffer Optional buffer to directly receive the result of the request .
* If unset the response body will be stored in the request .
*/
2022-01-26 00:46:18 -05:00
Result PerformBlocking ( const TCHAR * Uri , RequestVerb Verb , uint32 ContentLength , TConstArrayView < long > ExpectedErrorCodes )
2020-06-23 18:40:00 -04:00
{
static const char * CommonHeaders [ ] = {
2021-04-14 04:25:18 -04:00
" User-Agent: Unreal Engine " ,
2020-06-23 18:40:00 -04:00
nullptr
} ;
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_CurlPerform ) ;
// Setup request options
2022-02-08 01:56:17 -05:00
FString Url = FString : : Printf ( TEXT ( " %s/%s " ) , * EffectiveDomain , Uri ) ;
2020-06-23 18:40:00 -04:00
curl_easy_setopt ( Curl , CURLOPT_URL , TCHAR_TO_ANSI ( * Url ) ) ;
2022-02-08 01:56:17 -05:00
// Setup response header buffer. If caller has not setup a response data buffer, use internal.
2020-06-23 18:40:00 -04:00
WriteHeaderBufferPtr = & ResponseHeader ;
if ( WriteDataBufferPtr = = nullptr )
{
WriteDataBufferPtr = & ResponseBuffer ;
}
// Content-Length should always be set
Headers . Add ( FString : : Printf ( TEXT ( " Content-Length: %d " ) , ContentLength ) ) ;
// And auth token if it's set
if ( AuthorizationToken )
{
Headers . Add ( AuthorizationToken - > GetHeader ( ) ) ;
}
// Build headers list
curl_slist * CurlHeaders = nullptr ;
// Add common headers
for ( uint8 i = 0 ; CommonHeaders [ i ] ! = nullptr ; + + i )
{
CurlHeaders = curl_slist_append ( CurlHeaders , CommonHeaders [ i ] ) ;
}
// Setup added headers
for ( const FString & Header : Headers )
{
CurlHeaders = curl_slist_append ( CurlHeaders , TCHAR_TO_ANSI ( * Header ) ) ;
}
curl_easy_setopt ( Curl , CURLOPT_HTTPHEADER , CurlHeaders ) ;
// Shots fired!
CurlResult = curl_easy_perform ( Curl ) ;
// Get response code
bool bRedirected = false ;
if ( CURLE_OK = = curl_easy_getinfo ( Curl , CURLINFO_RESPONSE_CODE , & ResponseCode ) )
{
bRedirected = ( ResponseCode > = 300 & & ResponseCode < 400 ) ;
}
2022-01-26 00:46:18 -05:00
LogResult ( CurlResult , Uri , Verb , ExpectedErrorCodes ) ;
2020-06-23 18:40:00 -04:00
// Clean up
curl_slist_free_all ( CurlHeaders ) ;
return CurlResult = = CURLE_OK ? Success : Failed ;
}
2022-01-26 00:46:18 -05:00
void LogResult ( CURLcode Result , const TCHAR * Uri , RequestVerb Verb , TConstArrayView < long > ExpectedErrorCodes ) const
2020-06-23 18:40:00 -04:00
{
if ( Result = = CURLE_OK )
{
bool bSuccess = false ;
const TCHAR * VerbStr = nullptr ;
FString AdditionalInfo ;
switch ( Verb )
{
case Head :
2022-01-26 00:46:18 -05:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
2020-06-23 18:40:00 -04:00
VerbStr = TEXT ( " querying " ) ;
break ;
case Get :
2022-01-26 00:46:18 -05:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
2020-06-23 18:40:00 -04:00
VerbStr = TEXT ( " fetching " ) ;
AdditionalInfo = FString : : Printf ( TEXT ( " Received: %d bytes. " ) , BytesReceived ) ;
break ;
case Put :
2022-01-10 13:43:40 -05:00
case PutCompactBinary :
case PutCompressedBlob :
2022-01-26 00:46:18 -05:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
2020-06-23 18:40:00 -04:00
VerbStr = TEXT ( " updating " ) ;
AdditionalInfo = FString : : Printf ( TEXT ( " Sent: %d bytes. " ) , BytesSent ) ;
break ;
case Post :
case PostJson :
2022-01-26 00:46:18 -05:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
2020-06-23 18:40:00 -04:00
VerbStr = TEXT ( " posting " ) ;
break ;
case Delete :
2022-01-26 00:46:18 -05:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
2020-06-23 18:40:00 -04:00
VerbStr = TEXT ( " deleting " ) ;
break ;
}
if ( bSuccess )
{
UE_LOG (
LogDerivedDataCache ,
Verbose ,
2022-02-08 01:56:17 -05:00
TEXT ( " %s: Finished %s HTTP cache entry (response %d) from %s. %s " ) ,
* GetName ( ) ,
VerbStr ,
2020-06-23 18:40:00 -04:00
ResponseCode ,
Uri ,
* AdditionalInfo
) ;
}
else if ( bLogErrors )
{
// Print the response body if we got one, otherwise print header.
FString Response = GetAnsiBufferAsString ( ResponseBuffer . Num ( ) > 0 ? ResponseBuffer : ResponseHeader ) ;
Response . ReplaceCharInline ( ' \n ' , ' ' ) ;
Response . ReplaceCharInline ( ' \r ' , ' ' ) ;
2020-12-09 08:30:46 -04:00
// Dont log access denied as error, since tokens can expire mid session
if ( ResponseCode = = 401 )
{
UE_LOG (
LogDerivedDataCache ,
Verbose ,
2022-02-08 01:56:17 -05:00
TEXT ( " %s: Failed %s HTTP cache entry (response %d) from %s. Response: %s " ) ,
* GetName ( ) ,
2020-12-09 08:30:46 -04:00
VerbStr ,
ResponseCode ,
Uri ,
* Response
) ;
}
else
{
UE_LOG (
LogDerivedDataCache ,
2021-03-03 05:05:34 -04:00
Display ,
2022-02-08 01:56:17 -05:00
TEXT ( " %s: Failed %s HTTP cache entry (response %d) from %s. Response: %s " ) ,
* GetName ( ) ,
2020-12-09 08:30:46 -04:00
VerbStr ,
ResponseCode ,
Uri ,
* Response
) ;
}
2020-06-23 18:40:00 -04:00
}
}
else if ( bLogErrors )
{
UE_LOG (
LogDerivedDataCache ,
2021-03-03 05:05:34 -04:00
Display ,
2022-02-08 01:56:17 -05:00
TEXT ( " %s: Error while connecting to %s: %s " ) ,
* GetName ( ) ,
* EffectiveDomain ,
2020-06-23 18:40:00 -04:00
ANSI_TO_TCHAR ( curl_easy_strerror ( Result ) )
) ;
}
}
FString GetAnsiBufferAsString ( const TArray < uint8 > & Buffer ) const
{
// Content is NOT null-terminated; we need to specify lengths here
FUTF8ToTCHAR TCHARData ( reinterpret_cast < const ANSICHAR * > ( Buffer . GetData ( ) ) , Buffer . Num ( ) ) ;
return FString ( TCHARData . Length ( ) , TCHARData . Get ( ) ) ;
}
static size_t StaticDebugCallback ( CURL * Handle , curl_infotype DebugInfoType , char * DebugInfo , size_t DebugInfoSize , void * UserData )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = static_cast < FHttpRequest * > ( UserData ) ;
2020-06-23 18:40:00 -04:00
switch ( DebugInfoType )
{
case CURLINFO_TEXT :
{
// Truncate at 1023 characters. This is just an arbitrary number based on a buffer size seen in
// the libcurl code.
DebugInfoSize = FMath : : Min ( DebugInfoSize , ( size_t ) 1023 ) ;
// Calculate the actual length of the string due to incorrect use of snprintf() in lib/vtls/openssl.c.
char * FoundNulPtr = ( char * ) memchr ( DebugInfo , 0 , DebugInfoSize ) ;
int CalculatedSize = FoundNulPtr ! = nullptr ? FoundNulPtr - DebugInfo : DebugInfoSize ;
auto ConvertedString = StringCast < TCHAR > ( static_cast < const ANSICHAR * > ( DebugInfo ) , CalculatedSize ) ;
FString DebugText ( ConvertedString . Length ( ) , ConvertedString . Get ( ) ) ;
DebugText . ReplaceInline ( TEXT ( " \n " ) , TEXT ( " " ) , ESearchCase : : CaseSensitive ) ;
DebugText . ReplaceInline ( TEXT ( " \r " ) , TEXT ( " " ) , ESearchCase : : CaseSensitive ) ;
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: %p: '%s' " ) , * Request - > GetName ( ) , Request , * DebugText ) ;
2020-06-23 18:40:00 -04:00
}
break ;
case CURLINFO_HEADER_IN :
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: %p: Received header (%d bytes) " ) , * Request - > GetName ( ) , Request , DebugInfoSize ) ;
2020-06-23 18:40:00 -04:00
break ;
case CURLINFO_DATA_IN :
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: %p: Received data (%d bytes) " ) , * Request - > GetName ( ) , Request , DebugInfoSize ) ;
2020-06-23 18:40:00 -04:00
break ;
case CURLINFO_DATA_OUT :
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: %p: Sent data (%d bytes) " ) , * Request - > GetName ( ) , Request , DebugInfoSize ) ;
2020-06-23 18:40:00 -04:00
break ;
case CURLINFO_SSL_DATA_IN :
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: %p: Received SSL data (%d bytes) " ) , * Request - > GetName ( ) , Request , DebugInfoSize ) ;
2020-06-23 18:40:00 -04:00
break ;
case CURLINFO_SSL_DATA_OUT :
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: %p: Sent SSL data (%d bytes) " ) , * Request - > GetName ( ) , Request , DebugInfoSize ) ;
2020-06-23 18:40:00 -04:00
break ;
}
return 0 ;
}
static size_t StaticReadFn ( void * Ptr , size_t SizeInBlocks , size_t BlockSizeInBytes , void * UserData )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = static_cast < FHttpRequest * > ( UserData ) ;
2020-06-23 18:40:00 -04:00
TArrayView < const uint8 > & ReadDataView = Request - > ReadDataView ;
const size_t Offset = Request - > BytesSent ;
const size_t ReadSize = FMath : : Min ( ( size_t ) ReadDataView . Num ( ) - Offset , SizeInBlocks * BlockSizeInBytes ) ;
check ( ReadDataView . Num ( ) > = Offset + ReadSize ) ;
FMemory : : Memcpy ( Ptr , ReadDataView . GetData ( ) + Offset , ReadSize ) ;
Request - > BytesSent + = ReadSize ;
return ReadSize ;
return 0 ;
}
static size_t StaticWriteHeaderFn ( void * Ptr , size_t SizeInBlocks , size_t BlockSizeInBytes , void * UserData )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = static_cast < FHttpRequest * > ( UserData ) ;
2020-06-23 18:40:00 -04:00
const size_t WriteSize = SizeInBlocks * BlockSizeInBytes ;
TArray < uint8 > * WriteHeaderBufferPtr = Request - > WriteHeaderBufferPtr ;
if ( WriteHeaderBufferPtr & & WriteSize > 0 )
{
const size_t CurrentBufferLength = WriteHeaderBufferPtr - > Num ( ) ;
if ( CurrentBufferLength > 0 )
{
// Remove the previous zero termination
( * WriteHeaderBufferPtr ) [ CurrentBufferLength - 1 ] = ' ' ;
}
// Write the header
WriteHeaderBufferPtr - > Append ( ( const uint8 * ) Ptr , WriteSize + 1 ) ;
( * WriteHeaderBufferPtr ) [ WriteHeaderBufferPtr - > Num ( ) - 1 ] = 0 ; // Zero terminate string
return WriteSize ;
}
return 0 ;
}
static size_t StaticWriteBodyFn ( void * Ptr , size_t SizeInBlocks , size_t BlockSizeInBytes , void * UserData )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = static_cast < FHttpRequest * > ( UserData ) ;
2020-06-23 18:40:00 -04:00
const size_t WriteSize = SizeInBlocks * BlockSizeInBytes ;
TArray < uint8 > * WriteDataBufferPtr = Request - > WriteDataBufferPtr ;
if ( WriteDataBufferPtr & & WriteSize > 0 )
{
// If this is the first part of the body being received, try to reserve
// memory if content length is defined in the header.
if ( Request - > BytesReceived = = 0 & & Request - > WriteHeaderBufferPtr )
{
static const ANSICHAR * ContentLengthHeaderStr = " Content-Length: " ;
const ANSICHAR * Header = ( const ANSICHAR * ) Request - > WriteHeaderBufferPtr - > GetData ( ) ;
if ( const ANSICHAR * ContentLengthHeader = FCStringAnsi : : Strstr ( Header , ContentLengthHeaderStr ) )
{
size_t ContentLength = ( size_t ) FCStringAnsi : : Atoi64 ( ContentLengthHeader + strlen ( ContentLengthHeaderStr ) ) ;
if ( ContentLength > 0u & & ContentLength < UE_HTTPDDC_MAX_BUFFER_RESERVE )
{
WriteDataBufferPtr - > Reserve ( ContentLength ) ;
}
}
}
// Write to the target buffer
WriteDataBufferPtr - > Append ( ( const uint8 * ) Ptr , WriteSize ) ;
Request - > BytesReceived + = WriteSize ;
return WriteSize ;
}
return 0 ;
}
static size_t StaticSeekFn ( void * UserData , curl_off_t Offset , int Origin )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = static_cast < FHttpRequest * > ( UserData ) ;
2020-06-23 18:40:00 -04:00
size_t NewPosition = 0 ;
switch ( Origin )
{
case SEEK_SET : NewPosition = Offset ; break ;
case SEEK_CUR : NewPosition = Request - > BytesSent + Offset ; break ;
case SEEK_END : NewPosition = Request - > ReadDataView . Num ( ) + Offset ; break ;
}
// Make sure we don't seek outside of the buffer
if ( NewPosition < 0 | | NewPosition > = Request - > ReadDataView . Num ( ) )
{
return CURL_SEEKFUNC_FAIL ;
}
// Update the used offset
Request - > BytesSent = NewPosition ;
return CURL_SEEKFUNC_OK ;
}
} ;
2021-03-04 09:35:03 -04:00
//----------------------------------------------------------------------------------------------------------
// Forward declarations
//----------------------------------------------------------------------------------------------------------
2021-11-07 23:43:01 -05:00
bool VerifyPayload ( const FSHAHash & Hash , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArray < uint8 > & Payload ) ;
bool VerifyPayload ( const FIoHash & Hash , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArray < uint8 > & Payload ) ;
bool VerifyRequest ( const class FHttpRequest * Request , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArray < uint8 > & Payload ) ;
2021-04-28 16:22:18 -04:00
bool HashPayload ( class FHttpRequest * Request , const TArrayView < const uint8 > Payload ) ;
2021-04-05 14:36:58 -04:00
bool ShouldAbortForShutdown ( ) ;
2021-03-04 09:35:03 -04:00
2020-06-23 18:40:00 -04:00
//----------------------------------------------------------------------------------------------------------
// Request pool
//----------------------------------------------------------------------------------------------------------
2021-03-04 09:35:03 -04:00
2020-06-23 18:40:00 -04:00
/**
* Pool that manages a fixed set of requests . Users are required to release requests that have been
* acquired . Usable with \ ref FScopedRequestPtr which handles this automatically .
*/
struct FRequestPool
{
2022-02-08 01:56:17 -05:00
FRequestPool ( const TCHAR * InServiceUrl , const TCHAR * InEffectiveServiceUrl , FHttpAccessToken * InAuthorizationToken , uint32 PoolSize )
2020-06-23 18:40:00 -04:00
{
2021-03-04 09:35:03 -04:00
Pool . AddUninitialized ( PoolSize ) ;
2020-06-23 18:40:00 -04:00
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
Pool [ i ] . Usage = 0u ;
2022-02-08 01:56:17 -05:00
Pool [ i ] . Request = new FHttpRequest ( InServiceUrl , InEffectiveServiceUrl , InAuthorizationToken , true ) ;
2020-06-23 18:40:00 -04:00
}
2021-03-04 09:35:03 -04:00
2020-06-23 18:40:00 -04:00
}
~ FRequestPool ( )
{
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
// No requests should be in use by now.
2021-03-04 09:35:03 -04:00
check ( Pool [ i ] . Usage . load ( std : : memory_order_acquire ) = = 0u ) ;
2020-06-23 18:40:00 -04:00
delete Pool [ i ] . Request ;
}
}
2021-03-04 09:35:03 -04:00
/**
* Attempts to get a request is free . Once a request has been returned it is
* " owned by the caller and need to release it to the pool when work has been completed.
* @ return Usable request instance if one is available , otherwise null .
*/
2021-04-28 16:22:18 -04:00
FHttpRequest * GetFreeRequest ( )
2021-03-04 09:35:03 -04:00
{
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
if ( ! Pool [ i ] . Usage . load ( std : : memory_order_relaxed ) )
{
uint8 Expected = 0u ;
if ( Pool [ i ] . Usage . compare_exchange_strong ( Expected , 1u ) )
{
Pool [ i ] . Request - > Reset ( ) ;
return Pool [ i ] . Request ;
}
}
}
return nullptr ;
}
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
class FWaiter : public FThreadSafeRefCountedObject
{
public :
std : : atomic < FHttpRequest * > Request { nullptr } ;
FWaiter ( FRequestPool * InPool )
: Event ( FPlatformProcess : : GetSynchEventFromPool ( true ) )
, Pool ( InPool )
{
}
bool Wait ( uint32 TimeMS )
{
return Event - > Wait ( TimeMS ) ;
}
void Trigger ( )
{
Event - > Trigger ( ) ;
}
private :
~ FWaiter ( )
{
FPlatformProcess : : ReturnSynchEventToPool ( Event ) ;
if ( Request )
{
Pool - > ReleaseRequestToPool ( Request . exchange ( nullptr ) ) ;
}
}
FEvent * Event ;
FRequestPool * Pool ;
} ;
2020-06-23 18:40:00 -04:00
/**
* Block until a request is free . Once a request has been returned it is
* " owned by the caller and need to release it to the pool when work has been completed.
* @ return Usable request instance .
*/
2021-04-28 16:22:18 -04:00
FHttpRequest * WaitForFreeRequest ( )
2020-06-23 18:40:00 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_WaitForConnPool ) ;
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
FHttpRequest * Request = GetFreeRequest ( ) ;
if ( Request = = nullptr )
2020-06-23 18:40:00 -04:00
{
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
// Make it a fair by allowing each thread to register itself in a FIFO
// so that the first thread to start waiting is the first one to get a request.
FWaiter * Waiter = new FWaiter ( this ) ;
Waiter - > AddRef ( ) ; // One ref for the thread that will dequeue
Waiter - > AddRef ( ) ; // One ref for us
Waiters . enqueue ( Waiter ) ;
while ( ! Waiter - > Wait ( UE_HTTPDDC_BACKEND_WAIT_INTERVAL_MS ) )
{
// While waiting, allow us to check if a race occurred and a request has been freed
// between the time we checked for free requests and the time we queued ourself as a Waiter.
if ( ( Request = GetFreeRequest ( ) ) ! = nullptr )
{
// We abandon the FWaiter, it will be freed by the next dequeue
// and if it has a request, it will be queued back to the pool.
Waiter - > Release ( ) ;
return Request ;
}
}
Request = Waiter - > Request . exchange ( nullptr ) ;
Request - > Reset ( ) ;
Waiter - > Release ( ) ;
2020-06-23 18:40:00 -04:00
}
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
check ( Request ) ;
2021-03-04 09:35:03 -04:00
return Request ;
2020-06-23 18:40:00 -04:00
}
/**
* Release request to the pool .
* @ param Request Request that should be freed . Note that any buffer owened by the request can now be reset .
*/
2021-04-28 16:22:18 -04:00
void ReleaseRequestToPool ( FHttpRequest * Request )
2020-06-23 18:40:00 -04:00
{
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
if ( Pool [ i ] . Request = = Request )
{
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
// If only 1 user is remaining, we can give it to a waiter
// instead of releasing it back to the pool.
if ( Pool [ i ] . Usage = = 1u )
{
if ( FWaiter * Waiter = Waiters . dequeue ( ) )
{
Waiter - > Request = Request ;
Waiter - > Trigger ( ) ;
Waiter - > Release ( ) ;
return ;
}
}
2021-03-04 09:35:03 -04:00
Pool [ i ] . Usage - - ;
return ;
}
}
check ( false ) ;
}
/**
* While holding a request , make it shared across many users .
*/
2021-04-28 16:22:18 -04:00
void MakeRequestShared ( FHttpRequest * Request , uint8 Users )
2021-03-04 09:35:03 -04:00
{
check ( Users ! = 0 ) ;
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
if ( Pool [ i ] . Request = = Request )
{
Pool [ i ] . Usage = Users ;
2020-06-23 18:40:00 -04:00
return ;
}
}
check ( false ) ;
}
private :
struct FEntry
{
2021-03-04 09:35:03 -04:00
std : : atomic < uint8 > Usage ;
2021-04-28 16:22:18 -04:00
FHttpRequest * Request ;
2020-06-23 18:40:00 -04:00
} ;
2021-03-04 09:35:03 -04:00
TArray < FEntry > Pool ;
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
FAAArrayQueue < FWaiter > Waiters ;
2021-03-04 09:35:03 -04:00
FRequestPool ( ) = delete ;
2020-06-23 18:40:00 -04:00
} ;
//----------------------------------------------------------------------------------------------------------
// FScopedRequestPtr
//----------------------------------------------------------------------------------------------------------
/**
* Utility class to manage requesting and releasing requests from the \ ref FRequestPool .
*/
struct FScopedRequestPtr
{
public :
FScopedRequestPtr ( FRequestPool * InPool )
: Request ( InPool - > WaitForFreeRequest ( ) )
, Pool ( InPool )
{ }
~ FScopedRequestPtr ( )
{
Pool - > ReleaseRequestToPool ( Request ) ;
}
bool IsValid ( ) const
{
return Request ! = nullptr ;
}
2021-04-28 16:22:18 -04:00
FHttpRequest * Get ( ) const
2020-06-23 18:40:00 -04:00
{
check ( IsValid ( ) ) ;
return Request ;
}
2021-04-28 16:22:18 -04:00
FHttpRequest * operator - > ( )
2020-06-23 18:40:00 -04:00
{
check ( IsValid ( ) ) ;
return Request ;
}
private :
2021-04-28 16:22:18 -04:00
FHttpRequest * Request ;
2020-06-23 18:40:00 -04:00
FRequestPool * Pool ;
} ;
2021-03-04 09:35:03 -04:00
# if WITH_DATAREQUEST_HELPER
//----------------------------------------------------------------------------------------------------------
// FDataRequestHelper
//----------------------------------------------------------------------------------------------------------
/**
* Helper class for requesting data . Will batch requests once the number of concurrent requests reach a threshold .
*/
struct FDataRequestHelper
{
FDataRequestHelper ( FRequestPool * InPool , const TCHAR * InNamespace , const TCHAR * InBucket , const TCHAR * InCacheKey , TArray < uint8 > * OutData )
: Request ( nullptr )
, Pool ( InPool )
, bVerified ( false , 1 )
{
Request = Pool - > GetFreeRequest ( ) ;
if ( Request & & OutData ! = nullptr )
{
// We are below the threshold, make the connection immediately. OutData is set so this is a get.
FString Uri = FString : : Printf ( TEXT ( " api/v1/c/ddc/%s/%s/%s.raw " ) , InNamespace , InBucket , InCacheKey ) ;
2021-04-28 16:22:18 -04:00
const FHttpRequest : : Result Result = Request - > PerformBlockingDownload ( * Uri , OutData ) ;
if ( FHttpRequest : : IsSuccessResponse ( Request - > GetResponseCode ( ) ) )
2021-03-04 09:35:03 -04:00
{
2021-11-07 23:43:01 -05:00
if ( VerifyRequest ( Request , InNamespace , InBucket , InCacheKey , * OutData ) )
2021-03-04 09:35:03 -04:00
{
TRACE_COUNTER_ADD ( HttpDDC_GetHit , int64 ( 1 ) ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , int64 ( Request - > GetBytesReceived ( ) ) ) ;
bVerified [ 0 ] = true ;
}
}
}
else if ( Request )
{
// We are below the threshold, make the connection immediately. OutData is missing so this is a head.
FString Uri = FString : : Printf ( TEXT ( " api/v1/c/ddc/%s/%s/%s " ) , InNamespace , InBucket , InCacheKey ) ;
2021-04-28 16:22:18 -04:00
const FHttpRequest : : Result Result = Request - > PerformBlockingQuery < FHttpRequest : : Head > ( * Uri ) ;
if ( FHttpRequest : : IsSuccessResponse ( Request - > GetResponseCode ( ) ) )
2021-03-04 09:35:03 -04:00
{
TRACE_COUNTER_ADD ( HttpDDC_ExistHit , int64 ( 1 ) ) ;
bVerified [ 0 ] = true ;
}
}
else
{
// We have exceeded the threshold for concurrent connections, start or add this request
// to a batched request.
2021-07-19 13:09:47 -04:00
if ( IsQueueCandidate ( 1 , OutData & & ! OutData - > IsEmpty ( ) ) )
{
Request = QueueBatchRequest (
InPool ,
InNamespace ,
InBucket ,
TConstArrayView < const TCHAR * > ( { InCacheKey } ) ,
OutData ? TConstArrayView < TArray < uint8 > * > ( { OutData } ) : TConstArrayView < TArray < uint8 > * > ( ) ,
bVerified
) ;
}
if ( ! Request )
{
Request = Pool - > WaitForFreeRequest ( ) ;
FQueuedBatchEntry Entry {
InNamespace ,
InBucket ,
TConstArrayView < const TCHAR * > ( { InCacheKey } ) ,
OutData ? TConstArrayView < TArray < uint8 > * > ( { OutData } ) : TConstArrayView < TArray < uint8 > * > ( ) ,
OutData & & ! OutData - > IsEmpty ( ) ? FHttpRequest : : RequestVerb : : Get : FHttpRequest : : RequestVerb : : Head ,
& bVerified
} ;
PerformBatchQuery ( Request , TArrayView < FQueuedBatchEntry > ( & Entry , 1 ) ) ;
}
2021-03-04 09:35:03 -04:00
}
}
// Constructor specifically for batched head queries
FDataRequestHelper ( FRequestPool * InPool , const TCHAR * InNamespace , const TCHAR * InBucket , TConstArrayView < FString > InCacheKeys )
: Request ( nullptr )
, Pool ( InPool )
, bVerified ( false , InCacheKeys . Num ( ) )
{
// Transform the FString array to char pointers
TArray < const TCHAR * > CacheKeys ;
Algo : : Transform ( InCacheKeys , CacheKeys , [ ] ( const FString & Key ) { return * Key ; } ) ;
Request = Pool - > GetFreeRequest ( ) ;
2021-07-19 13:09:47 -04:00
if ( Request | | ! IsQueueCandidate ( InCacheKeys . Num ( ) , false ) )
2021-03-04 09:35:03 -04:00
{
// If the request is too big for existing batches, wait for a free connection and create our own.
if ( ! Request )
{
Request = Pool - > WaitForFreeRequest ( ) ;
}
FQueuedBatchEntry Entry {
InNamespace ,
InBucket ,
CacheKeys ,
TConstArrayView < TArray < uint8 > * > ( ) ,
2021-04-28 16:22:18 -04:00
FHttpRequest : : RequestVerb : : Head ,
2021-03-04 09:35:03 -04:00
& bVerified
} ;
PerformBatchQuery ( Request , TArrayView < FQueuedBatchEntry > ( & Entry , 1 ) ) ;
}
else
{
Request = QueueBatchRequest (
InPool ,
InNamespace ,
InBucket ,
CacheKeys ,
TConstArrayView < TArray < uint8 > * > ( ) ,
bVerified
) ;
2021-07-19 13:09:47 -04:00
if ( ! Request )
{
Request = Pool - > WaitForFreeRequest ( ) ;
FQueuedBatchEntry Entry {
InNamespace ,
InBucket ,
CacheKeys ,
TConstArrayView < TArray < uint8 > * > ( ) ,
FHttpRequest : : RequestVerb : : Head ,
& bVerified
} ;
PerformBatchQuery ( Request , TArrayView < FQueuedBatchEntry > ( & Entry , 1 ) ) ;
}
2021-03-04 09:35:03 -04:00
}
}
~ FDataRequestHelper ( )
{
if ( Request )
{
Pool - > ReleaseRequestToPool ( Request ) ;
}
}
static void StaticInitialize ( )
{
2021-11-07 23:43:01 -05:00
static bool bInitialized = false ;
check ( ! bInitialized ) ;
for ( FBatch & Batch : Batches )
2021-03-04 09:35:03 -04:00
{
2021-11-07 23:43:01 -05:00
Batch . Reserved = 0 ;
Batch . Ready = 0 ;
Batch . Complete = TUniquePtr < FEvent , FBatch : : FEventDeleter > ( FPlatformProcess : : GetSynchEventFromPool ( true ) ) ;
}
bInitialized = true ;
}
static void StaticShutdown ( )
{
for ( FBatch & Batch : Batches )
{
Batch . Complete . Reset ( ) ;
2021-03-04 09:35:03 -04:00
}
}
bool IsSuccess ( ) const
{
return bVerified [ 0 ] ;
}
const TBitArray < > & IsBatchSuccess ( ) const
{
return bVerified ;
}
int64 GetResponseCode ( ) const
{
return Request ? Request - > GetResponseCode ( ) : 0 ;
}
private :
struct FQueuedBatchEntry
{
const TCHAR * Namespace ;
const TCHAR * Bucket ;
TConstArrayView < const TCHAR * > CacheKeys ;
TConstArrayView < TArray < uint8 > * > OutDatas ;
2021-04-28 16:22:18 -04:00
FHttpRequest : : RequestVerb Verb ;
2021-03-04 09:35:03 -04:00
TBitArray < > * bSuccess ;
} ;
struct FBatch
{
2021-11-07 23:43:01 -05:00
struct FEventDeleter
2021-07-29 09:56:01 -04:00
{
void operator ( ) ( FEvent * Event )
{
FPlatformProcess : : ReturnSynchEventToPool ( Event ) ;
}
} ;
2021-03-04 09:35:03 -04:00
FQueuedBatchEntry Entries [ UE_HTTPDDC_BATCH_SIZE ] ;
std : : atomic < uint32 > Reserved ;
std : : atomic < uint32 > Ready ;
std : : atomic < uint32 > WeightHint ;
2021-04-28 16:22:18 -04:00
FHttpRequest * Request ;
2021-11-07 23:43:01 -05:00
TUniquePtr < FEvent , FEventDeleter > Complete ;
2021-03-04 09:35:03 -04:00
} ;
2021-04-28 16:22:18 -04:00
FHttpRequest * Request ;
2021-03-04 09:35:03 -04:00
FRequestPool * Pool ;
TBitArray < > bVerified ;
static std : : atomic < uint32 > FirstAvailableBatch ;
static TStaticArray < FBatch , UE_HTTPDDC_BATCH_NUM > Batches ;
2021-07-19 13:09:47 -04:00
static uint32 ComputeWeight ( int32 NumKeys , bool bHasDatas )
{
return NumKeys * ( bHasDatas ? UE_HTTPDDC_BATCH_GET_WEIGHT : UE_HTTPDDC_BATCH_HEAD_WEIGHT ) ;
}
static bool IsQueueCandidate ( int32 NumKeys , bool bHasDatas )
{
if ( NumKeys > UE_HTTPDDC_BATCH_SIZE )
{
return false ;
}
const uint32 Weight = ComputeWeight ( NumKeys , bHasDatas ) ;
if ( Weight > UE_HTTPDDC_BATCH_WEIGHT_HINT )
{
return false ;
}
return true ;
}
2021-03-04 09:35:03 -04:00
/**
* Queues up a request to be batched . Blocks until the query is made .
*/
2021-04-28 16:22:18 -04:00
static FHttpRequest * QueueBatchRequest ( FRequestPool * InPool ,
2021-03-04 09:35:03 -04:00
const TCHAR * InNamespace ,
const TCHAR * InBucket ,
TConstArrayView < const TCHAR * > InCacheKeys ,
TConstArrayView < TArray < uint8 > * > OutDatas ,
TBitArray < > & bOutVerified )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_BatchQuery ) ;
check ( InCacheKeys . Num ( ) = = OutDatas . Num ( ) | | OutDatas . Num ( ) = = 0 ) ;
const uint32 RequestNum = InCacheKeys . Num ( ) ;
2021-07-19 13:09:47 -04:00
const uint32 RequestWeight = ComputeWeight ( InCacheKeys . Num ( ) , ! OutDatas . IsEmpty ( ) ) ;
2021-03-04 09:35:03 -04:00
for ( int32 i = 0 ; i < Batches . Num ( ) ; i + + )
{
uint32 Index = ( FirstAvailableBatch . load ( std : : memory_order_relaxed ) + i ) % Batches . Num ( ) ;
FBatch & Batch = Batches [ Index ] ;
//Assign different weights to head vs. get queries
if ( Batch . WeightHint . load ( std : : memory_order_acquire ) + RequestWeight > UE_HTTPDDC_BATCH_WEIGHT_HINT )
{
continue ;
}
// Attempt to reserve a spot in the batch
const uint32 Reserve = Batch . Reserved . fetch_add ( 1 , std : : memory_order_acquire ) ;
if ( Reserve > = UE_HTTPDDC_BATCH_SIZE )
{
// We didn't manage to snag a valid reserve index try next batch
continue ;
}
// Add our weight to the batch. Note we are treating it as a hint, so don't syncronize.
const uint32 ActualWeight = Batch . WeightHint . fetch_add ( RequestWeight , std : : memory_order_release ) ;
TAnsiStringBuilder < 64 > BatchString ;
BatchString < < " HttpDDC_Batch " < < Index ;
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT ( * BatchString ) ;
if ( Reserve = = ( UE_HTTPDDC_BATCH_SIZE - 1 ) )
{
FirstAvailableBatch + + ;
}
Batch . Entries [ Reserve ] = FQueuedBatchEntry {
InNamespace ,
InBucket ,
InCacheKeys ,
OutDatas ,
2021-04-28 16:22:18 -04:00
OutDatas . Num ( ) ? FHttpRequest : : RequestVerb : : Get : FHttpRequest : : RequestVerb : : Head ,
2021-03-04 09:35:03 -04:00
& bOutVerified
} ;
// Signal we are ready for batch to be submitted
Batch . Ready . fetch_add ( 1u , std : : memory_order_release ) ;
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = nullptr ;
2021-03-04 09:35:03 -04:00
// The first to reserve a slot is the "driver" of the batch
if ( Reserve = = 0 )
{
Batch . Request = InPool - > WaitForFreeRequest ( ) ;
// Make sure no new requests are added
const uint32 Reserved = FMath : : Min ( ( uint32 ) UE_HTTPDDC_BATCH_SIZE , Batch . Reserved . fetch_add ( UE_HTTPDDC_BATCH_SIZE , std : : memory_order_acquire ) ) ;
// Give other threads time to copy their data to batch
while ( Batch . Ready . load ( std : : memory_order_acquire ) < Reserved ) { }
// Increment request ref count to reflect all waiting threads
InPool - > MakeRequestShared ( Batch . Request , Reserved ) ;
// Do the actual query and write response to respective target arrays
PerformBatchQuery ( Batch . Request , TArrayView < FQueuedBatchEntry > ( Batch . Entries , Batch . Ready ) ) ;
// Signal to waiting threads the batch is complete
Batch . Complete - > Trigger ( ) ;
// Store away the request and wait until other threads have too
Request = Batch . Request ;
while ( Batch . Ready . load ( std : : memory_order_acquire ) > 1 ) { }
//Reset batch for next use
Batch . Complete - > Reset ( ) ;
Batch . WeightHint . store ( 0 , std : : memory_order_release ) ;
Batch . Ready . store ( 0 , std : : memory_order_release ) ;
Batch . Reserved . store ( 0 , std : : memory_order_release ) ;
}
else
{
// Wait until "driver" has done query
Fix thread starvation for request in http backend by using a FIFO for thread waiting for a connection to be freed
Replace the sleep by a wait on an event triggered by the thread freeing the connection
Improve the test to output stats seen below and use the proper amount of threads on low core systems
Automation RunTests System.DerivedDataCache.HttpDerivedDataBackend.ConcurrentCachedDataProbablyExistsBatch
Before
RPS: 238, AvgLatency: 90.09 ms, MaxLatency: 7.77 s
RPS: 231, AvgLatency: 87.66 ms, MaxLatency: 7.77 s
RPS: 245, AvgLatency: 139.07 ms, MaxLatency: 10.08 s
After
RPS: 634, AvgLatency: 102.97 ms, MaxLatency: 0.26 s
RPS: 653, AvgLatency: 102.51 ms, MaxLatency: 0.26 s
RPS: 630, AvgLatency: 102.29 ms, MaxLatency: 0.26 s
#rnx
#rb Zousar.Shaker, Francis.Hurteau
#preflight 60ffe8317f21c90001f6116a
#ROBOMERGE-SOURCE: CL 16968010 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)
[CL 16968015 by danny couture in ue5-release-engine-test branch]
2021-07-27 09:26:42 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( WaitForMasterOfBatch ) ;
Batch . Complete - > Wait ( ~ 0 ) ;
}
2021-03-04 09:35:03 -04:00
// Store away request and signal we are done
Request = Batch . Request ;
Batch . Ready . fetch_sub ( 1u , std : : memory_order_release ) ;
}
return Request ;
}
return nullptr ;
}
/**
* Creates request uri and headers and submits the request
*/
2021-04-28 16:22:18 -04:00
static void PerformBatchQuery ( FHttpRequest * Request , TArrayView < FQueuedBatchEntry > Entries )
2021-03-04 09:35:03 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_BatchGet ) ;
const TCHAR * Uri ( TEXT ( " api/v1/c/ddc-rpc/batchget " ) ) ;
int64 ResponseCode = 0 ; uint32 Attempts = 0 ;
//Prepare request object
TArray < TSharedPtr < FJsonValue > > Operations ;
for ( const FQueuedBatchEntry & Entry : Entries )
{
for ( int32 KeyIdx = 0 ; KeyIdx < Entry . CacheKeys . Num ( ) ; KeyIdx + + )
{
TSharedPtr < FJsonObject > Object = MakeShared < FJsonObject > ( ) ;
Object - > SetField ( TEXT ( " bucket " ) , MakeShared < FJsonValueString > ( Entry . Bucket ) ) ;
Object - > SetField ( TEXT ( " key " ) , MakeShared < FJsonValueString > ( Entry . CacheKeys [ KeyIdx ] ) ) ;
2021-04-28 16:22:18 -04:00
if ( Entry . Verb = = FHttpRequest : : RequestVerb : : Head )
2021-03-04 09:35:03 -04:00
{
Object - > SetField ( TEXT ( " verb " ) , MakeShared < FJsonValueString > ( TEXT ( " HEAD " ) ) ) ;
}
Operations . Add ( MakeShared < FJsonValueObject > ( Object ) ) ;
}
}
TSharedPtr < FJsonObject > RequestObject = MakeShared < FJsonObject > ( ) ;
RequestObject - > SetField ( TEXT ( " namespace " ) , MakeShared < FJsonValueString > ( Entries [ 0 ] . Namespace ) ) ;
RequestObject - > SetField ( TEXT ( " operations " ) , MakeShared < FJsonValueArray > ( Operations ) ) ;
//Serialize to a buffer
FBufferArchive RequestData ;
if ( FJsonSerializer : : Serialize ( RequestObject . ToSharedRef ( ) , TJsonWriterFactory < ANSICHAR , TCondensedJsonPrintPolicy < ANSICHAR > > : : Create ( & RequestData ) ) )
{
2021-04-28 16:22:18 -04:00
Request - > PerformBlockingUpload < FHttpRequest : : PostJson > ( Uri , MakeArrayView ( RequestData ) ) ;
2021-03-04 09:35:03 -04:00
ResponseCode = Request - > GetResponseCode ( ) ;
if ( ResponseCode = = 200 )
{
const TArray < uint8 > & ResponseBuffer = Request - > GetResponseBuffer ( ) ;
const uint8 * Response = ResponseBuffer . GetData ( ) ;
const int32 ResponseSize = ResponseBuffer . Num ( ) ;
// Parse the response and move the data to the target requests.
if ( ParseBatchedResponse ( Response , ResponseSize , Entries ) )
{
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Batch query with %d operations completed. " ) , * Request - > GetName ( ) , Entries . Num ( ) ) ;
2021-03-04 09:35:03 -04:00
return ;
}
}
}
// If we get here the request failed.
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Batch query failed. Query: %s " ) , * Request - > GetName ( ) , ANSI_TO_TCHAR ( ( ANSICHAR * ) RequestData . GetData ( ) ) ) ;
2021-03-04 09:35:03 -04:00
// Set all batch operations to failures
for ( FQueuedBatchEntry Entry : Entries )
{
Entry . bSuccess - > SetRange ( 0 , Entry . CacheKeys . Num ( ) , false ) ;
}
}
2021-07-27 15:25:39 -04:00
// Above result value
enum class OpResult : uint8
{
Ok = 0 , // Op finished succesfully
Error = 1 , // Error during op
NotFound = 2 , // Key was not found
Exists = 3 // Used to indicate head op success
} ;
2021-03-22 18:23:46 -04:00
// Searches for potentially multiple key requests that are satisfied the given cache key result
// Search strategy is exhaustive forward search from the last found entry. If the results come in ordered the same as the requests,
// and there are no duplicates, the search will be somewhat efficient (still has to do exhaustive searching looking for duplicates).
// If the results are unordered or there are duplicates, search will become more inefficient.
struct FRequestSearchHelper
{
2021-07-27 15:25:39 -04:00
FRequestSearchHelper ( TArrayView < FQueuedBatchEntry > InRequests , const FUTF8ToTCHAR & InCacheKey , int32 InEntryIdx , int32 InKeyIdx , OpResult InRequestResult )
2021-03-22 18:23:46 -04:00
: Requests ( InRequests )
, CacheKey ( InCacheKey )
, StartEntryIdx ( InEntryIdx )
, StartKeyIdx ( InKeyIdx )
2021-07-27 15:25:39 -04:00
, RequestResult ( InRequestResult )
2021-03-22 18:23:46 -04:00
{ }
bool FindNext ( int32 & EntryIdx , int32 & KeyIdx )
{
int32 CurrentEntryIdx = EntryIdx ;
int32 CurrentKeyIdx = KeyIdx ;
do
{
2021-07-27 15:25:39 -04:00
// Do not match a get request with a head response code (i.e. Exists)
// or a head request with a get response code (i.e. Ok)
// if the response code is an error or not found they can be matched to both head or get request it doesn't matter
const FQueuedBatchEntry & CurrentRequest = Requests [ CurrentEntryIdx ] ;
bool bRequestTypeMatch = ! ( ( CurrentRequest . Verb = = FHttpRequest : : Get ) & & ( RequestResult = = OpResult : : Exists ) )
& & ! ( ( CurrentRequest . Verb = = FHttpRequest : : Head ) & & ( RequestResult = = OpResult : : Ok ) ) ;
if ( bRequestTypeMatch & & FCString : : Stricmp ( CurrentRequest . CacheKeys [ CurrentKeyIdx ] , CacheKey . Get ( ) ) = = 0 )
2021-03-22 18:23:46 -04:00
{
EntryIdx = CurrentEntryIdx ;
KeyIdx = CurrentKeyIdx ;
return true ;
}
} while ( AdvanceIndices ( CurrentEntryIdx , CurrentKeyIdx ) ) ;
return false ;
}
bool AdvanceIndices ( int32 & EntryIdx , int32 & KeyIdx )
{
if ( + + KeyIdx > = Requests [ EntryIdx ] . CacheKeys . Num ( ) )
{
EntryIdx = ( EntryIdx + 1 ) % Requests . Num ( ) ;
KeyIdx = 0 ;
}
return ! ( ( EntryIdx = = StartEntryIdx ) & & ( KeyIdx = = StartKeyIdx ) ) ;
}
TArrayView < FQueuedBatchEntry > Requests ;
const FUTF8ToTCHAR & CacheKey ;
int32 StartEntryIdx ;
int32 StartKeyIdx ;
2021-07-27 15:25:39 -04:00
OpResult RequestResult ;
2021-03-22 18:23:46 -04:00
} ;
2021-03-04 09:35:03 -04:00
/**
* Parses a batched response stream , moves the data to target requests and marks them with result .
* @ param Response Pointer to Response buffer
* @ param ResponseSize Size of response buffer
* @ param Requests Requests that will be filled with data .
* @ return True if response was successfully parsed , false otherwise .
*/
static bool ParseBatchedResponse ( const uint8 * ResponseStart , const int32 ResponseSize , TArrayView < FQueuedBatchEntry > Requests )
{
// The expected data stream is structured accordingly
// {"JPTR"} {PayloadCount:uint32} {{"JPEE"} {Name:cstr} {Result:uint8} {Hash:IoHash} {Size:uint64} {Payload...}} ...
const TCHAR ResponseErrorMessage [ ] = TEXT ( " Malformed response from server. " ) ;
const ANSICHAR * ProtocolMagic = " JPTR " ;
const ANSICHAR * PayloadMagic = " JPEE " ;
const uint32 MagicSize = 4 ;
const uint8 * Response = ResponseStart ;
const uint8 * ResponseEnd = Response + ResponseSize ;
// Check that the stream starts with the protocol magic
if ( FMemory : : Memcmp ( ProtocolMagic , Response , MagicSize ) ! = 0 )
{
UE_LOG ( LogDerivedDataCache , Display , ResponseErrorMessage ) ;
return false ;
}
Response + = MagicSize ;
// Number of payloads recieved
uint32 PayloadCount = * ( uint32 * ) Response ;
Response + = sizeof ( uint32 ) ;
uint32 PayloadIdx = 0 ; // Current processed result
int32 EntryIdx = 0 ; // Current Entry index
int32 KeyIdx = 0 ; // Current Key index for current Entry
while ( Response < ResponseEnd & & FMemory : : Memcmp ( PayloadMagic , Response , MagicSize ) = = 0 )
{
PayloadIdx + + ;
Response + = MagicSize ;
const ANSICHAR * PayloadNameA = ( const ANSICHAR * ) Response ;
Response + = FCStringAnsi : : Strlen ( PayloadNameA ) + 1 ; //String and zero termination
const ANSICHAR * CacheKeyA = FCStringAnsi : : Strrchr ( PayloadNameA , ' . ' ) + 1 ; // "namespace.bucket.cachekey"
2021-07-27 15:25:39 -04:00
// Result of the operation is used to match to the appropriate request (i.e. get or head)
OpResult PayloadResult = static_cast < OpResult > ( * Response ) ;
Response + = sizeof ( uint8 ) ;
const uint8 * ResponseRewindMark = Response ;
2021-03-22 18:23:46 -04:00
// Find the payload among the requests. Payloads may be returned in any order and if the same cache key was part of two requests,
// a single payload may satisfy multiple cache keys in multiple requests.
2021-03-04 09:35:03 -04:00
FUTF8ToTCHAR CacheKey ( CacheKeyA ) ;
2021-07-27 15:25:39 -04:00
FRequestSearchHelper RequestSearch ( Requests , CacheKey , EntryIdx , KeyIdx , PayloadResult ) ;
2021-03-22 18:23:46 -04:00
bool bFoundAny = false ;
while ( RequestSearch . FindNext ( EntryIdx , KeyIdx ) )
{
Response = ResponseRewindMark ;
bFoundAny = true ;
FQueuedBatchEntry & RequestOp = Requests [ EntryIdx ] ;
TBitArray < > & bSuccess = * RequestOp . bSuccess ;
switch ( PayloadResult )
{
case OpResult : : Ok :
{
// Payload hash of the following payload data
FIoHash PayloadHash = * ( FIoHash * ) Response ;
Response + = sizeof ( FIoHash ) ;
// Size of the following payload data
const uint64 PayloadSize = * ( uint64 * ) Response ;
Response + = sizeof ( uint64 ) ;
if ( PayloadSize > 0 )
{
2021-05-25 15:28:50 -04:00
if ( Response + PayloadSize > ResponseEnd )
{
UE_LOG ( LogDerivedDataCache , Display , ResponseErrorMessage ) ;
return false ;
}
2021-03-22 18:23:46 -04:00
if ( bSuccess [ KeyIdx ] )
{
Response + = PayloadSize ;
}
2021-07-23 16:16:35 -04:00
else
2021-03-22 18:23:46 -04:00
{
2021-07-15 17:31:20 -04:00
TArray < uint8 > * OutData = RequestOp . OutDatas [ KeyIdx ] ;
2021-03-22 18:23:46 -04:00
OutData - > Append ( Response , PayloadSize ) ;
Response + = PayloadSize ;
2021-07-27 15:25:39 -04:00
// Verify the received and parsed payload
2021-11-07 23:43:01 -05:00
if ( VerifyPayload ( PayloadHash , RequestOp . Namespace , RequestOp . Bucket , RequestOp . CacheKeys [ KeyIdx ] , * OutData ) )
2021-03-22 18:23:46 -04:00
{
TRACE_COUNTER_ADD ( HttpDDC_GetHit , int64 ( 1 ) ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , int64 ( PayloadSize ) ) ;
bSuccess [ KeyIdx ] = true ;
}
else
{
OutData - > Empty ( ) ;
bSuccess [ KeyIdx ] = false ;
}
}
}
else
{
bSuccess [ KeyIdx ] = false ;
}
}
break ;
case OpResult : : Exists :
{
TRACE_COUNTER_ADD ( HttpDDC_ExistHit , int64 ( 1 ) ) ;
bSuccess [ KeyIdx ] = true ;
}
break ;
default :
case OpResult : : Error :
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " Server error while getting %s " ) , CacheKey . Get ( ) ) ;
// intentional falltrough
case OpResult : : NotFound :
bSuccess [ KeyIdx ] = false ;
break ;
}
if ( ! RequestSearch . AdvanceIndices ( EntryIdx , KeyIdx ) )
{
break ;
}
}
if ( ! bFoundAny )
2021-03-04 09:35:03 -04:00
{
UE_LOG ( LogDerivedDataCache , Error , ResponseErrorMessage ) ;
return false ;
}
}
// Have we parsed all the payloads from the message?
if ( PayloadIdx ! = PayloadCount )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Found %d payloads but %d was reported. " ) , ResponseErrorMessage , PayloadIdx , PayloadCount ) ;
}
return true ;
}
} ;
TStaticArray < FDataRequestHelper : : FBatch , UE_HTTPDDC_BATCH_NUM > FDataRequestHelper : : Batches ;
std : : atomic < uint32 > FDataRequestHelper : : FirstAvailableBatch ;
//----------------------------------------------------------------------------------------------------------
// FDataUploadHelper
//----------------------------------------------------------------------------------------------------------
struct FDataUploadHelper
{
FDataUploadHelper ( FRequestPool * InPool ,
const TCHAR * InNamespace ,
const TCHAR * InBucket ,
const TCHAR * InCacheKey ,
const TArrayView < const uint8 > & InData ,
FDerivedDataCacheUsageStats & InUsageStats )
: ResponseCode ( 0 )
, bSuccess ( false )
, bQueued ( false )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = InPool - > GetFreeRequest ( ) ;
2021-03-04 09:35:03 -04:00
if ( Request )
{
ResponseCode = PerformPut ( Request , InNamespace , InBucket , InCacheKey , InData , InUsageStats ) ;
2021-04-28 16:22:18 -04:00
bSuccess = FHttpRequest : : IsSuccessResponse ( Request - > GetResponseCode ( ) ) ;
2021-03-04 09:35:03 -04:00
ProcessQueuedPutsAndReleaseRequest ( InPool , Request , InUsageStats ) ;
}
else
{
FQueuedEntry * Entry = new FQueuedEntry ( InNamespace , InBucket , InCacheKey , InData ) ;
QueuedPuts . Push ( Entry ) ;
bSuccess = true ;
bQueued = true ;
// A request may have been released while the entry was being queued.
Request = InPool - > GetFreeRequest ( ) ;
if ( Request )
{
ProcessQueuedPutsAndReleaseRequest ( InPool , Request , InUsageStats ) ;
}
}
}
bool IsSuccess ( ) const
{
return bSuccess ;
}
int64 GetResponseCode ( ) const
{
return ResponseCode ;
}
bool IsQueued ( ) const
{
return bQueued ;
}
private :
struct FQueuedEntry
{
FString Namespace ;
FString Bucket ;
FString CacheKey ;
TArray < uint8 > Data ;
FQueuedEntry ( const TCHAR * InNamespace , const TCHAR * InBucket , const TCHAR * InCacheKey , const TArrayView < const uint8 > InData )
: Namespace ( InNamespace )
, Bucket ( InBucket )
, CacheKey ( InCacheKey )
, Data ( InData ) // Copies the data!
{ }
} ;
static TLockFreePointerListUnordered < FQueuedEntry , PLATFORM_CACHE_LINE_SIZE > QueuedPuts ;
int64 ResponseCode ;
bool bSuccess ;
bool bQueued ;
2021-04-28 16:22:18 -04:00
static void ProcessQueuedPutsAndReleaseRequest ( FRequestPool * Pool , FHttpRequest * Request , FDerivedDataCacheUsageStats & UsageStats )
2021-03-04 09:35:03 -04:00
{
while ( Request )
{
2021-04-19 22:53:53 -04:00
// Make sure that whether we early exit or execute past the end of this scope that
// the request is released back to the pool.
2021-04-05 14:36:58 -04:00
{
2021-04-19 22:53:53 -04:00
ON_SCOPE_EXIT
{
Pool - > ReleaseRequestToPool ( Request ) ;
} ;
2021-04-05 14:36:58 -04:00
if ( ShouldAbortForShutdown ( ) )
{
return ;
}
2021-03-04 09:35:03 -04:00
2021-04-19 22:53:53 -04:00
while ( FQueuedEntry * Entry = QueuedPuts . Pop ( ) )
{
Request - > Reset ( ) ;
PerformPut ( Request , * Entry - > Namespace , * Entry - > Bucket , * Entry - > CacheKey , Entry - > Data , UsageStats ) ;
delete Entry ;
if ( ShouldAbortForShutdown ( ) )
{
return ;
}
}
}
2021-03-04 09:35:03 -04:00
// An entry may have been queued while the request was being released.
if ( QueuedPuts . IsEmpty ( ) )
{
break ;
}
// Process the queue again if a request is free, otherwise the thread that got the request will process it.
Request = Pool - > GetFreeRequest ( ) ;
}
}
2021-04-28 16:22:18 -04:00
static int64 PerformPut ( FHttpRequest * Request , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArrayView < const uint8 > Data , FDerivedDataCacheUsageStats & UsageStats )
2021-03-04 09:35:03 -04:00
{
COOK_STAT ( auto Timer = UsageStats . TimePut ( ) ) ;
HashPayload ( Request , Data ) ;
TStringBuilder < 256 > Uri ;
Uri . Appendf ( TEXT ( " api/v1/c/ddc/%s/%s/%s " ) , Namespace , Bucket , CacheKey ) ;
2021-04-28 16:22:18 -04:00
Request - > PerformBlockingUpload < FHttpRequest : : Put > ( * Uri , Data ) ;
2021-03-04 09:35:03 -04:00
const int64 ResponseCode = Request - > GetResponseCode ( ) ;
2021-04-28 16:22:18 -04:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
2021-03-04 09:35:03 -04:00
{
TRACE_COUNTER_ADD ( HttpDDC_BytesSent , int64 ( Request - > GetBytesSent ( ) ) ) ;
COOK_STAT ( Timer . AddHit ( Request - > GetBytesSent ( ) ) ) ;
}
return Request - > GetResponseCode ( ) ;
}
} ;
TLockFreePointerListUnordered < FDataUploadHelper : : FQueuedEntry , PLATFORM_CACHE_LINE_SIZE > FDataUploadHelper : : QueuedPuts ;
# endif // WITH_DATAREQUEST_HELPER
2020-06-23 18:40:00 -04:00
//----------------------------------------------------------------------------------------------------------
// Certificate checking
//----------------------------------------------------------------------------------------------------------
# if WITH_SSL
static int SslCertVerify ( int PreverifyOk , X509_STORE_CTX * Context )
{
if ( PreverifyOk = = 1 )
{
SSL * Handle = static_cast < SSL * > ( X509_STORE_CTX_get_ex_data ( Context , SSL_get_ex_data_X509_STORE_CTX_idx ( ) ) ) ;
check ( Handle ) ;
SSL_CTX * SslContext = SSL_get_SSL_CTX ( Handle ) ;
check ( SslContext ) ;
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = static_cast < FHttpRequest * > ( SSL_CTX_get_app_data ( SslContext ) ) ;
2020-06-23 18:40:00 -04:00
check ( Request ) ;
const FString & Domain = Request - > GetDomain ( ) ;
if ( ! FSslModule : : Get ( ) . GetCertificateManager ( ) . VerifySslCertificates ( Context , Domain ) )
{
PreverifyOk = 0 ;
}
}
return PreverifyOk ;
}
static CURLcode sslctx_function ( CURL * curl , void * sslctx , void * parm )
{
SSL_CTX * Context = static_cast < SSL_CTX * > ( sslctx ) ;
const ISslCertificateManager & CertificateManager = FSslModule : : Get ( ) . GetCertificateManager ( ) ;
CertificateManager . AddCertificatesToSslContext ( Context ) ;
SSL_CTX_set_verify ( Context , SSL_CTX_get_verify_mode ( Context ) , SslCertVerify ) ;
SSL_CTX_set_app_data ( Context , parm ) ;
/* all set to go */
return CURLE_OK ;
}
# endif //#if WITH_SSL
//----------------------------------------------------------------------------------------------------------
// Content parsing and checking
//----------------------------------------------------------------------------------------------------------
/**
2021-02-22 11:11:09 -04:00
* Verifies the integrity of the received data using supplied checksum .
* @ param Hash received hash value .
2021-11-07 23:43:01 -05:00
* @ param Namespace The namespace string used when originally fetching the request .
* @ param Bucket The bucket string used when originally fetching the request .
* @ param CacheKey The cache key string used when originally fetching the request .
2021-02-22 11:11:09 -04:00
* @ param Payload Payload received .
2020-06-23 18:40:00 -04:00
* @ return True if the data is correct , false if checksums doesn ' t match .
*/
2021-11-07 23:43:01 -05:00
bool VerifyPayload ( const FSHAHash & Hash , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArray < uint8 > & Payload )
2020-06-23 18:40:00 -04:00
{
FSHAHash PayloadHash ;
FSHA1 : : HashBuffer ( Payload . GetData ( ) , Payload . Num ( ) , PayloadHash . Hash ) ;
if ( Hash ! = PayloadHash )
{
UE_LOG ( LogDerivedDataCache ,
2021-11-07 23:43:01 -05:00
Display ,
TEXT ( " Checksum from server did not match received data (%s vs %s). Discarding cached result. Namespace: %s, Bucket: %s, Key: %s. " ) ,
2021-03-25 15:34:31 -04:00
* WriteToString < 48 > ( Hash ) ,
2021-11-07 23:43:01 -05:00
* WriteToString < 48 > ( PayloadHash ) ,
Namespace ,
Bucket ,
CacheKey
2021-02-22 11:11:09 -04:00
) ;
return false ;
}
return true ;
}
/**
* Verifies the integrity of the received data using supplied checksum .
* @ param Hash received hash value .
2021-11-07 23:43:01 -05:00
* @ param Namespace The namespace string used when originally fetching the request .
* @ param Bucket The bucket string used when originally fetching the request .
* @ param CacheKey The cache key string used when originally fetching the request .
2021-02-22 11:11:09 -04:00
* @ param Payload Payload received .
* @ return True if the data is correct , false if checksums doesn ' t match .
*/
2021-11-07 23:43:01 -05:00
bool VerifyPayload ( const FIoHash & Hash , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArray < uint8 > & Payload )
2021-02-22 11:11:09 -04:00
{
FIoHash PayloadHash = FIoHash : : HashBuffer ( Payload . GetData ( ) , Payload . Num ( ) ) ;
if ( Hash ! = PayloadHash )
{
UE_LOG ( LogDerivedDataCache ,
2021-11-07 23:43:01 -05:00
Display ,
TEXT ( " Checksum from server did not match received data (%s vs %s). Discarding cached result. Namespace: %s, Bucket: %s, Key: %s. " ) ,
2021-03-25 15:34:31 -04:00
* WriteToString < 48 > ( Hash ) ,
2021-11-07 23:43:01 -05:00
* WriteToString < 48 > ( PayloadHash ) ,
Namespace ,
Bucket ,
CacheKey
2020-06-23 18:40:00 -04:00
) ;
return false ;
}
return true ;
}
/**
2021-02-22 11:11:09 -04:00
* Verifies the integrity of the received data using supplied checksum .
* @ param Request Request that the data was be received with .
2021-11-07 23:43:01 -05:00
* @ param Namespace The namespace string used when originally fetching the request .
* @ param Bucket The bucket string used when originally fetching the request .
* @ param CacheKey The cache key string used when originally fetching the request .
2021-02-22 11:11:09 -04:00
* @ param Payload Payload received .
2020-06-23 18:40:00 -04:00
* @ return True if the data is correct , false if checksums doesn ' t match .
*/
2021-11-07 23:43:01 -05:00
bool VerifyRequest ( const FHttpRequest * Request , const TCHAR * Namespace , const TCHAR * Bucket , const TCHAR * CacheKey , const TArray < uint8 > & Payload )
2020-06-23 18:40:00 -04:00
{
2021-03-04 09:35:03 -04:00
FString ReceivedHashStr ;
if ( Request - > GetHeader ( " X-Jupiter-Sha1 " , ReceivedHashStr ) )
2020-06-23 18:40:00 -04:00
{
2021-03-04 09:35:03 -04:00
FSHAHash ReceivedHash ;
ReceivedHash . FromString ( ReceivedHashStr ) ;
2021-11-07 23:43:01 -05:00
return VerifyPayload ( ReceivedHash , Namespace , Bucket , CacheKey , Payload ) ;
2021-02-22 11:11:09 -04:00
}
2021-03-04 09:35:03 -04:00
if ( Request - > GetHeader ( " X-Jupiter-IoHash " , ReceivedHashStr ) )
2021-02-22 11:11:09 -04:00
{
2021-03-04 09:35:03 -04:00
FIoHash ReceivedHash ( ReceivedHashStr ) ;
2021-11-07 23:43:01 -05:00
return VerifyPayload ( ReceivedHash , Namespace , Bucket , CacheKey , Payload ) ;
2020-06-23 18:40:00 -04:00
}
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " %s: HTTP server did not send a content hash. Wrong server version? " ) , * Request - > GetName ( ) ) ;
2020-06-23 18:40:00 -04:00
return true ;
}
/**
* Adds a checksum ( as request header ) for a given payload . Jupiter will use this to verify the integrity
2021-02-22 11:11:09 -04:00
* of the received data .
2020-06-23 18:40:00 -04:00
* @ param Request Request that the data will be sent with .
* @ param Payload Payload that will be sent .
* @ return True on success , false on failure .
*/
2021-04-28 16:22:18 -04:00
bool HashPayload ( FHttpRequest * Request , const TArrayView < const uint8 > Payload )
2020-06-23 18:40:00 -04:00
{
2021-03-16 06:11:58 -04:00
FIoHash PayloadHash = FIoHash : : HashBuffer ( Payload . GetData ( ) , Payload . Num ( ) ) ;
2021-03-25 15:34:31 -04:00
Request - > SetHeader ( TEXT ( " X-Jupiter-IoHash " ) , * WriteToString < 48 > ( PayloadHash ) ) ;
2020-06-23 18:40:00 -04:00
return true ;
}
2021-04-05 14:36:58 -04:00
bool ShouldAbortForShutdown ( )
{
return ! GIsBuildMachine & & FDerivedDataBackend : : Get ( ) . IsShuttingDown ( ) ;
}
2022-01-10 13:43:40 -05:00
TConstArrayView < uint8 > MakeArrayView ( FSharedBuffer Buffer )
{
return TConstArrayView < uint8 > ( reinterpret_cast < const uint8 * > ( Buffer . GetData ( ) ) , Buffer . GetSize ( ) ) ;
}
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 ;
} ;
2020-06-23 18:40:00 -04:00
//----------------------------------------------------------------------------------------------------------
// FHttpAccessToken
//----------------------------------------------------------------------------------------------------------
FString FHttpAccessToken : : GetHeader ( )
{
Lock . ReadLock ( ) ;
FString Header = FString : : Printf ( TEXT ( " Authorization: Bearer %s " ) , * Token ) ;
Lock . ReadUnlock ( ) ;
return Header ;
}
void FHttpAccessToken : : SetHeader ( const TCHAR * InToken )
{
Lock . WriteLock ( ) ;
Token = InToken ;
Serial + + ;
Lock . WriteUnlock ( ) ;
}
uint32 FHttpAccessToken : : GetSerial ( ) const
{
return Serial ;
}
//----------------------------------------------------------------------------------------------------------
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-02-14 14:43:39 -05:00
class FHttpCacheStore final : public FDerivedDataBackendInterface
2022-01-11 11:57:38 -05:00
{
public :
/**
* Creates the backend , checks health status and attempts to acquire an access token .
*
* @ param ServiceUrl Base url to the service including schema .
* @ param Namespace Namespace to use .
* @ param StructuredNamespace Namespace to use for structured cache operations .
* @ param OAuthProvider Url to OAuth provider , for example " https://myprovider.com/oauth2/v1/token " .
* @ param OAuthClientId OAuth client identifier .
* @ param OAuthData OAuth form data to send to login service . Can either be the raw form data or a Windows network file address ( starting with " \\ " ) .
*/
2022-02-14 14:43:39 -05:00
FHttpCacheStore (
2022-01-11 11:57:38 -05:00
const TCHAR * ServiceUrl ,
const TCHAR * Namespace ,
const TCHAR * StructuredNamespace ,
const TCHAR * OAuthProvider ,
const TCHAR * OAuthClientId ,
const TCHAR * OAuthData ,
2022-02-14 14:43:39 -05:00
EBackendLegacyMode LegacyMode ,
2022-01-11 11:57:38 -05:00
bool bReadOnly ) ;
2022-02-14 14:43:39 -05:00
~ FHttpCacheStore ( ) ;
2022-01-11 11:57:38 -05:00
/**
* Checks is backend is usable ( reachable and accessible ) .
* @ return true if usable
*/
bool IsUsable ( ) const { return bIsUsable ; }
/** return true if this cache is writable **/
virtual bool IsWritable ( ) const override
{
return ! bReadOnly & & bIsUsable ;
}
virtual bool CachedDataProbablyExists ( const TCHAR * CacheKey ) override ;
virtual TBitArray < > CachedDataProbablyExistsBatch ( TConstArrayView < FString > CacheKeys ) override ;
virtual bool GetCachedData ( const TCHAR * CacheKey , TArray < uint8 > & OutData ) override ;
virtual EPutStatus PutCachedData ( const TCHAR * CacheKey , TArrayView < const uint8 > InData , bool bPutEvenIfExists ) override ;
virtual void RemoveCachedData ( const TCHAR * CacheKey , bool bTransient ) override ;
virtual TSharedRef < FDerivedDataCacheStatsNode > GatherUsageStats ( ) const override ;
virtual FString GetName ( ) const override ;
2022-01-27 23:16:00 -05:00
virtual TBitArray < > TryToPrefetch ( TConstArrayView < FString > CacheKeys ) override ;
2022-01-11 11:57:38 -05:00
virtual bool WouldCache ( const TCHAR * CacheKey , TArrayView < const uint8 > InData ) override ;
virtual ESpeedClass GetSpeedClass ( ) const override ;
virtual bool ApplyDebugOptions ( FBackendDebugOptions & InOptions ) override ;
void SetSpeedClass ( ESpeedClass InSpeedClass ) { SpeedClass = InSpeedClass ; }
2022-02-14 14:43:39 -05:00
EBackendLegacyMode GetLegacyMode ( ) const final { return LegacyMode ; }
2022-01-11 11:57:38 -05:00
virtual void Put (
TConstArrayView < FCachePutRequest > Requests ,
IRequestOwner & Owner ,
FOnCachePutComplete & & OnComplete ) override ;
virtual void Get (
TConstArrayView < FCacheGetRequest > Requests ,
IRequestOwner & Owner ,
FOnCacheGetComplete & & OnComplete ) override ;
2022-02-02 07:35:19 -05:00
virtual void PutValue (
TConstArrayView < FCachePutValueRequest > Requests ,
IRequestOwner & Owner ,
FOnCachePutValueComplete & & OnComplete ) override ;
virtual void GetValue (
TConstArrayView < FCacheGetValueRequest > Requests ,
IRequestOwner & Owner ,
FOnCacheGetValueComplete & & OnComplete ) override ;
2022-01-11 11:57:38 -05:00
virtual void GetChunks (
2022-01-18 04:47:59 -05:00
TConstArrayView < FCacheGetChunkRequest > Requests ,
2022-01-11 11:57:38 -05:00
IRequestOwner & Owner ,
2022-01-18 04:47:59 -05:00
FOnCacheGetChunkComplete & & OnComplete ) override ;
2022-01-11 11:57:38 -05:00
2022-02-14 14:43:39 -05:00
static FHttpCacheStore * GetAny ( )
2022-01-11 11:57:38 -05:00
{
return AnyInstance ;
}
const FString & GetDomain ( ) const { return Domain ; }
const FString & GetNamespace ( ) const { return Namespace ; }
const FString & GetStructuredNamespace ( ) const { return StructuredNamespace ; }
const FString & GetOAuthProvider ( ) const { return OAuthProvider ; }
const FString & GetOAuthClientId ( ) const { return OAuthClientId ; }
const FString & GetOAuthSecret ( ) const { return OAuthSecret ; }
private :
FString Domain ;
FString Namespace ;
FString StructuredNamespace ;
FString DefaultBucket ;
FString OAuthProvider ;
FString OAuthClientId ;
FString OAuthSecret ;
FCriticalSection AccessCs ;
FDerivedDataCacheUsageStats UsageStats ;
FBackendDebugOptions DebugOptions ;
FCriticalSection MissedKeysCS ;
TSet < FName > DebugMissedKeys ;
TSet < FCacheKey > DebugMissedCacheKeys ;
TUniquePtr < struct FRequestPool > GetRequestPools [ 2 ] ;
TUniquePtr < struct FRequestPool > PutRequestPools [ 2 ] ;
TUniquePtr < struct FHttpAccessToken > Access ;
bool bIsUsable ;
bool bReadOnly ;
uint32 FailedLoginAttempts ;
ESpeedClass SpeedClass ;
2022-02-14 14:43:39 -05:00
EBackendLegacyMode LegacyMode ;
static inline FHttpCacheStore * AnyInstance = nullptr ;
2022-01-11 11:57:38 -05:00
bool IsServiceReady ( ) ;
bool AcquireAccessToken ( ) ;
bool ShouldRetryOnError ( int64 ResponseCode ) ;
bool ShouldSimulateMiss ( const TCHAR * InKey ) ;
bool ShouldSimulateMiss ( const FCacheKey & Key ) ;
2022-01-14 10:53:17 -05:00
bool PutCacheRecord ( FStringView Name , const FCacheRecord & Record , const FCacheRecordPolicy & Policy , uint64 & OutWriteSize ) ;
2022-02-02 07:35:19 -05:00
uint64 PutRef ( const FCbPackage & Package , const FCacheKey & Key , FStringView Bucket , bool bFinalize , TArray < FIoHash > & OutNeededBlobHashes , bool & bOutPutCompletedSuccessfully ) ;
2022-01-11 11:57:38 -05:00
FOptionalCacheRecord GetCacheRecordOnly (
2022-01-14 10:53:17 -05:00
FStringView Name ,
2022-01-11 11:57:38 -05:00
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ) ;
FOptionalCacheRecord GetCacheRecord (
2022-01-14 10:53:17 -05:00
FStringView Name ,
2022-01-11 11:57:38 -05:00
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
EStatus & OutStatus ) ;
2022-02-02 07:35:19 -05:00
bool PutCacheValue (
FStringView Name ,
const FCacheKey & Key ,
const FValue & Value ,
ECachePolicy Policy ,
uint64 & OutWriteSize ) ;
bool GetCacheValueOnly (
FStringView Name ,
const FCacheKey & Key ,
ECachePolicy Policy ,
FValue & OutValue ) ;
bool GetCacheValue (
FStringView Name ,
const FCacheKey & Key ,
ECachePolicy Policy ,
FValue & OutValue ) ;
template < typename ValueType , typename ValuePrinterType >
2022-01-11 11:57:38 -05:00
bool TryGetCachedDataBatch (
2022-01-14 10:53:17 -05:00
FStringView Name ,
2022-01-11 11:57:38 -05:00
const FCacheKey & Key ,
2022-02-02 07:35:19 -05:00
TConstArrayView < ValueType > Values ,
TArray < FCompressedBuffer > & OutBuffers ,
ValuePrinterType ValuePrinter ) ;
template < typename ValueType , typename ValuePrinterType >
2022-01-14 10:53:17 -05:00
bool CachedDataProbablyExistsBatch (
FStringView Name ,
const FCacheKey & Key ,
2022-02-02 07:35:19 -05:00
TConstArrayView < ValueType > Values ,
ValuePrinterType ValuePrinter ) ;
2022-01-11 11:57:38 -05:00
} ;
2022-02-14 14:43:39 -05:00
FHttpCacheStore : : FHttpCacheStore (
2020-06-23 18:40:00 -04:00
const TCHAR * InServiceUrl ,
const TCHAR * InNamespace ,
2022-01-10 13:43:40 -05:00
const TCHAR * InStructuredNamespace ,
2020-06-23 18:40:00 -04:00
const TCHAR * InOAuthProvider ,
const TCHAR * InOAuthClientId ,
2021-04-21 13:19:11 -04:00
const TCHAR * InOAuthSecret ,
2022-02-14 14:43:39 -05:00
const EBackendLegacyMode InLegacyMode ,
2021-04-21 13:19:11 -04:00
const bool bInReadOnly )
2021-08-06 12:53:08 -04:00
: Domain ( InServiceUrl )
2020-06-23 18:40:00 -04:00
, Namespace ( InNamespace )
2022-01-10 13:43:40 -05:00
, StructuredNamespace ( InStructuredNamespace )
2020-06-23 18:40:00 -04:00
, DefaultBucket ( TEXT ( " default " ) )
, OAuthProvider ( InOAuthProvider )
, OAuthClientId ( InOAuthClientId )
, OAuthSecret ( InOAuthSecret )
, Access ( nullptr )
, bIsUsable ( false )
2021-04-21 13:19:11 -04:00
, bReadOnly ( bInReadOnly )
2020-06-23 18:40:00 -04:00
, FailedLoginAttempts ( 0 )
2021-03-18 05:51:42 -04:00
, SpeedClass ( ESpeedClass : : Slow )
2022-02-14 14:43:39 -05:00
, LegacyMode ( InLegacyMode )
2020-06-23 18:40:00 -04:00
{
2021-03-04 09:35:03 -04:00
# if WITH_DATAREQUEST_HELPER
FDataRequestHelper : : StaticInitialize ( ) ;
# endif
2020-06-23 18:40:00 -04:00
if ( IsServiceReady ( ) & & AcquireAccessToken ( ) )
{
2022-02-08 01:56:17 -05:00
FString EffectiveDomain ;
FString OriginalDomainPrefix ;
TAnsiStringBuilder < 64 > DomainResolveName ;
if ( Domain . StartsWith ( TEXT ( " http:// " ) ) )
{
DomainResolveName < < Domain . RightChop ( 7 ) ;
OriginalDomainPrefix = TEXT ( " http:// " ) ;
}
else if ( Domain . StartsWith ( TEXT ( " https:// " ) ) )
{
DomainResolveName < < Domain . RightChop ( 8 ) ;
OriginalDomainPrefix = TEXT ( " https:// " ) ;
}
else
{
DomainResolveName < < Domain ;
}
addrinfo * AddrResult = nullptr ;
addrinfo AddrHints ;
FMemory : : Memset ( & AddrHints , 0 , sizeof ( AddrHints ) ) ;
AddrHints . ai_flags = AI_CANONNAME ;
AddrHints . ai_family = AF_UNSPEC ;
if ( ! : : getaddrinfo ( * DomainResolveName , nullptr , & AddrHints , & AddrResult ) )
{
if ( AddrResult - > ai_canonname )
{
// Swap the domain with a canonical name from DNS so that if we are using regional redirection, we pin to a region.
EffectiveDomain = OriginalDomainPrefix + ANSI_TO_TCHAR ( AddrResult - > ai_canonname ) ;
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: Pinned to %s based on DNS canonical name. " ) ,
* Domain , * EffectiveDomain ) ;
}
else
{
EffectiveDomain = Domain ;
}
: : freeaddrinfo ( AddrResult ) ;
}
else
{
EffectiveDomain = Domain ;
}
GetRequestPools [ 0 ] = MakeUnique < FRequestPool > ( * Domain , * EffectiveDomain , Access . Get ( ) , UE_HTTPDDC_GET_REQUEST_POOL_SIZE ) ;
GetRequestPools [ 1 ] = MakeUnique < FRequestPool > ( * Domain , * EffectiveDomain , Access . Get ( ) , 1 ) ;
PutRequestPools [ 0 ] = MakeUnique < FRequestPool > ( * Domain , * EffectiveDomain , Access . Get ( ) , UE_HTTPDDC_PUT_REQUEST_POOL_SIZE ) ;
PutRequestPools [ 1 ] = MakeUnique < FRequestPool > ( * Domain , * EffectiveDomain , Access . Get ( ) , 1 ) ;
2020-06-23 18:40:00 -04:00
bIsUsable = true ;
}
2021-07-19 13:09:47 -04:00
AnyInstance = this ;
2020-06-23 18:40:00 -04:00
}
2022-02-14 14:43:39 -05:00
FHttpCacheStore : : ~ FHttpCacheStore ( )
2020-06-23 18:40:00 -04:00
{
2021-07-19 13:09:47 -04:00
if ( AnyInstance = = this )
{
AnyInstance = nullptr ;
}
2021-11-07 23:43:01 -05:00
# if WITH_DATAREQUEST_HELPER
FDataRequestHelper : : StaticShutdown ( ) ;
# endif
2020-06-23 18:40:00 -04:00
}
2022-02-14 14:43:39 -05:00
FString FHttpCacheStore : : GetName ( ) const
2020-06-23 18:40:00 -04:00
{
return Domain ;
}
2022-02-14 14:43:39 -05:00
TBitArray < > FHttpCacheStore : : TryToPrefetch ( TConstArrayView < FString > CacheKeys )
2020-06-23 18:40:00 -04:00
{
2022-01-27 23:16:00 -05:00
return CachedDataProbablyExistsBatch ( CacheKeys ) ;
2020-06-23 18:40:00 -04:00
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : WouldCache ( const TCHAR * CacheKey , TArrayView < const uint8 > InData )
2020-06-23 18:40:00 -04:00
{
2021-04-21 13:19:11 -04:00
return IsWritable ( ) ;
2020-06-23 18:40:00 -04:00
}
2022-02-14 14:43:39 -05:00
FHttpCacheStore : : ESpeedClass FHttpCacheStore : : GetSpeedClass ( ) const
2020-06-23 18:40:00 -04:00
{
2021-03-18 05:51:42 -04:00
return SpeedClass ;
2020-06-23 18:40:00 -04:00
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : ApplyDebugOptions ( 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-02-14 14:43:39 -05:00
bool FHttpCacheStore : : IsServiceReady ( )
2020-06-23 18:40:00 -04:00
{
2022-02-08 01:56:17 -05:00
FHttpRequest Request ( * Domain , * Domain , nullptr , false ) ;
2021-04-28 16:22:18 -04:00
FHttpRequest : : Result Result = Request . PerformBlockingDownload ( TEXT ( " health/ready " ) , nullptr ) ;
2020-06-23 18:40:00 -04:00
2021-04-28 16:22:18 -04:00
if ( Result = = FHttpRequest : : Success & & Request . GetResponseCode ( ) = = 200 )
2020-06-23 18:40:00 -04:00
{
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: HTTP DDC service status: %s. " ) , * Request . GetName ( ) , * Request . GetResponseAsString ( ) ) ;
2020-06-23 18:40:00 -04:00
return true ;
}
else
{
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " %s: Unable to reach HTTP DDC service at %s. Status: %d . Response: %s " ) , * Request . GetName ( ) , * Domain , Request . GetResponseCode ( ) , * Request . GetResponseAsString ( ) ) ;
2020-06-23 18:40:00 -04:00
}
return false ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : AcquireAccessToken ( )
2020-06-23 18:40:00 -04:00
{
2020-12-09 08:30:46 -04:00
// Avoid spamming the this if the service is down
2020-06-23 18:40:00 -04:00
if ( FailedLoginAttempts > UE_HTTPDDC_MAX_FAILED_LOGIN_ATTEMPTS )
{
return false ;
}
ensureMsgf ( OAuthProvider . StartsWith ( TEXT ( " http:// " ) ) | | OAuthProvider . StartsWith ( TEXT ( " https:// " ) ) ,
TEXT ( " The OAuth provider %s is not valid. Needs to be a fully qualified url. " ) ,
* OAuthProvider
) ;
// In case many requests wants to update the token at the same time
// get the current serial while we wait to take the CS.
const uint32 WantsToUpdateTokenSerial = Access . IsValid ( ) ? Access - > GetSerial ( ) : 0u ;
{
FScopeLock Lock ( & AccessCs ) ;
// Check if someone has beaten us to update the token, then it
// should now be valid.
if ( Access . IsValid ( ) & & Access - > GetSerial ( ) > WantsToUpdateTokenSerial )
{
return true ;
}
const uint32 SchemeEnd = OAuthProvider . Find ( TEXT ( " :// " ) ) + 3 ;
const uint32 DomainEnd = OAuthProvider . Find ( TEXT ( " / " ) , ESearchCase : : CaseSensitive , ESearchDir : : FromStart , SchemeEnd ) ;
FString AuthDomain ( DomainEnd , * OAuthProvider ) ;
FString Uri ( * OAuthProvider + DomainEnd + 1 ) ;
2022-02-08 01:56:17 -05:00
FHttpRequest Request ( * AuthDomain , * AuthDomain , nullptr , false ) ;
2020-06-23 18:40:00 -04:00
// If contents of the secret string is a file path, resolve and read form data.
2021-10-26 09:14:51 -04:00
if ( OAuthSecret . StartsWith ( TEXT ( " file:// " ) ) )
2020-06-23 18:40:00 -04:00
{
2021-10-26 09:14:51 -04:00
FString FilePath = OAuthSecret . Mid ( 7 , OAuthSecret . Len ( ) - 7 ) ;
2020-06-23 18:40:00 -04:00
FString SecretFileContents ;
2021-10-26 09:14:51 -04:00
if ( FFileHelper : : LoadFileToString ( SecretFileContents , * FilePath ) )
2020-06-23 18:40:00 -04:00
{
// Overwrite the filepath with the actual content.
OAuthSecret = SecretFileContents ;
}
else
{
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " %s: Failed to read OAuth form data file (%s). " ) , * Request . GetName ( ) , * OAuthSecret ) ;
2020-06-23 18:40:00 -04:00
return false ;
}
}
FString OAuthFormData = FString : : Printf (
TEXT ( " client_id=%s&scope=cache_access&grant_type=client_credentials&client_secret=%s " ) ,
* OAuthClientId ,
* OAuthSecret
) ;
TArray < uint8 > FormData ;
auto OAuthFormDataUTF8 = FTCHARToUTF8 ( * OAuthFormData ) ;
FormData . Append ( ( uint8 * ) OAuthFormDataUTF8 . Get ( ) , OAuthFormDataUTF8 . Length ( ) ) ;
2021-04-28 16:22:18 -04:00
FHttpRequest : : Result Result = Request . PerformBlockingUpload < FHttpRequest : : Post > ( * Uri , MakeArrayView ( FormData ) ) ;
2020-06-23 18:40:00 -04:00
2021-04-28 16:22:18 -04:00
if ( Result = = FHttpRequest : : Success & & Request . GetResponseCode ( ) = = 200 )
2020-06-23 18:40:00 -04:00
{
TSharedPtr < FJsonObject > ResponseObject = Request . GetResponseAsJsonObject ( ) ;
if ( ResponseObject )
{
FString AccessTokenString ;
int32 ExpiryTimeSeconds = 0 ;
int32 CurrentTimeSeconds = int32 ( FPlatformTime : : ToSeconds ( FPlatformTime : : Cycles ( ) ) ) ;
if ( ResponseObject - > TryGetStringField ( TEXT ( " access_token " ) , AccessTokenString ) & &
ResponseObject - > TryGetNumberField ( TEXT ( " expires_in " ) , ExpiryTimeSeconds ) )
{
if ( ! Access )
{
Access = MakeUnique < FHttpAccessToken > ( ) ;
}
Access - > SetHeader ( * AccessTokenString ) ;
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Logged in to HTTP DDC services. Expires in %d seconds. " ) , * Request . GetName ( ) , ExpiryTimeSeconds ) ;
2020-06-23 18:40:00 -04:00
//Schedule a refresh of the token ahead of expiry time (this will not work in commandlets)
if ( ! IsRunningCommandlet ( ) )
{
2021-08-16 11:09:22 -04:00
FTSTicker : : GetCoreTicker ( ) . AddTicker ( FTickerDelegate : : CreateLambda (
2020-06-23 18:40:00 -04:00
[ this ] ( float DeltaTime )
{
this - > AcquireAccessToken ( ) ;
return false ;
}
) , ExpiryTimeSeconds - 20.0f ) ;
}
2020-12-09 08:30:46 -04:00
// Reset failed login attempts, the service is indeed alive.
FailedLoginAttempts = 0 ;
2020-06-23 18:40:00 -04:00
return true ;
}
}
}
else
{
2022-02-08 01:56:17 -05:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " %s: Failed to log in to HTTP services. Server responed with code %d. " ) , * Request . GetName ( ) , Request . GetResponseCode ( ) ) ;
2020-06-23 18:40:00 -04:00
FailedLoginAttempts + + ;
}
}
return false ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : ShouldRetryOnError ( int64 ResponseCode )
2020-06-23 18:40:00 -04:00
{
// Access token might have expired, request a new token and try again.
if ( ResponseCode = = 401 & & AcquireAccessToken ( ) )
{
return true ;
}
// Too many requests, make a new attempt
if ( ResponseCode = = 429 )
{
return true ;
}
return false ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : ShouldSimulateMiss ( const TCHAR * InKey )
2021-11-07 23:43:01 -05:00
{
if ( DebugOptions . RandomMissRate = = 0 & & DebugOptions . SimulateMissTypes . IsEmpty ( ) )
{
return false ;
}
const FName Key ( InKey ) ;
const uint32 Hash = GetTypeHash ( Key ) ;
if ( FScopeLock Lock ( & MissedKeysCS ) ; DebugMissedKeys . ContainsByHash ( Hash , Key ) )
{
return true ;
}
if ( DebugOptions . ShouldSimulateMiss ( InKey ) )
{
FScopeLock Lock ( & MissedKeysCS ) ;
DebugMissedKeys . AddByHash ( Hash , Key ) ;
return true ;
}
return false ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : ShouldSimulateMiss ( const FCacheKey & Key )
2022-01-10 13:43:40 -05:00
{
if ( DebugOptions . RandomMissRate = = 0 & & DebugOptions . SimulateMissTypes . IsEmpty ( ) )
{
return false ;
}
const uint32 Hash = GetTypeHash ( Key ) ;
if ( FScopeLock Lock ( & MissedKeysCS ) ; DebugMissedCacheKeys . ContainsByHash ( Hash , Key ) )
{
return true ;
}
if ( DebugOptions . ShouldSimulateMiss ( Key ) )
{
FScopeLock Lock ( & MissedKeysCS ) ;
DebugMissedCacheKeys . AddByHash ( Hash , Key ) ;
return true ;
}
return false ;
}
2022-02-14 14:43:39 -05:00
uint64 FHttpCacheStore : : PutRef ( const FCbPackage & Package , const FCacheKey & Key , FStringView Bucket , bool bFinalize , TArray < FIoHash > & OutNeededBlobHashes , bool & bOutPutCompletedSuccessfully )
2022-01-10 13:43:40 -05:00
{
bOutPutCompletedSuccessfully = false ;
uint64 BytesSent = 0 ;
TStringBuilder < 256 > RefsUri ;
RefsUri < < " api/v1/refs/ " < < StructuredNamespace < < " / " < < Bucket < < " / " < < Key . Hash ;
if ( bFinalize )
{
RefsUri < < " /finalize/ " < < Package . GetObjectHash ( ) ;
}
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
if ( bFinalize )
{
Request - > PerformBlockingUpload < FHttpRequest : : Post > ( * RefsUri , TArrayView < const uint8 > ( ) ) ;
}
else
{
Request - > SetHeader ( TEXT ( " X-Jupiter-IoHash " ) , * WriteToString < 48 > ( Package . GetObjectHash ( ) ) ) ;
Request - > PerformBlockingUpload < FHttpRequest : : PutCompactBinary > ( * RefsUri , MakeArrayView ( Package . GetObject ( ) . GetBuffer ( ) . ToShared ( ) ) ) ;
}
ResponseCode = Request - > GetResponseCode ( ) ;
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
{
BytesSent + = Request - > GetBytesSent ( ) ;
if ( TSharedPtr < FJsonObject > ResponseObject = Request - > GetResponseAsJsonObject ( ) )
{
TArray < FString > NeedsArrayStrings ;
ResponseObject - > TryGetStringArrayField ( TEXT ( " needs " ) , NeedsArrayStrings ) ;
OutNeededBlobHashes . Empty ( NeedsArrayStrings . Num ( ) ) ;
for ( const FString & NeededString : NeedsArrayStrings )
{
FIoHash BlobHash ;
LexFromString ( BlobHash , * NeededString ) ;
if ( ! BlobHash . IsZero ( ) )
{
OutNeededBlobHashes . Add ( BlobHash ) ;
}
}
}
else
{
OutNeededBlobHashes . Empty ( ) ;
}
if ( OutNeededBlobHashes . IsEmpty ( ) )
{
bOutPutCompletedSuccessfully = true ;
}
break ;
}
else
{
OutNeededBlobHashes . Empty ( ) ;
}
}
return BytesSent ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : PutCacheRecord (
2022-01-14 10:53:17 -05:00
FStringView Name ,
2022-01-10 13:43:40 -05:00
const FCacheRecord & Record ,
const FCacheRecordPolicy & Policy ,
uint64 & OutWriteSize )
{
OutWriteSize = 0 ;
if ( ! IsWritable ( ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped put of %s from '%.*s' because this cache store is read-only " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Record . GetKey ( ) ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return false ;
}
const FCacheKey & Key = Record . GetKey ( ) ;
2022-01-31 11:52:13 -05:00
const ECachePolicy RecordPolicy = Policy . GetRecordPolicy ( ) ;
2022-01-10 13:43:40 -05:00
// Skip the request if storing to the cache is disabled.
// Http backends won't generally be "local" but including handling for this possibility for consistency
const ECachePolicy StoreFlag = SpeedClass = = ESpeedClass : : Local ? ECachePolicy : : StoreLocal : ECachePolicy : : StoreRemote ;
2022-01-31 11:52:13 -05:00
if ( ! EnumHasAnyFlags ( RecordPolicy , StoreFlag ) )
2022-01-10 13:43:40 -05:00
{
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped put of %s from '%.*s' due to cache policy " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return false ;
}
if ( ShouldSimulateMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for put of %s from '%.*s' " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return false ;
}
// TODO: Jupiter currently always overwrites. It doesn't have a "write if not present" feature (for records or attachments),
// but would require one to implement all policy correctly.
2022-02-02 07:35:19 -05:00
FString Bucket ( Key . Bucket . ToString ( ) ) ;
2022-01-10 13:43:40 -05:00
Bucket . ToLowerInline ( ) ;
bool bPutCompletedSuccessfully = false ;
FCbPackage Package = Record . Save ( ) ;
TArray < FIoHash > NeededBlobHashes ;
// Initial record upload
2022-02-02 07:35:19 -05:00
size_t PutRefBytesSent = PutRef ( Package , Record . GetKey ( ) , Bucket , false , NeededBlobHashes , bPutCompletedSuccessfully ) ;
2022-01-10 13:43:40 -05:00
OutWriteSize + = PutRefBytesSent ;
// TODO: blob uploading and finalization should be replaced with a single batch compressed blob upload endpoint in the future.
// Needed blob upload (if any missing)
for ( const FIoHash & NeededBlobHash : NeededBlobHashes )
{
TStringBuilder < 256 > CompressedBlobsUri ;
CompressedBlobsUri < < " api/v1/compressed-blobs/ " < < StructuredNamespace < < " / " < < NeededBlobHash ;
if ( const FCbAttachment * Attachment = Package . FindAttachment ( NeededBlobHash ) )
{
FSharedBuffer TempBuffer ;
TConstArrayView < uint8 > BlobArrayView ;
if ( Attachment - > IsCompressedBinary ( ) )
{
TempBuffer = Attachment - > AsCompressedBinary ( ) . GetCompressed ( ) . ToShared ( ) ;
BlobArrayView = MakeArrayView ( TempBuffer ) ;
}
else if ( Attachment - > IsBinary ( ) )
{
TempBuffer = FCompressedBuffer : : Compress ( Attachment - > AsCompositeBinary ( ) ) . GetCompressed ( ) . ToShared ( ) ;
BlobArrayView = MakeArrayView ( TempBuffer ) ;
}
else
{
TempBuffer = FCompressedBuffer : : Compress ( Attachment - > AsObject ( ) . GetBuffer ( ) ) . GetCompressed ( ) . ToShared ( ) ;
BlobArrayView = MakeArrayView ( TempBuffer ) ;
}
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
Request - > PerformBlockingUpload < FHttpRequest : : PutCompressedBlob > ( * CompressedBlobsUri , BlobArrayView ) ;
ResponseCode = Request - > GetResponseCode ( ) ;
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
{
OutWriteSize + = Request - > GetBytesSent ( ) ;
break ;
}
}
}
}
// Finalization (if any blobs needed)
if ( ! NeededBlobHashes . IsEmpty ( ) )
{
2022-02-02 07:35:19 -05:00
size_t FinalizeRefBytesSent = PutRef ( Package , Record . GetKey ( ) , Bucket , true , NeededBlobHashes , bPutCompletedSuccessfully ) ;
2022-01-10 13:43:40 -05:00
OutWriteSize + = FinalizeRefBytesSent ;
}
return bPutCompletedSuccessfully ;
}
2022-02-14 14:43:39 -05:00
FOptionalCacheRecord FHttpCacheStore : : GetCacheRecordOnly (
2022-01-14 10:53:17 -05:00
const FStringView Name ,
2022-01-10 13:43:40 -05:00
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy )
{
if ( ! IsUsable ( ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped get of %s from '%.*s' because this cache store is not available " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return FOptionalCacheRecord ( ) ;
}
// Skip the request if querying the cache is disabled.
const ECachePolicy QueryPolicy = SpeedClass = = ESpeedClass : : Local
? ECachePolicy : : QueryLocal : ECachePolicy : : QueryRemote ;
if ( ! EnumHasAnyFlags ( Policy . GetRecordPolicy ( ) , QueryPolicy ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped get of %s from '%.*s' due to cache policy " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return FOptionalCacheRecord ( ) ;
}
if ( ShouldSimulateMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for get of %s from '%.*s' " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return FOptionalCacheRecord ( ) ;
}
FOptionalCacheRecord Record ;
{
2022-02-02 07:35:19 -05:00
FString Bucket ( Key . Bucket . ToString ( ) ) ;
2022-01-10 13:43:40 -05:00
Bucket . ToLowerInline ( ) ;
TStringBuilder < 256 > RefsUri ;
RefsUri < < " api/v1/refs/ " < < StructuredNamespace < < " / " < < Bucket < < " / " < < Key . Hash ;
2022-01-14 10:53:17 -05:00
bool bIsSuccessfulResponse = false ;
2022-01-10 13:43:40 -05:00
FSharedBuffer ResponseBuffer ;
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
TArray < uint8 > ByteArray ;
Request - > SetHeader ( TEXT ( " Accept " ) , TEXT ( " application/x-ue-cb " ) ) ;
2022-01-26 00:46:18 -05:00
Request - > PerformBlockingDownload ( * RefsUri , & ByteArray , { 401 , 404 } ) ;
2022-01-10 13:43:40 -05:00
ResponseCode = Request - > GetResponseCode ( ) ;
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
{
ResponseBuffer = MakeSharedBufferFromArray ( MoveTemp ( ByteArray ) ) ;
2022-01-14 10:53:17 -05:00
bIsSuccessfulResponse = true ;
2022-01-10 13:43:40 -05:00
break ;
}
}
2022-01-14 10:53:17 -05:00
if ( ! bIsSuccessfulResponse )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with missing package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return Record ;
}
2022-01-10 13:43:40 -05:00
if ( ValidateCompactBinary ( ResponseBuffer , ECbValidateMode : : Default ) ! = ECbValidateError : : None )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid package for %s from '%.*s' " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return Record ;
2022-01-10 13:43:40 -05:00
}
Record = FCacheRecord : : Load ( FCbPackage ( FCbObject ( ResponseBuffer ) ) ) ;
if ( Record . IsNull ( ) )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with record load failure for %s from '%.*s' " ) ,
2022-01-14 10:53:17 -05:00
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return Record ;
}
}
return Record ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : PutCacheValue (
2022-02-02 07:35:19 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const FValue & Value ,
const ECachePolicy Policy ,
uint64 & OutWriteSize )
2022-01-14 10:53:17 -05:00
{
2022-02-02 07:35:19 -05:00
if ( ! IsWritable ( ) )
2022-01-14 10:53:17 -05:00
{
2022-02-02 07:35:19 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped put of %s from '%.*s' because this cache store is read-only " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
2022-01-14 10:53:17 -05:00
}
2022-02-02 07:35:19 -05:00
// Skip the request if storing to the cache is disabled.
const ECachePolicy StoreFlag = SpeedClass = = ESpeedClass : : Local ? ECachePolicy : : StoreLocal : ECachePolicy : : StoreRemote ;
if ( ! EnumHasAnyFlags ( Policy , StoreFlag ) )
2022-01-14 10:53:17 -05:00
{
2022-02-02 07:35:19 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped put of %s from '%.*s' due to cache policy " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
if ( ShouldSimulateMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for put of %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
// 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.
FString Bucket ( Key . Bucket . ToString ( ) ) ;
Bucket . ToLowerInline ( ) ;
bool bPutCompletedSuccessfully = false ;
FCbWriter Writer ;
Writer . BeginObject ( ) ;
Writer . AddBinaryAttachment ( " RawHash " , Value . GetRawHash ( ) ) ;
Writer . AddInteger ( " RawSize " , Value . GetRawSize ( ) ) ;
Writer . EndObject ( ) ;
FCbPackage Package ( Writer . Save ( ) . AsObject ( ) ) ;
TArray < FIoHash > NeededBlobHashes ;
// Initial record upload
size_t PutRefBytesSent = PutRef ( Package , Key , Bucket , false , NeededBlobHashes , bPutCompletedSuccessfully ) ;
OutWriteSize + = PutRefBytesSent ;
// Needed blob upload (if any missing)
if ( ! NeededBlobHashes . IsEmpty ( ) )
{
check ( NeededBlobHashes . Num ( ) = = 1 ) ;
check ( NeededBlobHashes [ 0 ] = = Value . GetRawHash ( ) ) ;
TStringBuilder < 256 > CompressedBlobsUri ;
CompressedBlobsUri < < " api/v1/compressed-blobs/ " < < StructuredNamespace < < " / " < < Value . GetRawHash ( ) ;
FSharedBuffer TempBuffer = Value . GetData ( ) . GetCompressed ( ) . ToShared ( ) ;
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
Request - > PerformBlockingUpload < FHttpRequest : : PutCompressedBlob > ( * CompressedBlobsUri , MakeArrayView ( TempBuffer ) ) ;
ResponseCode = Request - > GetResponseCode ( ) ;
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
{
OutWriteSize + = Request - > GetBytesSent ( ) ;
break ;
}
}
OutWriteSize + = PutRef ( Package , Key , Bucket , true , NeededBlobHashes , bPutCompletedSuccessfully ) ;
}
return true ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : GetCacheValueOnly (
2022-02-02 07:35:19 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const ECachePolicy Policy ,
FValue & OutValue )
{
if ( ! IsUsable ( ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped get of %s from '%.*s' because this cache store is not available " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
// Skip the request if querying the cache is disabled.
const ECachePolicy QueryFlag = SpeedClass = = ESpeedClass : : Local ? ECachePolicy : : QueryLocal : ECachePolicy : : QueryRemote ;
if ( ! EnumHasAnyFlags ( Policy , QueryFlag ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped get of %s from '%.*s' due to cache policy " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
if ( ShouldSimulateMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for get of %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
FString Bucket ( Key . Bucket . ToString ( ) ) ;
Bucket . ToLowerInline ( ) ;
TStringBuilder < 256 > RefsUri ;
RefsUri < < " api/v1/refs/ " < < StructuredNamespace < < " / " < < Bucket < < " / " < < Key . Hash ;
bool bIsSuccessfulResponse = false ;
FSharedBuffer ResponseBuffer ;
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
TArray < uint8 > ByteArray ;
Request - > SetHeader ( TEXT ( " Accept " ) , TEXT ( " application/x-ue-cb " ) ) ;
Request - > PerformBlockingDownload ( * RefsUri , & ByteArray , { 401 , 404 } ) ;
ResponseCode = Request - > GetResponseCode ( ) ;
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
{
ResponseBuffer = MakeSharedBufferFromArray ( MoveTemp ( ByteArray ) ) ;
bIsSuccessfulResponse = true ;
break ;
}
}
if ( ! bIsSuccessfulResponse )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with missing package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
if ( ValidateCompactBinary ( ResponseBuffer , ECbValidateMode : : Default ) ! = ECbValidateError : : None )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
FCbPackage Package ;
Package . SetObject ( FCbObject ( ResponseBuffer ) ) ;
const FCbObjectView Object = Package . GetObject ( ) ;
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' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
OutValue = FValue ( RawHash , RawSize ) ;
return true ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : GetCacheValue (
2022-02-02 07:35:19 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const ECachePolicy Policy ,
FValue & OutValue )
{
if ( GetCacheValueOnly ( Name , Key , Policy , OutValue ) )
{
if ( IsValueDataReady ( OutValue , Policy ) )
{
return true ;
}
2022-01-14 10:53:17 -05:00
if ( EnumHasAnyFlags ( Policy , ECachePolicy : : SkipData ) )
{
2022-02-08 01:56:17 -05:00
if ( CachedDataProbablyExistsBatch < FValue > ( Name , Key , { OutValue } , [ ] ( const FValue & Value ) { return FString ( TEXT ( " Default " ) ) ; } ) )
2022-02-02 07:35:19 -05:00
{
return true ;
}
}
else
{
TArray < FCompressedBuffer > Buffers ;
2022-02-08 01:56:17 -05:00
if ( TryGetCachedDataBatch < FValue > ( Name , Key , { OutValue } , Buffers , [ ] ( const FValue & Value ) { return FString ( TEXT ( " Default " ) ) ; } ) )
2022-02-02 07:35:19 -05:00
{
check ( Buffers . Num ( ) = = 1 ) ;
OutValue = FValue ( MoveTemp ( Buffers [ 0 ] ) ) ;
return true ;
}
2022-01-14 10:53:17 -05:00
}
}
return false ;
2022-02-02 07:35:19 -05:00
}
2022-01-14 10:53:17 -05:00
2022-02-14 14:43:39 -05:00
FOptionalCacheRecord FHttpCacheStore : : GetCacheRecord (
2022-01-14 10:53:17 -05:00
const FStringView Name ,
2022-01-10 13:43:40 -05:00
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
EStatus & OutStatus )
{
2022-01-14 10:53:17 -05:00
FOptionalCacheRecord Record = GetCacheRecordOnly ( Name , Key , Policy ) ;
2022-01-10 13:43:40 -05:00
if ( Record . IsNull ( ) )
{
OutStatus = EStatus : : Error ;
return Record ;
}
OutStatus = EStatus : : Ok ;
FCacheRecordBuilder RecordBuilder ( Key ) ;
if ( ! EnumHasAnyFlags ( Policy . GetRecordPolicy ( ) , ECachePolicy : : SkipMeta ) )
{
RecordBuilder . SetMeta ( FCbObject ( Record . Get ( ) . GetMeta ( ) ) ) ;
}
// TODO: There is not currently a batched GET endpoint for Jupiter. Once there is, all payload data should be fetched in one call.
// In the meantime, we try to keep the code structured in a way that is friendly to future batching of GETs.
TArray < FValueWithId > RequiredGets ;
TArray < FValueWithId > RequiredHeads ;
for ( FValueWithId Value : Record . Get ( ) . GetValues ( ) )
{
const ECachePolicy ValuePolicy = Policy . GetValuePolicy ( Value . GetId ( ) ) ;
2022-01-14 10:53:17 -05:00
if ( IsValueDataReady ( Value , ValuePolicy ) )
2022-01-10 13:43:40 -05:00
{
RecordBuilder . AddValue ( MoveTemp ( Value ) ) ;
}
else
{
2022-01-14 10:53:17 -05:00
if ( EnumHasAnyFlags ( ValuePolicy , ECachePolicy : : SkipData ) )
2022-01-10 13:43:40 -05:00
{
RequiredHeads . Emplace ( Value ) ;
}
else
{
RequiredGets . Emplace ( Value ) ;
}
}
}
2022-02-08 01:56:17 -05:00
if ( ! CachedDataProbablyExistsBatch < FValueWithId > ( Name , Key , RequiredHeads , [ ] ( const FValueWithId & Value ) { return WriteToString < 16 > ( Value . GetId ( ) ) ; } ) )
2022-01-10 13:43:40 -05:00
{
OutStatus = EStatus : : Error ;
return FOptionalCacheRecord ( ) ;
}
TArray < FCompressedBuffer > FetchedBuffers ;
2022-02-08 01:56:17 -05:00
if ( ! TryGetCachedDataBatch < FValueWithId > ( Name , Key , RequiredGets , FetchedBuffers , [ ] ( const FValueWithId & Value ) { return WriteToString < 16 > ( Value . GetId ( ) ) ; } ) )
2022-01-10 13:43:40 -05:00
{
OutStatus = EStatus : : Error ;
return FOptionalCacheRecord ( ) ;
}
for ( int32 Index = 0 ; Index < RequiredHeads . Num ( ) ; + + Index )
{
RecordBuilder . AddValue ( RequiredHeads [ Index ] . RemoveData ( ) ) ;
}
for ( int32 Index = 0 ; Index < RequiredGets . Num ( ) ; + + Index )
{
RecordBuilder . AddValue ( FValueWithId ( RequiredGets [ Index ] . GetId ( ) , FetchedBuffers [ Index ] ) ) ;
}
return RecordBuilder . Build ( ) ;
}
2022-02-02 07:35:19 -05:00
template < typename ValueType , typename ValuePrinterType >
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : TryGetCachedDataBatch (
2022-01-14 10:53:17 -05:00
const FStringView Name ,
2022-01-10 13:43:40 -05:00
const FCacheKey & Key ,
2022-02-02 07:35:19 -05:00
TConstArrayView < ValueType > Values ,
TArray < FCompressedBuffer > & OutBuffers ,
ValuePrinterType ValuePrinter )
2022-01-10 13:43:40 -05:00
{
2022-02-02 07:35:19 -05:00
for ( const ValueType & Value : Values )
2022-01-10 13:43:40 -05:00
{
const FIoHash & RawHash = Value . GetRawHash ( ) ;
TStringBuilder < 256 > CompressedBlobsUri ;
CompressedBlobsUri < < " api/v1/compressed-blobs/ " < < StructuredNamespace < < " / " < < RawHash ;
bool bHit = false ;
FCompressedBuffer CompressedBuffer ;
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
TArray < uint8 > ByteArray ;
2022-01-26 00:46:18 -05:00
const FHttpRequest : : Result Result = Request - > PerformBlockingDownload ( * CompressedBlobsUri , & ByteArray , { 404 } ) ;
2022-01-10 13:43:40 -05:00
ResponseCode = Request - > GetResponseCode ( ) ;
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
{
CompressedBuffer = FCompressedBuffer : : FromCompressed ( MakeSharedBufferFromArray ( MoveTemp ( ByteArray ) ) ) ;
bHit = true ;
break ;
}
}
if ( ! bHit )
{
UE_LOG ( LogDerivedDataCache , Verbose ,
TEXT ( " %s: Cache miss with missing value %s with hash %s for %s from '%.*s' " ) ,
2022-02-08 01:56:17 -05:00
* GetName ( ) , * ValuePrinter ( Value ) , * WriteToString < 48 > ( RawHash ) , * WriteToString < 96 > ( Key ) ,
2022-01-14 10:53:17 -05:00
Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return false ;
}
else if ( CompressedBuffer . GetRawHash ( ) ! = RawHash )
{
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: Cache miss with corrupted value %s with hash %s for %s from '%.*s' " ) ,
2022-02-08 01:56:17 -05:00
* GetName ( ) , * ValuePrinter ( Value ) , * WriteToString < 48 > ( RawHash ) ,
2022-01-14 10:53:17 -05:00
* WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return false ;
}
else
{
OutBuffers . Add ( CompressedBuffer ) ;
}
}
return true ;
}
2022-02-02 07:35:19 -05:00
template < typename ValueType , typename ValuePrinterType >
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : CachedDataProbablyExistsBatch (
2022-01-14 10:53:17 -05:00
const FStringView Name ,
2022-01-10 13:43:40 -05:00
const FCacheKey & Key ,
2022-02-02 07:35:19 -05:00
TConstArrayView < ValueType > Values ,
ValuePrinterType ValuePrinter )
2022-01-10 13:43:40 -05:00
{
2022-02-02 07:35:19 -05:00
for ( const ValueType & Value : Values )
2022-01-10 13:43:40 -05:00
{
const FIoHash & RawHash = Value . GetRawHash ( ) ;
TStringBuilder < 256 > CompressedBlobsUri ;
CompressedBlobsUri < < " api/v1/compressed-blobs/ " < < StructuredNamespace < < " / " < < RawHash ;
bool bIsHit = false ;
int64 ResponseCode = 0 ;
for ( uint32 Attempts = 0 ; ( Attempts < UE_HTTPDDC_MAX_ATTEMPTS ) & & ! ShouldAbortForShutdown ( ) & & ( Attempts = = 0 | | ShouldRetryOnError ( ResponseCode ) ) ; + + Attempts )
{
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
2022-01-26 00:46:18 -05:00
const FHttpRequest : : Result Result = Request - > PerformBlockingQuery < FHttpRequest : : Head > ( * CompressedBlobsUri , { 404 } ) ;
2022-01-10 13:43:40 -05:00
ResponseCode = Request - > GetResponseCode ( ) ;
2022-01-26 00:46:18 -05:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) | | ResponseCode = = 404 )
2022-01-10 13:43:40 -05:00
{
bIsHit = ( Result = = FHttpRequest : : Success & & FHttpRequest : : IsSuccessResponse ( ResponseCode ) ) ;
break ;
}
}
if ( ! bIsHit )
{
UE_LOG ( LogDerivedDataCache , Verbose ,
TEXT ( " %s: Cache miss with missing value %s with hash %s for %s from '%.*s' " ) ,
2022-02-08 01:56:17 -05:00
* GetName ( ) , * ValuePrinter ( Value ) , * WriteToString < 48 > ( RawHash ) , * WriteToString < 96 > ( Key ) ,
2022-01-14 10:53:17 -05:00
Name . Len ( ) , Name . GetData ( ) ) ;
2022-01-10 13:43:40 -05:00
return false ;
}
}
return true ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : CachedDataProbablyExists ( const TCHAR * CacheKey )
2020-06-23 18:40:00 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_Exist ) ;
TRACE_COUNTER_ADD ( HttpDDC_Exist , int64 ( 1 ) ) ;
COOK_STAT ( auto Timer = UsageStats . TimeProbablyExists ( ) ) ;
2021-03-04 09:35:03 -04:00
2021-11-07 23:43:01 -05:00
if ( ShouldSimulateMiss ( CacheKey ) )
{
return false ;
}
2021-03-04 09:35:03 -04:00
# if WITH_DATAREQUEST_HELPER
// Retry request until we get an accepted response or exhaust allowed number of attempts.
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
{
2021-03-17 04:27:28 -04:00
FDataRequestHelper RequestHelper ( GetRequestPools [ IsInGameThread ( ) ] . Get ( ) , * Namespace , * DefaultBucket , CacheKey , nullptr ) ;
2021-03-04 09:35:03 -04:00
const int64 ResponseCode = RequestHelper . GetResponseCode ( ) ;
2021-04-28 16:22:18 -04:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) & & RequestHelper . IsSuccess ( ) )
2021-03-04 09:35:03 -04:00
{
COOK_STAT ( Timer . AddHit ( 0 ) ) ;
return true ;
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
return false ;
}
}
# else
2020-06-23 18:40:00 -04:00
FString Uri = FString : : Printf ( TEXT ( " api/v1/c/ddc/%s/%s/%s " ) , * Namespace , * DefaultBucket , CacheKey ) ;
// Retry request until we get an accepted response or exhaust allowed number of attempts.
2021-01-11 11:34:27 -04:00
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
2020-06-23 18:40:00 -04:00
{
2021-03-17 04:27:28 -04:00
FScopedRequestPtr Request ( GetRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
2021-04-28 16:22:18 -04:00
const FHttpRequest : : Result Result = Request - > PerformBlockingQuery < FHttpRequest : : Head > ( * Uri ) ;
2021-01-11 11:34:27 -04:00
const int64 ResponseCode = Request - > GetResponseCode ( ) ;
2020-06-23 18:40:00 -04:00
2021-04-28 16:22:18 -04:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) | | ResponseCode = = 400 )
2020-06-23 18:40:00 -04:00
{
2021-04-28 16:22:18 -04:00
const bool bIsHit = ( Result = = FHttpRequest : : Success & & FHttpRequest : : IsSuccessResponse ( ResponseCode ) ) ;
2020-06-23 18:40:00 -04:00
if ( bIsHit )
{
TRACE_COUNTER_ADD ( HttpDDC_ExistHit , int64 ( 1 ) ) ;
2021-01-11 11:34:27 -04:00
COOK_STAT ( Timer . AddHit ( 0 ) ) ;
2020-06-23 18:40:00 -04:00
}
return bIsHit ;
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
2021-01-11 11:34:27 -04:00
break ;
2020-06-23 18:40:00 -04:00
}
}
2021-03-04 09:35:03 -04:00
# endif
2020-06-23 18:40:00 -04:00
return false ;
}
2022-02-14 14:43:39 -05:00
TBitArray < > FHttpCacheStore : : CachedDataProbablyExistsBatch ( TConstArrayView < FString > CacheKeys )
2021-01-11 11:34:27 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_Exist ) ;
TRACE_COUNTER_ADD ( HttpDDC_Exist , int64 ( 1 ) ) ;
COOK_STAT ( auto Timer = UsageStats . TimeProbablyExists ( ) ) ;
2021-03-04 09:35:03 -04:00
# if WITH_DATAREQUEST_HELPER
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
{
2021-03-17 04:27:28 -04:00
FDataRequestHelper RequestHelper ( GetRequestPools [ IsInGameThread ( ) ] . Get ( ) , * Namespace , * DefaultBucket , CacheKeys ) ;
2021-03-04 09:35:03 -04:00
const int64 ResponseCode = RequestHelper . GetResponseCode ( ) ;
2021-01-11 11:34:27 -04:00
2021-04-28 16:22:18 -04:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) & & RequestHelper . IsSuccess ( ) )
2021-03-04 09:35:03 -04:00
{
COOK_STAT ( Timer . AddHit ( 0 ) ) ;
2021-11-07 23:43:01 -05:00
TBitArray < > Results = RequestHelper . IsBatchSuccess ( ) ;
int32 ResultIndex = 0 ;
for ( const FString & CacheKey : CacheKeys )
{
if ( ShouldSimulateMiss ( * CacheKey ) )
{
Results [ ResultIndex ] = false ;
}
ResultIndex + + ;
}
return Results ;
2021-03-04 09:35:03 -04:00
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
2021-11-07 23:43:01 -05:00
TBitArray < > Results = RequestHelper . IsBatchSuccess ( ) ;
int32 ResultIndex = 0 ;
for ( const FString & CacheKey : CacheKeys )
{
if ( ShouldSimulateMiss ( * CacheKey ) )
{
Results [ ResultIndex ] = false ;
}
ResultIndex + + ;
}
return Results ;
2021-03-04 09:35:03 -04:00
}
}
# else
2021-01-11 11:34:27 -04:00
const TCHAR * const Uri = TEXT ( " api/v1/c/ddc-rpc " ) ;
TAnsiStringBuilder < 512 > Body ;
const FTCHARToUTF8 AnsiNamespace ( * Namespace ) ;
const FTCHARToUTF8 AnsiBucket ( * DefaultBucket ) ;
Body < < " { \" Operations \" :[ " ;
for ( const FString & CacheKey : CacheKeys )
{
Body < < " { \" Namespace \" : \" " < < AnsiNamespace . Get ( ) < < " \" , \" Bucket \" : \" " < < AnsiBucket . Get ( ) < < " \" , " ;
Body < < " \" Id \" : \" " < < FTCHARToUTF8 ( * CacheKey ) . Get ( ) < < " \" , \" Op \" : \" HEAD \" }, " ;
}
Body . RemoveSuffix ( 1 ) ;
Body < < " ]} " ;
TConstArrayView < uint8 > BodyView ( reinterpret_cast < const uint8 * > ( Body . ToString ( ) ) , Body . Len ( ) ) ;
// Retry request until we get an accepted response or exhaust allowed number of attempts.
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
{
2021-03-17 04:27:28 -04:00
FScopedRequestPtr Request ( GetRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
2021-04-28 16:22:18 -04:00
const FHttpRequest : : Result Result = Request - > PerformBlockingUpload < FHttpRequest : : PostJson > ( Uri , BodyView ) ;
2021-01-11 11:34:27 -04:00
const int64 ResponseCode = Request - > GetResponseCode ( ) ;
2021-04-28 16:22:18 -04:00
if ( Result = = FHttpRequest : : Success & & ResponseCode = = 200 )
2021-01-11 11:34:27 -04:00
{
TArray < TSharedPtr < FJsonValue > > ResponseArray = Request - > GetResponseAsJsonArray ( ) ;
2021-03-22 18:23:46 -04:00
TBitArray < > Exists ;
Exists . Reserve ( CacheKeys . Num ( ) ) ;
for ( const FString & CacheKey : CacheKeys )
2021-01-11 11:34:27 -04:00
{
2021-11-07 23:43:01 -05:00
if ( ShouldSimulateMiss ( * CacheKey ) )
{
Exists . Add ( false ) ;
}
else
{
const TSharedPtr < FJsonValue > * FoundResponse = Algo : : FindByPredicate ( ResponseArray , [ & CacheKey ] ( const TSharedPtr < FJsonValue > & Response ) {
FString Key ;
Response - > TryGetString ( Key ) ;
return Key = = CacheKey ;
} ) ;
2021-03-22 18:23:46 -04:00
2021-11-07 23:43:01 -05:00
Exists . Add ( FoundResponse ! = nullptr ) ;
}
2021-01-11 11:34:27 -04:00
}
2021-03-22 18:23:46 -04:00
if ( Exists . CountSetBits ( ) = = CacheKeys . Num ( ) )
{
TRACE_COUNTER_ADD ( HttpDDC_ExistHit , int64 ( 1 ) ) ;
COOK_STAT ( Timer . AddHit ( 0 ) ) ;
}
return Exists ;
2021-01-11 11:34:27 -04:00
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
break ;
}
}
2021-03-04 09:35:03 -04:00
# endif
2021-01-11 11:34:27 -04:00
return TBitArray < > ( false , CacheKeys . Num ( ) ) ;
}
2022-02-14 14:43:39 -05:00
bool FHttpCacheStore : : GetCachedData ( const TCHAR * CacheKey , TArray < uint8 > & OutData )
2020-06-23 18:40:00 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_Get ) ;
TRACE_COUNTER_ADD ( HttpDDC_Get , int64 ( 1 ) ) ;
COOK_STAT ( auto Timer = UsageStats . TimeGet ( ) ) ;
2021-11-07 23:43:01 -05:00
if ( ShouldSimulateMiss ( CacheKey ) )
{
return false ;
}
2021-03-04 09:35:03 -04:00
# if WITH_DATAREQUEST_HELPER
// Retry request until we get an accepted response or exhaust allowed number of attempts.
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
{
2021-03-17 04:27:28 -04:00
FDataRequestHelper RequestHelper ( GetRequestPools [ IsInGameThread ( ) ] . Get ( ) , * Namespace , * DefaultBucket , CacheKey , & OutData ) ;
2021-03-04 09:35:03 -04:00
const int64 ResponseCode = RequestHelper . GetResponseCode ( ) ;
2021-04-28 16:22:18 -04:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) & & RequestHelper . IsSuccess ( ) )
2021-03-04 09:35:03 -04:00
{
COOK_STAT ( Timer . AddHit ( OutData . Num ( ) ) ) ;
check ( OutData . Num ( ) > 0 ) ;
return true ;
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
return false ;
}
}
# else
2020-06-23 18:40:00 -04:00
FString Uri = FString : : Printf ( TEXT ( " api/v1/c/ddc/%s/%s/%s.raw " ) , * Namespace , * DefaultBucket , CacheKey ) ;
// Retry request until we get an accepted response or exhaust allowed number of attempts.
2021-03-04 09:35:03 -04:00
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
2020-06-23 18:40:00 -04:00
{
2021-03-17 04:27:28 -04:00
FScopedRequestPtr Request ( GetRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
2020-06-23 18:40:00 -04:00
if ( Request . IsValid ( ) )
{
2021-04-28 16:22:18 -04:00
FHttpRequest : : Result Result = Request - > PerformBlockingDownload ( * Uri , & OutData ) ;
2021-03-04 09:35:03 -04:00
const uint64 ResponseCode = Request - > GetResponseCode ( ) ;
2020-06-23 18:40:00 -04:00
// Request was successful, make sure we got all the expected data.
2021-11-07 23:43:01 -05:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) & & VerifyRequest ( Request . Get ( ) , * Namespace , * DefaultBucket , CacheKey , OutData ) )
2020-06-23 18:40:00 -04:00
{
TRACE_COUNTER_ADD ( HttpDDC_GetHit , int64 ( 1 ) ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , int64 ( Request - > GetBytesReceived ( ) ) ) ;
COOK_STAT ( Timer . AddHit ( Request - > GetBytesReceived ( ) ) ) ;
return true ;
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
return false ;
}
}
}
2021-03-04 09:35:03 -04:00
# endif
2020-06-23 18:40:00 -04:00
return false ;
}
2022-02-14 14:43:39 -05:00
FDerivedDataBackendInterface : : EPutStatus FHttpCacheStore : : PutCachedData ( const TCHAR * CacheKey , TArrayView < const uint8 > InData , bool bPutEvenIfExists )
2020-06-23 18:40:00 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_Put ) ;
2021-03-04 09:35:03 -04:00
2021-04-21 13:19:11 -04:00
if ( ! IsWritable ( ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s is read only. Skipping put of %s " ) , * GetName ( ) , CacheKey ) ;
return EPutStatus : : NotCached ;
}
2021-11-07 23:43:01 -05:00
// don't put anything we pretended didn't exist
if ( ShouldSimulateMiss ( CacheKey ) )
{
return EPutStatus : : Skipped ;
}
#if 0 // No longer WITH_DATAREQUEST_HELPER as async puts are unsupported except through the AsyncPutWrapper which expects the inner backend to perform the put synchronously
2021-03-04 09:35:03 -04:00
for ( int32 Attempts = 0 ; Attempts < UE_HTTPDDC_MAX_ATTEMPTS ; + + Attempts )
{
2021-03-17 04:27:28 -04:00
FDataUploadHelper Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) , * Namespace , * DefaultBucket , CacheKey , InData , UsageStats ) ;
2021-04-05 14:36:58 -04:00
if ( ShouldAbortForShutdown ( ) )
{
return EPutStatus : : NotCached ;
}
2021-03-04 09:35:03 -04:00
const int64 ResponseCode = Request . GetResponseCode ( ) ;
2021-04-28 16:22:18 -04:00
if ( Request . IsSuccess ( ) & & ( Request . IsQueued ( ) | | FHttpRequest : : IsSuccessResponse ( ResponseCode ) ) )
2021-03-04 09:35:03 -04:00
{
return Request . IsQueued ( ) ? EPutStatus : : Executing : EPutStatus : : Cached ;
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
return EPutStatus : : NotCached ;
}
}
# else
2020-06-23 18:40:00 -04:00
COOK_STAT ( auto Timer = UsageStats . TimePut ( ) ) ;
FString Uri = FString : : Printf ( TEXT ( " api/v1/c/ddc/%s/%s/%s " ) , * Namespace , * DefaultBucket , CacheKey ) ;
int64 ResponseCode = 0 ; uint32 Attempts = 0 ;
// Retry request until we get an accepted response or exhaust allowed number of attempts.
while ( ResponseCode = = 0 & & + + Attempts < UE_HTTPDDC_MAX_ATTEMPTS )
{
2021-04-06 10:20:06 -04:00
if ( ShouldAbortForShutdown ( ) )
{
return EPutStatus : : NotCached ;
}
2021-03-17 04:27:28 -04:00
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
2020-06-23 18:40:00 -04:00
if ( Request . IsValid ( ) )
{
// Append the content hash to the header
HashPayload ( Request . Get ( ) , InData ) ;
2021-11-07 23:43:01 -05:00
Request - > PerformBlockingUpload < FHttpRequest : : Put > ( * Uri , InData ) ;
2020-06-23 18:40:00 -04:00
ResponseCode = Request - > GetResponseCode ( ) ;
2021-11-07 23:43:01 -05:00
if ( FHttpRequest : : IsSuccessResponse ( ResponseCode ) )
2020-06-23 18:40:00 -04:00
{
TRACE_COUNTER_ADD ( HttpDDC_BytesSent , int64 ( Request - > GetBytesSent ( ) ) ) ;
COOK_STAT ( Timer . AddHit ( Request - > GetBytesSent ( ) ) ) ;
2021-01-06 14:18:01 -04:00
return EPutStatus : : Cached ;
2020-06-23 18:40:00 -04:00
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
2021-01-06 14:18:01 -04:00
return EPutStatus : : NotCached ;
2020-06-23 18:40:00 -04:00
}
ResponseCode = 0 ;
}
}
2021-03-04 09:35:03 -04:00
# endif // WITH_DATAREQUEST_HELPER
2021-01-06 14:18:01 -04:00
return EPutStatus : : NotCached ;
2020-06-23 18:40:00 -04:00
}
2022-02-14 14:43:39 -05:00
void FHttpCacheStore : : RemoveCachedData ( const TCHAR * CacheKey , bool bTransient )
2020-06-23 18:40:00 -04:00
{
2021-01-21 10:41:53 -04:00
// do not remove transient data as Jupiter does its own verification of the content and cleans itself up
2021-04-21 13:19:11 -04:00
if ( ! IsWritable ( ) | | bTransient )
2021-01-21 10:36:11 -04:00
return ;
2020-06-23 18:40:00 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( HttpDDC_Remove ) ;
FString Uri = FString : : Printf ( TEXT ( " api/v1/c/ddc/%s/%s/%s " ) , * Namespace , * DefaultBucket , CacheKey ) ;
int64 ResponseCode = 0 ; uint32 Attempts = 0 ;
// Retry request until we get an accepted response or exhaust allowed number of attempts.
while ( ResponseCode = = 0 & & + + Attempts < UE_HTTPDDC_MAX_ATTEMPTS )
{
2021-03-17 04:27:28 -04:00
FScopedRequestPtr Request ( PutRequestPools [ IsInGameThread ( ) ] . Get ( ) ) ;
2020-06-23 18:40:00 -04:00
if ( Request . IsValid ( ) )
{
2022-01-26 00:46:18 -05:00
FHttpRequest : : Result Result = Request - > PerformBlockingQuery < FHttpRequest : : Delete > ( * Uri , { } ) ;
2020-06-23 18:40:00 -04:00
ResponseCode = Request - > GetResponseCode ( ) ;
if ( ResponseCode = = 200 )
{
return ;
}
if ( ! ShouldRetryOnError ( ResponseCode ) )
{
return ;
}
ResponseCode = 0 ;
}
}
}
2022-02-14 14:43:39 -05:00
TSharedRef < FDerivedDataCacheStatsNode > FHttpCacheStore : : GatherUsageStats ( ) const
2020-06-23 18:40:00 -04:00
{
2021-12-16 19:56:56 -05:00
TSharedRef < FDerivedDataCacheStatsNode > Usage =
MakeShared < FDerivedDataCacheStatsNode > ( TEXT ( " Horde Storage " ) , FString : : Printf ( TEXT ( " %s (%s) " ) , * Domain , * Namespace ) , /*bIsLocal*/ false ) ;
2021-03-18 15:20:03 -04:00
Usage - > Stats . Add ( TEXT ( " " ) , UsageStats ) ;
return Usage ;
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-01-10 13:43:40 -05:00
for ( const FCachePutRequest & Request : Requests )
2021-04-28 16:22:18 -04:00
{
2022-01-10 13:43:40 -05:00
const FCacheRecord & Record = Request . Record ;
COOK_STAT ( auto Timer = UsageStats . TimePut ( ) ) ;
uint64 BytesSent = 0 ;
2022-01-14 10:53:17 -05:00
if ( PutCacheRecord ( Request . Name , Record , Request . Policy , BytesSent ) )
2021-04-28 16:22:18 -04:00
{
2022-01-10 13:43:40 -05:00
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache put complete for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Record . GetKey ( ) ) , * Request . Name ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesSent , int64 ( BytesSent ) ) ;
COOK_STAT ( Timer . AddHit ( BytesSent ) ) ;
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Ok ) ) ;
2022-01-10 13:43:40 -05:00
}
else
{
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Error ) ) ;
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-01-10 13:43:40 -05:00
for ( const FCacheGetRequest & Request : Requests )
2021-04-28 16:22:18 -04:00
{
2022-01-10 13:43:40 -05:00
COOK_STAT ( auto Timer = UsageStats . TimeGet ( ) ) ;
EStatus Status = EStatus : : Ok ;
2022-01-14 10:53:17 -05:00
if ( FOptionalCacheRecord Record = GetCacheRecord ( Request . Name , Request . Key , Request . Policy , Status ) )
2021-04-28 16:22:18 -04:00
{
2022-01-10 13:43:40 -05:00
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache hit for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key ) , * Request . Name ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , Private : : GetCacheRecordCompressedSize ( Record . Get ( ) ) ) ;
COOK_STAT ( Timer . AddHit ( Private : : GetCacheRecordCompressedSize ( Record . Get ( ) ) ) ) ;
2022-02-14 14:43:39 -05:00
OnComplete ( { Request . Name , MoveTemp ( Record ) . Get ( ) , Request . UserData , Status } ) ;
2022-01-10 13:43:40 -05:00
}
else
{
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( Status ) ) ;
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 )
{
for ( const FCachePutValueRequest & Request : Requests )
{
COOK_STAT ( auto Timer = UsageStats . TimePut ( ) ) ;
uint64 WriteSize = 0 ;
if ( PutCacheValue ( Request . Name , Request . Key , Request . Value , Request . Policy , WriteSize ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache put complete for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key ) , * Request . Name ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesSent , WriteSize ) ;
COOK_STAT ( if ( WriteSize ) { Timer . AddHit ( WriteSize ) ; } ) ;
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Ok ) ) ;
2022-02-02 07:35:19 -05:00
}
else
{
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Error ) ) ;
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 )
{
for ( const FCacheGetValueRequest & Request : Requests )
{
COOK_STAT ( auto Timer = UsageStats . TimeGet ( ) ) ;
FValue Value ;
if ( GetCacheValue ( Request . Name , Request . Key , Request . Policy , Value ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache hit for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key ) , * Request . Name ) ;
TRACE_COUNTER_ADD ( HttpDDC_BytesReceived , Value . GetData ( ) . GetCompressedSize ( ) ) ;
COOK_STAT ( Timer . AddHit ( Value . GetData ( ) . GetCompressedSize ( ) ) ) ;
2022-02-14 14:43:39 -05:00
OnComplete ( { Request . Name , Request . Key , Value , Request . UserData , EStatus : : Ok } ) ;
2022-02-02 07:35:19 -05:00
}
else
{
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Error ) ) ;
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-01-14 10:53:17 -05:00
// TODO: This is inefficient because Jupiter doesn't allow us to get only part of a compressed blob, so we have to
// get the whole thing and then decompress only the portion we need. Furthermore, because there is no propagation
// between cache stores during chunk requests, the fetched result won't end up in the local store.
// These efficiency issues will be addressed by changes to the Hierarchy that translate chunk requests that
// are missing in local/fast stores and have to be retrieved from slow stores into record requests instead. That
// will make this code path unused/uncommon as Jupiter will most always be a slow store with a local/fast store in front of it.
// Regardless, to adhere to the functional contract, this implementation must exist.
2022-01-18 04:47:59 -05:00
TArray < FCacheGetChunkRequest , TInlineAllocator < 16 > > SortedRequests ( Requests ) ;
2022-01-14 10:53:17 -05:00
SortedRequests . StableSort ( TChunkLess ( ) ) ;
bool bHasValue = false ;
FValue Value ;
FValueId ValueId ;
FCacheKey ValueKey ;
FCompressedBuffer ValueBuffer ;
FCompressedBufferReader ValueReader ;
EStatus ValueStatus = EStatus : : Error ;
FOptionalCacheRecord Record ;
2022-01-18 04:47:59 -05:00
for ( const FCacheGetChunkRequest & Request : SortedRequests )
2021-04-28 16:22:18 -04:00
{
2022-01-14 10:53:17 -05:00
const bool bExistsOnly = EnumHasAnyFlags ( Request . Policy , ECachePolicy : : SkipData ) ;
COOK_STAT ( auto Timer = bExistsOnly ? UsageStats . TimeProbablyExists ( ) : UsageStats . TimeGet ( ) ) ;
if ( ! ( bHasValue & & ValueKey = = Request . Key & & ValueId = = Request . Id ) | | ValueReader . HasSource ( ) < ! bExistsOnly )
{
ValueStatus = EStatus : : Error ;
ValueReader . ResetSource ( ) ;
ValueKey = { } ;
ValueId . Reset ( ) ;
Value . Reset ( ) ;
bHasValue = false ;
if ( Request . Id . IsValid ( ) )
{
if ( ! ( Record & & Record . Get ( ) . GetKey ( ) = = Request . Key ) )
{
FCacheRecordPolicyBuilder PolicyBuilder ( ECachePolicy : : None ) ;
PolicyBuilder . AddValuePolicy ( Request . Id , Request . Policy ) ;
Record . Reset ( ) ;
Record = GetCacheRecordOnly ( Request . Name , Request . Key , PolicyBuilder . Build ( ) ) ;
}
if ( Record )
{
const FValueWithId & ValueWithId = Record . Get ( ) . GetValue ( Request . Id ) ;
bHasValue = ValueWithId . IsValid ( ) ;
Value = ValueWithId ;
ValueId = Request . Id ;
ValueKey = Request . Key ;
if ( IsValueDataReady ( Value , Request . Policy ) )
{
ValueReader . SetSource ( Value . GetData ( ) ) ;
}
else
{
TArray < FCompressedBuffer > ValueBuffers ;
2022-02-08 01:56:17 -05:00
if ( TryGetCachedDataBatch < FValueWithId > ( Request . Name , Request . Key , : : MakeArrayView ( { ValueWithId } ) , ValueBuffers , [ ] ( const FValueWithId & Value ) { return WriteToString < 16 > ( Value . GetId ( ) ) ; } ) )
2022-01-14 10:53:17 -05:00
{
ValueBuffer = ValueBuffers [ 0 ] ;
ValueReader . SetSource ( ValueBuffer ) ;
}
else
{
ValueBuffer . Reset ( ) ;
ValueReader . ResetSource ( ) ;
}
}
}
}
else
{
2022-02-02 08:18:44 -05:00
ValueKey = Request . Key ;
bHasValue = GetCacheValueOnly ( Request . Name , Request . Key , Request . Policy , Value ) ;
if ( IsValueDataReady ( Value , Request . Policy ) )
{
ValueReader . SetSource ( Value . GetData ( ) ) ;
}
else
{
TArray < FCompressedBuffer > ValueBuffers ;
2022-02-08 01:56:17 -05:00
if ( TryGetCachedDataBatch < FValue > ( Request . Name , Request . Key , { Value } , ValueBuffers , [ ] ( const FValue & Value ) { return FString ( TEXT ( " Default " ) ) ; } ) )
2022-02-02 08:18:44 -05:00
{
ValueBuffer = ValueBuffers [ 0 ] ;
ValueReader . SetSource ( ValueBuffer ) ;
}
else
{
ValueBuffer . Reset ( ) ;
ValueReader . ResetSource ( ) ;
}
}
2022-01-14 10:53:17 -05:00
}
}
if ( bHasValue )
{
const uint64 RawOffset = FMath : : Min ( Value . GetRawSize ( ) , Request . RawOffset ) ;
const uint64 RawSize = FMath : : Min ( Value . GetRawSize ( ) - RawOffset , Request . RawSize ) ;
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache hit for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key , ' / ' , Request . Id ) , * Request . Name ) ;
COOK_STAT ( Timer . AddHit ( ! bExistsOnly ? RawSize : 0 ) ) ;
2022-02-14 14:43:39 -05:00
FSharedBuffer Buffer ;
if ( ! bExistsOnly )
2022-01-14 10:53:17 -05:00
{
2022-02-14 14:43:39 -05:00
Buffer = ValueReader . Decompress ( RawOffset , RawSize ) ;
2022-01-14 10:53:17 -05:00
}
2022-02-14 14:43:39 -05:00
const EStatus ChunkStatus = bExistsOnly | | Buffer . GetSize ( ) = = RawSize ? EStatus : : Ok : EStatus : : Error ;
OnComplete ( { Request . Name , Request . Key , Request . Id , Request . RawOffset ,
RawSize , Value . GetRawHash ( ) , MoveTemp ( Buffer ) , Request . UserData , ChunkStatus } ) ;
2022-01-14 10:53:17 -05:00
continue ;
}
2022-02-14 14:43:39 -05:00
OnComplete ( Request . MakeResponse ( EStatus : : Error ) ) ;
2021-04-28 16:22:18 -04:00
}
}
2022-02-14 14:43:39 -05:00
} // UE::DerivedData
2021-04-28 16:22:18 -04:00
2022-01-11 11:57:38 -05:00
# endif // WITH_HTTP_DDC_BACKEND
2022-02-14 14:43:39 -05:00
namespace UE : : DerivedData
2022-01-11 11:57:38 -05:00
{
2022-02-14 14:43:39 -05:00
ILegacyCacheStore * CreateHttpCacheStore (
2022-01-11 11:57:38 -05:00
const TCHAR * NodeName ,
const TCHAR * ServiceUrl ,
const TCHAR * Namespace ,
const TCHAR * StructuredNamespace ,
const TCHAR * OAuthProvider ,
const TCHAR * OAuthClientId ,
const TCHAR * OAuthData ,
const FDerivedDataBackendInterface : : ESpeedClass * ForceSpeedClass ,
2022-02-14 14:43:39 -05:00
EBackendLegacyMode LegacyMode ,
2022-01-11 11:57:38 -05:00
bool bReadOnly )
{
# if WITH_HTTP_DDC_BACKEND
2022-02-14 14:43:39 -05:00
FHttpCacheStore * Backend = new FHttpCacheStore ( ServiceUrl , Namespace , StructuredNamespace , OAuthProvider , OAuthClientId , OAuthData , LegacyMode , bReadOnly ) ;
2022-01-11 11:57:38 -05:00
if ( Backend - > IsUsable ( ) )
{
return Backend ;
}
2022-01-13 10:04:43 -05:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Node %s could not contact the service (%s), will not use it " ) , NodeName , ServiceUrl ) ;
2022-01-11 11:57:38 -05:00
delete Backend ;
return nullptr ;
# else
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " HTTP backend is not yet supported in the current build configuration. " ) ) ;
return nullptr ;
# endif
}
2022-02-14 14:43:39 -05:00
FDerivedDataBackendInterface * GetAnyHttpCacheStore (
2022-01-11 11:57:38 -05:00
FString & OutDomain ,
FString & OutOAuthProvider ,
FString & OutOAuthClientId ,
FString & OutOAuthSecret ,
FString & OutNamespace ,
FString & OutStructuredNamespace )
{
# if WITH_HTTP_DDC_BACKEND
2022-02-14 14:43:39 -05:00
if ( FHttpCacheStore * HttpBackend = FHttpCacheStore : : GetAny ( ) )
2022-01-14 10:53:17 -05:00
{
OutDomain = HttpBackend - > GetDomain ( ) ;
OutOAuthProvider = HttpBackend - > GetOAuthProvider ( ) ;
OutOAuthClientId = HttpBackend - > GetOAuthClientId ( ) ;
OutOAuthSecret = HttpBackend - > GetOAuthSecret ( ) ;
OutNamespace = HttpBackend - > GetNamespace ( ) ;
OutStructuredNamespace = HttpBackend - > GetStructuredNamespace ( ) ;
return HttpBackend ;
}
return nullptr ;
2022-01-11 11:57:38 -05:00
# else
return nullptr ;
# endif
}
2022-02-14 14:43:39 -05:00
} // UE::DerivedData