// 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& 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& 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 m_processEntries; u32 m_processEntriesStart = 0; Function m_processFinished; Event m_updateThreadLoop; Thread m_thread; Atomic 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 m_totalProcesses; Atomic m_queuedProcesses; Atomic m_activeLocalProcesses; Atomic m_activeRemoteProcesses; Atomic m_finishedProcesses; Atomic m_errorCount; Atomic m_cacheHitCount; Atomic m_cacheMissCount; Vector m_cacheClients; Vector 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