2021-08-04 14:56:02 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-08-05 02:27:45 -04:00
2022-12-09 16:11:41 -05:00
# include "Experimental/ZenServerInterface.h"
2021-08-05 02:27:45 -04:00
2021-08-04 14:56:02 -04:00
# include "ZenBackendUtils.h"
2021-10-12 21:21:22 -04:00
# include "ZenSerialization.h"
2022-03-01 07:20:31 -05:00
# include "ZenServerHttp.h"
2024-10-01 19:30:59 -04:00
# include "ZenServerState.h"
# include "ZenVersion.h"
2021-08-04 14:56:02 -04:00
2023-01-11 14:28:01 -05:00
# include "AnalyticsEventAttribute.h"
2022-12-06 14:37:09 -05:00
# include "Async/Async.h"
2024-01-09 10:20:00 -05:00
# include "Async/UniqueLock.h"
2024-02-09 04:44:22 -05:00
# include "Containers/AnsiString.h"
2021-11-18 14:37:34 -05:00
# include "Dom/JsonValue.h"
2021-09-22 13:00:17 -04:00
# include "HAL/FileManager.h"
# include "HAL/Platform.h"
# include "HAL/PlatformMisc.h"
2022-01-12 12:05:55 -05:00
# include "HAL/PlatformProcess.h"
2021-11-07 23:43:01 -05:00
# include "HAL/PlatformTime.h"
2021-09-22 13:00:17 -04:00
# include "Logging/LogScopedCategoryAndVerbosityOverride.h"
# include "Misc/App.h"
# include "Misc/ConfigCacheIni.h"
2024-02-08 13:18:53 -05:00
# include "Misc/ConfigContext.h"
2022-01-12 12:05:55 -05:00
# include "Misc/FileHelper.h"
2022-12-06 14:37:09 -05:00
# include "Misc/MonitoredProcess.h"
2021-08-05 22:30:50 -04:00
# include "Misc/Paths.h"
2021-10-25 20:05:28 -04:00
# include "Misc/PathViews.h"
2021-11-07 23:43:01 -05:00
# include "Misc/ScopedSlowTask.h"
# include "Misc/ScopeExit.h"
# include "Serialization/CompactBinarySerialization.h"
2022-12-06 14:37:09 -05:00
# include "Serialization/JsonReader.h"
# include "Serialization/JsonSerializer.h"
# include "Serialization/JsonWriter.h"
# include "String/LexFromString.h"
2023-03-14 06:43:23 -04:00
# include "Serialization/CompactBinaryWriter.h"
2021-09-22 13:00:17 -04:00
# if PLATFORM_WINDOWS
# include "Windows / AllowWindowsPlatformTypes.h"
# include <Windows.h>
# include "Windows / HideWindowsPlatformTypes.h"
# endif
2021-08-04 14:56:02 -04:00
2021-11-18 14:37:34 -05:00
# define ALLOW_SETTINGS_OVERRIDE_FROM_COMMANDLINE (UE_SERVER || !(UE_BUILD_SHIPPING))
namespace UE : : Zen
{
DEFINE_LOG_CATEGORY_STATIC ( LogZenServiceInstance , Log , All ) ;
2024-10-01 20:41:28 -04:00
struct FZenServiceLink
{
FString ServicePath ;
FString UtilityPath ;
FZenVersion Version ;
operator bool ( ) const
{
return ! ServicePath . IsEmpty ( ) & & ! UtilityPath . IsEmpty ( ) & & Version ;
}
static FZenServiceLink Read ( const FString & Filename )
{
FString JsonText ;
if ( FFileHelper : : LoadFileToString ( JsonText , * Filename ) )
{
TSharedPtr < FJsonObject > JsonObject ;
TSharedRef < TJsonReader < TCHAR > > Reader = TJsonReaderFactory < TCHAR > : : Create ( JsonText ) ;
if ( FJsonSerializer : : Deserialize ( Reader , JsonObject ) & & JsonObject . IsValid ( ) )
{
FString ServicePath = JsonObject - > Values . FindRef ( TEXT ( " ServicePath " ) ) - > AsString ( ) ;
FString UtilityPath = JsonObject - > Values . FindRef ( TEXT ( " UtilityPath " ) ) - > AsString ( ) ;
auto VersionObject = JsonObject - > Values . FindRef ( TEXT ( " Version " ) ) - > AsObject ( ) ;
if ( VersionObject )
{
uint32_t MajorVersion = static_cast < uint32_t > ( VersionObject - > TryGetField ( TEXT ( " Major " ) ) - > AsNumber ( ) ) ;
uint32_t MinorVersion = static_cast < uint32_t > ( VersionObject - > TryGetField ( TEXT ( " Minor " ) ) - > AsNumber ( ) ) ;
uint32_t PatchVersion = static_cast < uint32_t > ( VersionObject - > TryGetField ( TEXT ( " Patch " ) ) - > AsNumber ( ) ) ;
FString Details = VersionObject - > TryGetField ( TEXT ( " Details " ) ) - > AsString ( ) ;
return FZenServiceLink {
. ServicePath = ServicePath ,
. UtilityPath = UtilityPath ,
. Version = FZenVersion {
. MajorVersion = MajorVersion ,
. MinorVersion = MinorVersion ,
. PatchVersion = PatchVersion ,
. Details = Details }
} ;
}
}
}
return { } ;
}
static bool Write ( const FZenServiceLink & Link , const FString & Filename )
{
FString JsonTcharText ;
TSharedRef < TJsonWriter < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > > Writer = TJsonWriterFactory < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > : : Create ( & JsonTcharText ) ;
Writer - > WriteObjectStart ( ) ;
Writer - > WriteValue ( TEXT ( " ServicePath " ) , Link . ServicePath ) ;
Writer - > WriteValue ( TEXT ( " UtilityPath " ) , Link . UtilityPath ) ;
Writer - > WriteObjectStart ( TEXT ( " Version " ) ) ;
Writer - > WriteValue ( TEXT ( " Major " ) , Link . Version . MajorVersion ) ;
Writer - > WriteValue ( TEXT ( " Minor " ) , Link . Version . MinorVersion ) ;
Writer - > WriteValue ( TEXT ( " Patch " ) , Link . Version . PatchVersion ) ;
Writer - > WriteValue ( TEXT ( " Details " ) , Link . Version . Details ) ;
Writer - > WriteObjectEnd ( ) ;
Writer - > WriteObjectEnd ( ) ;
Writer - > Close ( ) ;
if ( ! FFileHelper : : SaveStringToFile ( JsonTcharText , * Filename ) )
{
return false ;
}
return true ;
}
} ;
static FString
GetLocalZenRootPath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( FPlatformProcess : : UserSettingsDir ( ) , * FApp : : GetEpicProductIdentifier ( ) , TEXT ( " Common " ) ) + TEXT ( " / " ) ) ;
}
static FString
GetServiceExecutableName ( )
{
return
# if PLATFORM_WINDOWS
TEXT ( " zenserver.exe " )
# else
TEXT ( " zenserver " )
# endif
;
}
static FString
GetUtilityExecutableName ( )
{
return
# if PLATFORM_WINDOWS
TEXT ( " zen.exe " )
# else
TEXT ( " zen " )
# endif
;
}
static FString
GetLocalInstallPath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( GetLocalZenRootPath ( ) , TEXT ( " Zen \\ Install " ) ) ) ;
}
static FString
GetServiceLinkPath ( )
{
return FPaths : : Combine ( GetLocalInstallPath ( ) , TEXT ( " zen.link " ) ) ;
}
static FString
GetServiceCopyInstallPath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( GetLocalInstallPath ( ) , GetServiceExecutableName ( ) ) ) ;
}
static FString
GetUtilityCopyInstallPath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( GetLocalInstallPath ( ) , GetUtilityExecutableName ( ) ) ) ;
}
static FString
GetInstallVersionCachePath ( )
{
FString InstallUtilityPath = GetUtilityCopyInstallPath ( ) ;
FString InstallVersionCache = FPaths : : SetExtension ( InstallUtilityPath , TEXT ( " version " ) ) ;
return InstallVersionCache ;
}
static FString
GetInTreeVersionCache ( )
{
return FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( FPaths : : EngineSavedDir ( ) , TEXT ( " Zen " ) , TEXT ( " zen.version " ) ) ) ;
}
static FString
GetServiceRunContextPath ( )
{
return FPaths : : SetExtension ( GetServiceCopyInstallPath ( ) , TEXT ( " .runcontext " ) ) ;
}
static FString
GetInTreeUtilityPath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPlatformProcess : : GenerateApplicationPath ( TEXT ( " zen " ) , EBuildConfiguration : : Development ) ) ;
}
static FString
GetInTreeServicePath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPlatformProcess : : GenerateApplicationPath ( TEXT ( " zenserver " ) , EBuildConfiguration : : Development ) ) ;
}
static FString
GetInTreeCrashpadHandlerFilePath ( )
{
return FPaths : : ConvertRelativePathToFull ( FPlatformProcess : : GenerateApplicationPath ( TEXT ( " crashpad_handler " ) , EBuildConfiguration : : Development ) ) ;
}
static FString
GetInstallCrashpadHandlerFilePath ( const FString & InTreePath )
{
return FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( GetLocalInstallPath ( ) , FString ( FPathViews : : GetCleanFilename ( InTreePath ) ) ) ) ;
}
static bool GetZenVersion ( const FString & UtilityPath , const FString & ServicePath , FZenVersion & OutVersion )
{
FString AbsoluteUtilityPath = FPaths : : ConvertRelativePathToFull ( UtilityPath ) ;
FMonitoredProcess MonitoredUtilityProcess ( AbsoluteUtilityPath , TEXT ( " version --detailed " ) , FPaths : : GetPath ( UtilityPath ) , true ) ;
bool bLaunched = MonitoredUtilityProcess . Launch ( ) ;
if ( ! bLaunched )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to launch zen utility to gather version data: '%s'. " ) , * UtilityPath ) ;
return false ;
}
while ( MonitoredUtilityProcess . Update ( ) )
{
FPlatformProcess : : Sleep ( 0.1f ) ;
if ( MonitoredUtilityProcess . GetDuration ( ) . GetTotalSeconds ( ) > 10 )
{
MonitoredUtilityProcess . Cancel ( true ) ;
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Cancelled launch of zen utility for gathering version data: '%s'. " ) , * UtilityPath ) ;
return false ;
}
}
FString OutputString = MonitoredUtilityProcess . GetFullOutputWithoutDelegate ( ) ;
if ( MonitoredUtilityProcess . GetReturnCode ( ) ! = 0 )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unexpected return code after launch of zen utility for gathering version data: '%s' (%d). Output: '%s' " ) , * UtilityPath , MonitoredUtilityProcess . GetReturnCode ( ) , * OutputString ) ;
return false ;
}
FString VersionOutputString = OutputString . TrimStartAndEnd ( ) ;
if ( ! OutVersion . TryParse ( * VersionOutputString ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Invalid version information after launch of zen utility for gathering version data: '%s' (`%s`) " ) , * UtilityPath , * VersionOutputString ) ;
return false ;
}
return true ;
}
2022-12-06 14:37:09 -05:00
static FZenVersion
GetZenVersion ( const FString & UtilityPath , const FString & ServicePath , const FString & VersionCachePath )
{
IFileManager & FileManager = IFileManager : : Get ( ) ;
FDateTime UtilityExecutableModificationTime = FileManager . GetTimeStamp ( * UtilityPath ) ;
FDateTime ServiceExecutableModificationTime = FileManager . GetTimeStamp ( * ServicePath ) ;
FDateTime VersionCacheModificationTime = FileManager . GetTimeStamp ( * VersionCachePath ) ;
2023-02-21 10:38:15 -05:00
bool VersionCacheIsOlderThanUtilityExecutable = VersionCacheModificationTime < UtilityExecutableModificationTime ;
bool VersionCacheIsOlderThanServerExecutable = VersionCacheModificationTime < ServiceExecutableModificationTime ;
2024-10-01 20:41:28 -04:00
bool VersionCacheIsUpToDate = ( ! VersionCacheIsOlderThanUtilityExecutable ) & & ( ! VersionCacheIsOlderThanServerExecutable ) ;
if ( VersionCacheIsUpToDate )
2022-12-06 14:37:09 -05:00
{
2024-10-01 20:41:28 -04:00
FString VersionFileContents ;
if ( FFileHelper : : LoadFileToString ( VersionFileContents , * VersionCachePath ) )
2022-12-06 14:37:09 -05:00
{
2024-10-01 20:41:28 -04:00
FZenVersion CachedVersion ;
if ( CachedVersion . TryParse ( * VersionFileContents ) )
2022-12-06 14:37:09 -05:00
{
2024-10-01 20:41:28 -04:00
return CachedVersion ;
2022-12-06 14:37:09 -05:00
}
}
}
2024-10-01 20:41:28 -04:00
auto GetFallbackVersion = [ UtilityExecutableModificationTime , ServiceExecutableModificationTime ] ( )
{
FZenVersion FallbackVersion ;
if ( UtilityExecutableModificationTime > ServiceExecutableModificationTime )
{
FallbackVersion . Details = UtilityExecutableModificationTime . ToString ( ) ;
return FallbackVersion ;
}
FallbackVersion . Details = ServiceExecutableModificationTime . ToString ( ) ;
return FallbackVersion ;
} ;
2022-12-06 14:37:09 -05:00
2024-10-01 20:41:28 -04:00
FZenVersion Version ;
if ( ! GetZenVersion ( UtilityPath , ServicePath , Version ) )
{
checkf ( false , TEXT ( " Unable to determine version using zen utility executable path: '%s'. " ) , * UtilityPath ) ;
Version = GetFallbackVersion ( ) ;
}
FFileHelper : : SaveStringToFile ( Version . ToString ( ) , * VersionCachePath ) ;
return Version ;
2022-12-06 14:37:09 -05:00
}
2023-06-14 12:24:33 -04:00
static void
PromptUserToSyncInTreeVersion ( const FString & ServerFilePath )
{
2023-10-10 18:26:11 -04:00
# if !IS_PROGRAM
2023-12-13 13:39:46 -05:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FText ZenSyncSourcePromptTitle = NSLOCTEXT ( " Zen " , " Zen_SyncSourcePromptTitle " , " Failed to launch " ) ;
2024-03-05 11:23:40 -05:00
FText ZenSyncSourcePromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_SyncSourcePromptText " , " Unreal Zen Storage Server can not verify installation. Please make sure your source installation in properly synced at '{0}' " ) , FText : : FromString ( FPaths : : GetPath ( ServerFilePath ) ) ) ;
2023-12-13 13:39:46 -05:00
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenSyncSourcePromptText . ToString ( ) , * ZenSyncSourcePromptTitle . ToString ( ) ) ;
}
else
2023-10-10 18:26:11 -04:00
# endif
2023-06-14 12:24:33 -04:00
{
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Unreal Zen Storage Server can not verify installation. Please make sure your source installation in properly synced at '%s' " ) , * FPaths : : GetPath ( ServerFilePath ) ) ;
2023-06-14 12:24:33 -04:00
}
}
2022-12-06 14:37:09 -05:00
static bool
2024-10-01 20:41:28 -04:00
IsInstallVersionOutOfDate ( const FString & InTreeUtilityPath , const FString & InstallUtilityPath , const FString & InTreeServicePath , const FString & InstallServicePath , const FString & InTreeVersionCache , const FString & InstallVersionCache )
2022-12-06 14:37:09 -05:00
{
2023-06-14 12:24:33 -04:00
IFileManager & FileManager = IFileManager : : Get ( ) ;
if ( ! FileManager . FileExists ( * InTreeUtilityPath ) | | ! FileManager . FileExists ( * InTreeServicePath ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " InTree version at '%s' is invalid " ) , * InTreeServicePath ) ;
PromptUserToSyncInTreeVersion ( InTreeServicePath ) ;
return false ;
}
2023-02-21 10:38:15 -05:00
// Always get the InTree utility path so cached version information is up to date
2024-10-01 20:41:28 -04:00
FZenVersion InTreeVersion = GetZenVersion ( InTreeUtilityPath , InTreeServicePath , InTreeVersionCache ) ;
2023-02-21 10:38:15 -05:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " InTree version at '%s' is '%s' " ) , * InTreeServicePath , * InTreeVersion . ToString ( ) ) ;
2022-12-06 14:37:09 -05:00
if ( ! FileManager . FileExists ( * InstallUtilityPath ) | | ! FileManager . FileExists ( * InstallServicePath ) )
{
2023-02-21 10:38:15 -05:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " No installation found at '%s' " ) , * InstallServicePath ) ;
2022-12-06 14:37:09 -05:00
return true ;
}
2024-10-01 20:41:28 -04:00
FZenVersion InstallVersion = GetZenVersion ( InstallUtilityPath , InstallServicePath , InstallVersionCache ) ;
2023-02-21 10:38:15 -05:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Installed version at '%s' is '%s' " ) , * InstallServicePath , * InstallVersion . ToString ( ) ) ;
2022-12-06 14:37:09 -05:00
2023-02-21 10:38:15 -05:00
if ( InstallVersion < InTreeVersion )
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Installed version at '%s' (%s) is older than '%s' (%s) " ) , * InstallServicePath , * InstallVersion . ToString ( ) , * InTreeServicePath , * InTreeVersion . ToString ( ) ) ;
return true ;
}
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " ForceZenInstall " ) ) )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Forcing install from '%s' (%s) over '%s' (%s) " ) , * InTreeServicePath , * InTreeVersion . ToString ( ) , * InstallServicePath , * InstallVersion . ToString ( ) ) ;
return true ;
}
return false ;
2022-12-06 14:37:09 -05:00
}
2021-11-18 14:37:34 -05:00
static bool
AttemptFileCopyWithRetries ( const TCHAR * Dst , const TCHAR * Src , double RetryDurationSeconds )
{
2024-10-01 20:41:28 -04:00
IFileManager & FileManager = IFileManager : : Get ( ) ;
uint32 CopyResult = FileManager . Copy ( Dst , Src , true , true , false ) ;
2021-11-18 14:37:34 -05:00
uint64 CopyWaitStartTime = FPlatformTime : : Cycles64 ( ) ;
while ( CopyResult ! = COPY_OK )
{
double CopyWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - CopyWaitStartTime ) ;
if ( CopyWaitDuration < RetryDurationSeconds )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
}
else
{
break ;
}
2024-10-01 20:41:28 -04:00
CopyResult = FileManager . Copy ( Dst , Src , true , true , false ) ;
2021-11-18 14:37:34 -05:00
}
2023-02-21 10:38:15 -05:00
if ( CopyResult = = COPY_OK )
{
return true ;
}
2023-05-26 15:52:39 -04:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " copy from '%s' to '%s', '%s' " ) , Src , Dst , CopyResult = = COPY_Fail ? TEXT ( " Failed to copy file " ) : TEXT ( " Cancelled file copy " ) ) ;
2023-02-21 10:38:15 -05:00
return false ;
2021-11-18 14:37:34 -05:00
}
2024-10-01 20:41:28 -04:00
static bool
AttemptFileDeleteWithRetries ( const TCHAR * Path , double RetryDurationSeconds )
{
IFileManager & FileManager = IFileManager : : Get ( ) ;
bool DeleteResult = FileManager . Delete ( Path , false , false , true ) ;
uint64 DeleteWaitStartTime = FPlatformTime : : Cycles64 ( ) ;
while ( ! DeleteResult )
{
double DeleteWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - DeleteWaitStartTime ) ;
if ( DeleteWaitDuration < RetryDurationSeconds )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
}
else
{
break ;
}
DeleteResult = FileManager . Delete ( Path , false , false , true ) ;
}
if ( DeleteResult )
{
return true ;
}
return false ;
}
2024-02-08 13:18:53 -05:00
static void EnsureEditorSettingsConfigLoaded ( )
{
# if !WITH_EDITOR
if ( GEditorSettingsIni . IsEmpty ( ) )
{
FConfigContext Context = FConfigContext : : ReadIntoGConfig ( ) ;
Context . GeneratedConfigDir = FPaths : : EngineEditorSettingsDir ( ) ;
Context . Load ( TEXT ( " EditorSettings " ) , GEditorSettingsIni ) ;
}
# endif
}
2021-11-18 14:37:34 -05:00
static void
DetermineLocalDataCachePath ( const TCHAR * ConfigSection , FString & DataPath )
{
FString DataPathEnvOverride ;
if ( GConfig - > GetString ( ConfigSection , TEXT ( " LocalDataCachePathEnvOverride " ) , DataPathEnvOverride , GEngineIni ) )
{
FString DataPathEnvOverrideValue = FPlatformMisc : : GetEnvironmentVariable ( * DataPathEnvOverride ) ;
if ( ! DataPathEnvOverrideValue . IsEmpty ( ) )
{
DataPath = DataPathEnvOverrideValue ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found environment variable %s=%s " ) , * DataPathEnvOverride , * DataPathEnvOverrideValue ) ;
}
if ( FPlatformMisc : : GetStoredValue ( TEXT ( " Epic Games " ) , TEXT ( " GlobalDataCachePath " ) , * DataPathEnvOverride , DataPathEnvOverrideValue ) )
{
if ( ! DataPathEnvOverrideValue . IsEmpty ( ) )
{
DataPath = DataPathEnvOverrideValue ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found registry key GlobalDataCachePath %s=%s " ) , * DataPathEnvOverride , * DataPath ) ;
}
}
}
2022-12-14 15:06:30 -05:00
FString DataPathCommandLineOverride ;
if ( GConfig - > GetString ( ConfigSection , TEXT ( " LocalDataCachePathCommandLineOverride " ) , DataPathCommandLineOverride , GEngineIni ) )
{
FString DataPathCommandLineOverrideValue ;
if ( FParse : : Value ( FCommandLine : : Get ( ) , * ( DataPathCommandLineOverride + TEXT ( " = " ) ) , DataPathCommandLineOverrideValue ) )
{
DataPath = DataPathCommandLineOverrideValue ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found command line override %s=%s " ) , * DataPathCommandLineOverride , * DataPath ) ;
}
}
// Paths starting with a '?' are looked up from config
if ( DataPath . StartsWith ( TEXT ( " ? " ) ) & & ! GConfig - > GetString ( TEXT ( " DerivedDataCacheSettings " ) , * DataPath + 1 , DataPath , GEngineIni ) )
{
DataPath . Empty ( ) ;
}
2021-11-18 14:37:34 -05:00
FString DataPathEditorOverrideSetting ;
if ( GConfig - > GetString ( ConfigSection , TEXT ( " LocalDataCachePathEditorOverrideSetting " ) , DataPathEditorOverrideSetting , GEngineIni ) )
{
2024-02-08 13:18:53 -05:00
EnsureEditorSettingsConfigLoaded ( ) ;
2021-11-18 14:37:34 -05:00
FString Setting = GConfig - > GetStr ( TEXT ( " /Script/UnrealEd.EditorSettings " ) , * DataPathEditorOverrideSetting , GEditorSettingsIni ) ;
if ( ! Setting . IsEmpty ( ) )
{
FString SettingPath ;
if ( FParse : : Value ( * Setting , TEXT ( " Path= " ) , SettingPath ) )
{
2023-01-17 19:10:35 -05:00
SettingPath . TrimQuotesInline ( ) ;
SettingPath . ReplaceEscapedCharWithCharInline ( ) ;
2021-11-18 14:37:34 -05:00
if ( ! SettingPath . IsEmpty ( ) )
{
DataPath = SettingPath ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found editor setting /Script/UnrealEd.EditorSettings.Path=%s " ) , * DataPath ) ;
}
}
}
}
}
2023-06-14 12:24:33 -04:00
static bool
2024-02-16 11:15:16 -05:00
DetermineDataPath ( const TCHAR * ConfigSection , FString & DataPath , bool & bHasInvalidPathConfigurations , bool & bIsDefaultDataPath )
2021-11-18 14:37:34 -05:00
{
2023-06-14 12:24:33 -04:00
auto ValidateDataPath = [ ] ( const FString & InDataPath )
2021-11-18 14:37:34 -05:00
{
2024-02-15 11:26:49 -05:00
if ( InDataPath . IsEmpty ( ) )
{
2023-06-14 12:24:33 -04:00
return FString { } ;
}
IFileManager & FileManager = IFileManager : : Get ( ) ;
2021-11-18 14:37:34 -05:00
FString FinalPath = FPaths : : ConvertRelativePathToFull ( InDataPath ) ;
FPaths : : NormalizeDirectoryName ( FinalPath ) ;
2023-06-14 12:24:33 -04:00
FFileStatData StatData = FileManager . GetStatData ( * InDataPath ) ;
if ( StatData . bIsValid & & StatData . bIsDirectory )
{
2024-03-01 08:58:49 -05:00
FString TestFilePath = FinalPath / FString : : Printf ( TEXT ( " .zen-startup-test-file-%d " ) , FPlatformProcess : : GetCurrentProcessId ( ) ) ;
2023-06-14 12:24:33 -04:00
FArchive * TestFile = FileManager . CreateFileWriter ( * TestFilePath , FILEWRITE_Silent ) ;
if ( ! TestFile )
{
return FString { } ;
}
TestFile - > Close ( ) ;
delete TestFile ;
FileManager . Delete ( * TestFilePath ) ;
2024-02-15 11:26:49 -05:00
return FinalPath ;
2023-06-14 12:24:33 -04:00
}
if ( FileManager . MakeDirectory ( * InDataPath , true ) )
{
return FinalPath ;
}
return FString { } ;
2021-11-18 14:37:34 -05:00
} ;
// Zen commandline
FString CommandLineOverrideValue ;
2023-06-14 12:24:33 -04:00
if ( FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " ZenDataPath= " ) , CommandLineOverrideValue ) & & ! CommandLineOverrideValue . IsEmpty ( ) )
2021-11-18 14:37:34 -05:00
{
2023-06-14 12:24:33 -04:00
if ( FString Path = ValidateDataPath ( CommandLineOverrideValue ) ; ! Path . IsEmpty ( ) )
2024-02-15 11:26:49 -05:00
{
2023-06-14 12:24:33 -04:00
DataPath = Path ;
2024-02-15 11:26:49 -05:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found command line override ZenDataPath=%s " ) , * CommandLineOverrideValue ) ;
2023-06-14 12:24:33 -04:00
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Skipping command line override ZenDataPath=%s due to an invalid path " ) , * CommandLineOverrideValue ) ;
2024-02-16 11:15:16 -05:00
bHasInvalidPathConfigurations = true ;
2021-11-18 14:37:34 -05:00
}
2022-06-14 15:37:29 -04:00
// Zen subprocess environment
2023-06-14 12:24:33 -04:00
if ( FString SubprocessDataPathEnvOverrideValue = FPlatformMisc : : GetEnvironmentVariable ( TEXT ( " UE-ZenSubprocessDataPath " ) ) ; ! SubprocessDataPathEnvOverrideValue . IsEmpty ( ) )
2022-06-14 15:37:29 -04:00
{
2023-06-14 12:24:33 -04:00
if ( FString Path = ValidateDataPath ( SubprocessDataPathEnvOverrideValue ) ; ! Path . IsEmpty ( ) )
2024-02-15 11:26:49 -05:00
{
2023-06-14 12:24:33 -04:00
DataPath = Path ;
2024-02-15 11:26:49 -05:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found subprocess environment variable UE-ZenSubprocessDataPath=%s " ) , * SubprocessDataPathEnvOverrideValue ) ;
2023-06-14 12:24:33 -04:00
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Skipping subprocess environment variable UE-ZenSubprocessDataPath=%s due to an invalid path " ) , * SubprocessDataPathEnvOverrideValue ) ;
2024-02-16 11:15:16 -05:00
bHasInvalidPathConfigurations = true ;
2022-06-14 15:37:29 -04:00
}
2021-11-18 14:37:34 -05:00
// Zen registry/stored
FString DataPathEnvOverrideValue ;
2023-06-14 12:24:33 -04:00
if ( FPlatformMisc : : GetStoredValue ( TEXT ( " Epic Games " ) , TEXT ( " Zen " ) , TEXT ( " DataPath " ) , DataPathEnvOverrideValue ) & & ! DataPathEnvOverrideValue . IsEmpty ( ) )
2021-11-18 14:37:34 -05:00
{
2023-06-14 12:24:33 -04:00
if ( FString Path = ValidateDataPath ( DataPathEnvOverrideValue ) ; ! Path . IsEmpty ( ) )
2021-11-18 14:37:34 -05:00
{
2023-06-14 12:24:33 -04:00
DataPath = Path ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found registry key Zen DataPath=%s " ) , * DataPathEnvOverrideValue ) ;
return true ;
2021-11-18 14:37:34 -05:00
}
2023-06-14 12:24:33 -04:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Skipping registry key Zen DataPath=%s due to an invalid path " ) , * DataPathEnvOverrideValue ) ;
2024-02-16 11:15:16 -05:00
bHasInvalidPathConfigurations = true ;
2021-11-18 14:37:34 -05:00
}
// Zen environment
2023-06-14 12:24:33 -04:00
if ( FString ZenDataPathEnvOverrideValue = FPlatformMisc : : GetEnvironmentVariable ( TEXT ( " UE-ZenDataPath " ) ) ; ! ZenDataPathEnvOverrideValue . IsEmpty ( ) )
2021-11-18 14:37:34 -05:00
{
2023-06-14 12:24:33 -04:00
if ( FString Path = ValidateDataPath ( ZenDataPathEnvOverrideValue ) ; ! Path . IsEmpty ( ) )
2024-02-15 11:26:49 -05:00
{
2023-06-14 12:24:33 -04:00
DataPath = Path ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found environment variable UE-ZenDataPath=%s " ) , * ZenDataPathEnvOverrideValue ) ;
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Skipping environment variable UE-ZenDataPath=%s due to an invalid path " ) , * ZenDataPathEnvOverrideValue ) ;
2024-02-16 11:15:16 -05:00
bHasInvalidPathConfigurations = true ;
2021-11-18 14:37:34 -05:00
}
// Follow local DDC (if outside workspace)
FString LocalDataCachePath ;
DetermineLocalDataCachePath ( ConfigSection , LocalDataCachePath ) ;
if ( ! LocalDataCachePath . IsEmpty ( ) & & ( LocalDataCachePath ! = TEXT ( " None " ) ) & & ! FPaths : : IsUnderDirectory ( LocalDataCachePath , FPaths : : RootDir ( ) ) )
{
2023-06-15 10:51:32 -04:00
FString ZenLocalDataCachePath = FPaths : : Combine ( LocalDataCachePath , TEXT ( " Zen " ) ) ;
if ( FString Path = ValidateDataPath ( ZenLocalDataCachePath ) ; ! Path . IsEmpty ( ) )
2023-06-14 12:24:33 -04:00
{
2023-06-23 00:49:34 -04:00
DataPath = Path ;
2023-06-14 12:24:33 -04:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found local data cache path=%s " ) , * LocalDataCachePath ) ;
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Skipping local data cache path=%s due to an invalid path " ) , * LocalDataCachePath ) ;
2024-02-16 11:15:16 -05:00
bHasInvalidPathConfigurations = true ;
2021-11-18 14:37:34 -05:00
}
// Zen config default
2023-06-14 12:24:33 -04:00
FString ConfigDefaultPath ;
GConfig - > GetString ( ConfigSection , TEXT ( " DataPath " ) , ConfigDefaultPath , GEngineIni ) ;
if ( ! ConfigDefaultPath . IsEmpty ( ) )
{
2024-02-16 11:15:16 -05:00
ConfigDefaultPath . ReplaceInline ( TEXT ( " %ENGINEVERSIONAGNOSTICINSTALLEDUSERDIR% " ) , * GetLocalZenRootPath ( ) ) ;
2023-06-14 12:24:33 -04:00
if ( FString Path = ValidateDataPath ( ConfigDefaultPath ) ; ! Path . IsEmpty ( ) )
{
DataPath = Path ;
2024-02-16 11:15:16 -05:00
bIsDefaultDataPath = true ;
2023-06-14 12:24:33 -04:00
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found Zen config default=%s " ) , * ConfigDefaultPath ) ;
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Skipping Zen config default=%s due to an invalid path " ) , * ConfigDefaultPath ) ;
2024-02-16 11:15:16 -05:00
bHasInvalidPathConfigurations = true ;
2023-06-14 12:24:33 -04:00
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unable to determine a valid Zen data path " ) ) ;
return false ;
2021-11-18 14:37:34 -05:00
}
static void
ReadUInt16FromConfig ( const TCHAR * Section , const TCHAR * Key , uint16 & Value , const FString & ConfigFile )
{
int32 ValueInt32 = Value ;
GConfig - > GetInt ( Section , Key , ValueInt32 , ConfigFile ) ;
Value = ( uint16 ) ValueInt32 ;
}
2022-01-21 04:24:08 -05:00
static bool
IsLocalHost ( const FString & Host )
{
if ( Host . Compare ( FString ( TEXT ( " localhost " ) ) , ESearchCase : : IgnoreCase ) = = 0 )
{
return true ;
}
if ( Host . Compare ( FString ( TEXT ( " 127.0.0.1 " ) ) ) = = 0 )
{
return true ;
}
2022-02-14 12:19:59 -05:00
if ( Host . Compare ( FString ( TEXT ( " [::1] " ) ) ) = = 0 )
{
return true ;
}
2022-01-21 04:24:08 -05:00
return false ;
}
2023-04-25 02:22:51 -04:00
static void
ApplyProcessLifetimeOverride ( bool & bLimitProcessLifetime )
{
FString LimitProcessLifetime = FPlatformMisc : : GetEnvironmentVariable ( TEXT ( " UE-ZenLimitProcessLifetime " ) ) ;
if ( ! LimitProcessLifetime . IsEmpty ( ) )
{
bLimitProcessLifetime = LimitProcessLifetime . ToBool ( ) ;
}
}
2023-06-14 12:24:33 -04:00
static void
PromptUserUnableToDetermineValidDataPath ( )
{
2023-10-10 18:26:11 -04:00
# if !IS_PROGRAM
2023-12-13 13:39:46 -05:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FString LogDirPath = FPaths : : ConvertRelativePathToFull ( FPaths : : ProjectLogDir ( ) ) ;
FText ZenInvalidDataPathPromptTitle = NSLOCTEXT ( " Zen " , " Zen_InvalidDataPathPromptTitle " , " No Valid Data Path Configuration " ) ;
2024-03-05 11:23:40 -05:00
FText ZenInvalidDataPathPromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_InvalidDataPathPromptText " , " Unreal Zen Storage Server can not determine a valid data path. \n Please check the log in '{0}' for details. \n Update your configuration and restart. " ) , FText : : FromString ( LogDirPath ) ) ;
2023-12-13 13:39:46 -05:00
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenInvalidDataPathPromptText . ToString ( ) , * ZenInvalidDataPathPromptTitle . ToString ( ) ) ;
}
else
2023-10-10 18:26:11 -04:00
# endif
2023-06-14 12:24:33 -04:00
{
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unreal Zen Storage Server is unable to determine a valid data path " ) ) ;
2023-06-14 12:24:33 -04:00
}
}
static void
PromptUserAboutInvalidValidDataPathConfiguration ( const FString & UsedDataPath )
{
2023-10-10 18:26:11 -04:00
# if !IS_PROGRAM
2023-12-13 13:39:46 -05:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FString LogDirPath = FPaths : : ConvertRelativePathToFull ( FPaths : : ProjectLogDir ( ) ) ;
FText ZenInvalidValidDataPathConfigurationPromptTitle = NSLOCTEXT ( " Zen " , " Zen_InvalidValidDataPathConfigurationPromptTitle " , " Invalid Data Paths " ) ;
2024-03-05 11:23:40 -05:00
FText ZenInvalidValidDataPathConfigurationPromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_InvalidValidDataPathConfigurationPromptText " , " Unreal Zen Storage Server has detected invalid data path configuration. \n Please check the log in '{0}' for details. \n \n Falling back to using '{1}' as data path. " ) , FText : : FromString ( LogDirPath ) , FText : : FromString ( UsedDataPath ) ) ;
2023-12-13 13:39:46 -05:00
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenInvalidValidDataPathConfigurationPromptText . ToString ( ) , * ZenInvalidValidDataPathConfigurationPromptTitle . ToString ( ) ) ;
}
else
2023-10-10 18:26:11 -04:00
# endif
2023-06-14 12:24:33 -04:00
{
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unreal Zen Storage Server has detected invalid data path configuration. Falling back to '%s' " ) , * UsedDataPath ) ;
2023-06-14 12:24:33 -04:00
}
}
2023-10-04 16:19:27 -04:00
# if PLATFORM_WINDOWS
static void
PromptUserIsUsingGoogleDriveAsDataPath ( )
{
2024-03-05 11:23:40 -05:00
# if !IS_PROGRAM
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FString LogDirPath = FPaths : : ConvertRelativePathToFull ( FPaths : : ProjectLogDir ( ) ) ;
FText ZenInvalidDataPathPromptTitle = NSLOCTEXT ( " Zen " , " Zen_GoogleDriveDataPathPromptTitle " , " Using Google Drive as a data path " ) ;
FText ZenInvalidDataPathPromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_GoogleDriveDataPathPromptText " , " Unreal Zen Storage Server is configured to use Google Drive as a data path, this is highly inadvisable. \n Please use a data path on a local physical drive. \n Check the log in '{0}' for details. \n Update your configuration and restart. " ) , FText : : FromString ( LogDirPath ) ) ;
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenInvalidDataPathPromptText . ToString ( ) , * ZenInvalidDataPathPromptTitle . ToString ( ) ) ;
}
else
# endif
2023-10-04 16:19:27 -04:00
{
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unreal Zen Storage Server is configured to use Google Drive as a data path, this is highly inadvisable. Please use a path on a local physical drive. " ) ) ;
2023-10-04 16:19:27 -04:00
}
}
# endif // PLATFORM_WINDOWS
2023-08-31 22:30:53 -04:00
static void ReadCbField ( FCbFieldView Field , UE : : Zen : : FZenSizeStats & OutValue )
{
FCbObjectView ObjectView = Field . AsObjectView ( ) ;
OutValue . Disk = ObjectView [ " disk " ] . AsDouble ( ) ;
OutValue . Memory = ObjectView [ " memory " ] . AsDouble ( ) ;
}
static void ReadCbField ( FCbFieldView Field , UE : : Zen : : FZenCIDSizeStats & OutValue )
{
FCbObjectView ObjectView = Field . AsObjectView ( ) ;
OutValue . Tiny = ObjectView [ " tiny " ] . AsInt64 ( ) ;
OutValue . Small = ObjectView [ " small " ] . AsInt64 ( ) ;
OutValue . Large = ObjectView [ " large " ] . AsInt64 ( ) ;
OutValue . Total = ObjectView [ " total " ] . AsInt64 ( ) ;
}
static void ReadCbField ( FCbFieldView Field , UE : : Zen : : FZenCIDStats & OutValue )
{
FCbObjectView ObjectView = Field . AsObjectView ( ) ;
ReadCbField ( ObjectView [ " size " ] , OutValue . Size ) ;
}
2024-10-01 20:41:28 -04:00
static FServiceAutoLaunchSettings : : EInstallMode ZenGetInstallModeFromString ( const FString & InstallMode )
{
if ( InstallMode . IsEmpty ( ) | | FCString : : Stricmp ( * InstallMode , TEXT ( " auto " ) ) = = 0 )
{
return FApp : : IsEngineInstalled ( ) ? FServiceAutoLaunchSettings : : EInstallMode : : Link : FServiceAutoLaunchSettings : : EInstallMode : : Copy ;
}
else if ( FCString : : Stricmp ( * InstallMode , TEXT ( " copy " ) ) = = 0 )
{
return FServiceAutoLaunchSettings : : EInstallMode : : Copy ;
}
else if ( FCString : : Stricmp ( * InstallMode , TEXT ( " link " ) ) = = 0 )
{
return FServiceAutoLaunchSettings : : EInstallMode : : Link ;
}
else
{
checkf ( false , TEXT ( " Invalid zenserver install mode: {InstallMode} " ) , * InstallMode ) ;
return FServiceAutoLaunchSettings : : EInstallMode : : Copy ;
}
}
static FString ZenGetInstallModeToString ( FServiceAutoLaunchSettings : : EInstallMode InstallMode )
{
switch ( InstallMode )
{
case FServiceAutoLaunchSettings : : EInstallMode : : Copy :
return TEXT ( " copy " ) ;
case FServiceAutoLaunchSettings : : EInstallMode : : Link :
return TEXT ( " link " ) ;
}
checkf ( false , TEXT ( " Invalid zenserver install mode: {%d} " ) , static_cast < int > ( InstallMode ) ) ;
return " " ;
}
2024-03-05 11:23:40 -05:00
bool
2021-11-18 14:37:34 -05:00
FServiceSettings : : ReadFromConfig ( )
{
check ( GConfig & & GConfig - > IsReadyForUse ( ) ) ;
const TCHAR * ConfigSection = TEXT ( " Zen " ) ;
bool bAutoLaunch = true ;
GConfig - > GetBool ( ConfigSection , TEXT ( " AutoLaunch " ) , bAutoLaunch , GEngineIni ) ;
if ( bAutoLaunch )
{
if ( ! TryApplyAutoLaunchOverride ( ) )
{
// AutoLaunch settings
const TCHAR * AutoLaunchConfigSection = TEXT ( " Zen.AutoLaunch " ) ;
SettingsVariant . Emplace < FServiceAutoLaunchSettings > ( ) ;
FServiceAutoLaunchSettings & AutoLaunchSettings = SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) ;
2024-02-16 11:15:16 -05:00
bool bHasInvalidPathConfigurations = false ;
if ( ! DetermineDataPath ( AutoLaunchConfigSection , AutoLaunchSettings . DataPath , bHasInvalidPathConfigurations , AutoLaunchSettings . bIsDefaultDataPath ) )
2023-06-14 12:24:33 -04:00
{
PromptUserUnableToDetermineValidDataPath ( ) ;
2024-03-05 11:23:40 -05:00
return false ;
2023-06-14 12:24:33 -04:00
}
2024-02-16 11:15:16 -05:00
else if ( bHasInvalidPathConfigurations )
2023-06-14 12:24:33 -04:00
{
PromptUserAboutInvalidValidDataPathConfiguration ( AutoLaunchSettings . DataPath ) ;
}
2023-10-04 16:19:27 -04:00
# if PLATFORM_WINDOWS
{
int32 DriveEnd = 0 ;
if ( AutoLaunchSettings . DataPath . FindChar ( ' : ' , DriveEnd ) )
{
FString DrivePath = AutoLaunchSettings . DataPath . Left ( DriveEnd + 1 ) ;
TCHAR VolumeName [ 128 ] ;
BOOL OK = GetVolumeInformation (
* DrivePath ,
VolumeName ,
127 ,
NULL ,
NULL ,
NULL ,
NULL ,
NULL ) ;
if ( OK )
{
VolumeName [ 127 ] = 0 ;
if ( FString ( VolumeName ) = = TEXT ( " Google Drive " ) )
{
PromptUserIsUsingGoogleDriveAsDataPath ( ) ;
}
}
}
}
# endif // PLATFORM_WINDOWS
2021-11-18 14:37:34 -05:00
GConfig - > GetString ( AutoLaunchConfigSection , TEXT ( " ExtraArgs " ) , AutoLaunchSettings . ExtraArgs , GEngineIni ) ;
ReadUInt16FromConfig ( AutoLaunchConfigSection , TEXT ( " DesiredPort " ) , AutoLaunchSettings . DesiredPort , GEngineIni ) ;
GConfig - > GetBool ( AutoLaunchConfigSection , TEXT ( " ShowConsole " ) , AutoLaunchSettings . bShowConsole , GEngineIni ) ;
GConfig - > GetBool ( AutoLaunchConfigSection , TEXT ( " LimitProcessLifetime " ) , AutoLaunchSettings . bLimitProcessLifetime , GEngineIni ) ;
2023-04-25 02:22:51 -04:00
ApplyProcessLifetimeOverride ( AutoLaunchSettings . bLimitProcessLifetime ) ;
2024-09-11 17:53:58 -04:00
GConfig - > GetBool ( AutoLaunchConfigSection , TEXT ( " AllowRemoteNetworkService " ) , AutoLaunchSettings . bAllowRemoteNetworkService , GEngineIni ) ;
2024-10-01 20:41:28 -04:00
FString InstallMode ;
if ( GConfig - > GetString ( AutoLaunchConfigSection , TEXT ( " InstallMode " ) , InstallMode , GEngineIni ) )
{
AutoLaunchSettings . InstallMode = ZenGetInstallModeFromString ( InstallMode ) ;
}
2024-02-08 13:18:53 -05:00
EnsureEditorSettingsConfigLoaded ( ) ;
2023-04-04 06:07:42 -04:00
GConfig - > GetBool ( TEXT ( " /Script/UnrealEd.CrashReportsPrivacySettings " ) , TEXT ( " bSendUnattendedBugReports " ) , AutoLaunchSettings . bSendUnattendedBugReports , GEditorSettingsIni ) ;
2021-11-18 14:37:34 -05:00
}
}
else
{
// ConnectExisting settings
const TCHAR * ConnectExistingConfigSection = TEXT ( " Zen.ConnectExisting " ) ;
SettingsVariant . Emplace < FServiceConnectSettings > ( ) ;
FServiceConnectSettings & ConnectExistingSettings = SettingsVariant . Get < FServiceConnectSettings > ( ) ;
GConfig - > GetString ( ConnectExistingConfigSection , TEXT ( " HostName " ) , ConnectExistingSettings . HostName , GEngineIni ) ;
ReadUInt16FromConfig ( ConnectExistingConfigSection , TEXT ( " Port " ) , ConnectExistingSettings . Port , GEngineIni ) ;
}
2024-03-05 11:23:40 -05:00
return true ;
2021-11-18 14:37:34 -05:00
}
2024-03-05 11:23:40 -05:00
bool
2023-03-14 06:43:23 -04:00
FServiceSettings : : ReadFromCompactBinary ( FCbFieldView Field )
2021-11-18 14:37:34 -05:00
{
2023-03-14 06:43:23 -04:00
if ( bool bAutoLaunchValue = Field [ " bAutoLaunch " ] . AsBool ( ) )
2021-11-18 14:37:34 -05:00
{
2023-03-14 06:43:23 -04:00
if ( ! TryApplyAutoLaunchOverride ( ) )
2021-11-18 14:37:34 -05:00
{
2023-03-14 06:43:23 -04:00
SettingsVariant . Emplace < FServiceAutoLaunchSettings > ( ) ;
FServiceAutoLaunchSettings & AutoLaunchSettings = SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) ;
2021-11-18 14:37:34 -05:00
2023-03-14 06:43:23 -04:00
if ( FCbObjectView AutoLaunchSettingsObject = Field [ " AutoLaunchSettings " ] . AsObjectView ( ) )
{
AutoLaunchSettings . DataPath = FString ( AutoLaunchSettingsObject [ " DataPath " ] . AsString ( ) ) ;
AutoLaunchSettings . ExtraArgs = FString ( AutoLaunchSettingsObject [ " ExtraArgs " ] . AsString ( ) ) ;
AutoLaunchSettings . DesiredPort = AutoLaunchSettingsObject [ " DesiredPort " ] . AsInt16 ( ) ;
AutoLaunchSettings . bShowConsole = AutoLaunchSettingsObject [ " ShowConsole " ] . AsBool ( ) ;
2024-02-16 11:15:16 -05:00
AutoLaunchSettings . bIsDefaultDataPath = AutoLaunchSettingsObject [ " IsDefaultDataPath " ] . AsBool ( ) ;
2023-03-14 06:43:23 -04:00
AutoLaunchSettings . bLimitProcessLifetime = AutoLaunchSettingsObject [ " LimitProcessLifetime " ] . AsBool ( ) ;
2023-04-25 02:22:51 -04:00
ApplyProcessLifetimeOverride ( AutoLaunchSettings . bLimitProcessLifetime ) ;
2024-09-11 17:53:58 -04:00
AutoLaunchSettings . bAllowRemoteNetworkService = AutoLaunchSettingsObject [ " AllowRemoteNetworkService " ] . AsBool ( ) ;
2023-04-04 06:07:42 -04:00
AutoLaunchSettings . bSendUnattendedBugReports = AutoLaunchSettingsObject [ " SendUnattendedBugReports " ] . AsBool ( ) ;
2023-06-23 00:49:34 -04:00
AutoLaunchSettings . bIsDefaultSharedRunContext = AutoLaunchSettingsObject [ " IsDefaultSharedRunContext " ] . AsBool ( AutoLaunchSettings . bIsDefaultSharedRunContext ) ;
2024-10-01 20:41:28 -04:00
AutoLaunchSettings . InstallMode = ZenGetInstallModeFromString ( FString ( AutoLaunchSettingsObject [ " InstallMode " ] . AsString ( ) ) ) ;
2021-11-18 14:37:34 -05:00
}
}
2023-03-14 06:43:23 -04:00
}
else
{
SettingsVariant . Emplace < FServiceConnectSettings > ( ) ;
FServiceConnectSettings & ConnectExistingSettings = SettingsVariant . Get < FServiceConnectSettings > ( ) ;
if ( FCbObjectView ConnectExistingSettingsObject = Field [ " ConnectExistingSettings " ] . AsObjectView ( ) )
2021-11-18 14:37:34 -05:00
{
2023-03-14 06:43:23 -04:00
ConnectExistingSettings . HostName = FString ( ConnectExistingSettingsObject [ " HostName " ] . AsString ( ) ) ;
ConnectExistingSettings . Port = ConnectExistingSettingsObject [ " Port " ] . AsInt16 ( ) ;
2021-11-18 14:37:34 -05:00
}
}
2024-03-05 11:23:40 -05:00
return true ;
2021-11-18 14:37:34 -05:00
}
2024-03-05 11:23:40 -05:00
bool
2021-11-18 14:37:34 -05:00
FServiceSettings : : ReadFromURL ( FStringView InstanceURL )
{
SettingsVariant . Emplace < FServiceConnectSettings > ( ) ;
FServiceConnectSettings & ConnectExistingSettings = SettingsVariant . Get < FServiceConnectSettings > ( ) ;
if ( InstanceURL . StartsWith ( TEXT ( " http:// " ) ) )
{
InstanceURL . RightChopInline ( 7 ) ;
}
2021-12-14 14:07:35 -05:00
int32 PortDelimIndex = INDEX_NONE ;
2022-02-15 10:52:53 -05:00
InstanceURL . FindLastChar ( TEXT ( ' : ' ) , PortDelimIndex ) ;
2021-11-18 14:37:34 -05:00
if ( PortDelimIndex ! = INDEX_NONE )
{
ConnectExistingSettings . HostName = InstanceURL . Left ( PortDelimIndex ) ;
LexFromString ( ConnectExistingSettings . Port , InstanceURL . RightChop ( PortDelimIndex + 1 ) ) ;
}
else
{
ConnectExistingSettings . HostName = InstanceURL ;
2023-10-18 19:44:18 -04:00
ConnectExistingSettings . Port = 8558 ;
2021-11-18 14:37:34 -05:00
}
2024-03-05 11:23:40 -05:00
return true ;
2021-11-18 14:37:34 -05:00
}
void
2023-03-14 06:43:23 -04:00
FServiceSettings : : WriteToCompactBinary ( FCbWriter & Writer ) const
2021-11-18 14:37:34 -05:00
{
bool bAutoLaunch = IsAutoLaunch ( ) ;
2023-03-14 06:43:23 -04:00
Writer < < " bAutoLaunch " < < bAutoLaunch ;
2021-11-18 14:37:34 -05:00
if ( bAutoLaunch )
{
const FServiceAutoLaunchSettings & AutoLaunchSettings = SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) ;
2023-03-14 06:43:23 -04:00
Writer . BeginObject ( " AutoLaunchSettings " ) ;
Writer < < " DataPath " < < AutoLaunchSettings . DataPath ;
Writer < < " ExtraArgs " < < AutoLaunchSettings . ExtraArgs ;
Writer < < " DesiredPort " < < AutoLaunchSettings . DesiredPort ;
Writer < < " ShowConsole " < < AutoLaunchSettings . bShowConsole ;
2024-02-16 11:15:16 -05:00
Writer < < " IsDefaultDataPath " < < AutoLaunchSettings . bIsDefaultDataPath ;
2023-03-14 06:43:23 -04:00
Writer < < " LimitProcessLifetime " < < AutoLaunchSettings . bLimitProcessLifetime ;
2024-09-11 17:53:58 -04:00
Writer < < " AllowRemoteNetworkService " < < AutoLaunchSettings . bAllowRemoteNetworkService ;
2023-04-04 06:07:42 -04:00
Writer < < " SendUnattendedBugReports " < < AutoLaunchSettings . bSendUnattendedBugReports ;
2023-06-23 00:49:34 -04:00
Writer < < " IsDefaultSharedRunContext " < < AutoLaunchSettings . bIsDefaultSharedRunContext ;
2024-10-01 20:41:28 -04:00
Writer < < " InstallMode " < < ZenGetInstallModeToString ( AutoLaunchSettings . InstallMode ) ;
2023-03-14 06:43:23 -04:00
Writer . EndObject ( ) ;
2021-11-18 14:37:34 -05:00
}
else
{
const FServiceConnectSettings & ConnectExistingSettings = SettingsVariant . Get < FServiceConnectSettings > ( ) ;
2023-03-14 06:43:23 -04:00
Writer . BeginObject ( " ConnectExistingSettings " ) ;
Writer < < " HostName " < < ConnectExistingSettings . HostName ;
Writer < < " Port " < < ConnectExistingSettings . Port ;
Writer . EndObject ( ) ;
2021-11-18 14:37:34 -05:00
}
}
bool
FServiceSettings : : TryApplyAutoLaunchOverride ( )
{
# if ALLOW_SETTINGS_OVERRIDE_FROM_COMMANDLINE
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " NoZenAutoLaunch " ) ) )
{
SettingsVariant . Emplace < FServiceConnectSettings > ( ) ;
FServiceConnectSettings & ConnectExistingSettings = SettingsVariant . Get < FServiceConnectSettings > ( ) ;
2022-02-14 12:19:59 -05:00
ConnectExistingSettings . HostName = TEXT ( " [::1] " ) ;
2023-10-18 19:44:18 -04:00
ConnectExistingSettings . Port = 8558 ;
2021-11-18 14:37:34 -05:00
return true ;
}
2022-01-21 04:24:08 -05:00
FString Host ;
if ( FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " -NoZenAutoLaunch= " ) , Host ) )
{
SettingsVariant . Emplace < FServiceConnectSettings > ( ) ;
FServiceConnectSettings & ConnectExistingSettings = SettingsVariant . Get < FServiceConnectSettings > ( ) ;
int32 PortDelimIndex = INDEX_NONE ;
if ( Host . FindChar ( TEXT ( ' : ' ) , PortDelimIndex ) )
{
ConnectExistingSettings . HostName = Host . Left ( PortDelimIndex ) ;
LexFromString ( ConnectExistingSettings . Port , Host . RightChop ( PortDelimIndex + 1 ) ) ;
}
else
{
ConnectExistingSettings . HostName = Host ;
2023-10-18 19:44:18 -04:00
ConnectExistingSettings . Port = 8558 ;
2022-01-21 04:24:08 -05:00
}
return true ;
}
2021-11-18 14:37:34 -05:00
# endif
return false ;
}
# if UE_WITH_ZEN
2021-08-04 14:56:02 -04:00
2022-01-26 10:54:48 -05:00
uint16 FZenServiceInstance : : AutoLaunchedPort = 0 ;
2023-08-02 19:28:23 -04:00
uint32 FZenServiceInstance : : AutoLaunchedPid = 0 ;
2022-01-26 10:54:48 -05:00
2023-02-14 04:51:30 -05:00
static bool
2023-03-10 15:57:34 -05:00
IsZenProcessUsingEffectivePort ( uint16 EffectiveListenPort )
2023-02-14 04:51:30 -05:00
{
2024-10-01 19:30:59 -04:00
ZenSharedEvent ShutDownEvent ( ZenSharedEvent : : GetShutdownEventName ( EffectiveListenPort ) ) ;
return ShutDownEvent . Exists ( ) ;
2024-02-09 04:44:22 -05:00
}
static bool
RequestZenShutdownOnEffectivePort ( uint16 EffectiveListenPort )
{
2024-10-01 19:30:59 -04:00
ZenSharedEvent ShutDownEvent ( ZenSharedEvent : : GetShutdownEventName ( EffectiveListenPort ) ) ;
if ( ! ShutDownEvent . Open ( ) )
2024-02-09 04:44:22 -05:00
{
return false ;
}
2024-10-01 19:30:59 -04:00
if ( ! ShutDownEvent . Set ( ) )
2024-02-09 04:44:22 -05:00
{
return false ;
}
return true ;
}
static bool ShutdownZenServerProcess ( int Pid , double MaximumWaitDurationSeconds = 25.0 )
{
const ZenServerState ServerState ( /* ReadOnly */ true ) ;
const ZenServerState : : ZenServerEntry * Entry = ServerState . LookupByPid ( Pid ) ;
if ( Entry )
{
uint16 EffectivePort = Entry - > EffectiveListenPort . load ( std : : memory_order_relaxed ) ;
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Requesting shut down of zenserver process %d runnning on effective port %u " ) , Pid , EffectivePort ) ;
if ( RequestZenShutdownOnEffectivePort ( EffectivePort ) )
{
uint64 ZenShutdownWaitStartTime = FPlatformTime : : Cycles64 ( ) ;
2024-10-01 19:30:59 -04:00
while ( ZenServerState : : IsProcessRunning ( Pid ) )
2024-02-09 04:44:22 -05:00
{
double ZenShutdownWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenShutdownWaitStartTime ) ;
if ( ZenShutdownWaitDuration < MaximumWaitDurationSeconds )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
}
else
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Timed out waiting for shut down of running service with pid %d " ) , Pid ) ;
break ;
}
}
}
}
2024-10-01 19:30:59 -04:00
if ( ZenServerState : : IsProcessRunning ( Pid ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Attempting termination of zenserver process with pid %d " ) , Pid ) ;
2024-10-01 19:30:59 -04:00
if ( ! ZenServerState : : Terminate ( Pid ) )
2024-02-09 04:44:22 -05:00
{
2024-10-01 19:30:59 -04:00
if ( ZenServerState : : IsProcessRunning ( Pid ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to terminate zenserver process with pid %d " ) , Pid ) ;
return false ;
}
}
}
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Successfully shut down zenserver process with pid %d " ) , Pid ) ;
return true ;
}
static bool ShutDownZenServerProcessExecutable ( const FString & ExecutablePath , double MaximumWaitDurationSeconds = 25.0 )
{
uint64 ZenShutdownWaitStartTime = FPlatformTime : : Cycles64 ( ) ;
uint32_t Pid = 0 ;
2024-10-01 19:30:59 -04:00
while ( ZenServerState : : FindRunningProcessId ( * ExecutablePath , & Pid ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Attempting to shut down of zenserver executable '%s' process with pid %d " ) , * ExecutablePath , Pid ) ;
double ZenShutdownWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenShutdownWaitStartTime ) ;
2024-10-01 20:41:28 -04:00
if ( ShutdownZenServerProcess ( Pid , MaximumWaitDurationSeconds - ZenShutdownWaitDuration ) )
2024-02-09 04:44:22 -05:00
{
return true ;
}
else
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to shut down zenserver executable '%s' process with pid %d " ) , * ExecutablePath , Pid ) ;
return false ;
}
}
return true ;
}
static bool ShutDownZenServerProcessLockingDataDir ( const FString & DataPath , double MaximumWaitDurationSeconds = 25.0 )
{
const FString LockFilePath = FPaths : : Combine ( DataPath , TEXT ( " .lock " ) ) ;
uint64 ZenShutdownWaitStartTime = FPlatformTime : : Cycles64 ( ) ;
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Lock file '%s' is not active, nothing to do " ) , * LockFilePath ) ;
return true ;
}
2024-10-01 19:30:59 -04:00
ZenLockFileData LockFileState = ZenLockFileData : : ReadCbLockFile ( * LockFilePath ) ;
2024-02-09 04:44:22 -05:00
if ( ! LockFileState . IsValid )
{
while ( true )
{
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-02-09 04:44:22 -05:00
{
return true ;
}
uint32_t Pid = 0 ;
2024-10-01 20:41:28 -04:00
if ( ! ZenServerState : : FindRunningProcessId ( * GetServiceExecutableName ( ) , & Pid ) )
2024-02-09 04:44:22 -05:00
{
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-03-12 01:52:39 -04:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Lock file '%s' is no longer active, nothing to do " ) , * LockFilePath ) ;
return true ;
}
2024-02-09 04:44:22 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to find zenserver process locking file '%s' " ) , * LockFilePath ) ;
return false ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Found locked but invalid lock file at '%s', attempting shut down of zenserver process with pid %d " ) , * LockFilePath , Pid ) ;
double ZenShutdownWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenShutdownWaitStartTime ) ;
2024-10-01 20:41:28 -04:00
if ( ! ShutdownZenServerProcess ( Pid , MaximumWaitDurationSeconds - ZenShutdownWaitDuration ) )
2024-02-09 04:44:22 -05:00
{
break ;
}
}
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Successfully shut down zenserver using lock file '%s' " ) , * LockFilePath ) ;
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to shut down zenserver process locking file '%s' " ) , * LockFilePath ) ;
return false ;
}
uint16 EffectivePort = LockFileState . EffectivePort ;
const ZenServerState ServerState ( /* ReadOnly */ true ) ;
const ZenServerState : : ZenServerEntry * Entry = ServerState . LookupByEffectiveListenPort ( EffectivePort ) ;
if ( Entry )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Requesting shut down of zenserver process using lock file '%s' with effective port %d " ) , * LockFilePath , EffectivePort ) ;
if ( RequestZenShutdownOnEffectivePort ( EffectivePort ) )
{
2024-10-01 19:30:59 -04:00
while ( ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-02-09 04:44:22 -05:00
{
double ZenShutdownWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenShutdownWaitStartTime ) ;
if ( ZenShutdownWaitDuration < MaximumWaitDurationSeconds )
{
FPlatformProcess : : Sleep ( 0.01f ) ;
}
else
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Timed out waiting for shut down of zensever process using lock file '%s' with effective port %u " ) , * LockFilePath , EffectivePort ) ;
break ;
}
}
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Successfully shut down zenserver process using lock file '%s' with effective port %u " ) , * LockFilePath , EffectivePort ) ;
return true ;
}
}
}
while ( true )
{
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-02-09 04:44:22 -05:00
{
return true ;
}
uint32_t Pid = 0 ;
2024-10-01 20:41:28 -04:00
if ( ! ZenServerState : : FindRunningProcessId ( * GetServiceExecutableName ( ) , & Pid ) )
2024-02-09 04:44:22 -05:00
{
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2024-03-12 01:52:39 -04:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Lock file '%s' is no longer active, nothing to do " ) , * LockFilePath ) ;
return true ;
}
2024-02-09 04:44:22 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to find zenserver process locking file '%s' " ) , * LockFilePath ) ;
return false ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Found locked but invalid lock file at '%s', attempting shut down of zenserver process with pid %d " ) , * LockFilePath , Pid ) ;
double ZenShutdownWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenShutdownWaitStartTime ) ;
2024-10-01 20:41:28 -04:00
if ( ! ShutdownZenServerProcess ( Pid , MaximumWaitDurationSeconds - ZenShutdownWaitDuration ) )
2024-02-09 04:44:22 -05:00
{
break ;
}
}
2024-10-01 19:30:59 -04:00
if ( ! ZenLockFileData : : IsLockFileLocked ( * LockFilePath ) )
2024-02-09 04:44:22 -05:00
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Successfully shut down zenserver using lock file '%s' " ) , * LockFilePath ) ;
return true ;
}
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to shut down zenserver process locking file '%s' " ) , * LockFilePath ) ;
return false ;
}
2023-02-21 10:38:15 -05:00
static bool
2024-10-01 19:30:59 -04:00
IsZenProcessUsingDataDir ( const TCHAR * LockFilePath , ZenLockFileData * OutLockFileData )
2023-02-21 10:38:15 -05:00
{
2024-10-01 19:30:59 -04:00
if ( ZenLockFileData : : IsLockFileLocked ( LockFilePath , true ) )
2023-02-21 10:38:15 -05:00
{
2023-03-10 15:57:34 -05:00
if ( OutLockFileData )
2023-02-21 10:38:15 -05:00
{
// If an instance is running with this data path, check if we can use it and what port it is on
2024-10-01 19:30:59 -04:00
* OutLockFileData = ZenLockFileData : : ReadCbLockFile ( LockFilePath ) ;
2023-02-21 10:38:15 -05:00
}
return true ;
}
return false ;
}
2022-01-12 12:05:55 -05:00
static FString
DetermineCmdLineWithoutTransientComponents ( const FServiceAutoLaunchSettings & InSettings , int16 OverrideDesiredPort )
{
2022-01-19 18:28:03 -05:00
FString PlatformDataPath ( InSettings . DataPath ) ;
FPaths : : MakePlatformFilename ( PlatformDataPath ) ;
2022-01-12 12:05:55 -05:00
FString Parms ;
Parms . Appendf ( TEXT ( " --port %d --data-dir \" %s \" " ) ,
OverrideDesiredPort ,
2022-01-19 18:28:03 -05:00
* PlatformDataPath ) ;
2022-01-12 12:05:55 -05:00
if ( ! InSettings . ExtraArgs . IsEmpty ( ) )
{
Parms . AppendChar ( TEXT ( ' ' ) ) ;
Parms . Append ( InSettings . ExtraArgs ) ;
}
2022-04-29 18:10:02 -04:00
FString LogCommandLineOverrideValue ;
if ( FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " ZenLogPath= " ) , LogCommandLineOverrideValue ) )
{
if ( ! LogCommandLineOverrideValue . IsEmpty ( ) )
{
Parms . Appendf ( TEXT ( " --abslog \" %s \" " ) ,
* FPaths : : ConvertRelativePathToFull ( LogCommandLineOverrideValue ) ) ;
}
}
FString CfgCommandLineOverrideValue ;
if ( FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " ZenCfgPath= " ) , CfgCommandLineOverrideValue ) )
{
if ( ! CfgCommandLineOverrideValue . IsEmpty ( ) )
{
Parms . Appendf ( TEXT ( " --config \" %s \" " ) ,
* FPaths : : ConvertRelativePathToFull ( CfgCommandLineOverrideValue ) ) ;
}
}
2023-04-04 06:07:42 -04:00
if ( ! InSettings . bSendUnattendedBugReports )
{
Parms . Append ( TEXT ( " --no-sentry " ) ) ;
}
2024-09-11 17:53:58 -04:00
if ( ! InSettings . bAllowRemoteNetworkService )
2024-07-30 18:40:16 -04:00
{
Parms . Append ( TEXT ( " --http-forceloopback " ) ) ;
}
2022-01-12 12:05:55 -05:00
return Parms ;
}
2022-12-14 15:06:30 -05:00
bool
Private : : IsLocalAutoLaunched ( FStringView InstanceURL )
{
if ( ! InstanceURL . IsEmpty ( ) & & ! InstanceURL . Equals ( TEXT ( " <DefaultInstance> " ) ) )
{
FString TempURL ( InstanceURL ) ;
return IsLocalHost ( TempURL ) ;
}
return true ;
}
bool
Private : : GetLocalDataCachePathOverride ( FString & OutDataPath )
{
const TCHAR * AutoLaunchConfigSection = TEXT ( " Zen.AutoLaunch " ) ;
FString DataPath ;
DetermineLocalDataCachePath ( AutoLaunchConfigSection , DataPath ) ;
if ( DataPath . IsEmpty ( ) )
{
return false ;
}
OutDataPath = DataPath ;
return true ;
}
2022-12-06 14:37:09 -05:00
bool
TryGetLocalServiceRunContext ( FZenLocalServiceRunContext & OutContext )
{
2024-10-01 20:41:28 -04:00
return OutContext . ReadFromJsonFile ( * GetServiceRunContextPath ( ) ) ;
2022-12-06 14:37:09 -05:00
}
bool
FZenLocalServiceRunContext : : ReadFromJson ( FJsonObject & JsonObject )
{
Executable = JsonObject . Values . FindRef ( TEXT ( " Executable " ) ) - > AsString ( ) ;
CommandlineArguments = JsonObject . Values . FindRef ( TEXT ( " CommandlineArguments " ) ) - > AsString ( ) ;
WorkingDirectory = JsonObject . Values . FindRef ( TEXT ( " WorkingDirectory " ) ) - > AsString ( ) ;
DataPath = JsonObject . Values . FindRef ( TEXT ( " DataPath " ) ) - > AsString ( ) ;
bShowConsole = JsonObject . Values . FindRef ( TEXT ( " ShowConsole " ) ) - > AsBool ( ) ;
return true ;
}
void
FZenLocalServiceRunContext : : WriteToJson ( TJsonWriter < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > & Writer ) const
{
Writer . WriteValue ( TEXT ( " Executable " ) , Executable ) ;
Writer . WriteValue ( TEXT ( " CommandlineArguments " ) , CommandlineArguments ) ;
Writer . WriteValue ( TEXT ( " WorkingDirectory " ) , WorkingDirectory ) ;
Writer . WriteValue ( TEXT ( " DataPath " ) , DataPath ) ;
Writer . WriteValue ( TEXT ( " ShowConsole " ) , bShowConsole ) ;
}
bool
FZenLocalServiceRunContext : : ReadFromJsonFile ( const TCHAR * Filename )
{
FString JsonText ;
if ( ! FFileHelper : : LoadFileToString ( JsonText , Filename ) )
{
return false ;
}
TSharedPtr < FJsonObject > JsonObject ;
TSharedRef < TJsonReader < TCHAR > > Reader = TJsonReaderFactory < TCHAR > : : Create ( JsonText ) ;
if ( ! FJsonSerializer : : Deserialize ( Reader , JsonObject ) | | ! JsonObject . IsValid ( ) )
{
return false ;
}
return ReadFromJson ( * JsonObject ) ;
}
bool
FZenLocalServiceRunContext : : WriteToJsonFile ( const TCHAR * Filename ) const
{
FString JsonTcharText ;
TSharedRef < TJsonWriter < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > > Writer = TJsonWriterFactory < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > : : Create ( & JsonTcharText ) ;
Writer - > WriteObjectStart ( ) ;
WriteToJson ( * Writer ) ;
Writer - > WriteObjectEnd ( ) ;
Writer - > Close ( ) ;
if ( ! FFileHelper : : SaveStringToFile ( JsonTcharText , Filename ) )
{
return false ;
}
return true ;
}
bool
2023-03-10 15:57:34 -05:00
IsLocalServiceRunning ( const TCHAR * DataPath , uint16 * OutEffectivePort )
2022-12-06 14:37:09 -05:00
{
2023-03-09 11:25:59 -05:00
const FString LockFilePath = FPaths : : Combine ( DataPath , TEXT ( " .lock " ) ) ;
2024-10-01 19:30:59 -04:00
ZenLockFileData LockFileState ;
2023-03-10 15:57:34 -05:00
if ( IsZenProcessUsingDataDir ( * LockFilePath , & LockFileState ) )
{
2023-04-20 04:32:22 -04:00
if ( OutEffectivePort ! = nullptr & & LockFileState . IsValid & & LockFileState . IsReady )
2023-03-10 15:57:34 -05:00
{
* OutEffectivePort = LockFileState . EffectivePort ;
}
return true ;
}
return false ;
2022-12-06 14:37:09 -05:00
}
FProcHandle
StartLocalService ( const FZenLocalServiceRunContext & Context , const TCHAR * TransientArgs )
{
FString Parms = Context . GetCommandlineArguments ( ) ;
if ( TransientArgs )
{
Parms . Appendf ( TEXT ( " %s " ) , TransientArgs ) ;
}
2023-02-13 14:23:19 -05:00
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Launching executable '%s', working dir '%s', data dir '%s', args '%s' " ) , * Context . GetExecutable ( ) , * Context . GetWorkingDirectory ( ) , * Context . GetDataPath ( ) , * Parms ) ;
2022-12-06 14:37:09 -05:00
FProcHandle Proc ;
# if PLATFORM_WINDOWS
FString PlatformExecutable = Context . GetExecutable ( ) ;
FPaths : : MakePlatformFilename ( PlatformExecutable ) ;
FString PlatformWorkingDirectory = Context . GetWorkingDirectory ( ) ;
FPaths : : MakePlatformFilename ( PlatformWorkingDirectory ) ;
{
2024-01-31 16:16:41 -05:00
// We could switch to FPlatformProcess::CreateProc for Windows as well if we are able to add the CREATE_BREAKAWAY_FROM_JOB flag
// as that is needed on CI to stop Horde from terminating the zenserver process
2022-12-06 14:37:09 -05:00
STARTUPINFO StartupInfo = {
sizeof ( STARTUPINFO ) ,
NULL , NULL , NULL ,
( : : DWORD ) CW_USEDEFAULT ,
( : : DWORD ) CW_USEDEFAULT ,
( : : DWORD ) CW_USEDEFAULT ,
( : : DWORD ) CW_USEDEFAULT ,
( : : DWORD ) 0 , ( : : DWORD ) 0 , ( : : DWORD ) 0 ,
( : : DWORD ) STARTF_USESHOWWINDOW ,
( : : WORD ) ( Context . GetShowConsole ( ) ? SW_SHOWMINNOACTIVE : SW_HIDE ) ,
0 , NULL ,
HANDLE ( nullptr ) ,
HANDLE ( nullptr ) ,
HANDLE ( nullptr )
} ;
FString CommandLine = FString : : Printf ( TEXT ( " \" %s \" %s " ) , * PlatformExecutable , * Parms ) ;
2024-04-03 16:47:09 -04:00
: : DWORD CreationFlagsArray [ ] = {
NORMAL_PRIORITY_CLASS | DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB , // Try with the breakaway flag first
NORMAL_PRIORITY_CLASS | DETACHED_PROCESS // If that fails (access denied), try without the breakaway flag next
} ;
for ( : : DWORD CreationFlags : CreationFlagsArray )
2022-12-06 14:37:09 -05:00
{
2024-04-03 16:47:09 -04:00
PROCESS_INFORMATION ProcInfo ;
2024-04-05 16:54:36 -04:00
if ( CreateProcess ( NULL , CommandLine . GetCharArray ( ) . GetData ( ) , nullptr , nullptr , false , CreationFlags , nullptr , PlatformWorkingDirectory . GetCharArray ( ) . GetData ( ) , & StartupInfo , & ProcInfo ) )
2024-04-03 16:47:09 -04:00
{
: : CloseHandle ( ProcInfo . hThread ) ;
Proc = FProcHandle ( ProcInfo . hProcess ) ;
break ;
}
2022-12-06 14:37:09 -05:00
}
2024-04-03 16:47:09 -04:00
if ( ! Proc . IsValid ( ) )
2022-12-06 14:37:09 -05:00
{
2024-01-31 16:16:41 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed launching %s status: %d. " ) , * CommandLine , GetLastError ( ) ) ;
2022-12-06 14:37:09 -05:00
}
}
# else
{
bool bLaunchDetached = true ;
bool bLaunchHidden = true ;
bool bLaunchReallyHidden = ! Context . GetShowConsole ( ) ;
uint32 * OutProcessID = nullptr ;
int32 PriorityModifier = 0 ;
void * PipeWriteChild = nullptr ;
void * PipeReadChild = nullptr ;
Proc = FPlatformProcess : : CreateProc (
* Context . GetExecutable ( ) ,
* Parms ,
bLaunchDetached ,
bLaunchHidden ,
bLaunchReallyHidden ,
OutProcessID ,
PriorityModifier ,
* Context . GetWorkingDirectory ( ) ,
PipeWriteChild ,
PipeReadChild ) ;
}
# endif
return Proc ;
}
bool
StopLocalService ( const TCHAR * DataPath , double MaximumWaitDurationSeconds )
{
2023-03-10 15:57:34 -05:00
const FString LockFilePath = FPaths : : Combine ( DataPath , TEXT ( " .lock " ) ) ;
2024-10-01 19:30:59 -04:00
ZenLockFileData LockFileState ;
if ( ZenLockFileData : : IsLockFileLocked ( * LockFilePath , true ) )
2022-12-06 14:37:09 -05:00
{
2024-02-09 04:44:22 -05:00
return ShutDownZenServerProcessLockingDataDir ( DataPath , MaximumWaitDurationSeconds ) ;
2022-12-06 14:37:09 -05:00
}
return true ;
}
2024-10-01 20:41:28 -04:00
FString
GetLocalServiceInstallPath ( )
{
if ( FZenServiceLink Link = FZenServiceLink : : Read ( GetServiceLinkPath ( ) ) ; Link )
{
return Link . ServicePath ;
}
else
{
return GetServiceCopyInstallPath ( ) ;
}
}
FString
GetLocalInstallUtilityPath ( )
{
if ( FZenServiceLink Link = FZenServiceLink : : Read ( GetServiceLinkPath ( ) ) ; Link )
{
return Link . UtilityPath ;
}
else
{
return GetUtilityCopyInstallPath ( ) ;
}
}
2022-12-06 14:37:09 -05:00
FString
GetLocalServiceInstallVersion ( bool bDetailed )
{
IFileManager & FileManager = IFileManager : : Get ( ) ;
2024-10-01 20:41:28 -04:00
if ( FZenServiceLink Link = FZenServiceLink : : Read ( GetServiceLinkPath ( ) ) ; Link )
2022-12-06 14:37:09 -05:00
{
2024-10-01 20:41:28 -04:00
if ( ! FileManager . FileExists ( * Link . ServicePath ) )
{
return FZenVersion ( ) . ToString ( bDetailed ) ;
}
return Link . Version . ToString ( bDetailed ) ;
2022-12-06 14:37:09 -05:00
}
2024-10-01 20:41:28 -04:00
else
{
const FString ServicePath = GetServiceCopyInstallPath ( ) ;
2022-12-06 14:37:09 -05:00
2024-10-01 20:41:28 -04:00
if ( ! FileManager . FileExists ( * ServicePath ) )
{
return FZenVersion ( ) . ToString ( bDetailed ) ;
}
2022-12-06 14:37:09 -05:00
2024-10-01 20:41:28 -04:00
FZenVersion InstallVersion = GetZenVersion ( GetUtilityCopyInstallPath ( ) , ServicePath , GetInstallVersionCachePath ( ) ) ;
return InstallVersion . ToString ( bDetailed ) ;
}
2022-12-06 14:37:09 -05:00
}
2021-11-18 14:37:34 -05:00
static bool GIsDefaultServicePresent = false ;
2021-08-04 14:56:02 -04:00
2021-09-22 13:00:17 -04:00
FZenServiceInstance & GetDefaultServiceInstance ( )
2021-08-04 14:56:02 -04:00
{
2021-09-22 13:00:17 -04:00
static FZenServiceInstance DefaultServiceInstance ;
2021-11-18 14:37:34 -05:00
GIsDefaultServicePresent = true ;
2021-09-22 13:00:17 -04:00
return DefaultServiceInstance ;
}
2021-11-18 14:37:34 -05:00
bool IsDefaultServicePresent ( )
{
return GIsDefaultServicePresent ;
}
2021-10-25 20:05:28 -04:00
FScopeZenService : : FScopeZenService ( )
: FScopeZenService ( FStringView ( ) )
{
}
2021-09-22 13:00:17 -04:00
FScopeZenService : : FScopeZenService ( FStringView InstanceURL )
{
if ( ! InstanceURL . IsEmpty ( ) & & ! InstanceURL . Equals ( TEXT ( " <DefaultInstance> " ) ) )
2021-08-04 14:56:02 -04:00
{
2021-11-18 14:37:34 -05:00
UniqueNonDefaultInstance = MakeUnique < FZenServiceInstance > ( InstanceURL ) ;
2021-09-22 13:00:17 -04:00
ServiceInstance = UniqueNonDefaultInstance . Get ( ) ;
2021-08-04 14:56:02 -04:00
}
2021-09-22 13:00:17 -04:00
else
2021-08-04 14:56:02 -04:00
{
2021-09-22 13:00:17 -04:00
ServiceInstance = & GetDefaultServiceInstance ( ) ;
2021-08-04 14:56:02 -04:00
}
2021-09-22 13:00:17 -04:00
}
2021-08-04 14:56:02 -04:00
2021-11-18 14:37:34 -05:00
FScopeZenService : : FScopeZenService ( FServiceSettings & & InSettings )
2021-09-22 13:00:17 -04:00
{
2021-11-18 14:37:34 -05:00
UniqueNonDefaultInstance = MakeUnique < FZenServiceInstance > ( MoveTemp ( InSettings ) ) ;
2021-10-25 20:05:28 -04:00
ServiceInstance = UniqueNonDefaultInstance . Get ( ) ;
}
FScopeZenService : : ~ FScopeZenService ( )
{ }
FZenServiceInstance : : FZenServiceInstance ( )
2021-11-18 14:37:34 -05:00
: FZenServiceInstance ( FStringView ( ) )
2021-10-25 20:05:28 -04:00
{
}
2021-11-18 14:37:34 -05:00
FZenServiceInstance : : FZenServiceInstance ( FStringView InstanceURL )
2021-10-25 20:05:28 -04:00
{
2021-11-18 14:37:34 -05:00
if ( InstanceURL . IsEmpty ( ) )
2021-10-25 20:05:28 -04:00
{
2021-11-18 14:37:34 -05:00
Settings . ReadFromConfig ( ) ;
2023-01-03 15:58:30 -05:00
if ( Settings . IsAutoLaunch ( ) )
{
// Ensure that the zen data path is inherited by subprocesses
FPlatformMisc : : SetEnvironmentVar ( TEXT ( " UE-ZenSubprocessDataPath " ) , * Settings . SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) . DataPath ) ;
}
2021-10-25 20:05:28 -04:00
}
else
{
2021-11-18 14:37:34 -05:00
Settings . ReadFromURL ( InstanceURL ) ;
2021-10-25 20:05:28 -04:00
}
2021-11-18 14:37:34 -05:00
Initialize ( ) ;
2021-10-25 20:05:28 -04:00
}
2021-11-18 14:37:34 -05:00
FZenServiceInstance : : FZenServiceInstance ( FServiceSettings & & InSettings )
: Settings ( MoveTemp ( InSettings ) )
2021-09-22 13:00:17 -04:00
{
2021-11-18 14:37:34 -05:00
Initialize ( ) ;
2021-08-04 14:56:02 -04:00
}
FZenServiceInstance : : ~ FZenServiceInstance ( )
{
}
2024-01-09 06:51:57 -05:00
const FString FZenServiceInstance : : GetPath ( ) const
{
if ( Settings . IsAutoLaunch ( ) )
{
return Settings . SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) . DataPath ;
}
return GetURL ( ) ;
}
bool
2021-08-04 14:56:02 -04:00
FZenServiceInstance : : IsServiceRunning ( )
{
2021-11-18 14:37:34 -05:00
return ! Settings . IsAutoLaunch ( ) | | bHasLaunchedLocal ;
2021-08-04 14:56:02 -04:00
}
bool
FZenServiceInstance : : IsServiceReady ( )
{
2024-03-12 01:52:39 -04:00
uint32 Attempt = 0 ;
while ( IsServiceRunning ( ) )
2021-09-22 13:00:17 -04:00
{
TStringBuilder < 128 > ZenDomain ;
ZenDomain < < HostName < < TEXT ( " : " ) < < Port ;
Zen : : FZenHttpRequest Request ( ZenDomain . ToString ( ) , false ) ;
2022-02-25 15:48:16 -05:00
Zen : : FZenHttpRequest : : Result Result = Request . PerformBlockingDownload ( TEXTVIEW ( " health/ready " ) , nullptr , Zen : : EContentType : : Text ) ;
2021-09-22 13:00:17 -04:00
if ( Result = = Zen : : FZenHttpRequest : : Result : : Success & & Zen : : IsSuccessCode ( Request . GetResponseCode ( ) ) )
{
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Unreal Zen Storage Server HTTP service at %s status: %s. " ) , ZenDomain . ToString ( ) , * Request . GetResponseAsString ( ) ) ;
2021-09-22 13:00:17 -04:00
return true ;
}
2024-03-12 01:52:39 -04:00
if ( IsServiceRunningLocally ( ) )
2021-09-22 13:00:17 -04:00
{
2024-03-12 01:52:39 -04:00
if ( Attempt > 4 )
2023-11-15 17:17:29 -05:00
{
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unable to reach Unreal Zen Storage Server HTTP service at %s. Status: %d . Response: %s " ) , ZenDomain . ToString ( ) , Request . GetResponseCode ( ) , * Request . GetResponseAsString ( ) ) ;
2024-03-12 01:52:39 -04:00
break ;
2023-11-15 17:17:29 -05:00
}
2021-09-22 13:00:17 -04:00
}
2024-03-12 01:52:39 -04:00
else
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Unable to reach Unreal Zen Storage Server HTTP service at %s. Status: %d . Response: %s " ) , ZenDomain . ToString ( ) , Request . GetResponseCode ( ) , * Request . GetResponseAsString ( ) ) ;
break ;
}
Attempt + + ;
2021-09-22 13:00:17 -04:00
}
2021-08-04 14:56:02 -04:00
return false ;
}
2022-12-16 15:47:14 -05:00
bool
FZenServiceInstance : : TryRecovery ( )
{
if ( ! bHasLaunchedLocal )
{
return false ;
}
2024-10-01 20:41:28 -04:00
const FString ExecutablePath = GetLocalServiceInstallPath ( ) ;
const FString ExecutionContextFilePath = GetServiceRunContextPath ( ) ;
2022-12-16 15:47:14 -05:00
static std : : atomic < int64 > LastRecoveryTicks ;
static bool bLastRecoveryResult = false ;
const FTimespan MaximumWaitForHealth = FTimespan : : FromSeconds ( 30 ) ;
const FTimespan MinimumDurationSinceLastRecovery = FTimespan : : FromMinutes ( 2 ) ;
FTimespan TimespanSinceLastRecovery = FDateTime : : UtcNow ( ) - FDateTime ( LastRecoveryTicks . load ( std : : memory_order_relaxed ) ) ;
if ( TimespanSinceLastRecovery > MinimumDurationSinceLastRecovery )
{
2024-10-01 20:41:28 -04:00
FSystemWideCriticalSection RecoveryCriticalSection ( TEXT ( " ZenServerLaunch " ) , MaximumWaitForHealth ) ;
2024-06-11 11:05:27 -04:00
if ( ! RecoveryCriticalSection . IsValid ( ) )
{
// A recovery is already in progress but did not complete in time, we assume we failed and let recovery continue on a different thread
return false ;
}
2023-08-02 19:28:23 -04:00
2024-06-13 04:03:10 -04:00
// We test if the service is healthy as a different process might already have triggered a recovery
bLastRecoveryResult = IsServiceReady ( ) ;
if ( bLastRecoveryResult )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Local ZenServer status: Healthy. Skipping recovery " ) ) ;
}
else
{
// Update timespan since it may have changed since we waited to enter the crit section
TimespanSinceLastRecovery = FDateTime : : UtcNow ( ) - FDateTime ( LastRecoveryTicks . load ( std : : memory_order_relaxed ) ) ;
if ( TimespanSinceLastRecovery > MinimumDurationSinceLastRecovery )
2022-12-16 15:47:14 -05:00
{
2024-06-13 04:03:10 -04:00
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Local ZenServer recovery being attempted... " ) ) ;
bool bShutdownExistingInstance = true ;
2023-02-21 10:38:15 -05:00
{
2024-06-13 04:03:10 -04:00
const ZenServerState ServerState ( /* ReadOnly */ true ) ;
const ZenServerState : : ZenServerEntry * Entry = ServerState . LookupByEffectiveListenPort ( Port ) ;
if ( Entry )
2023-08-02 19:28:23 -04:00
{
2024-06-13 04:03:10 -04:00
if ( Entry - > Pid . load ( std : : memory_order_relaxed ) ! = AutoLaunchedPid )
{
// The running process pid is not the same as the one we launched. The process was relaunched elsewhere. Avoid shutting it down again.
bShutdownExistingInstance = false ;
}
}
}
if ( bShutdownExistingInstance & & ! ShutdownZenServerProcess ( ( int ) AutoLaunchedPid ) ) // !ShutdownRunningServiceUsingEffectivePort(Port))
{
return false ;
}
2024-10-01 20:41:28 -04:00
AutoLaunch ( Settings . SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) , ExecutablePath , ExecutionContextFilePath , HostName , Port ) ;
2024-06-13 04:03:10 -04:00
FDateTime StartedWaitingForHealth = FDateTime : : UtcNow ( ) ;
bLastRecoveryResult = IsServiceReady ( ) ;
while ( ! bLastRecoveryResult )
{
FTimespan WaitForHealth = FDateTime : : UtcNow ( ) - StartedWaitingForHealth ;
if ( WaitForHealth > MaximumWaitForHealth )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Local ZenServer recovery timed out waiting for service to become healthy " ) ) ;
break ;
2023-08-02 19:28:23 -04:00
}
2024-06-13 04:03:10 -04:00
FPlatformProcess : : Sleep ( 0.5f ) ;
if ( ! IsZenProcessUsingEffectivePort ( Port ) )
{
2024-10-01 20:41:28 -04:00
AutoLaunch ( Settings . SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) , ExecutablePath , ExecutionContextFilePath , HostName , Port ) ;
2024-06-13 04:03:10 -04:00
}
bLastRecoveryResult = IsServiceReady ( ) ;
}
LastRecoveryTicks . store ( FDateTime : : UtcNow ( ) . GetTicks ( ) , std : : memory_order_relaxed ) ;
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Local ZenServer recovery finished. " ) ) ;
if ( bLastRecoveryResult )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Local ZenServer post recovery status: Healthy " ) ) ;
}
else
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Local ZenServer post recovery status: NOT healthy " ) ) ;
}
2022-12-16 15:47:14 -05:00
}
}
}
return bLastRecoveryResult ;
}
2023-08-23 19:05:54 -04:00
bool
FZenServiceInstance : : AddSponsorProcessIDs ( TArrayView < uint32 > SponsorProcessIDs )
{
ZenServerState State ( /*bReadOnly*/ false ) ;
ZenServerState : : ZenServerEntry * Entry = State . LookupByEffectiveListenPort ( Port ) ;
if ( Entry )
{
bool bAllAdded = true ;
for ( uint32 SponsorProcessID : SponsorProcessIDs )
{
if ( ! Entry - > AddSponsorProcess ( SponsorProcessID ) )
{
bAllAdded = false ;
}
}
return bAllAdded ;
}
return false ;
}
2022-02-10 03:30:55 -05:00
uint16
FZenServiceInstance : : GetAutoLaunchedPort ( )
{
return AutoLaunchedPort ;
}
2021-11-18 14:37:34 -05:00
void
FZenServiceInstance : : Initialize ( )
2021-09-22 13:00:17 -04:00
{
2021-11-18 14:37:34 -05:00
if ( Settings . IsAutoLaunch ( ) )
2021-09-22 13:00:17 -04:00
{
2024-10-01 20:41:28 -04:00
uint64 ZenAutoLaunchStartTime = FPlatformTime : : Cycles64 ( ) ;
const FServiceAutoLaunchSettings & AutoLaunchSettings = Settings . SettingsVariant . Get < FServiceAutoLaunchSettings > ( ) ;
bool ServiceIsInstalled = ConditionalUpdateLocalInstall ( AutoLaunchSettings . InstallMode ) ;
if ( ServiceIsInstalled )
2021-09-22 13:00:17 -04:00
{
2024-10-01 20:41:28 -04:00
const FString ExecutablePath = GetLocalServiceInstallPath ( ) ;
const FString ExecutionContextFilePath = GetServiceRunContextPath ( ) ;
2024-03-21 17:55:38 -04:00
int LaunchAttempts = 0 ;
2024-03-19 17:27:44 -04:00
const FTimespan MaximumWaitForHealth = FTimespan : : FromSeconds ( 20 ) ;
FDateTime StartedWaitingForHealth = FDateTime : : UtcNow ( ) ;
while ( true )
2023-02-21 10:38:15 -05:00
{
2023-08-02 19:28:23 -04:00
{
2024-10-01 20:41:28 -04:00
FSystemWideCriticalSection RecoveryCriticalSection ( TEXT ( " ZenServerLaunch " ) , FTimespan : : FromSeconds ( 5 ) ) ;
if ( ! RecoveryCriticalSection . IsValid ( ) )
2024-03-19 17:27:44 -04:00
{
2024-10-01 20:41:28 -04:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Local ZenServer AutoLaunch initialization timed out waiting for other process to complete startup/recovery " ) ) ;
2024-03-19 17:27:44 -04:00
}
2024-10-01 20:41:28 -04:00
else
2024-03-12 01:52:39 -04:00
{
2024-10-01 20:41:28 -04:00
bHasLaunchedLocal = AutoLaunch ( AutoLaunchSettings , ExecutablePath , ExecutionContextFilePath , HostName , Port ) ;
if ( bHasLaunchedLocal )
{
const ZenServerState State ( /*ReadOnly*/ true ) ;
const ZenServerState : : ZenServerEntry * RunningEntry = State . LookupByEffectiveListenPort ( Port ) ;
if ( RunningEntry ! = nullptr )
{
AutoLaunchedPid = RunningEntry - > Pid . load ( std : : memory_order_relaxed ) ;
}
AutoLaunchedPort = Port ;
bIsRunningLocally = true ;
if ( IsServiceReady ( ) )
{
break ;
}
}
2024-03-12 01:52:39 -04:00
}
}
2024-03-19 17:27:44 -04:00
2024-10-01 20:41:28 -04:00
+ + LaunchAttempts ;
2024-03-19 17:27:44 -04:00
FTimespan WaitForHealth = FDateTime : : UtcNow ( ) - StartedWaitingForHealth ;
2024-03-21 17:55:38 -04:00
if ( ( WaitForHealth > MaximumWaitForHealth ) & & ( LaunchAttempts > 1 ) )
2024-03-19 17:27:44 -04:00
{
bHasLaunchedLocal = false ;
bIsRunningLocally = false ;
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Local ZenServer AutoLaunch initialization timed out waiting for service to become healthy " ) ) ;
break ;
}
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Awaiting ZenServer readiness " ) ) ;
FPlatformProcess : : Sleep ( 0.5f ) ;
2023-02-21 10:38:15 -05:00
}
2021-09-22 13:00:17 -04:00
}
2024-10-01 20:41:28 -04:00
double ZenAutoLaunchDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenAutoLaunchStartTime ) ;
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Local ZenServer AutoLaunch initialization completed in %.3lf seconds " ) , ZenAutoLaunchDuration ) ;
2021-09-22 13:00:17 -04:00
}
else
{
2021-11-18 14:37:34 -05:00
const FServiceConnectSettings & ConnectExistingSettings = Settings . SettingsVariant . Get < FServiceConnectSettings > ( ) ;
HostName = ConnectExistingSettings . HostName ;
Port = ConnectExistingSettings . Port ;
2022-01-21 04:24:08 -05:00
bIsRunningLocally = IsLocalHost ( HostName ) ;
2021-09-22 13:00:17 -04:00
}
2021-11-18 14:37:34 -05:00
URL = WriteToString < 64 > ( TEXT ( " http:// " ) , HostName , TEXT ( " : " ) , Port , TEXT ( " / " ) ) ;
2021-09-22 13:00:17 -04:00
}
2023-03-09 11:25:59 -05:00
static void
PromptUserToStopRunningServerInstanceForUpdate ( const FString & ServerFilePath )
2021-09-22 13:00:17 -04:00
{
2023-09-25 11:40:16 -04:00
# if !IS_PROGRAM
2023-12-13 13:39:46 -05:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FText ZenUpdatePromptTitle = NSLOCTEXT ( " Zen " , " Zen_UpdatePromptTitle " , " Update required " ) ;
2024-03-05 11:23:40 -05:00
FText ZenUpdatePromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_UpdatePromptText " , " Unreal Zen Storage Server needs to be updated to a new version. Please shut down Unreal Editor and any tools that are using the ZenServer at '{0}' " ) , FText : : FromString ( ServerFilePath ) ) ;
2023-12-13 13:39:46 -05:00
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenUpdatePromptText . ToString ( ) , * ZenUpdatePromptTitle . ToString ( ) ) ;
}
else
2023-09-25 11:40:16 -04:00
# endif
2021-09-22 13:00:17 -04:00
{
2023-02-21 10:38:15 -05:00
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Unreal Zen Storage Server needs to be updated to a new version. Please shut down any tools that are using the ZenServer at '%s' " ) , * ServerFilePath ) ;
2021-09-22 13:00:17 -04:00
}
}
2023-03-09 11:25:59 -05:00
static void
PromptUserOfLockedDataFolder ( const FString & DataPath )
{
2023-09-25 11:40:16 -04:00
# if !IS_PROGRAM
2023-12-13 13:39:46 -05:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FText ZenLaunchFailurePromptTitle = NSLOCTEXT ( " Zen " , " Zen_NonLocalProcessUsesDataDirPromptTitle " , " Failed to launch " ) ;
2024-03-05 11:23:40 -05:00
FText ZenLaunchFailurePromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_NonLocalProcessUsesDataDirPromptText " , " Unreal Zen Storage Server Failed to auto launch, an unknown process is locking the data folder '{0}' " ) , FText : : FromString ( DataPath ) ) ;
2023-12-13 13:39:46 -05:00
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenLaunchFailurePromptText . ToString ( ) , * ZenLaunchFailurePromptTitle . ToString ( ) ) ;
}
else
2023-09-25 11:40:16 -04:00
# endif
2023-03-09 11:25:59 -05:00
{
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unreal Zen Storage Server Failed to auto launch, an unknown process is locking the data folder '%s' " ) , * DataPath ) ;
2023-03-09 11:25:59 -05:00
}
}
static void
PromptUserOfFailedShutDownOfExistingProcess ( uint16 Port )
{
2023-09-25 11:40:16 -04:00
# if !IS_PROGRAM
2023-12-13 13:39:46 -05:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FText ZenLaunchFailurePromptTitle = NSLOCTEXT ( " Zen " , " Zen_ShutdownFailurePromptTitle " , " Failed to launch " ) ;
2024-03-05 11:23:40 -05:00
FText ZenLaunchFailurePromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_ShutdownFailurePromptText " , " Unreal Zen Storage Server Failed to auto launch, failed to shut down currently running service using port '{0}' " ) , FText : : AsNumber ( Port , & FNumberFormattingOptions : : DefaultNoGrouping ( ) ) ) ;
2023-12-13 13:39:46 -05:00
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenLaunchFailurePromptText . ToString ( ) , * ZenLaunchFailurePromptTitle . ToString ( ) ) ;
}
else
2023-09-25 11:40:16 -04:00
# endif
2023-03-09 11:25:59 -05:00
{
// Just log as there is no one to show a message
2024-03-05 11:23:40 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unreal Zen Storage Server Failed to auto launch, failed to shut down currently running service using port %u " ) , Port ) ;
2023-03-09 11:25:59 -05:00
}
}
2024-10-01 20:41:28 -04:00
bool
FZenServiceInstance : : ConditionalUpdateLocalInstall ( FServiceAutoLaunchSettings : : EInstallMode InstallMode )
2021-09-22 13:00:17 -04:00
{
2024-10-01 20:41:28 -04:00
if ( InstallMode = = FServiceAutoLaunchSettings : : EInstallMode : : Link )
2021-09-22 13:00:17 -04:00
{
2024-10-01 20:41:28 -04:00
const FString LinkPath = GetServiceLinkPath ( ) ;
IFileManager & FileManager = IFileManager : : Get ( ) ;
bool LinkIsValid = false ;
const FZenServiceLink Link = FZenServiceLink : : Read ( LinkPath ) ;
if ( Link )
2021-09-22 13:00:17 -04:00
{
2024-10-01 20:41:28 -04:00
if ( ! FileManager . FileExists ( * Link . ServicePath ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Found service link file '%s' pointing to unreachable service executable '%s' " ) , * LinkPath , * Link . ServicePath ) ;
}
else if ( ! FileManager . FileExists ( * Link . UtilityPath ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Found service link file '%s' pointing to unreachable utility executable '%s' " ) , * LinkPath , * Link . UtilityPath ) ;
}
else
{
LinkIsValid = true ;
}
}
else if ( FileManager . FileExists ( * LinkPath ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Found invalid service link file '%s', ignoring it " ) , * LinkPath ) ;
}
FString InTreeServicePath = GetInTreeServicePath ( ) ;
if ( LinkIsValid & & InTreeServicePath = = Link . ServicePath )
{
// If the running process already points to our executable and we have a valid link file we are good to go
uint32_t Pid = 0 ;
if ( ZenServerState : : FindRunningProcessId ( * InTreeServicePath , & Pid ) )
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Service link '%s' pointing to '%s', version %s is up and running " ) , * LinkPath , * Link . ServicePath , * Link . Version . ToString ( false ) ) ;
return true ;
}
2021-09-22 13:00:17 -04:00
}
2024-10-01 20:41:28 -04:00
FString InTreeUtilityPath = GetInTreeUtilityPath ( ) ;
FZenVersion InTreeVersion ;
if ( ! GetZenVersion ( InTreeUtilityPath , InTreeServicePath , InTreeVersion ) )
2022-12-06 14:37:09 -05:00
{
2024-10-01 20:41:28 -04:00
checkf ( false , TEXT ( " Unable to determine version using zen utility executable path: '%s'. " ) , * InTreeUtilityPath ) ;
return false ;
}
if ( LinkIsValid )
{
if ( Link . Version < InTreeVersion )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Installing service link '%s' to '%s', version %s " ) , * LinkPath , * InTreeServicePath , * InTreeVersion . ToString ( false ) ) ;
}
else
{
// If the instance is running, assume it is valid and up to date
uint32_t Pid = 0 ;
if ( ZenServerState : : FindRunningProcessId ( * Link . ServicePath , & Pid ) )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Service link '%s' pointing to '%s', version %s is up to date and running " ) , * LinkPath , * Link . ServicePath , * Link . Version . ToString ( false ) ) ;
return true ;
}
// Verify that the executable pointed at is runnable and is of matching version
FZenVersion LinkedVersion ;
if ( GetZenVersion ( Link . UtilityPath , Link . ServicePath , LinkedVersion ) )
{
if ( LinkedVersion = = Link . Version )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Service link '%s' pointing to '%s', version %s is up to date " ) , * LinkPath , * Link . ServicePath , * Link . Version . ToString ( false ) ) ;
return true ;
}
else
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Updating service link '%s' to '%s', version %s (link '%s', version %s does not match executable version %s) " ) , * LinkPath , * InTreeServicePath , * InTreeVersion . ToString ( false ) , * Link . ServicePath , * Link . Version . ToString ( false ) , * LinkedVersion . ToString ( false ) ) ;
}
}
else
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Updating service link '%s' to '%s', version %s (link '%s', version %s pointing to invalid executable) " ) , * LinkPath , * InTreeServicePath , * InTreeVersion . ToString ( false ) , * Link . ServicePath , * Link . Version . ToString ( false ) ) ;
}
}
2022-12-06 14:37:09 -05:00
}
2023-02-21 10:38:15 -05:00
2024-10-01 20:41:28 -04:00
FZenServiceLink NewLink { . ServicePath = InTreeServicePath , . UtilityPath = InTreeUtilityPath , . Version = InTreeVersion } ;
if ( ! FZenServiceLink : : Write ( NewLink , LinkPath ) )
2023-02-21 10:38:15 -05:00
{
2024-10-01 20:41:28 -04:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed to update service link file '%s' " ) , * LinkPath ) ;
return false ;
2023-02-21 10:38:15 -05:00
}
2024-10-01 20:41:28 -04:00
FString ServiceCopyInstallPath = GetServiceCopyInstallPath ( ) ;
if ( FileManager . FileExists ( * ServiceCopyInstallPath ) )
{
ShutDownZenServerProcessExecutable ( ServiceCopyInstallPath ) ;
}
2023-02-21 10:38:15 -05:00
2024-10-01 20:41:28 -04:00
TArray < FString > FilesToCleanUp { GetUtilityCopyInstallPath ( ) , ServiceCopyInstallPath , GetInstallVersionCachePath ( ) , GetInstallVersionCachePath ( ) } ;
# if PLATFORM_WINDOWS
FilesToCleanUp . Add ( FPaths : : SetExtension ( FilesToCleanUp [ 0 ] , TEXT ( " pdb " ) ) ) ;
FilesToCleanUp . Add ( FPaths : : SetExtension ( FilesToCleanUp [ 1 ] , TEXT ( " pdb " ) ) ) ;
# endif // PLATFORM_WINDOWS
for ( const FString & FileToCleanUp : FilesToCleanUp )
{
// If zenserver is still running we may fail to clean up a file. Not critical, just try again next startup
if ( ! AttemptFileDeleteWithRetries ( * FileToCleanUp , 1.0 ) )
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Failed cleaning up file {%s} (not critical) " ) , * FileToCleanUp ) ;
}
}
return true ;
2022-06-22 18:18:26 -04:00
}
2024-10-01 20:41:28 -04:00
else if ( InstallMode = = FServiceAutoLaunchSettings : : EInstallMode : : Copy )
{
FString InTreeUtilityPath = GetInTreeUtilityPath ( ) ;
FString InstallUtilityPath = GetUtilityCopyInstallPath ( ) ;
FString InTreeVersionCache = GetInTreeVersionCache ( ) ;
FString InTreeServicePath = GetInTreeServicePath ( ) ;
FString InstallServicePath = GetServiceCopyInstallPath ( ) ;
FString InstallVersionCache = GetInstallVersionCachePath ( ) ;
IFileManager & FileManager = IFileManager : : Get ( ) ;
bool bMainExecutablesUpdated = false ;
if ( IsInstallVersionOutOfDate ( InTreeUtilityPath , InstallUtilityPath , InTreeServicePath , InstallServicePath , InTreeVersionCache , InstallVersionCache ) )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Installing service from '%s' to '%s' " ) , * InTreeServicePath , * InstallServicePath ) ;
if ( ! ShutDownZenServerProcessExecutable ( InstallServicePath ) )
{
PromptUserToStopRunningServerInstanceForUpdate ( InstallServicePath ) ;
return false ;
}
// Even after waiting for the process to shut down we have a tolerance for failure when overwriting the target files
if ( ! AttemptFileCopyWithRetries ( * InstallServicePath , * InTreeServicePath , 5.0 ) )
{
PromptUserToStopRunningServerInstanceForUpdate ( InstallServicePath ) ;
return false ;
}
if ( ! AttemptFileCopyWithRetries ( * InstallUtilityPath , * InTreeUtilityPath , 5.0 ) )
{
PromptUserToStopRunningServerInstanceForUpdate ( InstallServicePath ) ;
return false ;
}
AttemptFileCopyWithRetries ( * InstallVersionCache , * InTreeVersionCache , 1.0 ) ;
bMainExecutablesUpdated = true ;
}
2021-09-22 13:00:17 -04:00
# if PLATFORM_WINDOWS
2024-10-01 20:41:28 -04:00
struct FZenExecutable
2024-04-12 16:11:55 -04:00
{
2024-10-01 20:41:28 -04:00
FString & InTreeFilePath ;
FString & InstallFilePath ;
} ;
const FZenExecutable ZenExecutables [ ] = {
// Service executable (zenserver.exe)
{ InTreeServicePath , InstallServicePath } ,
// Utility executable (zen.exe)
{ InTreeUtilityPath , InstallUtilityPath } ,
} ;
for ( const FZenExecutable & Executable : ZenExecutables )
{
FString InTreeSymbolFilePath = FPaths : : SetExtension ( Executable . InTreeFilePath , TEXT ( " pdb " ) ) ;
FString InstallSymbolFilePath = FPaths : : SetExtension ( Executable . InstallFilePath , TEXT ( " pdb " ) ) ;
if ( FileManager . FileExists ( * InTreeSymbolFilePath ) & & ( bMainExecutablesUpdated | | ! FileManager . FileExists ( * InstallSymbolFilePath ) ) )
{
AttemptFileCopyWithRetries ( * InstallSymbolFilePath , * InTreeSymbolFilePath , 1.0 ) ;
}
2024-04-12 16:11:55 -04:00
}
2021-09-22 13:00:17 -04:00
# endif
2022-06-22 18:18:26 -04:00
2024-10-01 20:41:28 -04:00
FString InTreeCrashpadHandlerFilePath = GetInTreeCrashpadHandlerFilePath ( ) ;
FString InstallCrashpadHandlerFilePath = GetInstallCrashpadHandlerFilePath ( InTreeCrashpadHandlerFilePath ) ;
2022-06-22 18:18:26 -04:00
2024-10-01 20:41:28 -04:00
if ( FileManager . FileExists ( * InTreeCrashpadHandlerFilePath ) & & ( bMainExecutablesUpdated | | ! FileManager . FileExists ( * InstallCrashpadHandlerFilePath ) ) )
{
AttemptFileCopyWithRetries ( * InstallCrashpadHandlerFilePath , * InTreeCrashpadHandlerFilePath , 1.0 ) ;
}
const FString LinkPath = GetServiceLinkPath ( ) ;
if ( FZenServiceLink Link = FZenServiceLink : : Read ( LinkPath ) ; Link )
{
ShutDownZenServerProcessExecutable ( Link . ServicePath ) ;
if ( ! AttemptFileDeleteWithRetries ( * LinkPath , 1.0 ) )
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Failed cleaning up file {%s} (not critical) " ) , * LinkPath ) ;
}
}
return true ;
}
else
{
return false ;
2021-09-22 13:00:17 -04:00
}
}
2021-10-25 20:05:28 -04:00
bool
2024-10-01 20:41:28 -04:00
FZenServiceInstance : : AutoLaunch ( const FServiceAutoLaunchSettings & InSettings , const FString & ExecutablePath , const FString & ExecutionContextFilePath , FString & OutHostName , uint16 & OutPort )
2021-10-25 20:05:28 -04:00
{
2021-11-07 23:43:01 -05:00
IFileManager & FileManager = IFileManager : : Get ( ) ;
2022-01-12 12:05:55 -05:00
const FString LockFilePath = FPaths : : Combine ( InSettings . DataPath , TEXT ( " .lock " ) ) ;
2022-10-18 16:44:32 -04:00
FString WorkingDirectory = FPaths : : GetPath ( ExecutablePath ) ;
2021-10-25 20:05:28 -04:00
2024-10-01 19:30:59 -04:00
ZenLockFileData LockFileState ;
2023-03-09 11:25:59 -05:00
uint64 ZenWaitForRunningProcessReadyStartTime = FPlatformTime : : Cycles64 ( ) ;
2023-03-10 15:57:34 -05:00
while ( IsZenProcessUsingDataDir ( * LockFilePath , & LockFileState ) & & LockFileState . IsValid & & ! LockFileState . IsReady )
2022-01-12 12:05:55 -05:00
{
2023-03-09 11:25:59 -05:00
// Server is starting up, wait for it to get ready
double ZenWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenWaitForRunningProcessReadyStartTime ) ;
if ( ZenWaitDuration > 5.0 )
2022-01-12 12:05:55 -05:00
{
2023-03-09 11:25:59 -05:00
break ;
2022-01-12 12:05:55 -05:00
}
2023-03-09 11:25:59 -05:00
FPlatformProcess : : Sleep ( 0.1f ) ;
2024-10-01 19:30:59 -04:00
LockFileState = ZenLockFileData ( ) ;
2022-01-12 12:05:55 -05:00
}
2024-02-10 13:08:03 -05:00
bool bShutDownExistingInstanceForDataPath = true ;
uint32 ShutdownExistingInstanceForPid = 0 ;
2023-03-09 11:25:59 -05:00
bool bLaunchNewInstance = true ;
2023-03-10 15:57:34 -05:00
if ( LockFileState . IsReady )
2023-03-09 11:25:59 -05:00
{
2024-02-02 11:42:14 -05:00
const ZenServerState State ( /*ReadOnly*/ true ) ;
2024-03-06 16:52:12 -05:00
if ( State . LookupByPid ( LockFileState . ProcessId ) = = nullptr & & IsZenProcessUsingDataDir ( * LockFilePath , nullptr ) )
2023-03-09 11:25:59 -05:00
{
2024-03-06 16:52:12 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Found locked valid lock file '%s' but can't find registered process (Pid: %d), will attempt shut down " ) , * LockFilePath , LockFileState . ProcessId ) ;
bShutDownExistingInstanceForDataPath = true ;
2023-03-09 11:25:59 -05:00
}
else
{
2024-03-06 16:52:12 -05:00
if ( InSettings . bIsDefaultSharedRunContext )
{
FZenLocalServiceRunContext DesiredRunContext ;
DesiredRunContext . Executable = ExecutablePath ;
DesiredRunContext . CommandlineArguments = DetermineCmdLineWithoutTransientComponents ( InSettings , InSettings . DesiredPort ) ;
DesiredRunContext . WorkingDirectory = WorkingDirectory ;
DesiredRunContext . DataPath = InSettings . DataPath ;
DesiredRunContext . bShowConsole = InSettings . bShowConsole ;
FZenLocalServiceRunContext CurrentRunContext ;
bool ReadCurrentContextOK = CurrentRunContext . ReadFromJsonFile ( * ExecutionContextFilePath ) ;
if ( ReadCurrentContextOK & & ( DesiredRunContext = = CurrentRunContext ) )
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found existing instance running on port %u matching our settings, no actions needed " ) , InSettings . DesiredPort ) ;
bLaunchNewInstance = false ;
bShutDownExistingInstanceForDataPath = false ;
}
else
{
FString JsonTcharText ;
{
TSharedRef < TJsonWriter < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > > Writer = TJsonWriterFactory < TCHAR , TPrettyJsonPrintPolicy < TCHAR > > : : Create ( & JsonTcharText ) ;
Writer - > WriteObjectStart ( ) ;
Writer - > WriteObjectStart ( " Current " ) ;
CurrentRunContext . WriteToJson ( * Writer ) ;
Writer - > WriteObjectEnd ( ) ;
Writer - > WriteObjectStart ( " Desired " ) ;
DesiredRunContext . WriteToJson ( * Writer ) ;
Writer - > WriteObjectEnd ( ) ;
Writer - > WriteObjectEnd ( ) ;
Writer - > Close ( ) ;
}
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found existing instance running on port %u with different run context, will attempt shut down \n {%s} " ) , InSettings . DesiredPort , * JsonTcharText ) ;
bShutDownExistingInstanceForDataPath = true ;
bLaunchNewInstance = true ;
}
}
else
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found existing instance running on port %u when not using shared context, will use it " ) , InSettings . DesiredPort ) ;
bShutDownExistingInstanceForDataPath = false ;
bLaunchNewInstance = false ;
}
2023-03-09 11:25:59 -05:00
}
}
2023-03-10 15:57:34 -05:00
else
2023-03-09 11:25:59 -05:00
{
2024-02-10 13:08:03 -05:00
const ZenServerState State ( /*ReadOnly*/ true ) ;
const ZenServerState : : ZenServerEntry * RunningEntry = State . LookupByDesiredListenPort ( InSettings . DesiredPort ) ;
if ( RunningEntry ! = nullptr )
{
// It is necessary to tear down an existing zenserver running on our desired port but in a different data path because:
// 1. zenserver won't accept port collision with itself, and will instead say "Exiting since there is already a process listening to port ..."
// 2. When UE is changing data directories (eg: DDC path config change) we don't want to leave zenservers running on the past directories for no reason
// Unlike other shutdown scenarios, this one can't be done based on our desired data path because the zenserver we want to shut down is running in a different data path
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " Found existing instance running on port %u with different data directory, will attempt shutdown " ) , InSettings . DesiredPort ) ;
ShutdownExistingInstanceForPid = RunningEntry - > Pid ;
}
else
{
UE_LOG ( LogZenServiceInstance , Log , TEXT ( " No current process using the data dir found, launching a new instance " ) ) ;
}
bShutDownExistingInstanceForDataPath = false ;
2024-02-09 04:44:22 -05:00
bLaunchNewInstance = true ;
2023-03-09 11:25:59 -05:00
}
2024-02-10 13:08:03 -05:00
if ( bShutDownExistingInstanceForDataPath )
2022-01-12 12:05:55 -05:00
{
2024-02-09 04:44:22 -05:00
if ( ! ShutDownZenServerProcessLockingDataDir ( InSettings . DataPath ) )
2023-02-13 14:23:19 -05:00
{
2024-02-09 04:44:22 -05:00
PromptUserOfFailedShutDownOfExistingProcess ( InSettings . DesiredPort ) ;
2023-02-23 10:55:10 -05:00
return false ;
}
2023-03-09 11:25:59 -05:00
}
2023-02-23 10:55:10 -05:00
2024-02-10 13:08:03 -05:00
if ( ShutdownExistingInstanceForPid ! = 0 )
{
if ( ! ShutdownZenServerProcess ( ShutdownExistingInstanceForPid ) )
{
PromptUserOfFailedShutDownOfExistingProcess ( InSettings . DesiredPort ) ;
return false ;
}
}
2024-03-12 13:15:49 -04:00
if ( bLaunchNewInstance )
2023-03-09 11:25:59 -05:00
{
2024-02-16 11:15:16 -05:00
if ( InSettings . bIsDefaultDataPath & & InSettings . bIsDefaultSharedRunContext )
{
// See if the default data path is migrating, and if so, clean up after the old one.
// Non-default data paths don't do the same thing because users are free to switch them back and forth
// and expext the contents to remain when they change. Only the default one cleans up after itself
// to avoid a situation wherey the accumulate over time as the default location changes in config.
// This cleanup is best-effort and may fail if an instance is unexpectedly still using the previous path.
EnsureEditorSettingsConfigLoaded ( ) ;
FString InUseDefaultDataPath ;
if ( ! GConfig - > GetString ( TEXT ( " /Script/UnrealEd.ZenServerSettings " ) , TEXT ( " InUseDefaultDataPath " ) , InUseDefaultDataPath , GEditorSettingsIni ) )
{
InUseDefaultDataPath = FPaths : : ConvertRelativePathToFull ( FPaths : : Combine ( FPlatformProcess : : ApplicationSettingsDir ( ) , TEXT ( " Zen \\ Data " ) ) ) ;
}
if ( ! InUseDefaultDataPath . IsEmpty ( ) )
{
const FString InUseLockFilePath = FPaths : : Combine ( InUseDefaultDataPath , TEXT ( " .lock " ) ) ;
if ( ! FPaths : : IsSamePath ( InUseDefaultDataPath , InSettings . DataPath ) & & ! IsZenProcessUsingDataDir ( * InUseLockFilePath , nullptr ) )
{
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Migrating default data path from '%s' to '%s'. Old location will be deleted. " ) , * InUseDefaultDataPath , * InSettings . DataPath ) ;
IFileManager : : Get ( ) . DeleteDirectory ( * InUseDefaultDataPath , false , true ) ;
}
}
}
2023-03-10 15:57:34 -05:00
FString ParmsWithoutTransients = DetermineCmdLineWithoutTransientComponents ( InSettings , InSettings . DesiredPort ) ;
2022-12-06 14:37:09 -05:00
FString TransientParms ;
2021-11-18 14:37:34 -05:00
2022-01-19 18:28:03 -05:00
if ( InSettings . bLimitProcessLifetime )
2021-11-18 14:37:34 -05:00
{
2022-12-06 14:37:09 -05:00
TransientParms . Appendf ( TEXT ( " --owner-pid %d " ) , FPlatformProcess : : GetCurrentProcessId ( ) ) ;
2021-11-18 14:37:34 -05:00
}
2022-12-06 14:37:09 -05:00
FZenLocalServiceRunContext EffectiveRunContext ;
EffectiveRunContext . Executable = ExecutablePath ;
EffectiveRunContext . CommandlineArguments = ParmsWithoutTransients ;
EffectiveRunContext . WorkingDirectory = WorkingDirectory ;
EffectiveRunContext . DataPath = InSettings . DataPath ;
EffectiveRunContext . bShowConsole = InSettings . bShowConsole ;
2021-11-18 14:37:34 -05:00
2024-10-01 19:30:59 -04:00
FString StartupEventName = ZenSharedEvent : : GetStartupEventName ( ) ;
ZenSharedEvent StartupEvent ( StartupEventName ) ;
if ( ! StartupEvent . Create ( ) )
{
return false ;
}
TransientParms . Appendf ( TEXT ( " --child-id %s " ) , * StartupEventName ) ;
2022-12-06 14:37:09 -05:00
FProcHandle Proc = StartLocalService ( EffectiveRunContext , TransientParms . IsEmpty ( ) ? nullptr : * TransientParms ) ;
2023-03-09 11:25:59 -05:00
2024-10-01 19:30:59 -04:00
if ( Proc . IsValid ( ) )
2023-03-09 11:25:59 -05:00
{
2024-10-01 19:30:59 -04:00
// Only write run context if we're using the default shared run context
if ( InSettings . bIsDefaultSharedRunContext )
{
EffectiveRunContext . WriteToJsonFile ( * ExecutionContextFilePath ) ;
}
2023-02-21 10:38:15 -05:00
2024-10-01 19:30:59 -04:00
bool ZenServerIsReady = false ;
FScopedSlowTask WaitForZenReadySlowTask ( 0 , NSLOCTEXT ( " Zen " , " Zen_WaitingForReady " , " Waiting for ZenServer to be ready " ) ) ;
uint64 ZenWaitStartTime = FPlatformTime : : Cycles64 ( ) ;
enum class EWaitDurationPhase
{
Short ,
Medium ,
Long
} DurationPhase = EWaitDurationPhase : : Short ;
while ( FPlatformProcess : : IsProcRunning ( Proc ) )
{
if ( StartupEvent . Wait ( 5000 ) )
{
ZenServerIsReady = true ;
break ;
}
double ZenWaitDuration = FPlatformTime : : ToSeconds64 ( FPlatformTime : : Cycles64 ( ) - ZenWaitStartTime ) ;
2024-10-01 19:31:04 -04:00
if ( ZenWaitDuration > = 10.0 )
2024-10-01 19:30:59 -04:00
{
if ( DurationPhase = = EWaitDurationPhase : : Short )
{
2024-10-01 19:31:04 -04:00
if ( ! FPlatformProcess : : IsProcRunning ( Proc ) )
{
2024-10-01 19:30:59 -04:00
# if !IS_PROGRAM
2024-10-01 19:31:04 -04:00
if ( ! FApp : : IsUnattended ( ) & & ! IsRunningCommandlet ( ) & & ! GIsRunningUnattendedScript )
{
FText ZenLaunchFailurePromptTitle = NSLOCTEXT ( " Zen " , " Zen_LaunchFailurePromptTitle " , " Failed to launch " ) ;
2024-10-01 19:30:59 -04:00
2024-10-01 19:31:04 -04:00
FFormatNamedArguments FormatArguments ;
FString LogFilePath = FPaths : : Combine ( InSettings . DataPath , TEXT ( " logs " ) , TEXT ( " zenserver.log " ) ) ;
FPaths : : MakePlatformFilename ( LogFilePath ) ;
FormatArguments . Add ( TEXT ( " LogFilePath " ) , FText : : FromString ( LogFilePath ) ) ;
FText ZenLaunchFailurePromptText = FText : : Format ( NSLOCTEXT ( " Zen " , " Zen_LaunchFailurePromptText " , " Unreal Zen Storage Server failed to launch. Please check the ZenServer log file for details: \n {LogFilePath} " ) , FormatArguments ) ;
FPlatformMisc : : MessageBoxExt ( EAppMsgType : : Ok , * ZenLaunchFailurePromptText . ToString ( ) , * ZenLaunchFailurePromptTitle . ToString ( ) ) ;
break ;
}
else
2024-10-01 19:30:59 -04:00
# endif
2024-10-01 19:31:04 -04:00
{
// Just log as there is no one to show a message
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Unreal Zen Storage Server did not launch in the expected duration " ) ) ;
break ;
}
2024-10-01 19:30:59 -04:00
}
2024-10-01 19:31:04 -04:00
2024-10-01 19:30:59 -04:00
// Note that the dialog may not show up when zenserver is needed early in the launch cycle, but this will at least ensure
// the splash screen is refreshed with the appropriate text status message.
WaitForZenReadySlowTask . MakeDialog ( true , false ) ;
UE_LOG ( LogZenServiceInstance , Display , TEXT ( " Waiting for ZenServer to be ready... " ) ) ;
DurationPhase = EWaitDurationPhase : : Medium ;
}
# if !IS_PROGRAM
else if ( ! ( FApp : : IsUnattended ( ) | | IsRunningCommandlet ( ) | | GIsRunningUnattendedScript ) & & ZenWaitDuration > 20.0 & & ( DurationPhase = = EWaitDurationPhase : : Medium ) )
{
FText ZenLongWaitPromptTitle = NSLOCTEXT ( " Zen " , " Zen_LongWaitPromptTitle " , " Wait for ZenServer? " ) ;
FText ZenLongWaitPromptText = NSLOCTEXT ( " Zen " , " Zen_LongWaitPromptText " , " Unreal Zen Storage Server is taking a long time to launch. It may be performing maintenance. Keep waiting? " ) ;
if ( FPlatformMisc : : MessageBoxExt ( EAppMsgType : : YesNo , * ZenLongWaitPromptText . ToString ( ) , * ZenLongWaitPromptTitle . ToString ( ) ) = = EAppReturnType : : No )
{
break ;
}
DurationPhase = EWaitDurationPhase : : Long ;
}
# endif
if ( WaitForZenReadySlowTask . ShouldCancel ( ) )
{
break ;
}
}
}
if ( ! ZenServerIsReady )
{
if ( FPlatformProcess : : IsProcRunning ( Proc ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Terminating unresponsive process for executable '%s' on port %u " ) , * ExecutablePath , InSettings . DesiredPort ) ;
FPlatformProcess : : TerminateProc ( Proc , true ) ;
}
}
}
else
2022-01-12 12:05:55 -05:00
{
2023-03-10 15:57:34 -05:00
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed launch service using executable '%s' on port %u " ) , * ExecutablePath , InSettings . DesiredPort ) ;
2023-02-21 10:38:15 -05:00
return false ;
2022-01-12 12:05:55 -05:00
}
2021-11-18 14:37:34 -05:00
}
2023-03-10 15:57:34 -05:00
else if ( InSettings . bLimitProcessLifetime )
{
ZenServerState State ( /*ReadOnly*/ false ) ;
ZenServerState : : ZenServerEntry * RunningEntry = State . LookupByDesiredListenPort ( InSettings . DesiredPort ) ;
if ( RunningEntry = = nullptr )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed attach as sponsor process to executable '%s' on port %u, can't find entry in shared state " ) , * ExecutablePath , InSettings . DesiredPort ) ;
}
else if ( ! RunningEntry - > AddSponsorProcess ( FPlatformProcess : : GetCurrentProcessId ( ) ) )
{
UE_LOG ( LogZenServiceInstance , Warning , TEXT ( " Failed attach as sponsor process to executable '%s' on port %u, to many sponsored processes attached already " ) , * ExecutablePath , InSettings . DesiredPort ) ;
}
}
2021-09-22 13:00:17 -04:00
2024-02-16 11:15:16 -05:00
if ( InSettings . bIsDefaultDataPath & & InSettings . bIsDefaultSharedRunContext )
{
GConfig - > SetString ( TEXT ( " /Script/UnrealEd.ZenServerSettings " ) , TEXT ( " InUseDefaultDataPath " ) , * InSettings . DataPath , GEditorSettingsIni ) ;
}
2022-02-14 12:19:59 -05:00
OutHostName = TEXT ( " [::1] " ) ;
2021-11-07 23:43:01 -05:00
// Default to assuming that we get to run on the port we want
2023-03-10 15:57:34 -05:00
OutPort = InSettings . DesiredPort ;
2021-11-07 23:43:01 -05:00
2024-10-01 19:30:59 -04:00
ZenLockFileData RunningLockFileState = ZenLockFileData : : ReadCbLockFile ( * LockFilePath ) ;
if ( ! RunningLockFileState . IsValid )
2021-11-07 23:43:01 -05:00
{
2024-10-01 19:30:59 -04:00
return false ;
2021-11-07 23:43:01 -05:00
}
2024-10-01 19:30:59 -04:00
if ( ! RunningLockFileState . IsReady )
{
return false ;
}
OutPort = RunningLockFileState . EffectivePort ;
return true ;
2021-09-22 13:00:17 -04:00
}
2021-10-12 21:21:22 -04:00
bool
2023-08-31 22:30:53 -04:00
FZenServiceInstance : : GetCacheStats ( FZenCacheStats & Stats )
2021-10-12 21:21:22 -04:00
{
2024-01-09 10:20:00 -05:00
UE : : Zen : : FZenHttpRequest * Request = nullptr ;
2022-02-14 12:19:59 -05:00
{
2024-01-09 10:20:00 -05:00
TUniqueLock Lock ( LastCacheStatsMutex ) ;
// If we've already requested stats and they are ready then grab them
if ( CacheStatsRequest . IsReady ( ) = = true )
{
LastCacheStats = CacheStatsRequest . Get ( ) ;
LastCacheStatsTime = FPlatformTime : : Cycles64 ( ) ;
2022-03-01 07:20:31 -05:00
2024-01-09 10:20:00 -05:00
CacheStatsRequest . Reset ( ) ;
}
2022-03-01 07:20:31 -05:00
2024-01-09 10:20:00 -05:00
// Make a copy of the last updated stats
Stats = LastCacheStats ;
2022-02-14 12:19:59 -05:00
2024-01-09 10:20:00 -05:00
const uint64 CurrentTime = FPlatformTime : : Cycles64 ( ) ;
constexpr double MinTimeBetweenRequestsInSeconds = 0.5 ;
const double DeltaTimeInSeconds = FPlatformTime : : ToSeconds64 ( CurrentTime - LastCacheStatsTime ) ;
if ( CacheStatsRequest . IsValid ( ) | | DeltaTimeInSeconds < = MinTimeBetweenRequestsInSeconds )
{
return Stats . bIsValid ;
}
2022-03-01 07:20:31 -05:00
2023-08-31 22:30:53 -04:00
if ( ! CacheStatsHttpRequest . IsValid ( ) )
2022-03-02 22:10:10 -05:00
{
TStringBuilder < 128 > ZenDomain ;
ZenDomain < < HostName < < TEXT ( " : " ) < < Port ;
2023-08-31 22:30:53 -04:00
CacheStatsHttpRequest = MakePimpl < FZenHttpRequest > ( ZenDomain . ToString ( ) , false ) ;
2022-03-02 22:10:10 -05:00
}
2024-01-09 10:20:00 -05:00
Request = CacheStatsHttpRequest . Get ( ) ;
}
# if WITH_EDITOR
EAsyncExecution ThreadPool = EAsyncExecution : : LargeThreadPool ;
# else
EAsyncExecution ThreadPool = EAsyncExecution : : ThreadPool ;
# endif
2022-03-01 07:20:31 -05:00
// We've not got any requests in flight and we've met a given time requirement for requests
2024-01-09 10:20:00 -05:00
CacheStatsRequest = Async ( ThreadPool , [ Request ]
2024-01-18 22:43:41 -05:00
{
2024-01-09 10:20:00 -05:00
check ( Request ! = nullptr ) ;
Request - > Reset ( ) ;
2023-08-31 22:30:53 -04:00
2024-01-18 22:43:41 -05:00
TArray64 < uint8 > GetBuffer ;
2024-01-09 10:20:00 -05:00
FZenHttpRequest : : Result Result = Request - > PerformBlockingDownload ( TEXTVIEW ( " /stats/z$ " ) , & GetBuffer , Zen : : EContentType : : CbObject ) ;
2023-08-31 22:30:53 -04:00
2024-01-18 22:43:41 -05:00
FZenCacheStats Stats ;
2023-08-31 22:30:53 -04:00
2024-01-09 10:20:00 -05:00
if ( Result = = Zen : : FZenHttpRequest : : Result : : Success & & Request - > GetResponseCode ( ) = = 200 )
2024-01-18 22:43:41 -05:00
{
FCbFieldView RootView ( GetBuffer . GetData ( ) ) ;
Stats . bIsValid = LoadFromCompactBinary ( RootView , Stats ) ;
}
2021-10-12 21:21:22 -04:00
2024-01-18 22:43:41 -05:00
return Stats ;
} ) ;
2021-10-12 21:21:22 -04:00
2023-08-31 22:30:53 -04:00
return Stats . bIsValid ;
}
bool
FZenServiceInstance : : GetProjectStats ( FZenProjectStats & Stats )
{
2024-01-09 10:20:00 -05:00
UE : : Zen : : FZenHttpRequest * Request = nullptr ;
2023-08-31 22:30:53 -04:00
{
2024-01-09 10:20:00 -05:00
TUniqueLock Lock ( LastProjectStatsMutex ) ;
// If we've already requested stats and they are ready then grab them
if ( ProjectStatsRequest . IsReady ( ) = = true )
{
LastProjectStats = ProjectStatsRequest . Get ( ) ;
LastProjectStatsTime = FPlatformTime : : Cycles64 ( ) ;
2023-08-31 22:30:53 -04:00
2024-01-09 10:20:00 -05:00
ProjectStatsRequest . Reset ( ) ;
}
2023-08-31 22:30:53 -04:00
2024-01-09 10:20:00 -05:00
// Make a copy of the last updated stats
Stats = LastProjectStats ;
2023-08-31 22:30:53 -04:00
2024-01-09 10:20:00 -05:00
const uint64 CurrentTime = FPlatformTime : : Cycles64 ( ) ;
constexpr double MinTimeBetweenRequestsInSeconds = 0.5 ;
const double DeltaTimeInSeconds = FPlatformTime : : ToSeconds64 ( CurrentTime - LastProjectStatsTime ) ;
2023-08-31 22:30:53 -04:00
2024-01-09 10:20:00 -05:00
if ( ProjectStatsRequest . IsValid ( ) | | DeltaTimeInSeconds < = MinTimeBetweenRequestsInSeconds )
{
return Stats . bIsValid ;
}
2023-08-31 22:30:53 -04:00
if ( ! ProjectStatsHttpRequest . IsValid ( ) )
{
TStringBuilder < 128 > ZenDomain ;
ZenDomain < < HostName < < TEXT ( " : " ) < < Port ;
ProjectStatsHttpRequest = MakePimpl < FZenHttpRequest > ( ZenDomain . ToString ( ) , false ) ;
}
2024-01-09 10:20:00 -05:00
Request = ProjectStatsHttpRequest . Get ( ) ;
}
2023-08-31 22:30:53 -04:00
2024-01-09 10:20:00 -05:00
# if WITH_EDITOR
EAsyncExecution ThreadPool = EAsyncExecution : : LargeThreadPool ;
# else
EAsyncExecution ThreadPool = EAsyncExecution : : ThreadPool ;
# endif
// We've not got any requests in flight and we've met a given time requirement for requests
ProjectStatsRequest = Async ( ThreadPool , [ Request ]
{
check ( Request ) ;
Request - > Reset ( ) ;
2023-08-31 22:30:53 -04:00
TArray64 < uint8 > GetBuffer ;
2024-01-09 10:20:00 -05:00
FZenHttpRequest : : Result Result = Request - > PerformBlockingDownload ( TEXTVIEW ( " /stats/prj " ) , & GetBuffer , Zen : : EContentType : : CbObject ) ;
2023-08-31 22:30:53 -04:00
FZenProjectStats Stats ;
2024-01-09 10:20:00 -05:00
if ( Result = = Zen : : FZenHttpRequest : : Result : : Success & & Request - > GetResponseCode ( ) = = 200 )
2023-08-31 22:30:53 -04:00
{
FCbFieldView RootView ( GetBuffer . GetData ( ) ) ;
Stats . bIsValid = LoadFromCompactBinary ( RootView , Stats ) ;
}
return Stats ;
} ) ;
return Stats . bIsValid ;
2021-10-12 21:21:22 -04:00
}
2023-01-05 19:52:49 -05:00
bool
FZenServiceInstance : : GetGCStatus ( FGCStatus & Status )
{
check ( IsInGameThread ( ) ) ;
// If we've already requested status and it is ready then grab it
if ( GCStatusRequest . IsReady ( ) = = true )
{
LastGCStatus = GCStatusRequest . Get ( ) ;
LastGCStatusTime = FPlatformTime : : Cycles64 ( ) ;
GCStatusRequest . Reset ( ) ;
}
// Make a copy of the last updated status
if ( LastGCStatus . IsSet ( ) )
{
Status = LastGCStatus . GetValue ( ) ;
}
const uint64 CurrentTime = FPlatformTime : : Cycles64 ( ) ;
constexpr double MinTimeBetweenRequestsInSeconds = 0.5 ;
const double DeltaTimeInSeconds = FPlatformTime : : ToSeconds64 ( CurrentTime - LastGCStatusTime ) ;
if ( ! GCStatusRequest . IsValid ( ) & & DeltaTimeInSeconds > MinTimeBetweenRequestsInSeconds )
{
# if WITH_EDITOR
EAsyncExecution ThreadPool = EAsyncExecution : : LargeThreadPool ;
# else
EAsyncExecution ThreadPool = EAsyncExecution : : ThreadPool ;
# endif
if ( ! GCStatusHttpRequest . IsValid ( ) )
{
TStringBuilder < 128 > ZenDomain ;
ZenDomain < < HostName < < TEXT ( " : " ) < < Port ;
GCStatusHttpRequest = MakePimpl < FZenHttpRequest > ( ZenDomain . ToString ( ) , false ) ;
}
// We've not got any requests in flight and we've met a given time requirement for requests
GCStatusRequest = Async ( ThreadPool , [ this ]
{
UE : : Zen : : FZenHttpRequest & Request = * GCStatusHttpRequest . Get ( ) ;
Request . Reset ( ) ;
TArray64 < uint8 > GetBuffer ;
FZenHttpRequest : : Result Result = Request . PerformBlockingDownload ( TEXTVIEW ( " /admin/gc " ) , & GetBuffer , Zen : : EContentType : : CbObject ) ;
TOptional < FGCStatus > GCStatus ;
if ( Result = = Zen : : FZenHttpRequest : : Result : : Success & & Request . GetResponseCode ( ) = = 200 )
{
FCbObjectView RootObjectView ( GetBuffer . GetData ( ) ) ;
GCStatus . Emplace ( ) ;
GCStatus - > Description = FString ( RootObjectView [ " Status " ] . AsString ( ) ) ;
}
return GCStatus ;
} ) ;
}
return LastGCStatus . IsSet ( ) ;
}
bool
FZenServiceInstance : : RequestGC ( const bool * OverrideCollectSmallObjects , const uint32 * OverrideMaxCacheDuration )
{
TStringBuilder < 128 > ZenDomain ;
ZenDomain < < HostName < < TEXT ( " : " ) < < Port ;
UE : : Zen : : FZenHttpRequest Request ( ZenDomain . ToString ( ) , false ) ;
TCHAR Separators [ ] = { TEXT ( ' ? ' ) , TEXT ( ' & ' ) } ;
int32 SeparatorIndex = 0 ;
TStringBuilder < 128 > Query ;
Query < < TEXTVIEW ( " /admin/gc " ) ;
if ( OverrideCollectSmallObjects )
{
2023-04-28 16:01:26 -04:00
Query < < Separators [ SeparatorIndex ] < < TEXT ( " smallobjects= " ) < < LexToString ( * OverrideCollectSmallObjects ) ;
2023-01-05 19:52:49 -05:00
SeparatorIndex = FMath : : Min ( SeparatorIndex + 1 , ( int32 ) UE_ARRAY_COUNT ( Separators ) ) ;
}
if ( OverrideMaxCacheDuration )
{
2023-04-28 16:01:26 -04:00
Query < < Separators [ SeparatorIndex ] < < TEXT ( " maxcacheduration= " ) < < LexToString ( * OverrideMaxCacheDuration ) ;
2023-01-05 19:52:49 -05:00
SeparatorIndex = FMath : : Min ( SeparatorIndex + 1 , ( int32 ) UE_ARRAY_COUNT ( Separators ) ) ;
}
FZenHttpRequest : : Result Result = Request . PerformBlockingPost ( Query . ToString ( ) , FMemoryView ( ) ) ;
if ( Result = = Zen : : FZenHttpRequest : : Result : : Success & & Request . GetResponseCode ( ) = = 200 )
{
FCbObjectView ResponseObject = FCbObjectView ( Request . GetResponseBuffer ( ) . GetData ( ) ) ;
FUtf8StringView ResponseStatus = ResponseObject [ " status " ] . AsString ( ) ;
return ( ResponseStatus = = " Started " ) | | ( ResponseStatus = = " Running " ) ;
}
return false ;
}
2023-01-11 14:28:01 -05:00
bool
FZenServiceInstance : : GatherAnalytics ( TArray < FAnalyticsEventAttribute > & Attributes )
{
2023-09-20 02:58:55 -04:00
FZenCacheStats ZenCacheStats ;
FZenProjectStats ZenProjectStats ;
2023-01-11 14:28:01 -05:00
2023-09-20 02:58:55 -04:00
if ( GetCacheStats ( ZenCacheStats ) = = false )
return false ;
if ( GetProjectStats ( ZenProjectStats ) = = false )
2023-01-11 14:28:01 -05:00
return false ;
2023-03-23 13:43:00 -04:00
const FString BaseName = TEXT ( " Zen_ " ) ;
2023-01-11 14:28:01 -05:00
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Enabled " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . bIsValid & & ZenProjectStats . bIsValid ) ;
2023-01-11 14:28:01 -05:00
}
2023-09-20 02:58:55 -04:00
///////////// Cache
2023-01-11 14:28:01 -05:00
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cache_Size_Disk " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . Size . Disk ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cache_Size_Memory " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . Size . Memory ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Hits " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . Hits ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Misses " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . Misses ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Writes " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . Writes ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_HitRatio " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . HitRatio ) ;
2023-01-11 14:28:01 -05:00
}
2023-09-20 02:58:55 -04:00
{
FString AttrName = BaseName + TEXT ( " Cache_Cas_Hits " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . CidHits ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Cas_Misses " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . CidMisses ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Cas_Writes " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . CidWrites ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Requests " ) ;
2023-10-20 16:45:54 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Request . Count ) ;
2023-09-20 02:58:55 -04:00
}
{
FString AttrName = BaseName + TEXT ( " Cache_BadRequests " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . General . BadRequestCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Requests_Count " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Request . Count ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Requests_RateMean " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Request . RateMean ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Requests_TAverage " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Request . TAverage ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Requests_TMin " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Request . TMin ) ;
}
{
FString AttrName = BaseName + TEXT ( " Cache_Requests_TMax " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Request . TMax ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cache_TotalUploadedMB " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Upstream . TotalUploadedMB ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Upstream_TotalDownloadedMB " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Upstream . TotalDownloadedMB ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Upstream_TotalUploadedMB " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . Upstream . TotalUploadedMB ) ;
}
{
FString AttrName = BaseName + TEXT ( " Upstream_Requests_Count " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . UpstreamRequest . Count ) ;
}
{
FString AttrName = BaseName + TEXT ( " Upstream_Requests_RateMean " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . UpstreamRequest . RateMean ) ;
}
{
FString AttrName = BaseName + TEXT ( " Upstream_Requests_TAverage " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . UpstreamRequest . TAverage ) ;
}
{
FString AttrName = BaseName + TEXT ( " Upstream_Requests_TMin " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . UpstreamRequest . TMin ) ;
}
{
FString AttrName = BaseName + TEXT ( " Upstream_Requests_TMax " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . UpstreamRequest . TMax ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cas_Size_Large " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . CID . Size . Large ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cas_Size_Small " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . CID . Size . Small ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cas_Size_Tiny " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . CID . Size . Tiny ) ;
2023-01-11 14:28:01 -05:00
}
{
2023-03-23 13:43:00 -04:00
FString AttrName = BaseName + TEXT ( " Cas_Size_Total " ) ;
2023-09-20 02:58:55 -04:00
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenCacheStats . CID . Size . Total ) ;
}
///////////// Project
{
FString AttrName = BaseName + TEXT ( " Project_Size_Disk " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Size . Disk ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Size_Memory " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Size . Memory ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_WriteCount " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Project . WriteCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_ReadCount " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Project . ReadCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_DeleteCount " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Project . DeleteCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Oplog_WriteCount " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Oplog . WriteCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Oplog_ReadCount " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Oplog . ReadCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Oplog_DeleteCount " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Oplog . DeleteCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Op_Hits " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Op . HitCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Op_Misses " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Op . MissCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Op_Writes " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Op . WriteCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Chunk_Hits " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Chunk . HitCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Chunk_Misses " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Chunk . MissCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Chunk_Writes " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . Chunk . WriteCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Requests " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . RequestCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_BadRequests " ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , ZenProjectStats . General . BadRequestCount ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Op_HitRatio " ) ;
double Total = static_cast < double > ( ZenProjectStats . General . Op . HitCount + ZenProjectStats . General . Op . MissCount ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , Total > 0 ? static_cast < double > ( ZenProjectStats . General . Op . HitCount ) / Total : 0.0 ) ;
}
{
FString AttrName = BaseName + TEXT ( " Project_Chunk_HitRatio " ) ;
double Total = static_cast < double > ( ZenProjectStats . General . Chunk . HitCount + ZenProjectStats . General . Chunk . MissCount ) ;
Attributes . Emplace ( MoveTemp ( AttrName ) , Total > 0 ? static_cast < double > ( ZenProjectStats . General . Chunk . HitCount ) / Total : 0.0 ) ;
2023-01-11 14:28:01 -05:00
}
return true ;
}
2021-11-18 14:37:34 -05:00
# endif // UE_WITH_ZEN
2021-10-12 21:21:22 -04:00
2021-09-22 13:00:17 -04:00
}