private IPipGraphBuilder CreatePipGraphBuilder( LoggingContext loggingContext, MountsTable mountsTable, [CanBeNull] GraphReuseResult reuseResult) { var builder = new PipGraph.Builder( EngineSchedule.CreateEmptyPipTable(Context), Context, Scheduler.Tracing.Logger.Log, loggingContext, Configuration, mountsTable.MountPathExpander, fingerprintSalt: Configuration.Cache.CacheSalt, directoryMembershipFingerprinterRules: new Scheduler.DirectoryMembershipFingerprinterRuleSet(Configuration, Context.StringTable)); PatchablePipGraph patchableGraph = null; if (Configuration.FrontEnd.UseGraphPatching() && reuseResult?.IsPartialReuse == true) { Logger.Log.UsingPatchableGraphBuilder(loggingContext); patchableGraph = new PatchablePipGraph( oldPipGraph: reuseResult.PipGraph.DataflowGraph, oldPipTable: reuseResult.PipGraph.PipTable, graphBuilder: builder, maxDegreeOfParallelism: Configuration.FrontEnd.MaxFrontEndConcurrency()); } return((IPipGraphBuilder)patchableGraph ?? builder); }
private GraphReuseResult ReloadEngineSchedule( EngineSerializer serializer, CacheInitializationTask cacheInitializationTask, JournalState journalState, LoggingContext loggingContext, EngineState engineState, InputTracker.InputChanges inputChanges, string buildEngineFingerprint) { Tuple <EngineSchedule, EngineContext, IConfiguration> t = null; try { t = EngineSchedule.LoadAsync( Context, serializer, cacheInitializationTask, FileContentTable, journalState, Configuration, loggingContext, m_collector, m_directoryTranslator, engineState, tempCleaner: m_tempCleaner, buildEngineFingerprint).GetAwaiter().GetResult(); } catch (BuildXLException e) { Logger.Log.FailedReloadPipGraph(loggingContext, e.ToString()); } if (t == null) { return(GraphReuseResult.CreateForNoReuse(inputChanges)); } // Invalidate the old context to ensure nothing uses it anymore // Update engine state to deserialized state Context.Invalidate(); Context = t.Item2; // Update configuration state to deserialized state Configuration = t.Item3; // Copy the graph files to the session output if (Configuration.Distribution.BuildRole != DistributedBuildRoles.Worker) { // No need to link these files to the logs directory on workers since they are redundant with what's on the orchestrator m_executionLogGraphCopy = TryCreateHardlinksToScheduleFilesInSessionFolder(loggingContext, serializer); m_previousInputFilesCopy = TryCreateHardlinksToPreviousInputFilesInSessionFolder(loggingContext, serializer); } return(GraphReuseResult.CreateForFullReuse(t.Item1, inputChanges)); }
private GraphReuseResult ReloadPipGraphOnly( EngineSerializer serializer, LoggingContext loggingContext, EngineState engineState, InputTracker.InputChanges inputChanges) { Tuple <PipGraph, EngineContext> t = null; try { t = EngineSchedule.LoadPipGraphAsync( Context, serializer, Configuration, loggingContext, engineState).GetAwaiter().GetResult(); } catch (BuildXLException e) { Logger.Log.FailedReloadPipGraph(loggingContext, e.ToString()); } if (t == null) { return(GraphReuseResult.CreateForNoReuse(inputChanges)); } var newContext = t.Item2; if (!ShouldReuseReloadedEngineContextGivenHistoricData(loggingContext, newContext.NextHistoricTableSizes)) { return(GraphReuseResult.CreateForNoReuse(inputChanges)); } var newPathTable = newContext.PathTable; var pathRemapper = new PathRemapper(Context.PathTable, newPathTable); Configuration = new ConfigurationImpl(Configuration, pathRemapper); m_initialCommandLineConfiguration = new CommandLineConfiguration(m_initialCommandLineConfiguration, pathRemapper); // Invalidate the old context to ensure nothing uses it anymore // Update engine state to deserialized state Context.Invalidate(); Context = newContext; // Additionally recreate front end controller, because the old one uses the invalidated context. // - to fully initialize the front end, we have to go through all the steps that have already been // executed on the old controller; those steps are (1) InitializeHost, and (2) ParseConfig FrontEndController = m_frontEndControllerFactory.Create(Context.PathTable, Context.SymbolTable); FrontEndController.InitializeHost(Context.ToFrontEndContext(loggingContext), m_initialCommandLineConfiguration); FrontEndController.ParseConfig(m_initialCommandLineConfiguration); return(GraphReuseResult.CreateForPartialReuse(t.Item1, inputChanges)); }
private GraphReuseResult ReloadEngineSchedule( EngineSerializer serializer, CacheInitializationTask cacheInitializationTask, JournalState journalState, LoggingContext loggingContext, EngineState engineState, InputTracker.InputChanges inputChanges, string buildEngineFingerprint) { Tuple <EngineSchedule, EngineContext, IConfiguration> t = EngineSchedule.LoadAsync( Context, serializer, cacheInitializationTask, FileContentTable, journalState, Configuration, loggingContext, m_collector, m_directoryTranslator, engineState, symlinkDefinitionFile: IsDistributedWorker ? m_workerSymlinkDefinitionFile.Value : Configuration.Layout.SymlinkDefinitionFile, tempCleaner: m_tempCleaner, buildEngineFingerprint).GetAwaiter().GetResult(); if (t == null) { return(GraphReuseResult.CreateForNoReuse(inputChanges)); } // Invalidate the old context to ensure nothing uses it anymore // Update engine state to deserialized state Context.Invalidate(); Context = t.Item2; // Update configuration state to deserialized state Configuration = t.Item3; // Copy the graph files to the session output m_executionLogGraphCopy = TryCreateHardlinksToScheduleFilesInSessionFolder(loggingContext, serializer); m_previousInputFilesCopy = TryCreateHardlinksToPreviousInputFilesInSessionFolder(loggingContext, serializer); return(GraphReuseResult.CreateForFullReuse(t.Item1, inputChanges)); }
/// <summary> /// Loads configured symlink definitions (if not already loaded) /// Stores to cache for use by workers in distributed build /// Eagerly creates symlinks if lazy symlink creation is disabled /// </summary> public static async Task <Possible <SymlinkDefinitions> > TryPrepareSymlinkDefinitionsAsync( LoggingContext loggingContext, GraphReuseResult reuseResult, IConfiguration configuration, MasterService masterService, CacheInitializationTask cacheInitializerTask, PipExecutionContext context, ITempDirectoryCleaner tempDirectoryCleaner = null) { var pathTable = context.PathTable; bool isDistributedMaster = configuration.Distribution.BuildRole == DistributedBuildRoles.Master; Possible <SymlinkDefinitions> maybeSymlinkDefinitions = new Possible <SymlinkDefinitions>((SymlinkDefinitions)null); if (reuseResult?.IsFullReuse == true) { maybeSymlinkDefinitions = reuseResult.EngineSchedule.Scheduler.SymlinkDefinitions; } else if (configuration.Layout.SymlinkDefinitionFile.IsValid) { var symlinkFilePath = configuration.Layout.SymlinkDefinitionFile.ToString(pathTable); Logger.Log.SymlinkFileTraceMessage(loggingContext, I($"Loading symlink file from location '{symlinkFilePath}'.")); maybeSymlinkDefinitions = await SymlinkDefinitions.TryLoadAsync( loggingContext, pathTable, symlinkFilePath, symlinksDebugPath : configuration.Logging.LogsDirectory.Combine(pathTable, "DebugSymlinksDefinitions.log").ToString(pathTable), tempDirectoryCleaner : tempDirectoryCleaner); } if (!maybeSymlinkDefinitions.Succeeded || maybeSymlinkDefinitions.Result == null) { return(maybeSymlinkDefinitions); } // Need to store symlinks to cache for workers if (configuration.Distribution.BuildRole == DistributedBuildRoles.Master) { var possibleCacheInitializer = await cacheInitializerTask; if (!possibleCacheInitializer.Succeeded) { return(possibleCacheInitializer.Failure); } Logger.Log.SymlinkFileTraceMessage(loggingContext, I($"Storing symlink file for use by workers.")); var symlinkFile = configuration.Layout.SymlinkDefinitionFile.Expand(pathTable); var possibleStore = await TryStoreToCacheAsync( loggingContext, cache : possibleCacheInitializer.Result.CreateCacheForContext(context).ArtifactContentCache, symlinkFile : symlinkFile); if (!possibleStore.Succeeded) { return(possibleStore.Failure); } masterService.SymlinkFileContentHash = possibleStore.Result; Logger.Log.SymlinkFileTraceMessage(loggingContext, I($"Stored symlink file for use by workers.")); } if (!configuration.Schedule.UnsafeLazySymlinkCreation || configuration.Engine.PopulateSymlinkDirectories.Count != 0) { // Symlink definition file is defined, and BuildXL intends to create it eagerly. // At this point master and worker should have had its symlink definition file, if specified. if (!FileContentManager.CreateSymlinkEagerly(loggingContext, configuration, pathTable, maybeSymlinkDefinitions.Result, context.CancellationToken)) { return(new Failure <string>("Failed eagerly creating symlinks")); } } return(maybeSymlinkDefinitions); }
/// <summary> /// Attempt to reuse the pip graph from a previous run /// </summary> private GraphReuseResult AttemptToReuseGraph( LoggingContext outerLoggingContext, int maxDegreeOfParallelism, GraphFingerprint graphFingerprint, IReadOnlyDictionary <string, string> properties, CacheInitializationTask cacheInitializationTask, JournalState journalState, EngineState engineState) { Contract.Ensures(Contract.Result <GraphReuseResult>() != null); GraphCacheCheckStatistics cacheGraphStats = CheckGraphCacheReuse( outerLoggingContext, maxDegreeOfParallelism, graphFingerprint, properties, cacheInitializationTask, journalState, out var serializer, out var inputChanges); // There are 3 cases in which we should reload the graph // - we have a graph cache hit // - the build is configured to reload the graph no matter what // - graph patching is enabled and the reason for cache miss was 'SpecFileChanges' var shouldReload = cacheGraphStats.WasHit || Configuration.Cache.CachedGraphPathToLoad.IsValid || PartialReloadCondition(Configuration.FrontEnd, cacheGraphStats); if (!shouldReload) { return(GraphReuseResult.CreateForNoReuse(inputChanges)); } bool fullReload = !PartialReloadCondition(Configuration.FrontEnd, cacheGraphStats); // Now we actually reload the graph var reloadStats = default(GraphCacheReloadStatistics); using (var tb = TimedBlock <EmptyStruct, GraphCacheReloadStatistics> .Start( outerLoggingContext, Statistics.GraphCacheReload, (context, emptyStruct) => { if (fullReload) { Logger.Log.ReloadingPipGraphStart(context); } else { Logger.Log.PartiallyReloadingEngineState(context); } }, default(EmptyStruct), (context, graphCacheCheckStatistics) => { Logger.Log.PartiallyReloadingEngineStateComplete(context, graphCacheCheckStatistics); m_enginePerformanceInfo.GraphReloadDurationMs = graphCacheCheckStatistics.ElapsedMilliseconds; }, () => reloadStats)) { var reuseResult = fullReload ? ReloadEngineSchedule( serializer, cacheInitializationTask, journalState, tb.LoggingContext, engineState, inputChanges, graphFingerprint?.ExactFingerprint.BuildEngineHash.ToString()) : ReloadPipGraphOnly(serializer, tb.LoggingContext, engineState, inputChanges); // Set telemetry statistics reloadStats.SerializedFileSizeBytes = serializer.BytesDeserialized; reloadStats.Success = !reuseResult.IsNoReuse; return(reuseResult); } }
private bool ConstructAndEvaluateGraph( LoggingContext loggingContext, FrontEndEngineAbstraction frontEndEngineAbstration, CacheInitializationTask engineCacheTask, MountsTable mountsTable, EvaluationFilter evaluationFilter, [CanBeNull] GraphReuseResult reuseResult, out PipGraph pipGraph) { Contract.Requires(frontEndEngineAbstration != null); Contract.Requires(engineCacheTask != null); Contract.Requires(mountsTable != null); pipGraph = null; IPipGraphBuilder pipGraphBuilder = null; if (!AddConfigurationMountsAndCompleteInitialization(loggingContext, mountsTable)) { return(false); } IDictionary <ModuleId, MountsTable> moduleMountsTableMap; if (!mountsTable.PopulateModuleMounts(Configuration.ModulePolicies.Values, out moduleMountsTableMap)) { Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged after MountTable.PopulateModuleMounts()"); return(false); } m_visualization?.MountsTable.MakeAvailable(mountsTable); if ((Configuration.Engine.Phase & EnginePhases.Schedule) != 0) { pipGraphBuilder = CreatePipGraphBuilder(loggingContext, mountsTable, reuseResult); } // Have to do some horrible magic here to get to a proper Task<T> with the BuildXL cache since // someone updated the engine cache to be an await style pattern, and there is no way to get to the EngineCache // If the cache was fast to startup, but perhaps blocked itself on first access we wouldn't have to do all these hoops. Func <Task <Possible <EngineCache> > > getBuildCacheTask = async() => { return((await engineCacheTask).Then(engineCache => engineCache.CreateCacheForContext())); }; if (!FrontEndController.PopulateGraph( getBuildCacheTask(), pipGraphBuilder, frontEndEngineAbstration, evaluationFilter, Configuration, m_initialCommandLineConfiguration.Startup)) { LogFrontEndStats(loggingContext); Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged after FrontEndController.PopulateGraph()"); return(false); } LogFrontEndStats(loggingContext); // Pip graph must become immutable now that evaluation is done (required to construct a scheduler). return(pipGraphBuilder == null || (pipGraph = pipGraphBuilder.Build()) != null); }