Files
UnrealEngine/Engine/Source/Runtime/Experimental/IoStore/OnDemand/Private/IasHostGroup.cpp
2025-05-18 13:04:45 +08:00

397 lines
8.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "IasHostGroup.h"
#include "Algo/Find.h"
#include "Async/UniqueLock.h"
#include "HAL/IConsoleManager.h"
#include "IO/IoStoreOnDemand.h"
#include "IO/OnDemandHostGroup.h"
#include "LatencyTesting.h"
#include "Logging/StructuredLog.h"
#include "Statistics.h"
namespace UE::IoStore
{
int32 GIasHttpErrorSampleCount = 8;
static FAutoConsoleVariableRef CVar_IasHttpErrorSampleCount(
TEXT("ias.HttpErrorSampleCount"),
GIasHttpErrorSampleCount,
TEXT("Number of samples for computing the moving average of failed HTTP requests")
);
float GIasHttpErrorHighWater = 0.5f;
static FAutoConsoleVariableRef CVar_IasHttpErrorHighWater(
TEXT("ias.HttpErrorHighWater"),
GIasHttpErrorHighWater,
TEXT("High water mark when HTTP streaming will be disabled")
);
struct FBitWindow
{
void Reset()
{
Reset(Bits.Num());
}
void Reset(uint32 Count)
{
Count = FMath::RoundUpToPowerOfTwo(Count);
Bits.SetNum(int32(Count), false);
Counter = 0;
Mask = Count - 1;
}
void Add(bool bValue)
{
const uint32 Idx = Counter++ & Mask;
Bits[Idx] = bValue;
}
float AvgSetBits() const
{
return float(Bits.CountSetBits()) / float(Bits.Num());
}
private:
TBitArray<> Bits;
uint32 Counter = 0;
uint32 Mask = 0;
};
struct FIASHostGroup::FImpl
{
FImpl() = default;
FImpl(FName InName, FAnsiStringView InTestPath)
: TestPath(InTestPath)
, Name(InName)
{
}
FImpl(FName InName, FOnDemandHostGroup& InHostGroup)
: HostGroup(InHostGroup)
, Name(InName)
{
}
~FImpl() = default;
void Reset(FOnDemandHostGroup&& InHostGroup)
{
HostGroup = MoveTemp(InHostGroup);
HttpErrorHistory.Reset(GIasHttpErrorSampleCount);
bHttpEnabled = true;
}
FOnDemandHostGroup HostGroup;
FBitWindow HttpErrorHistory;
FAnsiString TestPath;
FName Name;
bool bHttpEnabled = true;
};
TIoStatusOr<FIASHostGroup> FIASHostGroup::Create(FName Name, TConstArrayView<FAnsiString> HostUrls)
{
TIoStatusOr<FOnDemandHostGroup> Result = FOnDemandHostGroup::Create(HostUrls);
if (!Result.IsOk())
{
return Result.Status();
}
FIASHostGroup HostGroup(Name, Result.ConsumeValueOrDie());
HostGroup.Impl->HttpErrorHistory.Reset(GIasHttpErrorSampleCount);
return HostGroup;
}
TIoStatusOr<FIASHostGroup> FIASHostGroup::Create(FName Name, TConstArrayView<FString> HostUrls)
{
TIoStatusOr<FOnDemandHostGroup> Result = FOnDemandHostGroup::Create(HostUrls);
if (!Result.IsOk())
{
return Result.Status();
}
FIASHostGroup HostGroup(Name, Result.ConsumeValueOrDie());
HostGroup.Impl->HttpErrorHistory.Reset(GIasHttpErrorSampleCount);
return HostGroup;
}
FIASHostGroup::FIASHostGroup()
: Impl(MakeShared<FImpl>())
{
}
FIASHostGroup::FIASHostGroup(FName Name, FAnsiStringView TestPath)
: Impl(MakeShared<FImpl>(Name, TestPath))
{
}
FIASHostGroup::FIASHostGroup(FName Name, FOnDemandHostGroup&& HostGroup)
: Impl(MakeShared<FImpl>(Name, HostGroup))
{
}
FIASHostGroup::FIASHostGroup(FIASHostGroup::FSharedImpl&& InImpl)
: Impl(MoveTemp(InImpl))
{
}
FName FIASHostGroup::GetName() const
{
return Impl->Name;
}
const FAnsiString& FIASHostGroup::GetTestPath() const
{
return Impl->TestPath;
}
bool FIASHostGroup::IsResolved() const
{
return !Impl->HostGroup.IsEmpty();
}
bool FIASHostGroup::IsConnected() const
{
return Impl->HostGroup.PrimaryHostIndex() != INDEX_NONE;
}
FIoStatus FIASHostGroup::Resolve(TConstArrayView<FAnsiString> HostUrls)
{
if (IsResolved())
{
return FIoStatus(EIoErrorCode::InvalidCode, TEXT("Host group is already resolved"));
}
TIoStatusOr<FOnDemandHostGroup> Result = FOnDemandHostGroup::Create(HostUrls);
if (!Result.IsOk())
{
return Result.Status();
}
Impl->Reset(Result.ConsumeValueOrDie());
return FIoStatus(EIoErrorCode::Ok);
}
FIoStatus FIASHostGroup::Resolve(TConstArrayView<FString> HostUrls)
{
if (IsResolved())
{
return FIoStatus(EIoErrorCode::InvalidCode, TEXT("Host group is already resolved"));
}
TIoStatusOr<FOnDemandHostGroup> Result = FOnDemandHostGroup::Create(HostUrls);
if (!Result.IsOk())
{
return Result.Status();
}
Impl->Reset(Result.ConsumeValueOrDie());
return FIoStatus(EIoErrorCode::Ok);
}
void FIASHostGroup::Connect(int32 HostIndex)
{
Impl->bHttpEnabled = true;
Impl->HttpErrorHistory.Reset();
SetPrimaryHost(HostIndex);
}
void FIASHostGroup::Disconnect()
{
Impl->bHttpEnabled = false;
Impl->HttpErrorHistory.Reset();
SetPrimaryHost(INDEX_NONE);
}
FIASHostGroup::EReconnectionResult FIASHostGroup::AttemptReconnection(uint32 TimeoutMs, std::atomic_bool& CancellationToken)
{
if (!IsConnected())
{
UE_LOGFMT(LogIas, Log, "[{HostName}] Trying to reconnect to any available endpoint...", GetName());
if (const int32 Idx = ConnectionTest(GetHostUrls(), Impl->TestPath, TimeoutMs, CancellationToken); Idx != INDEX_NONE)
{
Connect(Idx);
UE_LOGFMT(LogIas, Log, "[{HostName}] Successfully reconnected to '{Url}'", GetPrimaryHostUrl(), GetName());
return EReconnectionResult::Reconnected;
}
else
{
return EReconnectionResult::FailedToConnect;
}
}
else if (GetPrimaryHostIndex() != 0)
{
if (const int32 Idx = ConnectionTest(GetHostUrls().Left(1), Impl->TestPath, TimeoutMs, CancellationToken); Idx != INDEX_NONE)
{
SetPrimaryHost(Idx);
UE_LOGFMT(LogIas, Log, "[{HostName}] Reconnected to primary host '{Url}'", GetPrimaryHostUrl(), GetName());
}
}
return EReconnectionResult::AlreadyConnected;
}
void FIASHostGroup::OnSuccessfulResponse()
{
Impl->HttpErrorHistory.Add(false);
}
bool FIASHostGroup::OnFailedResponse()
{
Impl->HttpErrorHistory.Add(true);
const float Average = Impl->HttpErrorHistory.AvgSetBits();
const bool bAboveHighWaterMark = Average > GIasHttpErrorHighWater;
UE_LOG(LogIas, Log, TEXT("[%s] %.2f%% the last %d HTTP requests failed"), *Impl->Name.ToString(), Average * 100.0f, GIasHttpErrorSampleCount);
if (bAboveHighWaterMark && IsConnected())
{
Disconnect();
UE_LOG(LogIas, Warning, TEXT("[%s] Host group disabled due to high water mark of %.2f of the last %d requests reached"),
*Impl->Name.ToString(), GIasHttpErrorHighWater * 100.0f, GIasHttpErrorSampleCount);
return true;
}
return false;
}
const FOnDemandHostGroup& FIASHostGroup::GetUnderlyingHostGroup() const
{
return Impl->HostGroup;
}
void FIASHostGroup::SetPrimaryHost(int32 Index) const
{
Impl->HostGroup.SetPrimaryHost(Index);
}
FAnsiStringView FIASHostGroup::GetPrimaryHostUrl() const
{
return Impl->HostGroup.PrimaryHost();
}
int32 FIASHostGroup::GetPrimaryHostIndex() const
{
return Impl->HostGroup.PrimaryHostIndex();
}
TConstArrayView<FAnsiString> FIASHostGroup::GetHostUrls() const
{
return Impl->HostGroup.Hosts();
}
FHostGroupManager& FHostGroupManager::Get()
{
static FHostGroupManager Instance;
return Instance;
}
TIoStatusOr<FIASHostGroup> FHostGroupManager::Register(FName Name, FAnsiStringView TestPath)
{
FIASHostGroup HostGroup(Name, TestPath);
{
UE::TUniqueLock _(Mutex);
HostGroups.Add(HostGroup);
}
return MoveTemp(HostGroup);
}
TIoStatusOr<FIASHostGroup> FHostGroupManager::Register(FName Name, TConstArrayView<FAnsiString> HostUrls)
{
TIoStatusOr<FIASHostGroup> Result = FIASHostGroup::Create(Name, HostUrls);
if (Result.IsOk())
{
UE::TUniqueLock _(Mutex);
HostGroups.Add(Result.ValueOrDie());
}
return Result;
}
FIASHostGroup FHostGroupManager::Find(FName Name)
{
UE::TUniqueLock _(Mutex);
FIASHostGroup* FoundHostGroup = Algo::FindByPredicate(HostGroups, [Name](const FIASHostGroup& HostGroup)->bool
{
return HostGroup.GetName() == Name;
});
if (FoundHostGroup != nullptr)
{
return *FoundHostGroup;
}
else
{
return FIASHostGroup();
}
}
void FHostGroupManager::ForEachHostGroup(TFunctionRef<void(const FIASHostGroup&)> Callback) const
{
UE::TUniqueLock _(Mutex);
for (const FIASHostGroup& Host : HostGroups)
{
Callback(Host);
}
}
void FHostGroupManager::Tick(uint32 TimeoutMs, std::atomic_bool& CancellationToken)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FHostGroupManager::Tick);
UE::TUniqueLock _(Mutex);
for (FIASHostGroup& HostGroup : HostGroups)
{
if (HostGroup.AttemptReconnection(TimeoutMs, CancellationToken) == FIASHostGroup::EReconnectionResult::Reconnected)
{
FOnDemandIoBackendStats::Get()->OnHttpConnected(); // TODO: Try to avoid singleton access somehow
}
}
}
void FHostGroupManager::DisconnectAll()
{
UE::TUniqueLock _(Mutex);
for (FIASHostGroup& HostGroup : HostGroups)
{
HostGroup.Disconnect();
}
}
uint32 FHostGroupManager::GetNumDisconnctedHosts() const
{
uint32 NumDisconnectedHosts = 0;
UE::TUniqueLock _(Mutex);
for (const FIASHostGroup& HostGroup : HostGroups)
{
NumDisconnectedHosts += HostGroup.IsConnected() == false ? 1 : 0;
}
return NumDisconnectedHosts;
}
} //namespace UE::IoStore