in Public/Src/Engine/Dll/Engine.cs [2779:3176]
private ConstructScheduleResult ConstructSchedule(
LoggingContext loggingContext,
int maxDegreeOfParallelism,
CacheInitializationTask cacheInitializationTask,
JournalState journalState,
EngineState engineState,
out EngineSchedule engineSchedule,
out RootFilter rootFilter)
{
Contract.Requires(maxDegreeOfParallelism > 0, "maxDegreeOfParallelism > 0");
engineSchedule = null;
rootFilter = null;
bool enablePartialEvaluation = Configuration.FrontEnd.UsePartialEvaluation();
bool reusedGraph = false;
if (Configuration.Engine.Phase == EnginePhases.None)
{
EndPhase(EnginePhases.None);
return ConstructScheduleResult.None;
}
var cachedGraphIdToLoad = Configuration.Cache.CachedGraphIdToLoad;
GraphFingerprint graphFingerprint;
if (!string.IsNullOrEmpty(cachedGraphIdToLoad))
{
var parsed = Fingerprint.TryParse(cachedGraphIdToLoad, out var fingerprint);
Contract.Assert(parsed);
// the id is already validated by argument parsing.
var compositeFingerprint = CompositeGraphFingerprint.Zero;
compositeFingerprint.OverallFingerprint = new ContentFingerprint(fingerprint);
graphFingerprint = new GraphFingerprint(compositeFingerprint, compositeFingerprint);
}
else
{
// The fingerprint needs to include the values that get evaluated.
EvaluationFilter partialEvaluationData = EvaluationFilter.Empty;
if (enablePartialEvaluation &&
!EngineSchedule.TryGetEvaluationFilter(
loggingContext,
Context,
m_initialCommandLineConfiguration,
Configuration,
out partialEvaluationData))
{
Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
return ConstructScheduleResult.Failure;
}
graphFingerprint = Configuration.Distribution.BuildRole != DistributedBuildRoles.Worker
? GraphFingerprinter.TryComputeFingerprint(
loggingContext,
m_initialCommandLineConfiguration.Startup,
Configuration,
Context.PathTable,
// Get deserialized version of evaluation filter so that it no longer depends on path table.
partialEvaluationData.GetDeserializedFilter(),
FileContentTable,
m_commitId,
TestHooks)
: null;
}
GraphReuseResult reuseResult = null;
var phase = Configuration.Engine.Phase;
if (phase.HasFlag(EnginePhases.Schedule)
&&
((IsGraphCacheConsumptionAllowed() && graphFingerprint != null) ||
Configuration.Distribution.BuildRole == DistributedBuildRoles.Worker))
{
reuseResult = AttemptToReuseGraph(
loggingContext,
maxDegreeOfParallelism,
graphFingerprint,
m_initialCommandLineConfiguration.Startup.Properties,
cacheInitializationTask,
journalState,
engineState);
if (TestHooks != null)
{
TestHooks.GraphReuseResult = reuseResult;
}
if (reuseResult.IsFullReuse)
{
engineSchedule = reuseResult.EngineSchedule;
reusedGraph = true;
}
if (engineSchedule == null)
{
if (Configuration.Distribution.BuildRole == DistributedBuildRoles.Worker)
{
Logger.Log.DistributionWorkerCouldNotLoadGraph(loggingContext);
return ConstructScheduleResult.Failure;
}
var cacheConfiguration = Configuration.Cache;
if (HasExplicitlyLoadedGraph(cacheConfiguration))
{
if (cacheConfiguration.CachedGraphPathToLoad.IsValid)
{
Logger.Log.PipGraphByPathFailure(loggingContext, cacheConfiguration.CachedGraphPathToLoad.ToString(Context.PathTable));
return ConstructScheduleResult.Failure;
}
if (!string.IsNullOrEmpty(cacheConfiguration.CachedGraphIdToLoad))
{
Logger.Log.PipGraphByIdFailure(loggingContext, cacheConfiguration.CachedGraphIdToLoad);
return ConstructScheduleResult.Failure;
}
Contract.Assert(false, "Unhandled explicit graph load");
}
}
m_enginePerformanceInfo.CacheInitializationDurationMs = (long)cacheInitializationTask.InitializationTime.TotalMilliseconds;
}
if (Configuration.Engine.ExitOnNewGraph && !reusedGraph)
{
Logger.Log.ExitOnNewGraph(loggingContext);
return ConstructScheduleResult.ExitOnNewGraph;
}
m_buildViewModel.SetContext(Context);
try
{
if (engineSchedule == null)
{
// We have established a graph cache miss, and may choose to save a graph for next time.
// We need an input tracker to accumulate all spec, config, assembly, etc. inputs which
// we use as part of constructing a new graph; the resulting assertions are
// used to validate re-use of this new graph on subsequent runs.
InputTracker inputTrackerForGraphConstruction;
if (graphFingerprint != null)
{
if (reuseResult?.InputChanges != null)
{
inputTrackerForGraphConstruction = InputTracker.ContinueExistingTrackerWithInputChanges(
loggingContext,
FileContentTable,
reuseResult.InputChanges,
graphFingerprint.ExactFingerprint);
// For instance, a spec is changed and it uses 'glob' instead of 'globR' and we should remove some tracked directories.
// Instead of modifying the set of directory fingerprints, let's retrack the directories by starting with an empty set.
inputTrackerForGraphConstruction.ClearDirectoryFingerprints();
}
else
{
inputTrackerForGraphConstruction = InputTracker.Create(
loggingContext,
FileContentTable,
journalState,
graphFingerprint.ExactFingerprint);
}
}
else
{
inputTrackerForGraphConstruction = InputTracker.CreateDisabledTracker(loggingContext);
}
// must not use previously created 'mountsTable' (in 'DoRun') because the context
// and its path table might have been invalidated/changed in the meantime
var mountsTable = MountsTable.CreateAndRegister(loggingContext, Context, Configuration, m_initialCommandLineConfiguration.Startup.Properties);
using (var frontEndEngineAbstraction = new FrontEndEngineImplementation(
loggingContext,
Context.PathTable,
Configuration,
m_initialCommandLineConfiguration.Startup,
mountsTable,
inputTrackerForGraphConstruction,
m_snapshotCollector,
m_directoryTranslator,
() => FileContentTable,
Configuration.Logging.GetTimerUpdatePeriodInMs(),
reuseResult?.IsPartialReuse == true,
FrontEndController.RegisteredFrontEnds))
{
PipGraph newlyEvaluatedGraph;
if (TestHooks?.FrontEndEngineAbstraction != null)
{
TestHooks.FrontEndEngineAbstraction.Value = frontEndEngineAbstraction;
}
var sw = Stopwatch.StartNew();
// Create the evaluation filter
EvaluationFilter evaluationFilter;
if (enablePartialEvaluation)
{
if (!EngineSchedule.TryGetEvaluationFilter(
loggingContext,
Context,
m_initialCommandLineConfiguration,
Configuration,
out evaluationFilter))
{
Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
return ConstructScheduleResult.Failure;
}
}
else
{
evaluationFilter = EvaluationFilter.Empty;
}
if (!ConstructAndEvaluateGraph(
loggingContext,
frontEndEngineAbstraction,
cacheInitializationTask,
mountsTable,
evaluationFilter,
reuseResult,
out newlyEvaluatedGraph))
{
Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during DScript graph construction.");
return ConstructScheduleResult.Failure;
}
m_enginePerformanceInfo.GraphConstructionDurationMs = sw.ElapsedMilliseconds;
if (Configuration.Engine.LogStatistics)
{
BuildXL.Tracing.Logger.Log.Statistic(
loggingContext,
new BuildXL.Tracing.Statistic
{
Name = "GraphConstruction.DurationMs",
Value = m_enginePerformanceInfo.GraphConstructionDurationMs
});
}
if (m_snapshotCollector != null)
{
if (newlyEvaluatedGraph != null)
{
m_snapshotCollector.SetPipGraph(newlyEvaluatedGraph);
}
// Collect all mounts.
foreach (var mount in mountsTable.AllMounts)
{
m_snapshotCollector.RecordMount(mount);
}
}
if (!phase.HasFlag(EnginePhases.Schedule))
{
return reusedGraph ? ConstructScheduleResult.ReusedExistingGraph : ConstructScheduleResult.ConstructedNewGraph;
}
Contract.Assert(newlyEvaluatedGraph != null);
// TODO: Shouldn't need to force the cache until the execution phase; this can be fixed when BuildXLScheduler is constructed only in the Execute phase.
Possible<CacheInitializer> possibleCacheInitializer = cacheInitializationTask.GetAwaiter().GetResult();
if (!possibleCacheInitializer.Succeeded)
{
// StorageCacheStartupError has been logged by CacheInitializer
return ConstructScheduleResult.Failure;
}
CacheInitializer cacheInitializerForGraphConstruction = possibleCacheInitializer.Result;
engineSchedule = EngineSchedule.Create(
loggingContext,
context: Context,
cacheInitializer: cacheInitializerForGraphConstruction,
configuration: Configuration,
fileContentTable: FileContentTable,
pipGraph: newlyEvaluatedGraph,
journalState: journalState,
mountPathExpander: mountsTable.MountPathExpander,
directoryMembershipFingerprinterRules: new DirectoryMembershipFingerprinterRuleSet(Configuration, Context.StringTable),
performanceCollector: m_collector,
directoryTranslator: m_directoryTranslator,
maxDegreeOfParallelism: Configuration.FrontEnd.MaxFrontEndConcurrency(),
tempCleaner: m_tempCleaner,
buildEngineFingerprint: graphFingerprint?.ExactFingerprint.BuildEngineHash.ToString(),
detoursListener: TestHooks?.DetoursListener);
if (engineSchedule == null)
{
Contract.Assert(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
return ConstructScheduleResult.Failure;
}
// Dispose engine state if and only if a new engine schedule is constructed from scratch.
engineState?.Dispose();
Contract.Assert(
!EngineState.IsUsable(engineState),
"Previous engine state must be unusable if a new engine schedule is constructed from scratch.");
var envVarsImpactingBuild = frontEndEngineAbstraction.ComputeEnvironmentVariablesImpactingBuild();
var mountsImpactingBuild = frontEndEngineAbstraction.ComputeEffectiveMounts();
var availableEnvVars = frontEndEngineAbstraction.GetAllEnvironmentVariables();
// Disable caching graph in the unit tests which do not set the app deployment.
if (TestHooks == null || TestHooks.AppDeployment != null)
{
m_graphCacheContentCachePut = CacheEngineScheduleStateAsync(
loggingContext,
graphFingerprint,
engineSchedule,
inputTrackerForGraphConstruction,
envVarsImpactingBuild,
mountsImpactingBuild,
availableEnvVars,
mountsTable.MountsByName);
}
if (IsDistributedOrchestrator)
{
// In a distributed build, we must synchronously wait for the graph to be placed in the content cache.
if (!m_graphCacheContentCachePut.GetAwaiter().GetResult())
{
Logger.Log.ErrorUnableToCacheGraphDistributedBuild(loggingContext);
return ConstructScheduleResult.Failure;
}
}
}
}
Contract.Assert(engineSchedule != null);
// When constructing the graph above, we exit early if the schedule phase is not requested. But if we
// didn't have to construct the engineSchedue, we wouldn't have early returned and still may not
// have the schedule phase.
if (!phase.HasFlag(EnginePhases.Schedule))
{
return reusedGraph ? ConstructScheduleResult.ReusedExistingGraph : ConstructScheduleResult.ConstructedNewGraph;
}
// If the graph cache is a hit and engineState is not null and disposed, use the previous state to speed the scheduling process.
SchedulerState previousSchedulerState = EngineState.IsUsable(engineState) ? engineState.SchedulerState : null;
// Bail out if the build was canceled during graph construction
if (engineSchedule.IsTerminating)
{
return ConstructScheduleResult.Failure;
}
if (TestHooks != null)
{
TestHooks.TempCleanerTempDirectory = m_tempCleaner.TempDirectory;
}
// Now that graph is constructed and saved, workers can be attached
if (IsDistributedOrchestrator && phase.HasFlag(EnginePhases.Execute))
{
m_orchestratorService.EnableDistribution(engineSchedule);
}
if (!engineSchedule.PrepareForBuild(
loggingContext,
m_initialCommandLineConfiguration,
Configuration,
previousSchedulerState,
ref rootFilter,
GetNonScrubbablePaths(),
m_enginePerformanceInfo))
{
MakeScheduleInfoAvailableToViewer(engineSchedule);
Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
return ConstructScheduleResult.Failure;
}
}
finally
{
if (phase.HasFlag(EnginePhases.Schedule))
{
EndPhase(EnginePhases.Schedule);
}
}
MakeScheduleInfoAvailableToViewer(engineSchedule);
if (TestHooks?.Scheduler != null)
{
TestHooks.Scheduler.Value = engineSchedule.Scheduler;
}
// Need to thread through the process start time checking it from the currently running process is not
// valid for server mode builds.
engineSchedule.Scheduler.SetProcessStartTime(m_processStartTimeUtc);
// We are done scheduling. Log configuration statistics used for scheduling.
Logger.Log.ScheduleConstructedWithConfiguration(loggingContext, Configuration.GetStatistics().ResolverKinds);
return reusedGraph ? ConstructScheduleResult.ReusedExistingGraph : ConstructScheduleResult.ConstructedNewGraph;
}