public async Task Simple01_Failed_DoNotAllowAsyncTransitionWhileWaitingEvent(bool mockWorkflowStore) { var workflowEngineBuildResult = BuildWorkflowEngine(mockWorkflowStore, false, new NoOpActivity(), new NoOpEventHandler()); IEvent initialEvent = new SimpleDomainEntityEvent(Package01, "initial"); await workflowEngineBuildResult.WorkflowEngine.ProcessEvent(WorkflowPackage01Id, initialEvent); var package01WorkflowInstance = workflowEngineBuildResult.GetWorkflowInstance(Package01); Assert.IsTrue(string.Equals(package01WorkflowInstance.CurrentStateCode, "process", StringComparison.OrdinalIgnoreCase)); Assert.IsTrue(package01WorkflowInstance.CurrentStateProgress == StateExecutionProgress.AwaitingEvent); Assert.IsTrue(Package01.GetProperty <bool>("IsSent")); IWorkflowMessage workflowMessage = new AsynchronousTransitionWorkflowMessage(WorkflowPackage01Id, package01WorkflowInstance.Id, "{}"); await workflowEngineBuildResult.WorkflowEngine.ProcessMessage(workflowMessage); package01WorkflowInstance = workflowEngineBuildResult.GetWorkflowInstance(Package01); Assert.IsTrue(string.Equals(package01WorkflowInstance.CurrentStateCode, "failed", StringComparison.OrdinalIgnoreCase)); Assert.IsTrue(package01WorkflowInstance.CurrentStateProgress == StateExecutionProgress.Completed); }
private async Task TransitionToState(WorkflowContext workflowContext, StateConfiguration stateConfiguration, TransitionConfiguration transitionConfiguration, CancellationToken cancellationToken = default) { // move to failed state with no additional checks in synchronous fashion if (stateConfiguration.Type == StateTypeConfiguration.Failed) { await ProcessState(workflowContext, stateConfiguration, cancellationToken).ConfigureAwait(false); return; } // in case if transition is trying to been done to the same state where workflow instance is ==> move to failed if (string.Equals(workflowContext.WorkflowInstance.CurrentStateCode, stateConfiguration.Code, StringComparison.OrdinalIgnoreCase)) { Log.Warning("{workflowInstanceId} is attempted to be moved to the same state as it is now [{state}], moving it to failed state", workflowContext.WorkflowInstance.Id, stateConfiguration.Code); await ProcessState(workflowContext, workflowContext.WorkflowConfiguration.GetFailedStateConfiguration(), cancellationToken).ConfigureAwait(false); return; } // if state has any event ==> move to awaiting event sub-state if (stateConfiguration.Events.Any()) { // [assumption] state cannot have both event and incoming async transition (this check is done during WF configuration loading) workflowContext.WorkflowInstance.CurrentStateCode = stateConfiguration.Code; workflowContext.WorkflowInstance.CurrentStateProgress = StateExecutionProgress.AwaitingEvent; await SaveWorkflowInstance(workflowContext.WorkflowInstance, cancellationToken).ConfigureAwait(false); Log.Debug("{workflowInstanceId} has been moved to [{state}::{subState}]", workflowContext.WorkflowInstance.Id, stateConfiguration.Code, StateExecutionProgress.AwaitingEvent); return; } // process synchronous / delayed transition if (transitionConfiguration.Type.In(TransitionTypeConfiguration.Synchronous, TransitionTypeConfiguration.AsynchronousWithDelay)) { // save current transition as a checkpoint workflowContext.WorkflowInstance.CurrentStateCode = stateConfiguration.Code; workflowContext.WorkflowInstance.CurrentStateProgress = StateExecutionProgress.Started; await SaveWorkflowInstance(workflowContext.WorkflowInstance, cancellationToken).ConfigureAwait(false); Log.Debug("{workflowInstanceId} is moved to [{state}::{subState}]", workflowContext.WorkflowInstance.Id, stateConfiguration.Code, StateExecutionProgress.Started); await ProcessState(workflowContext, stateConfiguration, cancellationToken).ConfigureAwait(false); return; } // process asynchronous-immediate transition if (transitionConfiguration.Type == TransitionTypeConfiguration.AsynchronousImmediate) { workflowContext.WorkflowInstance.CurrentStateCode = stateConfiguration.Code; workflowContext.WorkflowInstance.CurrentStateProgress = StateExecutionProgress.AwaitingAsyncTransition; await SaveWorkflowInstance(workflowContext.WorkflowInstance, cancellationToken).ConfigureAwait(false); var workflowMessage = new AsynchronousTransitionWorkflowMessage(workflowContext.WorkflowConfiguration.Id, workflowContext.WorkflowInstance.Id); await WorkflowEngineBuilder.WorkflowMessageTransportFactoryProvider .CreateMessageTransportFactory(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration.Type) .CreateMessageTransport(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration.Address) .Send(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration, workflowMessage, cancellationToken) .ConfigureAwait(false); // TODO: make sure that asynchronous-immediate transition will not have race conditions with current workflow engine // ==> send them as delayed workflow messages with default delay interval (1 second) // var delay = TimeSpan.FromSeconds(1D); // var currentStateCode = workflowContext.WorkflowInstance.CurrentStateCode; // var workflowMessage = new DelayedTransitionWorkflowMessage(workflowContext.WorkflowConfiguration.Id, // workflowContext.WorkflowInstance.Id, delay, currentStateCode, transitionConfiguration.MoveToState); // await WorkflowEngineBuilder.WorkflowMessageTransportFactory // .CreateMessageTransport(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration.Address) // .SendWithDelay(workflowContext.WorkflowConfiguration.RuntimeConfiguration.EndpointConfiguration, workflowMessage, cancellationToken).ConfigureAwait(false); Log.Debug("{workflowInstanceId} has been moved to [{state}::{subState}]", workflowContext.WorkflowInstance.Id, stateConfiguration.Code, StateExecutionProgress.AwaitingAsyncTransition); return; } throw new WorkflowException("Non synchronous/asynchronous-immediate transitions are not supported"); }