internal ExecutionContext(ExecutionContextData contextData, IExecutionContext parent, IModule module, ImmutableArray <IDocument> inputs) { _contextData = contextData ?? throw new ArgumentNullException(nameof(contextData)); _logger = CreateLogger(parent, module, contextData.PipelinePhase, contextData.Services.GetRequiredService <ILoggerFactory>()); Parent = parent; Module = module ?? throw new ArgumentNullException(nameof(module)); Inputs = inputs; }
internal ExecutionContext(ExecutionContextData contextData, IExecutionContext parent, IModule module, ImmutableArray <IDocument> inputs) { _contextData = contextData ?? throw new ArgumentNullException(nameof(contextData)); _logger = contextData.Services.GetRequiredService <ILogger <ExecutionContext> >(); _logPrefix = GetLogPrefix(parent, module, contextData.PipelinePhase); Parent = parent; Module = module ?? throw new ArgumentNullException(nameof(module)); Inputs = inputs; IExecutionContext.Current = this; }
// This is the main execute method called by the engine public async Task ExecuteAsync( Engine engine, Guid executionId, CancellationTokenSource cancellationTokenSource) { if (_disposed) { throw new ObjectDisposedException(nameof(PipelinePhase)); } if (_modules.Count == 0) { _logger.LogDebug($"Pipeline {PipelineName}/{Phase} contains no modules, skipping"); Outputs = GetInputs(); return; } System.Diagnostics.Stopwatch pipelineStopwatch = System.Diagnostics.Stopwatch.StartNew(); _logger.LogDebug($"Executing pipeline {PipelineName}/{Phase} with {_modules.Count} module(s)"); try { // Execute all modules in the pipeline with a new DI scope per phase IServiceScopeFactory serviceScopeFactory = engine.Services.GetRequiredService <IServiceScopeFactory>(); using (IServiceScope serviceScope = serviceScopeFactory.CreateScope()) { ExecutionContextData contextData = new ExecutionContextData(engine, executionId, this, serviceScope.ServiceProvider, cancellationTokenSource.Token); Outputs = await Engine.ExecuteModulesAsync(contextData, null, _modules, GetInputs(), _logger); pipelineStopwatch.Stop(); _logger.LogInformation($"Executed pipeline {PipelineName}/{Phase} in {pipelineStopwatch.ElapsedMilliseconds} ms resulting in {Outputs.Length} output document(s)"); } } catch (Exception ex) { if (!(ex is OperationCanceledException)) { _logger.LogCritical($"Exception while executing pipeline {PipelineName}/{Phase}: {ex}"); cancellationTokenSource.Cancel(); } Outputs = ImmutableArray <IDocument> .Empty; throw; } // Store the result documents, but only if this is the Process phase of a non-isolated pipeline if (!Pipeline.Isolated && Phase == Phase.Process) { engine.Documents.AddOrUpdate( PipelineName, Outputs, (_, __) => Outputs); } }
// This executes the specified modules with the specified input documents internal static async Task <ImmutableArray <IDocument> > ExecuteModulesAsync(ExecutionContextData contextData, IExecutionContext parent, IEnumerable <IModule> modules, ImmutableArray <IDocument> inputs, ILogger logger) { ImmutableArray <IDocument> outputs = ImmutableArray <IDocument> .Empty; if (modules != null) { foreach (IModule module in modules.Where(x => x != null)) { string moduleName = module.GetType().Name; try { // Check for cancellation contextData.CancellationToken.ThrowIfCancellationRequested(); // Execute the module System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); logger.LogDebug("Executing module {0} with {1} input document(s)", moduleName, inputs.Length); ExecutionContext moduleContext = new ExecutionContext(contextData, parent, module, inputs); IEnumerable <IDocument> moduleResult = await(module.ExecuteAsync(moduleContext) ?? Task.FromResult <IEnumerable <IDocument> >(null)); // Handle a null Task return outputs = moduleResult.ToImmutableDocumentArray(); // Log results stopwatch.Stop(); logger.LogDebug( "Executed module {0} in {1} ms resulting in {2} output document(s)", moduleName, stopwatch.ElapsedMilliseconds, outputs.Length); inputs = outputs; } catch (Exception ex) { if (!(ex is OperationCanceledException)) { logger.LogError($"Error while executing module {moduleName}: {ex.Message}"); } outputs = ImmutableArray <IDocument> .Empty; throw; } } } return(outputs); }
// This is the main execute method called by the engine public async Task ExecuteAsync(Engine engine, ConcurrentDictionary <string, PhaseResult[]> phaseResults) { if (_disposed) { throw new ObjectDisposedException(nameof(PipelinePhase)); } // Raise the before event await engine.Events.RaiseAsync(new BeforePipelinePhaseExecution(engine.ExecutionId, PipelineName, Phase)); // Skip the phase if there are no modules if (_modules.Count == 0) { _logger.LogDebug($"{PipelineName}/{Phase} » Pipeline contains no modules, skipping"); Outputs = GetInputs(); return; } // Execute the phase ImmutableArray <IDocument> inputs = GetInputs(); DateTimeOffset startTime = DateTimeOffset.Now; System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); _logger.LogInformation($"-> {PipelineName}/{Phase} » Starting {PipelineName} {Phase} phase execution... ({inputs.Length} input document(s), {_modules.Count} module(s))"); try { // Execute all modules in the pipeline with a new DI scope per phase IServiceScopeFactory serviceScopeFactory = engine.Services.GetRequiredService <IServiceScopeFactory>(); using (IServiceScope serviceScope = serviceScopeFactory.CreateScope()) { ExecutionContextData contextData = new ExecutionContextData( this, engine, phaseResults, serviceScope.ServiceProvider); Outputs = await Engine.ExecuteModulesAsync(contextData, null, _modules, inputs, _logger); stopwatch.Stop(); _logger.LogInformation($" {PipelineName}/{Phase} » Finished {PipelineName} {Phase} phase execution ({Outputs.Length} output document(s), {stopwatch.ElapsedMilliseconds} ms)"); } } catch (Exception ex) { if (!(ex is OperationCanceledException)) { _logger.LogCritical($"Exception while executing pipeline {PipelineName}/{Phase}: {ex}"); } Outputs = ImmutableArray <IDocument> .Empty; throw; } finally { stopwatch.Stop(); } // Raise the after event await engine.Events.RaiseAsync(new AfterPipelinePhaseExecution(engine.ExecutionId, PipelineName, Phase, Outputs, stopwatch.ElapsedMilliseconds)); // Record the results PhaseResult phaseResult = new PhaseResult(PipelineName, Phase, Outputs, startTime, stopwatch.ElapsedMilliseconds); phaseResults.AddOrUpdate( phaseResult.PipelineName, _ => { PhaseResult[] results = new PhaseResult[4]; results[(int)phaseResult.Phase] = phaseResult; return(results); }, (_, results) => { if (results[(int)phaseResult.Phase] != null) { // Sanity check, we should never hit this throw new InvalidOperationException($"Results for phase {phaseResult.Phase} have already been added"); } results[(int)phaseResult.Phase] = phaseResult; return(results); }); }