/// <summary> /// Registers an activity implementation with Cadence. /// </summary> /// <typeparam name="TActivity">The <see cref="ActivityBase"/> derived class implementing the activity.</typeparam> /// <param name="activityTypeName"> /// Optionally specifies a custom activity type name that will be used /// for identifying the activity implementation in Cadence. This defaults /// to the fully qualified <typeparamref name="TActivity"/> type name. /// </param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> /// <exception cref="ActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all services you will be injecting into activities via /// <see cref="NeonHelper.ServiceContainer"/> before you call this as well as /// registering of your activity implementations before starting workers. /// </note> /// </remarks> public async Task RegisterActivityAsync <TActivity>(string activityTypeName = null, string domain = null) where TActivity : ActivityBase { await SyncContext.Clear; CadenceHelper.ValidateActivityImplementation(typeof(TActivity)); CadenceHelper.ValidateActivityTypeName(activityTypeName); EnsureNotDisposed(); if (activityWorkerStarted) { throw new ActivityWorkerStartedException(); } var activityType = typeof(TActivity); if (string.IsNullOrEmpty(activityTypeName)) { activityTypeName = CadenceHelper.GetActivityTypeName(activityType, activityType.GetCustomAttribute <ActivityAttribute>()); } await ActivityBase.RegisterAsync(this, activityType, activityTypeName, ResolveDomain(domain)); lock (registeredActivityTypes) { registeredActivityTypes.Add(CadenceHelper.GetActivityInterface(typeof(TActivity))); } }
/// <summary> /// Executes the target activity method. /// </summary> /// <param name="client">The associated Cadence client.</param> /// <param name="argBytes">The encoded activity arguments.</param> /// <returns>The encoded activity results.</returns> private async Task <byte[]> InvokeAsync(CadenceClient client, byte[] argBytes) { await SyncContext.Clear; var parameters = activityMethod.GetParameters(); var parameterTypes = new Type[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { parameterTypes[i] = parameters[i].ParameterType; } var resultType = activityMethod.ReturnType; var args = CadenceHelper.BytesToArgs(dataConverter, argBytes, parameterTypes); var serializedResult = Array.Empty <byte>(); if (resultType.IsGenericType) { // Activity method returns: Task<T> var result = await NeonHelper.GetTaskResultAsObjectAsync((Task)activityMethod.Invoke(this, args)); serializedResult = client.DataConverter.ToData(result); } else { // Activity method returns: Task await(Task) activityMethod.Invoke(this, args); } return(serializedResult); }
/// <summary> /// Signals the workflow. /// </summary> /// <param name="signalName">The signal name.</param> /// <param name="args">The signal arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if the child workflow has not been started.</exception> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed /// are compatible with the target workflow signal arguments. /// </note> /// </remarks> public async Task SignalAsync(string signalName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (Execution == null) { throw new InvalidOperationException("The stub must be started first."); } var reply = await parentWorkflow.ExecuteNonParallel( async() => { return((WorkflowSignalChildReply)await client.CallProxyAsync( new WorkflowSignalChildRequest() { ContextId = parentWorkflow.ContextId, ChildId = childExecution.ChildId, SignalName = signalName, SignalArgs = CadenceHelper.ArgsToBytes(client.DataConverter, args) })); }); reply.ThrowOnError(); }
//--------------------------------------------------------------------- // Public Cadence workflow related operations. /// <summary> /// Registers a workflow implementation with Cadence. /// </summary> /// <typeparam name="TWorkflow">The <see cref="WorkflowBase"/> derived class implementing the workflow.</typeparam> /// <param name="workflowTypeName"> /// Optionally specifies a custom workflow type name that will be used /// for identifying the workflow implementation in Cadence. This defaults /// to the fully qualified <typeparamref name="TWorkflow"/> type name. /// </param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if another workflow class has already been registered for <paramref name="workflowTypeName"/>.</exception> /// <exception cref="WorkflowWorkerStartedException"> /// Thrown if a workflow worker has already been started for the client. You must /// register workflow implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all of your workflow implementations before starting workers. /// </note> /// </remarks> public async Task RegisterWorkflowAsync <TWorkflow>(string workflowTypeName = null, string domain = null) where TWorkflow : WorkflowBase { await SyncContext.ClearAsync; CadenceHelper.ValidateWorkflowImplementation(typeof(TWorkflow)); CadenceHelper.ValidateWorkflowTypeName(workflowTypeName); EnsureNotDisposed(); if (workflowWorkerStarted) { throw new WorkflowWorkerStartedException(); } var workflowType = typeof(TWorkflow); if (string.IsNullOrEmpty(workflowTypeName)) { workflowTypeName = CadenceHelper.GetWorkflowTypeName(workflowType, workflowType.GetCustomAttribute <WorkflowAttribute>()); } await WorkflowBase.RegisterAsync(this, workflowType, workflowTypeName, ResolveDomain(domain)); lock (registeredWorkflowTypes) { registeredWorkflowTypes.Add(CadenceHelper.GetWorkflowInterface(typeof(TWorkflow))); } }
/// <summary> /// Creates a typed workflow stub that can be used to start as well as /// query and signal the workflow via the type-safe interface methods. /// </summary> /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam> /// <param name="options">Optionally specifies the workflow options.</param> /// <param name="workflowTypeName"> /// Optionally specifies the workflow type name by overriding the fully /// qualified <typeparamref name="TWorkflowInterface"/> type name or the name /// specified by a <see cref="WorkflowAttribute"/>. /// </param> /// <param name="domain">Optionally overrides the client's default domain.</param> /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns> /// <remarks> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke and then the first method called on a workflow stub must be /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>. /// </remarks> public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(WorkflowOptions options = null, string workflowTypeName = null, string domain = null) where TWorkflowInterface : WorkflowBase { CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); throw new NotImplementedException(); }
/// <summary> /// Scans the assembly passed looking for activity implementations derived from /// <see cref="ActivityBase"/> and tagged by <see cref="ActivityAttribute"/> and /// registers them with Cadence. /// </summary> /// <param name="assembly">The target assembly.</param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="TypeLoadException"> /// Thrown for types tagged by <see cref="ActivityAttribute"/> that are not /// derived from <see cref="ActivityBase"/>. /// </exception> /// <exception cref="InvalidOperationException">Thrown if one of the tagged classes conflict with an existing registration.</exception> /// <exception cref="ActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all services you will be injecting into activities via /// <see cref="NeonHelper.ServiceContainer"/> before you call this as well as /// registering of your activity implementations before starting workers. /// </note> /// </remarks> public async Task RegisterAssemblyActivitiesAsync(Assembly assembly, string domain = null) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(assembly != null, nameof(assembly)); EnsureNotDisposed(); if (activityWorkerStarted) { throw new ActivityWorkerStartedException(); } foreach (var type in assembly.GetTypes().Where(type => type.IsClass)) { var activityAttribute = type.GetCustomAttribute <ActivityAttribute>(); if (activityAttribute != null && activityAttribute.AutoRegister) { var activityTypeName = CadenceHelper.GetActivityTypeName(type, activityAttribute); await ActivityBase.RegisterAsync(this, type, activityTypeName, ResolveDomain(domain)); lock (registeredActivityTypes) { registeredActivityTypes.Add(CadenceHelper.GetActivityInterface(type)); } } } }
//--------------------------------------------------------------------- // Public Cadence workflow related operations. /// <summary> /// Registers a workflow implementation with Cadence. /// </summary> /// <typeparam name="TWorkflow">The <see cref="IWorkflowBase"/> derived class implementing the workflow.</typeparam> /// <param name="workflowTypeName"> /// Optionally specifies a custom workflow type name that will be used /// for identifying the workflow implementation in Cadence. This defaults /// to the fully qualified <typeparamref name="TWorkflow"/> type name. /// </param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if another workflow class has already been registered for <paramref name="workflowTypeName"/>.</exception> /// <exception cref="CadenceWorkflowWorkerStartedException"> /// Thrown if a workflow worker has already been started for the client. You must /// register activity workflow implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all of your workflow implementations before starting a workflow worker. /// </note> /// </remarks> public async Task RegisterWorkflowAsync <TWorkflow>(string workflowTypeName = null, string domain = null) where TWorkflow : WorkflowBase { CadenceHelper.ValidateWorkflowImplementation(typeof(TWorkflow)); CadenceHelper.ValidateWorkflowTypeName(workflowTypeName); if (string.IsNullOrEmpty(workflowTypeName)) { workflowTypeName = workflowTypeName ?? typeof(TWorkflow).FullName; } if (workflowWorkerStarted) { throw new CadenceWorkflowWorkerStartedException(); } // We need to register a workflow type name for each workflow method, // appending the method type separator and method name for workflow // methods that specify names. if (!WorkflowBase.Register(this, typeof(TWorkflow), workflowTypeName)) { var reply = (WorkflowRegisterReply) await CallProxyAsync( new WorkflowRegisterRequest() { Name = workflowTypeName, Domain = ResolveDomain(domain) }); reply.ThrowOnError(); } }
/// <summary> /// Converts the instance into an internal <see cref="InternalStartWorkflowOptions"/>. /// </summary> /// <returns>The corresponding <see cref="InternalStartWorkflowOptions"/>.</returns> internal InternalStartWorkflowOptions ToInternal() { Dictionary <string, byte[]> encodedMemos = null; if (Memo != null && Memo.Count > 0) { encodedMemos = new Dictionary <string, byte[]>(); foreach (var item in Memo) { encodedMemos.Add(item.Key, NeonHelper.JsonSerializeToBytes(item.Value)); } } return(new InternalStartWorkflowOptions() { ID = this.WorkflowId, TaskList = this.TaskList, DecisionTaskStartToCloseTimeout = CadenceHelper.ToCadence(this.TaskStartToCloseTimeout.Value), ExecutionStartToCloseTimeout = CadenceHelper.ToCadence(this.ScheduleToCloseTimeout.Value), RetryPolicy = this.RetryOptions?.ToInternal(), WorkflowIdReusePolicy = (int)(this.WorkflowIdReusePolicy == WorkflowIdReusePolicy.UseDefault ? Cadence.WorkflowIdReusePolicy.AllowDuplicateFailedOnly : this.WorkflowIdReusePolicy), CronSchedule = this.CronSchedule, Memo = encodedMemos }); }
//--------------------------------------------------------------------- // Cadence activity related operations. /// <summary> /// Registers an activity implementation with Cadence. /// </summary> /// <typeparam name="TActivity">The <see cref="IActivityBase"/> derived class implementing the activity.</typeparam> /// <param name="activityTypeName"> /// Optionally specifies a custom activity type name that will be used /// for identifying the activity implementation in Cadence. This defaults /// to the fully qualified <typeparamref name="TActivity"/> type name. /// </param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> /// <exception cref="CadenceActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all of your activity implementations before starting a workflow worker. /// </note> /// </remarks> public async Task RegisterActivityAsync <TActivity>(string activityTypeName = null) where TActivity : ActivityBase { CadenceHelper.ValidateActivityImplementation(typeof(TActivity)); if (string.IsNullOrEmpty(activityTypeName)) { activityTypeName = activityTypeName ?? typeof(TActivity).FullName; } if (activityWorkerStarted) { throw new CadenceActivityWorkerStartedException(); } if (!ActivityBase.Register(this, typeof(TActivity), activityTypeName)) { var reply = (ActivityRegisterReply) await CallProxyAsync( new ActivityRegisterRequest() { Name = activityTypeName }); reply.ThrowOnError(); } }
/// <summary> /// Queries the workflow. /// </summary> /// <typeparam name="TQueryResult">The query result type.</typeparam> /// <param name="queryName">Identifies the query.</param> /// <param name="args">The query arguments.</param> /// <returns>The query result.</returns> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters and /// result type passed are compatible with the target workflow query arguments. /// </note> /// </remarks> public async Task <TQueryResult> QueryAsync <TQueryResult>(string queryName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(queryName), nameof(queryName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (execution == null) { throw new InvalidOperationException("The stub must be started first."); } var reply = (WorkflowQueryReply)await client.CallProxyAsync( new WorkflowQueryRequest() { WorkflowId = execution.WorkflowId, RunId = execution.RunId, Domain = options.Domain, QueryName = queryName, QueryArgs = CadenceHelper.ArgsToBytes(client.DataConverter, args) }); reply.ThrowOnError(); return(client.DataConverter.FromData <TQueryResult>(reply.Result)); }
/// <summary> /// Creates a typed workflow stub connected to a known workflow execution. /// This can be used to signal and query the workflow via the type-safe /// interface methods. /// </summary> /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam> /// <param name="workflowId">Specifies the workflow ID.</param> /// <param name="runId">Optionally specifies the workflow's run ID.</param> /// <param name="workflowTypeName"> /// Optionally specifies the workflow type name by overriding the fully /// qualified <typeparamref name="TWorkflowInterface"/> type name or the name /// specified by a <see cref="WorkflowAttribute"/>. /// </param> /// <param name="domain">Optionally overrides the client's default domain.</param> /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns> /// <remarks> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke and then the first method called on a workflow stub must be /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>. /// </remarks> public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(string workflowId, string runId = null, string workflowTypeName = null, string domain = null) where TWorkflowInterface : WorkflowBase { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowId)); CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); throw new NotImplementedException(); }
/// <summary> /// Converts this instance into the corresponding internal object. /// </summary> /// <returns>The equivalent <see cref="InternalLocalActivityOptions"/>.</returns> internal InternalLocalActivityOptions ToInternal() { return(new InternalLocalActivityOptions() { ScheduleToCloseTimeout = CadenceHelper.ToCadence(this.ScheduleToCloseTimeout), RetryPolicy = RetryOptions?.ToInternal() }); }
public async Task SignalAsync(WorkflowExecution execution, string signalName, params object[] args) { await SyncContext.Clear; var dataConverter = Activity.Client.DataConverter; await Activity.Client.SignalWorkflowAsync(execution, signalName, CadenceHelper.ArgsToBytes(dataConverter, args)); }
/// <summary> /// Creates a typed workflow stub that can be used to start as well as /// query and signal the workflow via the type-safe interface methods. /// </summary> /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam> /// <param name="options">Optionally specifies the workflow options.</param> /// <param name="workflowTypeName"> /// Optionally specifies the workflow type name by overriding the fully /// qualified <typeparamref name="TWorkflowInterface"/> type name or the name /// specified by a <see cref="WorkflowAttribute"/>. /// </param> /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns> /// <remarks> /// <para> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke and then the first method called on a workflow stub must be /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>. /// </para> /// <note> /// <para> /// .NET and Java workflows can implement multiple workflow method using attributes /// and annotations to assign unique names to each. Each workflow method is actually /// registered with Cadence as a distinct workflow type. Workflow methods with a blank /// or <c>null</c> name will simply be registered using the workflow type name. /// </para> /// <para> /// Workflow methods with a name will be registered using a combination of the workflow /// type name and the method name, using <b>"::"</b> as the separator, like: /// </para> /// <code> /// WORKFLOW-TYPENAME::METHOD-NAME /// </code> /// </note> /// </remarks> public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(WorkflowOptions options = null, string workflowTypeName = null) where TWorkflowInterface : class { CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); EnsureNotDisposed(); return(StubManager.NewWorkflowStub <TWorkflowInterface>(this, options: options, workflowTypeName: workflowTypeName)); }
/// <summary> /// Starts the target activity that returns <c>void</c>, passing the specified arguments. /// </summary> /// <param name="args">The arguments to be passed to the activity.</param> /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns> /// <exception cref="InvalidOperationException">Thrown when attempting to start a stub more than once.</exception> /// <remarks> /// <para> /// You must take care to pass parameters that are compatible with the target activity parameters. /// These are checked at runtime but not while compiling. /// </para> /// <note> /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once. /// </note> /// </remarks> public async Task <IAsyncFuture> StartAsync(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); parentWorkflow.SetStackTrace(); if (hasStarted) { throw new InvalidOperationException("Cannot start a future stub more than once."); } var parameters = targetMethod.GetParameters(); if (parameters.Length != args.Length) { throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters)); } hasStarted = true; // Cast the input parameters to the target types so that developers won't need to expicitly // cast things like integers into longs, floats into doubles, etc. for (int i = 0; i < args.Length; i++) { args[i] = CadenceHelper.ConvertArg(parameters[i].ParameterType, args[i]); } // Start the activity. var client = parentWorkflow.Client; var dataConverter = client.DataConverter; var activityConstructor = typeof(TActivityImplementation).GetConstructor(Type.EmptyTypes); var activityId = parentWorkflow.GetNextActivityId(); var activityActionId = parentWorkflow.RegisterActivityAction(typeof(TActivityImplementation), activityConstructor, targetMethod); var reply = await parentWorkflow.ExecuteNonParallel( async() => { return((ActivityStartLocalReply)await client.CallProxyAsync( new ActivityStartLocalRequest() { ContextId = parentWorkflow.ContextId, ActivityId = activityId, ActivityTypeId = activityActionId, Args = CadenceHelper.ArgsToBytes(dataConverter, args), Options = options.ToInternal() })); }); reply.ThrowOnError(); parentWorkflow.UpdateReplay(reply); // Create and return the future. return(new AsyncFuture(parentWorkflow, activityId)); }
/// <summary> /// Creates a typed workflow stub connected to a known workflow execution /// using IDs. This can be used to signal and query the workflow via the /// type-safe interface methods. /// </summary> /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam> /// <param name="workflowId">Specifies the workflow ID.</param> /// <param name="runId">Optionally specifies the workflow's run ID.</param> /// <param name="domain">Optionally specifies a domain that </param> /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns> /// <remarks> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke and then the first method called on a workflow stub must be /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>. /// </remarks> public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(string workflowId, string runId = null, string domain = null) where TWorkflowInterface : class { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowId), nameof(workflowId)); CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); EnsureNotDisposed(); return(StubManager.NewWorkflowStub <TWorkflowInterface>(this, workflowId, runId, domain)); }
/// <summary> /// Starts the target workflow that returns <typeparamref name="TResult"/>, passing any specified arguments. /// </summary> /// <typeparam name="TResult">The workflow result type.</typeparam> /// <param name="args">The arguments to be passed to the workflow.</param> /// <returns>The <see cref="ChildWorkflowFuture{T}"/> with the <see cref="ChildWorkflowFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns> /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception> /// <remarks> /// <para> /// You must take care to pass parameters that are compatible with the target workflow parameters. /// These are checked at runtime but not while compiling. /// </para> /// <note> /// Any given <see cref="ChildWorkflowStub{TWorkflowInterface}"/> may only be executed once. /// </note> /// </remarks> public async Task <ChildWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); parentWorkflow.SetStackTrace(); if (hasStarted) { throw new InvalidOperationException("Cannot start a future stub more than once."); } var parameters = targetMethod.GetParameters(); if (parameters.Length != args.Length) { throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters)); } hasStarted = true; // Cast the input parameters to the target types so that developers won't need to expicitly // cast things like integers into longs, floats into doubles, etc. for (int i = 0; i < args.Length; i++) { args[i] = CadenceHelper.ConvertArg(parameters[i].ParameterType, args[i]); } // Start the child workflow and then construct and return the future. var client = parentWorkflow.Client; var execution = await client.StartChildWorkflowAsync(parentWorkflow, workflowTypeName, CadenceHelper.ArgsToBytes(client.DataConverter, args), options); // Initialize the type-safe stub property such that developers can call // any query or signal methods. Stub = StubManager.NewChildWorkflowStub <TWorkflowInterface>(client, parentWorkflow, workflowTypeName, execution); // Create and return the future. var resultType = targetMethod.ReturnType; if (resultType == typeof(Task)) { throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] does not return [void].", nameof(TWorkflowInterface)); } resultType = resultType.GenericTypeArguments.First(); if (!resultType.IsAssignableFrom(typeof(TResult))) { throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] returns [{resultType.FullName}] which is not compatible with [{nameof(TResult)}].", nameof(TWorkflowInterface)); } return(new ChildWorkflowFuture <TResult>(parentWorkflow, execution)); }
/// <summary> /// Creates a stub suitable for starting an external workflow and then waiting /// for the result as separate operations. /// </summary> /// <typeparam name="TWorkflowInterface">The target workflow interface.</typeparam> /// <param name="methodName"> /// Optionally identifies the target workflow method. This is the name specified in /// <c>[WorkflowMethod]</c> attribute for the workflow method or <c>null</c>/empty for /// the default workflow method. /// </param> /// <param name="options">Optionally specifies custom <see cref="WorkflowOptions"/>.</param> /// <returns>A <see cref="ChildWorkflowStub{TWorkflowInterface}"/> instance.</returns> public WorkflowFutureStub <TWorkflowInterface> NewWorkflowFutureStub <TWorkflowInterface>(string methodName = null, WorkflowOptions options = null) where TWorkflowInterface : class { CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); EnsureNotDisposed(); options = WorkflowOptions.Normalize(this, options, typeof(TWorkflowInterface)); return(new WorkflowFutureStub <TWorkflowInterface>(this, methodName, options)); }
//--------------------------------------------------------------------- // Static members /// <summary> /// Normalizes the options passed by creating or cloning a new instance as /// required and filling unset properties using default client settings. /// </summary> /// <param name="client">The associated Cadence client.</param> /// <param name="options">The input options or <c>null</c>.</param> /// <param name="activityInterface">Optionally specifies the activity interface definition.</param> /// <returns>The normalized options.</returns> /// <exception cref="ArgumentNullException">Thrown if a valid task list is not specified.</exception> internal static ActivityOptions Normalize(CadenceClient client, ActivityOptions options, Type activityInterface = null) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); if (options == null) { options = new ActivityOptions(); } else { options = options.Clone(); } if (string.IsNullOrEmpty(options.Domain)) { options.Domain = client.Settings.DefaultDomain; } if (options.ScheduleToCloseTimeout <= TimeSpan.Zero) { options.ScheduleToCloseTimeout = client.Settings.ActivityScheduleToCloseTimeout; } if (options.ScheduleToStartTimeout <= TimeSpan.Zero) { options.ScheduleToStartTimeout = client.Settings.ActivityScheduleToStartTimeout; } if (options.StartToCloseTimeout <= TimeSpan.Zero) { options.StartToCloseTimeout = client.Settings.ActivityStartToCloseTimeout; } if (string.IsNullOrEmpty(options.TaskList)) { if (activityInterface != null) { CadenceHelper.ValidateActivityInterface(activityInterface); var interfaceAttribute = activityInterface.GetCustomAttribute <ActivityInterfaceAttribute>(); if (interfaceAttribute != null && !string.IsNullOrEmpty(interfaceAttribute.TaskList)) { options.TaskList = interfaceAttribute.TaskList; } } } if (string.IsNullOrEmpty(options.TaskList)) { throw new ArgumentNullException(nameof(options), "You must specify a valid task list explicitly or via an [ActivityInterface(TaskList = \"my-tasklist\")] attribute on the target activity interface."); } return(options); }
/// <summary> /// Converts the instance into an internal <see cref="InternalStartWorkflowOptions"/>. /// </summary> /// <param name="client">The <see cref="CadenceClient"/>.</param> /// <param name="taskList">Optionally specifies the target task list.</param> /// <param name="methodAttribute">Optionally specifies a <see cref="WorkflowMethodAttribute"/>.</param> /// <returns>The corresponding <see cref="InternalStartWorkflowOptions"/>.</returns> internal InternalStartWorkflowOptions ToInternal(CadenceClient client, string taskList = null, WorkflowMethodAttribute methodAttribute = null) { Covenant.Requires <ArgumentNullException>(client != null); taskList = client.ResolveTaskList(taskList); // Merge optional settings from these options and the method attribute. var decisionTaskStartToCloseTimeout = TimeSpan.FromSeconds(10); var executionStartToCloseTimeout = CadenceClient.DefaultTimeout; var workflowIdReusePolicy = global::Neon.Cadence.WorkflowIdReusePolicy.AllowDuplicateFailedOnly; if (methodAttribute != null) { if (string.IsNullOrEmpty(taskList)) { if (methodAttribute.TaskList != null) { taskList = methodAttribute.TaskList; } else { taskList = client.Settings.DefaultTaskList; } } if (!DecisionTaskStartToCloseTimeout.HasValue && methodAttribute.TaskStartToCloseTimeoutSeconds > 0) { decisionTaskStartToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.TaskStartToCloseTimeoutSeconds); } if (!ExecutionStartToCloseTimeout.HasValue && methodAttribute.ExecutionStartToCloseTimeoutSeconds > 0) { executionStartToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.ExecutionStartToCloseTimeoutSeconds); } if (!WorkflowIdReusePolicy.HasValue && methodAttribute.WorkflowIdReusePolicy.HasValue) { workflowIdReusePolicy = methodAttribute.WorkflowIdReusePolicy.Value; } } return(new InternalStartWorkflowOptions() { ID = this.WorkflowId, TaskList = taskList, DecisionTaskStartToCloseTimeout = CadenceHelper.ToCadence(decisionTaskStartToCloseTimeout), ExecutionStartToCloseTimeout = CadenceHelper.ToCadence(executionStartToCloseTimeout), RetryPolicy = this.RetryOptions?.ToInternal(), WorkflowIdReusePolicy = (int)workflowIdReusePolicy, CronSchedule = this.CronSchedule, Memo = this.Memo }); }
/// <summary> /// Creates a typed workflow stub connected to a known workflow execution /// using a <see cref="WorkflowExecution"/>. This can be used to signal and /// query the workflow via the type-safe interface methods. /// </summary> /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam> /// <param name="execution">Specifies the <see cref="WorkflowExecution"/>.</param> /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns> /// <remarks> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke and then the first method called on a workflow stub must be /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>. /// </remarks> public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(WorkflowExecution execution) where TWorkflowInterface : class { Covenant.Requires <ArgumentNullException>(execution != null, nameof(execution)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(execution.WorkflowId), nameof(execution.WorkflowId)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(execution.RunId), nameof(execution.RunId)); CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); EnsureNotDisposed(); return(StubManager.NewWorkflowStub <TWorkflowInterface>(this, execution.WorkflowId, execution.RunId)); }
/// <summary> /// Executes the associated workflow and waits for it to complete, /// returning the workflow result. /// </summary> /// <typeparam name="TResult">The workflow result type.</typeparam> /// <param name="args">The workflow arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public async Task<TResult> ExecuteAsync<TResult>(params object[] args) { await SyncContext.Clear; Covenant.Requires<ArgumentNullException>(args != null, nameof(args)); EnsureNotStarted(); var argBytes = CadenceHelper.ArgsToBytes(client.DataConverter, args); Execution = await client.StartWorkflowAsync(WorkflowTypeName, argBytes, Options); return await GetResultAsync<TResult>(); }
/// <summary> /// Internal constructor. /// </summary> /// <param name="client">The associated client.</param> /// <param name="methodName"> /// Optionally identifies the target workflow method by the name specified in /// the <c>[WorkflowMethod]</c> attribute tagging the method. Pass a <c>null</c> /// or empty string to target the default method. /// </param> /// <param name="options">Optional workflow options.</param> internal WorkflowFutureStub(CadenceClient client, string methodName = null, WorkflowOptions options = null) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); var workflowInterface = typeof(WorkflowInterface); CadenceHelper.ValidateWorkflowInterface(workflowInterface); this.client = client; this.workflowTypeName = CadenceHelper.GetWorkflowTarget(workflowInterface, methodName).WorkflowTypeName; this.options = WorkflowOptions.Normalize(client, options); }
/// <summary> /// Converts the instance into an <see cref="InternalRetryPolicy"/>. /// </summary> /// <returns>The converted instance.</returns> internal InternalRetryPolicy ToInternal() { return(new InternalRetryPolicy() { BackoffCoefficient = this.BackoffCoefficient, ExpirationInterval = CadenceHelper.ToCadence(this.ExpirationInterval), InitialInterval = CadenceHelper.ToCadence(this.InitialInterval), MaximumAttempts = this.MaximumAttempts, MaximumInterval = CadenceHelper.ToCadence(this.MaximumInterval), NonRetriableErrorReasons = NonRetriableErrors.ToList() }); }
/// <summary> /// Signals the associated workflow, starting it if it hasn't already been started. /// </summary> /// <param name="signalName">Specifies the signal name.</param> /// <param name="signalArgs">Specifies the signal arguments.</param> /// <param name="startArgs">Specifies the workflow start arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public async Task<WorkflowExecution> SignalWithStartAsync(string signalName, object[] signalArgs, object[] startArgs) { await SyncContext.Clear; Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires<ArgumentNullException>(signalArgs != null, nameof(signalArgs)); Covenant.Requires<ArgumentNullException>(startArgs != null, nameof(startArgs)); var signalArgBytes = CadenceHelper.ArgsToBytes(client.DataConverter, signalArgs); var startArgBytes = CadenceHelper.ArgsToBytes(client.DataConverter, startArgs); return await client.SignalWorkflowWithStartAsync(this.WorkflowTypeName, signalName, signalArgBytes, startArgBytes, this.Options); }
/// <summary> /// Internal constructor. /// </summary> /// <param name="parentWorkflow">The associated parent workflow.</param> /// <param name="methodName"> /// Optionally identifies the target activity method by the name specified in /// the <c>[ActivityMethod]</c> attribute tagging the method. Pass a <c>null</c> /// or empty string to target the default method. /// </param> /// <param name="options">The activity options or <c>null</c>.</param> internal ActivityFutureStub(Workflow parentWorkflow, string methodName = null, ActivityOptions options = null) { Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); var activityInterface = typeof(TActivityInterface); CadenceHelper.ValidateActivityInterface(activityInterface); this.parentWorkflow = parentWorkflow; this.hasStarted = false; var activityTarget = CadenceHelper.GetActivityTarget(activityInterface, methodName); var methodAttribute = activityTarget.MethodAttribute; activityTypeName = activityTarget.ActivityTypeName; targetMethod = activityTarget.TargetMethod; if (options == null) { options = new ActivityOptions(); } else { options = options.Clone(); } if (string.IsNullOrEmpty(options.TaskList)) { options.TaskList = methodAttribute.TaskList; } if (options.HeartbeatTimeout <= TimeSpan.Zero) { options.HeartbeatTimeout = TimeSpan.FromSeconds(methodAttribute.HeartbeatTimeoutSeconds); } if (options.ScheduleToCloseTimeout <= TimeSpan.Zero) { options.ScheduleToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.ScheduleToCloseTimeoutSeconds); } if (options.ScheduleToStartTimeout <= TimeSpan.Zero) { options.ScheduleToStartTimeout = TimeSpan.FromSeconds(methodAttribute.ScheduleToStartTimeoutSeconds); } if (options.StartToCloseTimeout <= TimeSpan.Zero) { options.StartToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.StartToCloseTimeoutSeconds); } this.options = ActivityOptions.Normalize(parentWorkflow.Client, options); }
/// <summary> /// Converts the instance to its internal representation. /// </summary> internal InternalActivityOptions ToInternal() { return(new InternalActivityOptions() { TaskList = this.TaskList, ScheduleToCloseTimeout = CadenceHelper.ToCadence(this.ScheduleToCloseTimeout), ScheduleToStartTimeout = CadenceHelper.ToCadence(this.ScheduleToStartTimeout), StartToCloseTimeout = CadenceHelper.ToCadence(this.StartToCloseTimeout), HeartbeatTimeout = CadenceHelper.ToCadence(this.HeartbeatTimeout), WaitForCancellation = WaitForCancellation, RetryPolicy = RetryOptions?.ToInternal() }); }
/// <summary> /// Internal constructor. /// </summary> /// <param name="parentWorkflow">The associated parent workflow.</param> /// <param name="methodName"> /// Optionally identifies the target activity method by the name specified in /// the <c>[ActivityMethod]</c> attribute tagging the method. Pass a <c>null</c> /// or empty string to specify the default method. /// </param> /// <param name="options">The activity options or <c>null</c>.</param> internal LocalActivityFutureStub(Workflow parentWorkflow, string methodName = null, LocalActivityOptions options = null) { Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); var activityInterface = typeof(TActivityInterface); CadenceHelper.ValidateActivityInterface(activityInterface); this.parentWorkflow = parentWorkflow; this.hasStarted = false; this.targetMethod = CadenceHelper.GetActivityTarget(activityInterface, methodName).TargetMethod; this.options = LocalActivityOptions.Normalize(parentWorkflow.Client, options); }
/// <summary> /// Constructs an instance from a <see cref="LinearRetryPolicy"/>. /// </summary> /// <param name="policy">The policy.</param> public RetryOptions(LinearRetryPolicy policy) { Covenant.Requires <ArgumentNullException>(policy != null); this.InitialInterval = CadenceHelper.Normalize(policy.RetryInterval); this.BackoffCoefficient = 1.0; if (policy.Timeout.HasValue) { this.ExpirationInterval = CadenceHelper.Normalize(policy.Timeout.Value); } this.MaximumAttempts = policy.MaxAttempts; }
/// <summary> /// Converts this instance into the corresponding internal object. /// </summary> /// <returns>The equivalent <see cref="InternalChildWorkflowOptions"/>.</returns> internal InternalChildWorkflowOptions ToInternal() { return(new InternalChildWorkflowOptions() { Domain = this.Domain, ChildClosePolicy = (int)this.ChildPolicy, CronSchedule = this.CronSchedule, ExecutionStartToCloseTimeout = CadenceHelper.ToCadence(this.ScheduleToCloseTimeout.Value), RetryPolicy = this.RetryOptions?.ToInternal(), TaskList = this.TaskList ?? string.Empty, TaskStartToCloseTimeout = CadenceHelper.ToCadence(this.TaskStartToCloseTimeout.Value), WaitForCancellation = this.WaitUntilFinished, WorkflowID = this.WorkflowId, WorkflowIdReusePolicy = (int)(this.WorkflowIdReusePolicy == WorkflowIdReusePolicy.UseDefault ? Cadence.WorkflowIdReusePolicy.AllowDuplicateFailedOnly : this.WorkflowIdReusePolicy) }); }