/// <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); }
/// <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); } }
/// <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 }); }
/// <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 }); }
/// <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('+', '.')); }
/// <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) })); }
/// <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); }
/// <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]."); } }
/// <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); } }
/// <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); }
/// <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) })); }