public static async Task <bool> ShouldRetryAsync(this RetryPolicy retryPolicy,
                                                         StateMachineContext context,
                                                         int attempts,
                                                         TimeSpan elapsedDelay)
        {
            context.CheckArgNull(nameof(context));

            if (retryPolicy == null)
            {
                return(false);
            }
            else if (attempts >= retryPolicy.MaxAttempts)
            {
                return(false);
            }
            else if (retryPolicy.Delay != null)
            {
                var delay = attempts == 1 ? retryPolicy.Delay.Value : elapsedDelay;

                if (retryPolicy.Increment != null)
                {
                    delay += retryPolicy.Increment.Value;
                }
                else if (retryPolicy.Multiplier != null)
                {
                    delay *= retryPolicy.Multiplier.Value;
                }
Example #2
0
        public static async Task <JToken> ExecuteAsync(this SendEventAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            var eventDefinition = context.Workflow.Events.SingleOrDefault(ev => ev.Name.IsEqualTo(action.Event));

            if (eventDefinition == null)
            {
                throw new InvalidOperationException("Unable to resolve event definition: " + action.Event);
            }

            JToken payload = action.Expression?.EvalExpr(input, context) ?? new JObject();

            var evt = context.Host.CreateEventInstance(eventDefinition.Name,
                                                       eventDefinition.Type,
                                                       eventDefinition.Source,
                                                       payload,
                                                       action.ContextAttributes);

            Debug.Assert(evt != null);

            await context.Host.SendEventsAsync(new[] { evt }, context.CancelToken);

            return(JValue.CreateNull());
        }
        public static async Task <JToken> ExecuteAsync(this SequenceAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            var output = new JObject();

            for (var idx = 0; idx < action.Actions.Count; idx++)
            {
                var childAction = action.Actions.ElementAt(idx);

                Debug.Assert(childAction != null);

                var id = string.IsNullOrWhiteSpace(childAction.Name) ? idx.ToString() : childAction.Name;

                Debug.Assert(!string.IsNullOrWhiteSpace(id));

                output[id] = await childAction.ExecuteAsync(context, input);
            }

            return(output);
        }
        public static async Task <JToken> ExecuteAsync(this ParallelAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            var output = new JObject();

            var tasks = action.Actions.Select(async(a, idx) =>
            {
                var item = await a.ExecuteAsync(context, input.DeepClone());

                Debug.Assert(item != null);

                var id = string.IsNullOrWhiteSpace(a.Name) ? idx.ToString() : a.Name;

                return(id, item);
            }).ToList();

            if (action.CompletionType == ParallelCompletionType.And)
            {
                var results = await Task.WhenAll(tasks);

                Array.ForEach(results, tuple => output[tuple.id] = tuple.item);
            }
            else if (action.CompletionType == ParallelCompletionType.Xor)
            {
                var resultTask = await Task.WhenAny(tasks);

                var tuple = await resultTask;

                output[tuple.id] = tuple.item;
            }
            else
            {
                Debug.Assert(action.CompletionType == ParallelCompletionType.N_of_M);
                Debug.Assert(action.N > 0);

                var resultCount = 0;

                while (resultCount < action.N && resultCount < tasks.Count)
                {
                    var resultTask = await Task.WhenAny(tasks);

                    tasks.Remove(resultTask);

                    var tuple = await resultTask;

                    output[tuple.id] = tuple.item;

                    resultCount++;
                }
            }

            return(output);
        }
        public static Task <JToken> ExecuteAsync(this InjectDataAction action,
                                                 StateMachineContext context,
                                                 JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            return(Task.FromResult(action.Expression.EvalExpr(input, context) ?? JValue.CreateNull()));
        }
        public static async Task <JToken> ExecuteAsync(this DelayAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            await context.Host.DelayAsync(action.Timeout, context.CancelToken);

            return(JValue.CreateNull());
        }
Example #7
0
        public static async Task <JToken> ExecuteAsync(this InvokeSubflowAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            Func <CancellationToken, Task <JToken> > invokeTask = async token =>
            {
                var jobj = await context.Host.ExecuteSubflowAsync(action.SubflowName, input, token, action.WaitForCompletion);

                Debug.Assert(jobj != null);

                return(jobj);
            };

            JToken output;

            if (action.Timeout != null)
            {
                using var localTimeoutCancelTokenSource = new CancellationTokenSource();

                using var combined = CancellationTokenSource.CreateLinkedTokenSource(
                          localTimeoutCancelTokenSource.Token, context.CancelToken);

                Task <JToken> timeoutTask = context.Host.DelayAsync(action.Timeout.Value, combined.Token)
                                            .ContinueWith(_ =>
                {
                    return((JToken)JValue.CreateNull());
                });

                Debug.Assert(timeoutTask != null);

                output = await Task.WhenAny(timeoutTask, invokeTask(combined.Token)).Unwrap();

                if (!timeoutTask.IsCompleted)
                {
                    localTimeoutCancelTokenSource.Cancel();
                }
            }
            else
            {
                output = await invokeTask(context.CancelToken);
            }

            Debug.Assert(output != null);

            return(output);
        }
        public static JToken?EvalExpr(this string?expr, JToken json, StateMachineContext context)
        {
            json.CheckArgNull(nameof(json));
            context.CheckArgNull(nameof(context));

            if (string.IsNullOrWhiteSpace(expr))
            {
                return(json);
            }
            else if (!expr.IsJQExpression())
            {
                throw new InvalidOperationException("Invalid JQ expression: " + expr);
            }

            expr = expr.TrimJQExpression();

            if (expr.StartsWith("fn:"))
            {
                var functionName = expr[3..];
Example #9
0
        public static async Task <JToken> ExecuteAsync(this ForEachAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            Debug.Assert(!string.IsNullOrWhiteSpace(action.Input));

            var token = action.Input.EvalExpr(input, context);

            if (token == null || token.Type != JTokenType.Array)
            {
                throw new InvalidOperationException("Unable to resolve input collection for ForEach action.");
            }

            var inputCollection = (JArray)token;

            var max = action.MaxParallel ?? inputCollection.Count;

            Debug.Assert(action.Action != null);

            var outputs = new JArray();

            for (var i = 0; i < inputCollection.Count; i += max)
            {
                if (context.CancelToken.IsCancellationRequested)
                {
                    break;
                }

                var subsetTasks = inputCollection.Skip(i)
                                  .Take(max)
                                  .Select(json => action.Action.ExecuteAsync(context, json));

                var results = await Task.WhenAll(subsetTasks);

                Array.ForEach(results, outputs.Add);
            }

            return(outputs);
        }
Example #10
0
        public static async Task <State?> ExecuteAsync(this State state, StateMachineContext context)
        {
            state.CheckArgNull(nameof(state));
            context.CheckArgNull(nameof(context));

            JToken data = await EnterState(state, context);

            State?nextState = null;

            while (true)
            {
                var transition = await state.ResolveTransitionAsync(context, data);

                if (transition == null)
                {
                    break;
                }

                if (transition.Action != null)
                {
                    var result = await transition.Action.ExecuteAsync(context, data);

                    Debug.Assert(result != null);

                    result.Merge(context.Data, transition.ResultHandler, context);
                }

                nextState = context.Workflow.ResolveStateByName(transition.NextState);

                if (nextState != null)
                {
                    break;
                }
            }

            await ExitState(state, context, data);

            return(nextState);
        }
Example #11
0
        public static async Task <JToken> ExecuteAsync(this ModelAction action,
                                                       StateMachineContext context,
                                                       JToken input)
        {
            action.CheckArgNull(nameof(action));
            context.CheckArgNull(nameof(context));
            input.CheckArgNull(nameof(input));

            await context.RecordObservableActionAsync(ObservableAction.BeforeAction,
                                                      () => new Dictionary <string, object>
            {
                { "actionName", action.Name },
                { "actionType", action.GetType().FullName }
            });

            Func <StateMachineContext, JToken, Task <JToken> > executeFunc = action switch
            {
                InjectDataAction inject => inject.ExecuteAsync,
                ParallelAction parallel => parallel.ExecuteAsync,
                SequenceAction sequence => sequence.ExecuteAsync,
                SendEventAction send => send.ExecuteAsync,
                DelayAction delay => delay.ExecuteAsync,
                InvokeSubflowAction subflow => subflow.ExecuteAsync,
                InvokeFunctionAction function => function.ExecuteAsync,
                ForEachAction forEach => forEach.ExecuteAsync,
                                                               _ => throw new NotImplementedException("Action behavior not implemented: " + action.GetType().FullName)
            };

            Debug.Assert(executeFunc != null);

            var            attempts = 0;
            DateTimeOffset?start    = null;

            while (true)
            {
                try
                {
                    var result = await executeFunc(context, input);

                    await context.RecordObservableActionAsync(ObservableAction.AfterAction,
                                                              () => new Dictionary <string, object>
                    {
                        { "actionName", action.Name },
                        { "actionType", action.GetType().FullName }
                    });

                    return(result);
                }
                catch (Exception ex)
                {
                    if (action.TryHandleError(JObject.FromObject(ex),
                                              context,
                                              out RetryPolicy? retryPolicy))
                    {
                        if (retryPolicy == null)
                        {
                            return(JValue.CreateNull());
                        }
                        else
                        {
                            TimeSpan elapsedDelay;

                            if (start == null)
                            {
                                start        = DateTimeOffset.UtcNow;
                                elapsedDelay = TimeSpan.Zero;
                            }
                            else
                            {
                                elapsedDelay = DateTimeOffset.UtcNow.Subtract(start.Value);
                            }

                            var retry = await retryPolicy.ShouldRetryAsync(context, ++attempts, elapsedDelay);

                            if (retry)
                            {
                                continue;
                            }
                        }
                    }

                    throw;
                }
            }
        }