Beispiel #1
0
        /// <summary>
        /// Converts the instance into a public <see cref="WorkflowStatus"/>.
        /// </summary>
        public WorkflowStatus ToPublic()
        {
            var executionState = new WorkflowStatus()
            {
                Execution           = this.Execution.ToPublic(),
                TypeName            = this.WorkflowType.Name,
                WorkflowCloseStatus = (WorkflowExecutionCloseStatus)this.WorkflowCloseStatus,
                HistoryLength       = this.HistoryLength,
                ParentDomain        = this.ParentDomainId,
                ExecutionTime       = ComputeExecutionTime(),
                Memo = this.Memo?.Fields
            };

            if (this.StartTime > 0)
            {
                executionState.StartTime = CadenceHelper.UnixNanoToDateTimeUtc(this.StartTime);
            }

            if (this.CloseTime > 0)
            {
                executionState.CloseTime = CadenceHelper.UnixNanoToDateTimeUtc(this.CloseTime);
            }

            return(executionState);
        }
Beispiel #2
0
        /// <summary>
        /// Hack to compute the workflowm execution time.
        /// </summary>
        /// <returns>The execution <see cref="TimeSpan"/>.</returns>
        private TimeSpan ComputeExecutionTime()
        {
            // $hack(jefflill):
            //
            // This hack mitigates:
            //
            //      https://github.com/nforgeio/neonKUBE/issues/759

            if (this.StartTime > 0 && this.CloseTime > 0)
            {
                return(CadenceHelper.UnixNanoToDateTimeUtc(this.StartTime) - CadenceHelper.UnixNanoToDateTimeUtc(this.CloseTime));
            }
            else if (this.StartTime > 0)
            {
                var executionTime = DateTime.UtcNow - CadenceHelper.UnixNanoToDateTimeUtc(this.StartTime);

                // It's possible for this calculation to come out negative when the Cadence server
                // and client machine clock time is different.  We'll just return zero when this
                // happens.

                if (executionTime < TimeSpan.Zero)
                {
                    executionTime = TimeSpan.Zero;
                }

                return(executionTime);
            }
            else
            {
                return(TimeSpan.Zero);
            }
        }
Beispiel #3
0
 /// <summary>
 /// Converts the instance to a <see cref="PollerInfo"/>.
 /// </summary>
 /// <returns>The converted <see cref="PollerInfo"/>.</returns>
 public PollerInfo ToPublic()
 {
     return(new PollerInfo()
     {
         LastAccessTime = CadenceHelper.UnixNanoToDateTimeUtc(this.LastAccessTime),
         Identity = this.Identity,
         RatePerSecond = this.RatePerSecond
     });
 }
Beispiel #4
0
        /// <summary>
        /// Constructs an instance from a <see cref="LinearRetryPolicy"/>.
        /// </summary>
        /// <param name="policy">The policy.</param>
        public InternalRetryPolicy(LinearRetryPolicy policy)
        {
            Covenant.Requires <ArgumentNullException>(policy != null, nameof(policy));

            this.InitialInterval    = CadenceHelper.ToCadence(policy.RetryInterval);
            this.BackoffCoefficient = 1.0;

            if (policy.Timeout.HasValue)
            {
                this.ExpirationInterval = CadenceHelper.ToCadence(policy.Timeout.Value);
            }

            this.MaximumAttempts = policy.MaxAttempts;
        }
 /// <summary>
 /// Converts the instance into a public <see cref="ActivityTask"/>.
 /// </summary>
 public ActivityTask ToPublic()
 {
     return(new ActivityTask()
     {
         TaskToken = this.TaskToken,
         WorkflowTypeName = this.WorkflowType?.Name,
         WorkflowDomain = this.WorkflowDomain,
         WorkflowExecution = this.WorkflowExecution.ToPublic(),
         ActivityId = this.ActivityId,
         ActivityTypeName = this.ActivityType?.Name,
         TaskList = this.TaskList,
         HeartbeatTimeout = TimeSpan.FromTicks(this.HeartbeatTimeout / 100),
         ScheduledTimeUtc = CadenceHelper.ParseCadenceTimestamp(this.ScheduledTimestamp),
         StartedTimeUtc = CadenceHelper.ParseCadenceTimestamp(this.StartedTimestamp),
         DeadlineTimeUtc = CadenceHelper.ParseCadenceTimestamp(this.Deadline),
         Attempt = this.Attempt
     });
 }
Beispiel #6
0
        /// <summary>
        /// Returns the Cadence activity type name to be used for a activity interface or
        /// implementation class.
        /// </summary>
        /// <param name="activityType">The activity interface or implementation type.</param>
        /// <param name="activityAttribute">Specifies the <see cref="ActivityAttribute"/>.</param>
        /// <returns>The type name.</returns>
        /// <remarks>
        /// <para>
        /// If <paramref name="activityAttribute"/> is passed and <see cref="ActivityAttribute.Name"/>
        /// is not <c>null</c> or empty, then the name specified in the attribute is returned.
        /// </para>
        /// <para>
        /// Otherwise, we'll return the fully qualified name of the activity interface
        /// with the leadting "I" removed.
        /// </para>
        /// </remarks>
        internal static string GetActivityTypeName(Type activityType, ActivityAttribute activityAttribute)
        {
            Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType));

            if (activityAttribute != null && !string.IsNullOrEmpty(activityAttribute.Name))
            {
                return(activityAttribute.Name);
            }

            if (activityType.IsClass)
            {
                CadenceHelper.ValidateActivityImplementation(activityType);

                activityType = CadenceHelper.GetActivityInterface(activityType);
            }
            else
            {
                CadenceHelper.ValidateActivityInterface(activityType);
            }

            var fullName = activityType.FullName;
            var name     = activityType.Name;

            if (name.StartsWith("I") && name != "I")
            {
                // We're going to strip the leading "I" from the unqualified
                // type name (unless that's the only character).

                fullName  = fullName.Substring(0, fullName.Length - name.Length);
                fullName += name.Substring(1);
            }

            // We need to replace the "+" characters .NET uses for nested types into
            // "." so the result will be a valid C# type identifier.

            return(fullName.Replace('+', '.'));
        }
Beispiel #7
0
 /// <summary>
 /// Creates a local activity stub instance suitable for executing a non-local activity.
 /// </summary>
 /// <param name="client">The associated <see cref="CadenceClient"/>.</param>
 /// <param name="workflow">The parent workflow.</param>
 /// <param name="activityType">The activity implementation type.</param>
 /// <param name="options">Specifies the <see cref="LocalActivityOptions"/>.</param>
 /// <returns>The activity stub as an <see cref="object"/>.</returns>
 public object CreateLocal(CadenceClient client, Workflow workflow, Type activityType, LocalActivityOptions options)
 {
     return(localConstructor.Invoke(new object[] { client, client.DataConverter, workflow, activityType, options, CadenceHelper.GetActivityInterface(activityType) }));
 }
Beispiel #8
0
        /// <summary>
        /// Returns the activity type and method information for an activity interface and
        /// an optional target method name.
        /// </summary>
        /// <param name="activityInterface">The target activity interface.</param>
        /// <param name="methodName">
        /// Optionally specifies the target method name (as specified in the <c>[ActivityMethod]</c>
        /// attribiute tagging the activity method within the interface.
        /// </param>
        /// <returns>The activity type name for the activity interface as well as the method information and attribute.</returns>
        /// <exception cref="ArgumentException">Thrown if target method does not exist.</exception>
        /// <remarks>
        /// <paramref name="methodName"/> is optional.  When this is passed as <c>null</c>
        /// or empty, the default activity method will be targeted (if any).
        /// </remarks>
        internal static (string ActivityTypeName, MethodInfo TargetMethod, ActivityMethodAttribute MethodAttribute) GetActivityTarget(Type activityInterface, string methodName = null)
        {
            Covenant.Requires <ArgumentNullException>(activityInterface != null);

            CadenceHelper.ValidateActivityInterface(activityInterface);

            var activityAttribute = activityInterface.GetCustomAttribute <ActivityAttribute>();
            var methodAttribute   = (ActivityMethodAttribute)null;
            var targetMethod      = (MethodInfo)null;

            if (string.IsNullOrEmpty(methodName))
            {
                // Look for the entrypoint method with a null or empty method name.

                foreach (var method in activityInterface.GetMethods())
                {
                    methodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>();

                    if (methodAttribute != null)
                    {
                        if (string.IsNullOrEmpty(methodAttribute.Name))
                        {
                            targetMethod = method;
                            break;
                        }
                    }
                }
            }
            else
            {
                // Look for the entrypoint method with the matching method name.

                foreach (var method in activityInterface.GetMethods())
                {
                    methodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>();

                    if (methodAttribute != null)
                    {
                        if (methodName == methodAttribute.Name)
                        {
                            targetMethod = method;
                            break;
                        }
                    }
                }
            }

            if (targetMethod == null)
            {
                throw new ArgumentException($"Activity interface [{activityInterface.FullName}] does not have a method tagged by [ActivityMethod(Name = {methodName})].", nameof(activityInterface));
            }

            var activityTypeName = CadenceHelper.GetActivityTypeName(activityInterface, activityAttribute);

            if (!string.IsNullOrEmpty(methodAttribute.Name))
            {
                activityTypeName += $"::{methodAttribute.Name}";
            }

            return(activityTypeName, targetMethod, methodAttribute);
        }
Beispiel #9
0
        /// <summary>
        /// Ensures that the type passed is a valid activity interface.
        /// </summary>
        /// <param name="activityInterface">The type being tested.</param>
        /// <exception cref="ActivityTypeException">Thrown when the interface is not valid.</exception>
        internal static void ValidateActivityInterface(Type activityInterface)
        {
            Covenant.Requires <ArgumentNullException>(activityInterface != null, nameof(activityInterface));

            if (!activityInterface.IsInterface)
            {
                throw new ActivityTypeException($"[{activityInterface.FullName}] is not an interface.");
            }

            if (!activityInterface.Implements <IActivity>())
            {
                throw new ActivityTypeException($"[{activityInterface.FullName}] does not implement [{typeof(IActivity).FullName}].");
            }

            if (activityInterface.IsGenericType)
            {
                throw new ActivityTypeException($"[{activityInterface.FullName}] has generic type parameters.  Activity interfaces cannot be generic.");
            }

            if (!activityInterface.IsPublic && !activityInterface.IsNestedPublic)
            {
                throw new ActivityTypeException($"Activity interface [{activityInterface.FullName}] is not public.");
            }

            if (activityInterface.GetCustomAttribute <WorkflowInterfaceAttribute>() != null)
            {
                throw new WorkflowTypeException($"Workflow interface [{activityInterface.FullName}] cannot be tagged with [WorkflowInterface] because it doesn't define a workflow.");
            }

            if (activityInterface.GetCustomAttribute <ActivityAttribute>() != null)
            {
                throw new WorkflowTypeException($"Activity interface [{activityInterface.FullName}] cannot not be tagged with [Activity] because that is valid only for activity implementation classes.");
            }

            // Validate the activity methods.

            var activityNames = new HashSet <string>();

            foreach (var method in activityInterface.GetMethods())
            {
                var activityMethodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>();

                if (activityMethodAttribute == null)
                {
                    continue;
                }

                if (!CadenceHelper.IsTask(method.ReturnType))
                {
                    throw new WorkflowTypeException($"Activity interface method [{activityInterface.FullName}.{method.Name}()] must return a Task.");
                }

                var name = activityMethodAttribute.Name ?? string.Empty;

                if (activityNames.Contains(name))
                {
                    throw new ActivityTypeException($"Multiple [{activityInterface.FullName}] activity methods are tagged by [ActivityMethod(Name = \"{name}\")].");
                }

                activityNames.Add(name);
            }

            if (activityNames.Count == 0)
            {
                throw new ActivityTypeException($"Activity interface [{activityInterface.FullName}] does not define any methods tagged with [ActivityMethod].");
            }
        }
Beispiel #10
0
        /// <summary>
        /// Ensures that the type passed is a valid workflow interface.
        /// </summary>
        /// <param name="workflowInterface">The type being tested.</param>
        /// <exception cref="ActivityTypeException">Thrown when the interface is not valid.</exception>
        internal static void ValidateWorkflowInterface(Type workflowInterface)
        {
            Covenant.Requires <ArgumentNullException>(workflowInterface != null, nameof(workflowInterface));

            if (!workflowInterface.IsInterface)
            {
                throw new WorkflowTypeException($"[{workflowInterface.FullName}] is not an interface.");
            }

            if (!workflowInterface.Implements <IWorkflow>())
            {
                throw new WorkflowTypeException($"[{workflowInterface.FullName}] does not implement [{typeof(IWorkflow).FullName}].");
            }

            if (workflowInterface.IsGenericType)
            {
                throw new WorkflowTypeException($"[{workflowInterface.FullName}] has generic type parameters.  Workflow interfaces cannot be generic.");
            }

            if (!workflowInterface.IsPublic && !workflowInterface.IsNestedPublic)
            {
                throw new WorkflowTypeException($"Workflow interface [{workflowInterface.FullName}] is not public.");
            }

            if (workflowInterface.GetCustomAttribute <ActivityInterfaceAttribute>() != null)
            {
                throw new WorkflowTypeException($"Workflow interface [{workflowInterface.FullName}] cannot be tagged with [ActivityInterface] because it doesn't define an activity.");
            }

            if (workflowInterface.GetCustomAttribute <WorkflowAttribute>() != null)
            {
                throw new WorkflowTypeException($"Workflow interface [{workflowInterface.FullName}] cannot not be tagged with [Workflow] because that is valid only for activity implementation classes.");
            }

            // Validate the entrypoint method names and result types.

            var workflowNames = new HashSet <string>();

            foreach (var method in workflowInterface.GetMethods())
            {
                var workflowMethodAttribute = method.GetCustomAttribute <WorkflowMethodAttribute>();

                if (workflowMethodAttribute == null)
                {
                    continue;
                }

                if (!CadenceHelper.IsTask(method.ReturnType))
                {
                    throw new WorkflowTypeException($"Workflow interface method [{workflowInterface.FullName}.{method.Name}()] must return a Task.");
                }

                var name = workflowMethodAttribute.Name ?? string.Empty;

                if (workflowNames.Contains(name))
                {
                    throw new WorkflowTypeException($"Multiple workflow methods are tagged by [WorkflowMethod(Name = \"{name}\")].");
                }

                workflowNames.Add(name);
            }

            if (workflowNames.Count == 0)
            {
                throw new ActivityTypeException($"Workflow interface [{workflowInterface.FullName}] does not define any methods tagged with [WorkflowMethod].");
            }

            // Validate the signal method names and return types.

            var signalNames = new HashSet <string>();

            foreach (var method in workflowInterface.GetMethods())
            {
                var signalMethodAttribute = method.GetCustomAttribute <SignalMethodAttribute>();

                if (signalMethodAttribute == null)
                {
                    continue;
                }

                if (!CadenceHelper.IsTask(method.ReturnType))
                {
                    throw new WorkflowTypeException($"Workflow interface method [{workflowInterface.FullName}.{method.Name}()] must return a Task.");
                }

                var name = signalMethodAttribute.Name ?? string.Empty;

                if (signalNames.Contains(name))
                {
                    throw new WorkflowTypeException($"Multiple signal methods are tagged by [SignalMethod(name:\"{name}\")].");
                }

                signalNames.Add(name);
            }

            // Validate the signal method names and return types.

            var queryNames = new HashSet <string>();

            foreach (var method in workflowInterface.GetMethods())
            {
                var queryMethodAttribute = method.GetCustomAttribute <QueryMethodAttribute>();

                if (queryMethodAttribute == null)
                {
                    continue;
                }

                if (!CadenceHelper.IsTask(method.ReturnType))
                {
                    throw new WorkflowTypeException($"Workflow interface method [{workflowInterface.FullName}.{method.Name}()] must return a Task.");
                }

                var name = queryMethodAttribute.Name ?? string.Empty;

                if (queryNames.Contains(name))
                {
                    throw new WorkflowTypeException($"Multiple query methods are tagged by [QueryMethod(name:\"{name}\")].");
                }

                queryNames.Add(name);
            }
        }
Beispiel #11
0
        /// <summary>
        /// Returns the workflow type and method information for a workflow interface and
        /// an optional target method name.
        /// </summary>
        /// <param name="workflowInterface">The target workflow interface.</param>
        /// <param name="methodName">
        /// Optionally specifies the target method name (as specified in the <c>[WorkflowMethod]</c>
        /// attribiute tagging the workflow method within the interface.
        /// </param>
        /// <returns>The workflow type name for the workflow interface as well as the method information and attribute.</returns>
        /// <exception cref="ArgumentException">Thrown if target method does not exist.</exception>
        /// <remarks>
        /// <paramref name="methodName"/> is optional.  When this is passed as <c>null</c>
        /// or empty, the default workflow method will be targeted (if any).
        /// </remarks>
        internal static (string WorkflowTypeName, MethodInfo TargetMethod, WorkflowMethodAttribute MethodAttribute) GetWorkflowTarget(Type workflowInterface, string methodName = null)
        {
            Covenant.Requires <ArgumentNullException>(workflowInterface != null);

            CadenceHelper.ValidateWorkflowInterface(workflowInterface);

            var workflowAttribute = workflowInterface.GetCustomAttribute <WorkflowAttribute>();
            var methodAttribute   = (WorkflowMethodAttribute)null;
            var targetMethod      = (MethodInfo)null;

            if (string.IsNullOrEmpty(methodName))
            {
                // Look for the entrypoint method with a null or empty method name.

                foreach (var method in workflowInterface.GetMethods())
                {
                    methodAttribute = method.GetCustomAttribute <WorkflowMethodAttribute>();

                    if (methodAttribute != null)
                    {
                        if (string.IsNullOrEmpty(methodAttribute.Name))
                        {
                            targetMethod = method;
                            break;
                        }
                    }
                }
            }
            else
            {
                // Look for the entrypoint method with the matching method name.

                foreach (var method in workflowInterface.GetMethods())
                {
                    methodAttribute = method.GetCustomAttribute <WorkflowMethodAttribute>();

                    if (methodAttribute != null)
                    {
                        if (methodName == methodAttribute.Name)
                        {
                            targetMethod = method;
                            break;
                        }
                    }
                }
            }

            if (targetMethod == null)
            {
                throw new ArgumentException($"Workflow interface [{workflowInterface.FullName}] does not have a method tagged by [WorkflowMethod(Name = {methodName})].", nameof(workflowInterface));
            }

            var workflowTypeName = CadenceHelper.GetWorkflowTypeName(workflowInterface, workflowAttribute);

            if (!string.IsNullOrEmpty(methodAttribute.Name))
            {
                workflowTypeName += $"::{methodAttribute.Name}";
            }

            return(workflowTypeName, targetMethod, methodAttribute);
        }
Beispiel #12
0
        /// <summary>
        /// Creates a local activity stub instance suitable for executing a local activity.
        /// </summary>
        /// <param name="client">The associated <see cref="CadenceClient"/>.</param>
        /// <param name="workflow">The parent workflow.</param>
        /// <param name="activityType">The activity implementation type.</param>
        /// <param name="options">Specifies the <see cref="LocalActivityOptions"/> or <c>null</c>.</param>
        /// <returns>The activity stub as an <see cref="object"/>.</returns>
        public object CreateLocal(CadenceClient client, Workflow workflow, Type activityType, LocalActivityOptions options)
        {
            Covenant.Requires <ArgumentNullException>(client != null, nameof(client));
            Covenant.Requires <ArgumentNullException>(workflow != null, nameof(workflow));
            Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType));

            options = options ?? new LocalActivityOptions();

            return(localConstructor.Invoke(new object[] { client, client.DataConverter, workflow, activityType, options, CadenceHelper.GetActivityInterface(activityType) }));
        }