예제 #1
0
        /// <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)));
            }
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        /// <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();
        }
예제 #4
0
        //---------------------------------------------------------------------
        // 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)));
            }
        }
예제 #5
0
        /// <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();
        }
예제 #6
0
        /// <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));
                    }
                }
            }
        }
예제 #7
0
        //---------------------------------------------------------------------
        // 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();
            }
        }
예제 #8
0
        /// <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
            });
        }
예제 #9
0
        //---------------------------------------------------------------------
        // 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();
            }
        }
예제 #10
0
        /// <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));
        }
예제 #11
0
        /// <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();
        }
예제 #12
0
 /// <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()
     });
 }
예제 #13
0
            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));
            }
예제 #14
0
        /// <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));
        }
예제 #15
0
        /// <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));
        }
예제 #16
0
        /// <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));
        }
예제 #17
0
        /// <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));
        }
예제 #18
0
        /// <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));
        }
예제 #19
0
        //---------------------------------------------------------------------
        // 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);
        }
예제 #20
0
        /// <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
            });
        }
예제 #21
0
        /// <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));
        }
예제 #22
0
        /// <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>();
        }
예제 #23
0
        /// <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);
        }
예제 #24
0
 /// <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()
     });
 }
예제 #25
0
        /// <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);
        }
예제 #26
0
        /// <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);
        }
예제 #27
0
 /// <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()
     });
 }
예제 #28
0
        /// <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);
        }
예제 #29
0
        /// <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;
        }
예제 #30
0
 /// <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)
     });
 }