public async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, OrchestrationStatus[] dedupeStatuses) { using (var dbContext = _dbContextFactory()) { var executionStartedEvent = creationMessage.Event as ExecutionStartedEvent; var instanceId = creationMessage.OrchestrationInstance.InstanceId; var executionId = creationMessage.OrchestrationInstance.ExecutionId; var instance = await dbContext.Instances .Include(i => i.LastExecution) .FirstOrDefaultAsync(i => i.InstanceId == instanceId); if (instance != null) { if (dedupeStatuses != null && dedupeStatuses.Contains(instance.LastExecution.Status)) { return; } if (!IsFinalInstanceStatus(instance.LastExecution.Status)) { throw new Exception("Orchestration has an active execution"); } } var runtimeState = new OrchestrationRuntimeState(new[] { executionStartedEvent }); if (instance == null) { instance = _instanceMapper.CreateInstance(executionStartedEvent); await dbContext.Instances.AddAsync(instance); } else { _instanceMapper.UpdateInstance(instance, runtimeState); } var execution = _executionMapper.CreateExecution(runtimeState); await dbContext.Executions.AddAsync(execution); var knownQueues = new Dictionary <string, string> { [instance.InstanceId] = QueueMapper.ToQueueName(runtimeState.Name, runtimeState.Version) }; var orchestrationWorkItem = await _orchestrationMessageMapper.CreateOrchestrationMessageAsync( creationMessage, 0, dbContext, knownQueues ); await dbContext.OrchestrationMessages.AddAsync(orchestrationWorkItem); await dbContext.SaveChangesAsync(); } }
public async Task CompleteTaskOrchestrationWorkItemAsync( TaskOrchestrationWorkItem workItem, OrchestrationRuntimeState newOrchestrationRuntimeState, IList <TaskMessage> outboundMessages, IList <TaskMessage> orchestratorMessages, IList <TaskMessage> timerMessages, TaskMessage continuedAsNewMessage, OrchestrationState orchestrationState) { using (var dbContext = _dbContextFactory()) { var session = workItem.Session as EFCoreOrchestrationSession; // Create child orchestrations foreach (var executionStartedEvent in orchestratorMessages.Select(m => m.Event).OfType <ExecutionStartedEvent>()) { var childInstance = _instanceMapper.CreateInstance(executionStartedEvent); await dbContext.Instances.AddAsync(childInstance); var childRuntimeState = new OrchestrationRuntimeState(new[] { executionStartedEvent }); var childExecution = _executionMapper.CreateExecution(childRuntimeState); await dbContext.Executions.AddAsync(childExecution); } var orchestrationQueueName = QueueMapper.ToQueueName(orchestrationState.Name, orchestrationState.Version); var knownQueues = new Dictionary <string, string> { [orchestrationState.OrchestrationInstance.InstanceId] = orchestrationQueueName }; if (orchestrationState.ParentInstance != null) { knownQueues[orchestrationState.ParentInstance.OrchestrationInstance.InstanceId] = QueueMapper.ToQueueName(orchestrationState.ParentInstance.Name, orchestrationState.ParentInstance.Version); } // Write messages var activityMessages = outboundMessages .Select(m => _activityMessageMapper.CreateActivityMessage(m, orchestrationQueueName)) .ToArray(); var orchestatorMessages = await orchestratorMessages .Select((m, i) => _orchestrationMessageMapper.CreateOrchestrationMessageAsync(m, i, dbContext, knownQueues)) .WhenAllSerial(); var timerOrchestrationMessages = await timerMessages .Select((m, i) => _orchestrationMessageMapper.CreateOrchestrationMessageAsync(m, i, dbContext, knownQueues)) .WhenAllSerial(); var continuedAsNewOrchestrationMessage = continuedAsNewMessage != null ? await _orchestrationMessageMapper.CreateOrchestrationMessageAsync(continuedAsNewMessage, 0, dbContext, knownQueues) : null; await dbContext.ActivityMessages.AddRangeAsync(activityMessages); await dbContext.OrchestrationMessages.AddRangeAsync(orchestatorMessages); await dbContext.OrchestrationMessages.AddRangeAsync(timerOrchestrationMessages); if (continuedAsNewOrchestrationMessage != null) { await dbContext.OrchestrationMessages.AddAsync(continuedAsNewOrchestrationMessage); } // Remove executed messages dbContext.AttachRange(session.Messages); dbContext.OrchestrationMessages.RemoveRange(session.Messages); // Update instance var instance = session.Instance; dbContext.Instances.Attach(instance); _instanceMapper.UpdateInstance(instance, newOrchestrationRuntimeState); // Update current execution EnrichNewEventsInput(workItem.OrchestrationRuntimeState, outboundMessages, orchestratorMessages); session.Execution = await SaveExecutionAsync(dbContext, workItem.OrchestrationRuntimeState, session.Execution); // Update new execution if (newOrchestrationRuntimeState != workItem.OrchestrationRuntimeState) { EnrichNewEventsInput(newOrchestrationRuntimeState, outboundMessages, orchestratorMessages); session.Execution = await SaveExecutionAsync(dbContext, newOrchestrationRuntimeState); } await dbContext.SaveChangesAsync(); session.RuntimeState = newOrchestrationRuntimeState; session.ClearMessages(); } }
private async Task RewindInstanceAsync(OrchestrationDbContext dbContext, string instanceId, string reason) { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // REWIND ALGORITHM: // 1. Finds failed execution of specified orchestration instance to rewind // 2. Finds failure entities to clear and over-writes them (as well as corresponding trigger events) // 3. Identifies sub-orchestration failure(s) from parent instance and calls RewindHistoryAsync recursively on failed sub-orchestration child instance(s) // 4. Resets orchestration status of rewound instance in instance store table to prepare it to be restarted // 5. Restart that doesn't have failed suborchestrations with a generic event //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// var lastExecution = dbContext.Instances .Where(i => i.InstanceId == instanceId) .Select(i => i.LastExecution) .FirstOrDefault(); var events = await dbContext.Events .Where(e => e.ExecutionId == lastExecution.ExecutionId) .ToArrayAsync(); var historyEvents = events .ToDictionary(e => _options.DataConverter.Deserialize <HistoryEvent>(e.Content)); bool hasFailedSubOrchestrations = false; foreach (var historyEvent in historyEvents.Keys) { if (historyEvent is TaskFailedEvent taskFailedEvent) { var taskScheduledEvent = historyEvents.Keys.OfType <TaskScheduledEvent>() .FirstOrDefault(e => e.EventId == taskFailedEvent.TaskScheduledId); var rewoundTaskScheduledData = _options.RewindDataConverter.Serialize(new { taskScheduledEvent.EventType, taskScheduledEvent.Name, taskScheduledEvent.Version, taskScheduledEvent.Input }); historyEvents[taskScheduledEvent].Content = _options.DataConverter.Serialize( new GenericEvent(taskScheduledEvent.EventId, $"Rewound: {rewoundTaskScheduledData}") { Timestamp = taskScheduledEvent.Timestamp } ); var rewoundTaskFailedData = _options.RewindDataConverter.Serialize(new { taskFailedEvent.EventType, taskFailedEvent.Reason, taskFailedEvent.TaskScheduledId }); historyEvents[taskFailedEvent].Content = _options.DataConverter.Serialize( new GenericEvent(taskFailedEvent.EventId, $"Rewound: {rewoundTaskFailedData}") { Timestamp = taskFailedEvent.Timestamp } ); } else if (historyEvent is SubOrchestrationInstanceFailedEvent soFailedEvent) { hasFailedSubOrchestrations = true; var soCreatedEvent = historyEvents.Keys.OfType <SubOrchestrationInstanceCreatedEvent>() .FirstOrDefault(e => e.EventId == soFailedEvent.TaskScheduledId); var rewoundSoCreatedData = _options.RewindDataConverter.Serialize(new { soCreatedEvent.EventType, soCreatedEvent.Name, soCreatedEvent.Version, soCreatedEvent.Input }); historyEvents[soCreatedEvent].Content = _options.DataConverter.Serialize( new GenericEvent(soCreatedEvent.EventId, $"Rewound: {rewoundSoCreatedData}") { Timestamp = soCreatedEvent.Timestamp } ); var rewoundSoFailedData = _options.RewindDataConverter.Serialize(new { soFailedEvent.EventType, soFailedEvent.Reason, soFailedEvent.TaskScheduledId }); historyEvents[soFailedEvent].Content = _options.DataConverter.Serialize( new GenericEvent(soFailedEvent.EventId, $"Rewound: {rewoundSoFailedData}") { Timestamp = soFailedEvent.Timestamp } ); // recursive call to clear out failure events on child instances await RewindInstanceAsync(dbContext, soCreatedEvent.InstanceId, reason); } else if (historyEvent is ExecutionCompletedEvent executionCompletedEvent && executionCompletedEvent.OrchestrationStatus == OrchestrationStatus.Failed) { var rewoundExecutionCompletedData = _options.RewindDataConverter.Serialize(new { executionCompletedEvent.EventType, executionCompletedEvent.Result, executionCompletedEvent.OrchestrationStatus }); historyEvents[executionCompletedEvent].Content = _options.DataConverter.Serialize( new GenericEvent(executionCompletedEvent.EventId, $"Rewound: {rewoundExecutionCompletedData}") { Timestamp = executionCompletedEvent.Timestamp } ); } } // Reset execution status lastExecution.Status = OrchestrationStatus.Running; lastExecution.LastUpdatedTime = DateTime.UtcNow; if (!hasFailedSubOrchestrations) { var orchestrationInstance = new OrchestrationInstance { InstanceId = instanceId }; var taskMessage = new TaskMessage { OrchestrationInstance = orchestrationInstance, Event = new GenericEvent(-1, reason) }; var knownQueues = new Dictionary <string, string> { [lastExecution.InstanceId] = QueueMapper.ToQueueName(lastExecution.Name, lastExecution.Version) }; var orchestrationMessage = await _orchestrationMessageMapper.CreateOrchestrationMessageAsync(taskMessage, 0, dbContext, knownQueues); await dbContext.OrchestrationMessages.AddAsync(orchestrationMessage); } }