Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/Common/Public/UbaScheduler.h
2025-05-18 13:04:45 +08:00

201 lines
7.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UbaMemory.h"
#include "UbaProcessStartInfo.h"
#include "UbaThread.h"
namespace uba
{
class CacheClient;
class Config;
class ConfigTable;
class Process;
class RootPaths;
class SessionServer;
struct NextProcessInfo;
struct ProcessStartInfoHolder;
struct SchedulerCreateInfo
{
SchedulerCreateInfo(SessionServer& s) : session(s) {}
void Apply(const Config& config);
SessionServer& session;
CacheClient** cacheClients = nullptr; // Set cache clients for scheduler to use when building
u32 cacheClientCount = 0;
u32 maxLocalProcessors = ~0u; // Max local processors to use. ~0u means it will use all processors
bool enableProcessReuse = false; // If this is true, the system will allow processes to be reused when they're asking for it.
bool forceRemote = false; // Force all processes that can run remotely to run remotely.
bool forceNative = false; // Force all processes to run native (not detoured)
bool writeToCache = false; // Set to true in combination with setting cacheClient to populate cache
ConfigTable* processConfigs = nullptr;
};
struct EnqueueProcessInfo
{
EnqueueProcessInfo(const ProcessStartInfo& i) : info(i) {}
const ProcessStartInfo& info;
float weight = 1.0f; // Weight of process. This is used towards max local processors. If a process is multithreaded it is likely it's weight should be more than 1.0
bool canDetour = true; // If true, uba will detour the process. If false it will just create pipes for std out and then run the process as-is.
bool canExecuteRemotely = true; // If true, this process can run on other machines, if false it will always be executed locally
const void* knownInputs = nullptr; // knownInputs is a memory block with null terminated tchar strings followed by an empty null terminated string to end.
u32 knownInputsBytes = 0; // knownInputsBytes is the total size in bytes of knownInputs
u32 knownInputsCount = 0; // knownInputsCount is the number of strings in the memory block
const u32* dependencies = nullptr; // An array of u32 holding indicies to processes this process depends on. Index is a rolling number returned by EnqueueProcess
u32 dependencyCount = 0; // Number of elements in dependencies
u32 cacheBucketId = 0; // Bucket that cache should be fetched from. Zero means that it will not fetch anything
};
class Scheduler
{
public:
Scheduler(const SchedulerCreateInfo& info);
~Scheduler();
void Start(); // Start scheduler thread. Should be called before server starts listen to connections if using remote help
void Stop(); // Will wait on all active processes and then exit.
void Cancel(); // Cancel and wait
void SetMaxLocalProcessors(u32 maxLocalProcessors); // Set max local processes
void SetAllowDisableRemoteExecution(bool allow); // Allow scheduler to tell clients to disconnect early if running out of processes
u32 EnqueueProcess(const EnqueueProcessInfo& info); // Returns index of process. Index is a rolling number
void GetStats(u32& outQueued, u32& outActiveLocal, u32& outActiveRemote, u32& outFinished); // This is not "threadsafe".. which means that you are not guaranteed to get the exact correct state.
bool IsEmpty(); // Returns true if scheduler is entirely empty.. as in no processes are left in the system
bool EnqueueFromFile(const tchar* yamlFilename, const Function<void(EnqueueProcessInfo&)>& enqueued = {}); // Enqueue actions from file. Example of format of file is at the end of this file
bool EnqueueFromSpecialJson(const tchar* jsonFilename, const tchar* workingDir, const tchar* description, RootsHandle rootsHandle, void* userData = nullptr);
void SetProcessFinishedCallback(const Function<void(const ProcessHandle&)>& processFinished); // Set callback
SessionServer& GetSession() { return m_session; }
u32 GetProcessCountThatCanRunRemotelyNow();
float GetProcessWeightThatCanRunRemotelyNow();
private:
struct ExitProcessInfo;
struct ProcessStartInfo2;
enum ProcessStatus : u8
{
ProcessStatus_QueuedForCache,
ProcessStatus_QueuedForRun,
ProcessStatus_Running,
ProcessStatus_Success,
ProcessStatus_Failed,
ProcessStatus_Skipped,
};
void ThreadLoop();
void SkipAllQueued();
void Cleanup();
void RemoteProcessReturned(Process& process);
void HandleCacheMissed(u32 processIndex);
void RemoteSlotAvailable(bool isCrossArchitecture);
void ProcessExited(ExitProcessInfo* info, const ProcessHandle& handle);
u32 PopProcess(bool isLocal, ProcessStatus& outPrevStatus);
bool RunQueuedProcess(bool isLocal);
bool HandleReuseMessage(Process& process, NextProcessInfo& outNextProcess, u32 prevExitCode);
void ExitProcess(ExitProcessInfo& info, Process& process, u32 exitCode, bool fromCache);
void SkipProcess(ProcessStartInfo2& info);
void UpdateQueueCounter(int offset);
void UpdateActiveProcessCounter(bool isLocal, int offset);
void FinishProcess(const ProcessHandle& handle);
SessionServer& m_session;
u32 m_maxLocalProcessors;
struct ProcessEntry
{
ProcessStartInfo2* info;
u32* dependencies;
u32 dependencyCount;
ProcessStatus status;
bool canDetour;
bool canExecuteRemotely;
};
ReaderWriterLock m_processEntriesLock;
Vector<ProcessEntry> m_processEntries;
u32 m_processEntriesStart = 0;
Function<void(const ProcessHandle&)> m_processFinished;
Event m_updateThreadLoop;
Thread m_thread;
Atomic<bool> m_loop;
bool m_enableProcessReuse;
bool m_forceRemote;
bool m_forceNative;
bool m_allowDisableRemoteExecution = false;
ConfigTable* m_processConfigs = nullptr;
float m_activeLocalProcessWeight = 0.0f;
u32 m_activeCacheQueries = 0;
Atomic<u32> m_totalProcesses;
Atomic<u32> m_queuedProcesses;
Atomic<u32> m_activeLocalProcesses;
Atomic<u32> m_activeRemoteProcesses;
Atomic<u32> m_finishedProcesses;
Atomic<u32> m_errorCount;
Atomic<u32> m_cacheHitCount;
Atomic<u32> m_cacheMissCount;
Vector<CacheClient*> m_cacheClients;
Vector<RootPaths*> m_rootPaths;
bool m_writeToCache;
Scheduler(const Scheduler&) = delete;
void operator=(const Scheduler&) = delete;
};
}
#if 0
// Example of yaml file with processes that can be queued up in scheduler
// id - Not used right now. Number does not matter because id will be a rolling number
// app - Application to execute
// arg - Arguments to application
// dir - Working directory
// desc - Description of process
// weight - How much cpu this process is using. Optional. Defaults to 1.0
// remote - Decides if this process can execute remotely. Optional. Defaults to true
// detour - Decides if this process can be detoured. Optional. Defaults to true.
// dep - Dependencies. An array of indices to other processes
processes:
- id: 0
app: C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64\cl.exe
arg: @"..\Intermediate\Build\Win64\x64\UnrealPak\Development\Core\SharedPCH.Core.Cpp20.h.obj.rsp"
dir: E:\dev\fn\Engine\Source
desc: SharedPCH.Core.Cpp20.cpp
weight: 1.25
remote: false
- id: 44
app: C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64\cl.exe
arg: @"..\Intermediate\Build\Win64\x64\UnrealPak\Development\Json\Module.Json.cpp.obj.rsp"
dir: E:\dev\fn\Engine\Source
desc: Module.Json.cpp
weight: 1.5
dep: [0]
- id: 337
app: E:\dev\fn\Engine\Binaries\ThirdParty\DotNet\6.0.302\windows\dotnet.exe
arg: "E:\dev\fn\Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll" -Mode=WriteMetadata -Input="E:\dev\fn\Engine\Intermediate\Build\Win64\x64\UnrealPak\Development\TargetMetadata.dat" -Version=2
dir: E:\dev\fn\Engine\Source
desc: UnrealPak.target
detour: false
dep: [336, 0]
#endif