public ExecutionContextData(Engine engine, Guid executionId, PipelinePhase pipelinePhase, IServiceProvider services, CancellationToken cancellationToken) { Engine = engine ?? throw new ArgumentNullException(nameof(engine)); ExecutionId = executionId; PipelinePhase = pipelinePhase ?? throw new ArgumentNullException(nameof(pipelinePhase)); Services = services ?? throw new ArgumentNullException(nameof(services)); Outputs = new PipelineOutputs(engine.Documents, pipelinePhase, engine.Pipelines); CancellationToken = cancellationToken; }
/// <summary> /// Executes the specified pipelines and pipelines with <see cref="ExecutionPolicy.Always"/> policies. /// </summary> /// <param name="pipelines"> /// The pipelines to execute or <c>null</c>/empty to only execute pipelines with the <see cref="ExecutionPolicy.Always"/> policy. /// </param> /// <param name="normalPipelines"> /// <c>true</c> to run pipelines with the <see cref="ExecutionPolicy.Normal"/> policy in addition /// to the pipelines specified or <c>false</c> to only run the specified pipelines. /// </param> /// <param name="cancellationToken"> /// A cancellation token that can be used to cancel the execution. /// </param> /// <returns>The output documents from each executed pipeline.</returns> public async Task <IPipelineOutputs> ExecuteAsync(string[] pipelines, bool normalPipelines, CancellationToken cancellationToken = default) { // Setup await default(SynchronizationContextRemover); CheckDisposed(); // Make sure only one execution is running if (ExecutionId != Guid.Empty) { throw new ExecutionException($"Execution with ID {ExecutionId} is already executing, only one execution can be run at once"); } ExecutionId = Guid.NewGuid(); CancellationToken = cancellationToken; try { // Create the phase results for this execution ConcurrentDictionary <string, PhaseResult[]> phaseResults = new ConcurrentDictionary <string, PhaseResult[]>(StringComparer.OrdinalIgnoreCase); Outputs = new PipelineOutputs(phaseResults); // Create the pipeline phases (this also validates the pipeline graph) if (_phases == null) { _phases = GetPipelinePhases(_pipelines, _logger); } // Verify pipelines HashSet <string> executingPipelines = GetExecutingPipelines(pipelines, normalPipelines); if (executingPipelines.Count == 0) { _logger.LogWarning("No pipelines are configured or specified for execution."); return(Outputs); } // Log _logger.LogInformation($"Executing {executingPipelines.Count} pipelines ({string.Join(", ", executingPipelines.OrderBy(x => x))})"); _logger.LogDebug($"Execution ID {ExecutionId}"); System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); // Raise before event await Events.RaiseAsync(new BeforeEngineExecution(this, ExecutionId)); // Do a check for the same input/output path if (FileSystem.InputPaths.Any(x => x.Equals(FileSystem.OutputPath))) { _logger.LogWarning("The output path is also one of the input paths which can cause unexpected behavior and is usually not advised"); } // Clean paths CleanTempPath(); if (Settings.GetBool(Keys.CleanOutputPath)) { CleanOutputPath(); } // Get and run all phase tasks Task[] phaseTasks = null; try { // Get and execute all phases phaseTasks = GetPhaseTasks(executingPipelines, phaseResults); await Task.WhenAll(phaseTasks); } finally { stopwatch.Stop(); } // Raise after event await Events.RaiseAsync(new AfterEngineExecution(this, ExecutionId, Outputs, stopwatch.ElapsedMilliseconds)); // Log execution summary table if (phaseResults.Count > 0) { _logger.LogInformation(GetExecutionSummary(phaseResults)); } // Clean up _logger.LogInformation($"Finished execution in {stopwatch.ElapsedMilliseconds} ms"); return(Outputs); } finally { ExecutionId = Guid.Empty; CancellationToken = default; } }
/// <summary> /// Executes the specified pipelines and pipelines with <see cref="ExecutionPolicy.Always"/> policies. /// </summary> /// <param name="pipelines"> /// The pipelines to execute or <c>null</c>/empty to only execute pipelines with the <see cref="ExecutionPolicy.Always"/> policy. /// </param> /// <param name="defaultPipelines"> /// <c>true</c> to run the default pipelines in addition to the pipelines specified /// or <c>false</c> to only run the specified pipelines. /// </param> /// <param name="cancellationTokenSource"> /// A cancellation token source that can be used to cancel the execution. /// </param> /// <returns>The output documents from each executed pipeline.</returns> public async Task <IPipelineOutputs> ExecuteAsync(string[] pipelines, bool defaultPipelines, CancellationTokenSource cancellationTokenSource) { // Setup await default(SynchronizationContextRemover); CheckDisposed(); Guid executionId = Guid.NewGuid(); ConcurrentDictionary <string, PhaseResult[]> phaseResults = new ConcurrentDictionary <string, PhaseResult[]>(StringComparer.OrdinalIgnoreCase); PipelineOutputs outputs = new PipelineOutputs(phaseResults); // Create the pipeline phases (this also validates the pipeline graph) // Also add the service-based pipelines as late as possible so other services have been configured if (_phases == null) { AddServicePipelines(); _phases = GetPipelinePhases(_pipelines, _logger); } // Verify pipelines HashSet <string> executingPipelines = GetExecutingPipelines(pipelines, defaultPipelines); if (executingPipelines.Count == 0) { _logger.LogWarning("No pipelines are configured or specified for execution."); return(outputs); } // Log _logger.LogInformation($"Executing {executingPipelines.Count} pipelines ({string.Join(", ", executingPipelines.OrderBy(x => x))})"); _logger.LogDebug($"Execution ID {executionId}"); System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); // Raise before event await Events.RaiseAsync(new BeforeEngineExecution(this, executionId)); // Do a check for the same input/output path if (FileSystem.InputPaths.Any(x => x.Equals(FileSystem.OutputPath))) { _logger.LogWarning("The output path is also one of the input paths which can cause unexpected behavior and is usually not advised"); } // Clean paths CleanTempPath(); if (Settings.GetBool(Keys.CleanOutputPath)) { CleanOutputPath(); } // Get phase tasks Task[] phaseTasks = null; try { // Get and execute all phases phaseTasks = GetPhaseTasks(executionId, executingPipelines, phaseResults, cancellationTokenSource); await Task.WhenAll(phaseTasks); } catch (Exception ex) { if (!(ex is OperationCanceledException)) { _logger.LogCritical("Error during execution"); } throw; } finally { stopwatch.Stop(); } // Raise after event await Events.RaiseAsync(new AfterEngineExecution(this, executionId, outputs, stopwatch.ElapsedMilliseconds)); // Log execution summary table _logger.LogInformation( "Execution summary: (number of output documents per pipeline and phase)" + Environment.NewLine + Environment.NewLine + phaseResults .OrderBy(x => x.Key) .ToStringTable( new[] { "Pipeline", nameof(Phase.Input), nameof(Phase.Process), nameof(Phase.Transform), nameof(Phase.Output), "Total Time" }, x => x.Key, x => GetPhaseResultTableString(x.Value[(int)Phase.Input]), x => GetPhaseResultTableString(x.Value[(int)Phase.Process]), x => GetPhaseResultTableString(x.Value[(int)Phase.Transform]), x => GetPhaseResultTableString(x.Value[(int)Phase.Output]), x => ((x.Value[(int)Phase.Input]?.ElapsedMilliseconds ?? 0) + (x.Value[(int)Phase.Process]?.ElapsedMilliseconds ?? 0) + (x.Value[(int)Phase.Transform]?.ElapsedMilliseconds ?? 0) + (x.Value[(int)Phase.Output]?.ElapsedMilliseconds ?? 0)).ToString() + " ms"));