예제 #1
0
        /// <summary>
        /// Registers a workflow implementation with Temporal.
        /// </summary>
        /// <typeparam name="TWorkflow">The <see cref="WorkflowBase"/> derived class implementing the workflow.</typeparam>
        /// <param name="disableDuplicateCheck">Disable checks for duplicate registrations.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the worker has already been started.  You must register workflow
        /// and activity implementations before starting workers.
        /// </exception>
        /// <remarks>
        /// <note>
        /// Be sure to register all of your workflow implementations before starting a worker.
        /// </note>
        /// </remarks>
        public async Task RegisterWorkflowAsync <TWorkflow>(bool disableDuplicateCheck = false)
            where TWorkflow : WorkflowBase
        {
            await SyncContext.Clear;

            TemporalHelper.ValidateWorkflowImplementation(typeof(TWorkflow));
            EnsureNotDisposed();
            EnsureCanRegister();

            var workflowType = typeof(TWorkflow);

            lock (registeredWorkflowTypes)
            {
                if (registeredWorkflowTypes.Contains(workflowType))
                {
                    if (disableDuplicateCheck)
                    {
                        return;
                    }
                    else
                    {
                        throw new RegistrationException($"Workflow implementation [{workflowType.FullName}] has already been registered.");
                    }
                }

                registeredWorkflowTypes.Add(workflowType);
            }
        }
예제 #2
0
        /// <summary>
        /// Registers an activity implementation with Temporal.
        /// </summary>
        /// <typeparam name="TActivity">The <see cref="ActivityBase"/> derived class implementing the activity.</typeparam>
        /// <param name="disableDuplicateCheck">Disable checks for duplicate activity registrations.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the worker has already been started.  You must register workflow
        /// and activity implementations before starting a worker.
        /// </exception>
        /// <exception cref="RegistrationException">Thrown when there's a problem with the registration.</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 a worker.
        /// </note>
        /// </remarks>
        public async Task RegisterActivityAsync <TActivity>(bool disableDuplicateCheck = false)
            where TActivity : ActivityBase
        {
            await SyncContext.Clear;

            TemporalHelper.ValidateActivityImplementation(typeof(TActivity));
            EnsureNotDisposed();
            EnsureCanRegister();

            lock (registeredActivityTypes)
            {
                var activityType = typeof(TActivity);

                if (registeredActivityTypes.Contains(activityType))
                {
                    if (disableDuplicateCheck)
                    {
                        return;
                    }
                    else
                    {
                        throw new RegistrationException($"Activity implementation [{typeof(TActivity).FullName}] has already been registered.");
                    }
                }

                registeredActivityTypes.Add(activityType);
            }
        }
예제 #3
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,
                Namespace  = options.Namespace,
                QueryName  = queryName,
                QueryArgs  = TemporalHelper.ArgsToBytes(client.DataConverter, args)
            });

            reply.ThrowOnError();

            return(client.DataConverter.FromData <TQueryResult>(reply.Result));
        }
예제 #4
0
        /// <summary>
        /// Executes the target activity method.
        /// </summary>
        /// <param name="client">The associated Temporal client.</param>
        /// <param name="argBytes">The encoded activity arguments.</param>
        /// <returns>The encoded activity results.</returns>
        private async Task <byte[]> InvokeAsync(TemporalClient 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             = TemporalHelper.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);
        }
예제 #5
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 = TemporalHelper.ArgsToBytes(client.DataConverter, args)
                }));
            });

            reply.ThrowOnError();
        }
예제 #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 Temporal.
        /// </summary>
        /// <param name="assembly">The target assembly.</param>
        /// <param name="disableDuplicateCheck">Disable checks for duplicate activity registrations.</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="InvalidOperationException">
        /// Thrown if the worker has already been started.  You must register workflow
        /// and activity implementations before starting workers.
        /// </exception>
        /// <exception cref="RegistrationException">Thrown when there's a problem with the registration.</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 a worker.
        /// </note>
        /// </remarks>
        public async Task RegisterAssemblyActivitiesAsync(Assembly assembly, bool disableDuplicateCheck = false)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(assembly != null, nameof(assembly));
            EnsureNotDisposed();
            EnsureCanRegister();

            lock (registeredActivityTypes)
            {
                foreach (var activityType in assembly.GetTypes().Where(type => type.IsClass))
                {
                    var activityAttribute = activityType.GetCustomAttribute <ActivityAttribute>();

                    if (activityAttribute != null && activityAttribute.AutoRegister)
                    {
                        var activityTypeName = TemporalHelper.GetActivityTypeName(activityType, activityAttribute);

                        if (registeredActivityTypes.Contains(activityType))
                        {
                            if (disableDuplicateCheck)
                            {
                                continue;
                            }
                            else
                            {
                                throw new RegistrationException($"Activity implementation [{activityType.FullName}] has already been registered.");
                            }
                        }

                        registeredActivityTypes.Add(activityType);
                    }
                }
            }
        }
예제 #7
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] = TemporalHelper.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,
                    WorkerId = parentWorkflow.Worker.WorkerId,
                    ActivityId = activityId,
                    ActivityTypeId = activityActionId,
                    Args = TemporalHelper.ArgsToBytes(dataConverter, args),
                    Options = options
                }));
            });

            reply.ThrowOnError();
            parentWorkflow.UpdateReplay(reply);

            // Create and return the future.

            return(new AsyncFuture(parentWorkflow, activityId));
        }
예제 #8
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, TemporalHelper.ArgsToBytes(dataConverter, args));
            }
예제 #9
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] = TemporalHelper.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, TemporalHelper.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));
        }
예제 #10
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(TemporalClient client, string methodName = null, StartWorkflowOptions options = null)
        {
            Covenant.Requires <ArgumentNullException>(client != null, nameof(client));

            var workflowInterface = typeof(WorkflowInterface);
            var method            = TemporalHelper.GetWorkflowMethod(workflowInterface, methodName);

            TemporalHelper.ValidateWorkflowInterface(workflowInterface);

            this.client           = client;
            this.workflowTypeName = TemporalHelper.GetWorkflowTarget(workflowInterface, methodName).WorkflowTypeName;
            this.options          = StartWorkflowOptions.Normalize(client, options, workflowInterface, method);
        }
예제 #11
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);

            TemporalHelper.ValidateActivityInterface(activityInterface);

            this.parentWorkflow = parentWorkflow;
            this.hasStarted     = false;
            this.targetMethod   = TemporalHelper.GetActivityTarget(activityInterface, methodName).TargetMethod;
            this.options        = LocalActivityOptions.Normalize(parentWorkflow.Client, options);
        }
예제 #12
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 = TemporalHelper.ArgsToBytes(client.DataConverter, args);

            Execution = await client.StartWorkflowAsync(WorkflowTypeName, argBytes, Options);

            return(await GetResultAsync <TResult>());
        }
예제 #13
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 = TemporalHelper.ArgsToBytes(client.DataConverter, signalArgs);
            var startArgBytes  = TemporalHelper.ArgsToBytes(client.DataConverter, startArgs);

            return(await client.SignalWorkflowWithStartAsync(this.WorkflowTypeName, signalName, signalArgBytes, startArgBytes, this.Options));
        }
예제 #14
0
        /// <summary>
        /// Constructs an instance from a <see cref="LinearRetryPolicy"/>.
        /// </summary>
        /// <param name="policy">The policy.</param>
        public RetryPolicy(LinearRetryPolicy policy)
        {
            Covenant.Requires <ArgumentNullException>(policy != null, nameof(policy));

            this.InitialInterval    = TemporalHelper.Normalize(policy.RetryInterval);
            this.BackoffCoefficient = 1.0;

            if (policy.Timeout.HasValue)
            {
                this.ExpirationInterval = TemporalHelper.Normalize(policy.Timeout.Value);
            }

            this.MaximumAttempts = policy.MaxAttempts;
        }
예제 #15
0
        /// <summary>
        /// Signals the associated workflow.
        /// </summary>
        /// <param name="signalName">Specifies the signal name.</param>
        /// <param name="args">Specifies the signal arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        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));
            EnsureStarted();

            if (Execution == null)
            {
                throw new InvalidOperationException("Signal cannot be sent because the stub doesn't have the workflow execution.");
            }

            var argBytes = TemporalHelper.ArgsToBytes(client.DataConverter, args);

            await client.SignalWorkflowAsync(Execution, signalName, argBytes, client.ResolveNamespace(Options?.Namespace));
        }
예제 #16
0
        /// <summary>
        /// Starts the workflow, returning an <see cref="IAsyncFuture"/> that can be used
        /// to wait for the the workflow to complete and obtain its result.
        /// </summary>
        /// <typeparam name="TResult">The workflow result type.</typeparam>
        /// <param name="args">The workflow arguments.</param>
        /// <returns>An <see cref="ExternalWorkflowFuture{TResult}"/> that can be used to retrieve the workflow result as an <c>object</c>.</returns>
        /// <exception cref="InvalidOperationException">Thrown if the workflow has already been started.</exception>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed
        /// and the result type are compatible with the target workflow method.
        /// </note>
        /// </remarks>
        public async Task <ExternalWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (execution != null)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            execution = await client.StartWorkflowAsync(workflowTypeName, TemporalHelper.ArgsToBytes(client.DataConverter, args), options);

            // Create and return the future.

            return(new ExternalWorkflowFuture <TResult>(client, execution, options.Namespace));
        }
예제 #17
0
        /// <summary>
        ///  Queries the associated workflow specifying the expected result type as
        ///  a parameter.
        /// </summary>
        /// <param name="resultType">Specifies the query result type.</param>
        /// <param name="queryType">Specifies the query type.</param>
        /// <param name="args">Specifies the query arguments.</param>
        /// <returns>The query result as a <c>dynamic</c>.</returns>
        public async Task <object> QueryAsync(Type resultType, string queryType, params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(queryType), nameof(queryType));
            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));
            EnsureStarted();

            if (Execution == null)
            {
                throw new InvalidOperationException("Query cannot be sent because the stub doesn't have the workflow execution.");
            }

            var argBytes = TemporalHelper.ArgsToBytes(client.DataConverter, args);

            return(client.DataConverter.FromData(resultType, await client.QueryWorkflowAsync(Execution, queryType, argBytes, client.ResolveNamespace(Options?.Namespace))));
        }
예제 #18
0
        /// <summary>
        /// Internal constructor.
        /// </summary>
        /// <param name="parentWorkflow">The associated parent workflow.</param>
        /// <param name="methodName">Identifies the target workflow method or <c>null</c> or empty.</param>
        /// <param name="options">The child workflow options or <c>null</c>.</param>
        internal ChildWorkflowStub(Workflow parentWorkflow, string methodName, ChildWorkflowOptions options)
        {
            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));

            var workflowInterface = typeof(TWorkflowInterface);

            TemporalHelper.ValidateWorkflowInterface(workflowInterface);

            this.parentWorkflow = parentWorkflow;
            this.options        = options;
            this.hasStarted     = false;

            var workflowTarget = TemporalHelper.GetWorkflowTarget(workflowInterface, methodName);

            this.workflowTypeName = workflowTarget.WorkflowTypeName;
            this.targetMethod     = workflowTarget.TargetMethod;
        }
예제 #19
0
        /// <summary>
        /// Signals the workflow.
        /// </summary>
        /// <param name="signalName">Specifies the signal name.</param>
        /// <param name="args">Specifies the signal arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        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 (parentWorkflow != null)
            {
                var stub = parentWorkflow.NewLocalActivityStub <ILocalOperations, LocalOperations>();

                await stub.SignalAsync(Execution, signalName, args);
            }
            else
            {
                await client.SignalWorkflowAsync(Execution, signalName, TemporalHelper.ArgsToBytes(client.DataConverter, args));
            }
        }
예제 #20
0
        /// <summary>
        /// <b>EXPERIMENTAL:</b> This method synchronously signals the workflow and returns
        /// only after the workflow has processed received and processed the signal as opposed
        /// to <see cref="SignalAsync"/> which is fire-and-forget and does not wait for the
        /// signal to be processed.
        /// </summary>
        /// <param name="signalName">
        /// The signal name as defined by the <see cref="SignalMethodAttribute"/>
        /// decorating the workflow signal method.
        /// </param>
        /// <param name="args">The signal arguments.</param>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed
        /// are compatible with the target workflow arguments.  No compile-time type checking
        /// is performed for this method.
        /// </note>
        /// </remarks>
        public async Task SyncSignalAsync(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 signalId        = Guid.NewGuid().ToString("d");
            var argBytes        = TemporalHelper.ArgsToBytes(client.DataConverter, args);
            var signalCall      = new SyncSignalCall(signalName, signalId, argBytes);
            var signalCallBytes = TemporalHelper.ArgsToBytes(client.DataConverter, new object[] { signalCall });

            await client.SyncSignalWorkflowAsync(execution, signalName, signalId, signalCallBytes, options.Namespace);
        }
예제 #21
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);

            TemporalHelper.ValidateActivityInterface(activityInterface);

            this.parentWorkflow = parentWorkflow;
            this.client         = parentWorkflow.Client;
            this.hasStarted     = false;

            var activityTarget  = TemporalHelper.GetActivityTarget(activityInterface, methodName);
            var methodAttribute = activityTarget.MethodAttribute;

            this.activityTypeName = activityTarget.ActivityTypeName;
            this.targetMethod     = activityTarget.TargetMethod;
            this.options          = ActivityOptions.Normalize(client, options, typeof(TActivityInterface), activityTarget.TargetMethod);
        }
예제 #22
0
        /// <summary>
        /// Called internally to initialize the activity.
        /// </summary>
        /// <param name="worker">The worker hosting the activity.</param>
        /// <param name="activityType">Specifies the target activity type.</param>
        /// <param name="activityMethod">Specifies the target activity method.</param>
        /// <param name="dataConverter">Specifies the data converter to be used for parameter and result serilization.</param>
        /// <param name="contextId">The activity's context ID.</param>
        internal void Initialize(Worker worker, Type activityType, MethodInfo activityMethod, IDataConverter dataConverter, long contextId)
        {
            Covenant.Requires <ArgumentNullException>(worker != null, nameof(worker));
            Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType));
            Covenant.Requires <ArgumentNullException>(activityMethod != null, nameof(activityMethod));
            Covenant.Requires <ArgumentNullException>(dataConverter != null, nameof(dataConverter));
            TemporalHelper.ValidateActivityImplementation(activityType);

            this.worker                  = worker;
            this.Client                  = worker.Client;
            this.Activity                = new Activity(this);
            this.activityType            = activityType;
            this.activityMethod          = activityMethod;
            this.dataConverter           = dataConverter;
            this.ContextId               = contextId;
            this.CancellationTokenSource = new CancellationTokenSource();
            this.CancellationToken       = CancellationTokenSource.Token;
            this.logger                  = LogManager.Default.GetLogger(module: activityType.FullName);
        }
예제 #23
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 future 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.");
            }

            hasStarted = true;

            // Start the activity.

            var client        = parentWorkflow.Client;
            var dataConverter = client.DataConverter;
            var activityId    = parentWorkflow.GetNextActivityId();

            var reply = await parentWorkflow.ExecuteNonParallel(
                async() =>
            {
                return((ActivityStartReply)await client.CallProxyAsync(
                           new ActivityStartRequest()
                {
                    ContextId = parentWorkflow.ContextId,
                    ActivityId = activityId,
                    Activity = activityTypeName,
                    Args = TemporalHelper.ArgsToBytes(dataConverter, args),
                    Options = options
                }));
            });

            reply.ThrowOnError();
            parentWorkflow.UpdateReplay(reply);

            // Create and return the future.

            return(new AsyncFuture(parentWorkflow, activityId));
        }
예제 #24
0
        /// <summary>
        /// Creates a local activity stub instance suitable for executing a non-local activity.
        /// </summary>
        /// <param name="client">The associated <see cref="TemporalClient"/>.</param>
        /// <param name="workflow">The parent workflow.</param>
        /// <param name="activityType">The activity implementation type.</param>
        /// <param name="options">Specifies the <see cref="LocalActivityOptions"/> or <c>null</c>.</param>
        /// <returns>The activity stub as an <see cref="object"/>.</returns>
        public object CreateLocal(TemporalClient client, Workflow workflow, Type activityType, LocalActivityOptions options)
        {
            Covenant.Requires <ArgumentNullException>(client != null, nameof(client));
            Covenant.Requires <ArgumentNullException>(workflow != null, nameof(workflow));
            Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType));

            options = options ?? new LocalActivityOptions();

            return(localConstructor.Invoke(new object[] { client, client.DataConverter, workflow, activityType, options, TemporalHelper.GetActivityInterface(activityType) }));
        }
예제 #25
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="name">
        /// Optionally specifies the activity type name used to
        /// register an activity implementation with Temporal.
        /// </param>
        public ActivityAttribute(string name = null)
        {
            TemporalHelper.ValidateActivityTypeName(name);

            this.Name = name;
        }
예제 #26
0
        /// <summary>
        /// Handles internal <see cref="TemporalClient.SignalSync"/> workflow signals.
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        internal async Task <WorkflowSignalInvokeReply> OnSyncSignalAsync(WorkflowSignalInvokeRequest request)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(request != null, nameof(request));

            try
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.Signal;

                var workflow = GetWorkflow(request.ContextId);

                if (workflow != null)
                {
                    Workflow.Current = workflow.Workflow;   // Initialize the ambient workflow information.

                    // The signal arguments should be just a single [SyncSignalCall] that specifies
                    // the target signal and also includes its encoded arguments.

                    var signalCallArgs = TemporalHelper.BytesToArgs(JsonDataConverter.Instance, request.SignalArgs, new Type[] { typeof(SyncSignalCall) });
                    var signalCall     = (SyncSignalCall)signalCallArgs[0];
                    var signalMethod   = workflow.Workflow.MethodMap.GetSignalMethod(signalCall.TargetSignal);
                    var userSignalArgs = TemporalHelper.BytesToArgs(Client.DataConverter, signalCall.UserArgs, signalMethod.GetParameterTypes());

                    Workflow.Current.SignalId = signalCall.SignalId;

                    // Create a dictionary with the signal method arguments keyed by parameter name.

                    var args       = new Dictionary <string, object>();
                    var parameters = signalMethod.GetParameters();

                    for (int i = 0; i < parameters.Length; i++)
                    {
                        args.Add(parameters[i].Name, userSignalArgs[i]);
                    }

                    // Persist the state that the signal status queries will examine.
                    // We're also going to use the presence of this state to make
                    // synchronous signal calls idempotent by ensuring that we'll
                    // only call the signal method once per signal ID.
                    //
                    // Note that it's possible that a record has already exists.

                    var signalStatus = workflow.SetSignalStatus(signalCall.SignalId, args, out var newSignal);

                    if (newSignal && signalMethod != null)
                    {
                        Workflow.Current = workflow.Workflow;   // Initialize the ambient workflow information for workflow library code.

                        var result    = (object)null;
                        var exception = (Exception)null;

                        // Execute the signal method (if there is one).

                        try
                        {
                            if (TemporalHelper.IsTask(signalMethod.ReturnType))
                            {
                                // Method returns [Task]: AKA void.

                                await(Task)(signalMethod.Invoke(workflow, userSignalArgs));
                            }
                            else
                            {
                                // Method returns [Task<T>]: AKA a result.

                                // $note(jefflill):
                                //
                                // I would have liked to do something like this:
                                //
                                //      result = await (Task<object>)(method.Invoke(workflow, userArgs));
                                //
                                // here, but that not going to work because the Task<T>
                                // being returned won't typically be a Task<object>,
                                // so the cast will fail.
                                //
                                // So instead, I'm going to use reflection to obtain the
                                // Task.Result property and then obtain the result from that.

                                var task           = (Task)(signalMethod.Invoke(workflow, userSignalArgs));
                                var resultProperty = task.GetType().GetProperty("Result");

                                await task;

                                result = resultProperty.GetValue(task);
                            }
                        }
                        catch (Exception e)
                        {
                            exception = e;
                        }

                        if (exception?.GetType() == typeof(WaitForSignalReplyException))
                        {
                            // This will be thrown by synchronous signal handlers that marshalled
                            // the signal to the workflow logic.  We're going to ignore the signal
                            // method result in this case and NOT MARK THE SIGNAL AS COMPLETED.
                        }
                        else
                        {
                            var syncSignalStatus = workflow.GetSignalStatus(signalCall.SignalId);

                            if (syncSignalStatus != null)
                            {
                                if (exception == null)
                                {
                                    syncSignalStatus.Result = Client.DataConverter.ToData(result);
                                }
                                else
                                {
                                    log.LogError(exception);

                                    syncSignalStatus.Error = SyncSignalException.GetError(exception);
                                }

                                syncSignalStatus.Completed = true;
                            }
                            else
                            {
                                Covenant.Assert(false); // This should never happen.
                            }
                        }

                        return(new WorkflowSignalInvokeReply()
                        {
                            RequestId = request.RequestId
                        });
                    }
                    else
                    {
                        return(new WorkflowSignalInvokeReply()
                        {
                            Error = new EntityNotExistsException($"Workflow type [{workflow.GetType().FullName}] does not define a signal handler for [signalName={request.SignalName}].").ToTemporalError()
                        });
                    }
                }
                else
                {
                    // I don't believe we'll ever land here because that would mean that
                    // Temporal sends signals to a workflow that hasn't started running
                    // on a worker (which wouldn't make sense).
                    //
                    // We're going go ahead and send a reply, just in case.

                    return(new WorkflowSignalInvokeReply());
                }
            }
            catch (Exception e)
            {
                log.LogError(e);

                return(new WorkflowSignalInvokeReply()
                {
                    Error = new TemporalError(e)
                });
            }
            finally
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.None;
            }
        }
예제 #27
0
        /// <summary>
        /// Handles workflow queries.
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        internal async Task <WorkflowQueryInvokeReply> OnQueryAsync(WorkflowQueryInvokeRequest request)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(request != null, nameof(request));

            try
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.Query;

                var workflow = GetWorkflow(request.ContextId);

                if (workflow != null)
                {
                    Workflow.Current = workflow.Workflow;   // Initialize the ambient workflow information for workflow library code.

                    // Handle built-in queries.

                    switch (request.QueryName)
                    {
                    case TemporalClient.QueryStack:

                        var trace = string.Empty;

                        if (workflow.StackTrace != null)
                        {
                            trace = workflow.StackTrace.ToString();
                        }

                        return(new WorkflowQueryInvokeReply()
                        {
                            RequestId = request.RequestId,
                            Result = Client.DataConverter.ToData(trace)
                        });

                    case TemporalClient.QuerySyncSignal:

                        // The arguments for this signal is the (string) ID of the target
                        // signal being polled for status.

                        var syncSignalArgs   = TemporalHelper.BytesToArgs(JsonDataConverter.Instance, request.QueryArgs, new Type[] { typeof(string) });
                        var syncSignalId     = (string)(syncSignalArgs.Length > 0 ? syncSignalArgs[0] : null);
                        var syncSignalStatus = workflow.GetSignalStatus(syncSignalId);

                        Covenant.Assert(false);     // This should never happen

                        if (syncSignalStatus.Completed)
                        {
                            // Indicate that the completed signal has reported the status
                            // to the calling client as well as returned the result, if any.

                            syncSignalStatus.Acknowledged    = true;
                            syncSignalStatus.AcknowledgeTime = DateTime.UtcNow;
                        }

                        return(new WorkflowQueryInvokeReply()
                        {
                            RequestId = request.RequestId,
                            Result = Client.DataConverter.ToData(syncSignalStatus)
                        });
                    }

                    // Handle user queries.

                    var method = workflow.Workflow.MethodMap.GetQueryMethod(request.QueryName);

                    if (method != null)
                    {
                        var resultType           = method.ReturnType;
                        var methodParameterTypes = method.GetParameterTypes();

                        var serializedResult = Array.Empty <byte>();

                        if (resultType.IsGenericType)
                        {
                            // Query method returns: Task<T>

                            var result = await NeonHelper.GetTaskResultAsObjectAsync((Task)method.Invoke(workflow, TemporalHelper.BytesToArgs(Client.DataConverter, request.QueryArgs, methodParameterTypes)));

                            serializedResult = Client.DataConverter.ToData(result);
                        }
                        else
                        {
                            // Query method returns: Task

                            await(Task) method.Invoke(workflow, TemporalHelper.BytesToArgs(Client.DataConverter, request.QueryArgs, methodParameterTypes));
                        }

                        return(new WorkflowQueryInvokeReply()
                        {
                            RequestId = request.RequestId,
                            Result = serializedResult
                        });
                    }
                    else
                    {
                        return(new WorkflowQueryInvokeReply()
                        {
                            Error = new EntityNotExistsException($"Workflow type [{workflow.GetType().FullName}] does not define a query handler for [queryType={request.QueryName}].").ToTemporalError()
                        });
                    }
                }
                else
                {
                    return(new WorkflowQueryInvokeReply()
                    {
                        Error = new EntityNotExistsException($"Workflow with [contextID={request.ContextId}] does not exist.").ToTemporalError()
                    });
                }
            }
            catch (Exception e)
            {
                log.LogError(e);

                return(new WorkflowQueryInvokeReply()
                {
                    Error = new TemporalError(e)
                });
            }
            finally
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.None;
            }
        }
예제 #28
0
        /// <summary>
        /// Registers a workflow implementation with temporal-proxy.
        /// </summary>
        /// <param name="workflowType">The workflow implementation type.</param>
        /// <exception cref="RegistrationException">Thrown when there's a problem with the registration.</exception>
        private async Task RegisterWorkflowImplementationAsync(Type workflowType)
        {
            await SyncContext.Clear;

            TemporalHelper.ValidateWorkflowImplementation(workflowType);

            var methodMap = WorkflowMethodMap.Create(workflowType);

            // We need to register each workflow method that implements a workflow interface method
            // with the same signature that that was tagged by [WorkflowMethod].
            //
            // First, we'll create a dictionary that maps method signatures from any inherited
            // interfaces that are tagged by [WorkflowMethod] to the attribute.

            var methodSignatureToAttribute = new Dictionary <string, WorkflowMethodAttribute>();

            foreach (var interfaceType in workflowType.GetInterfaces())
            {
                foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
                {
                    var workflowMethodAttribute = method.GetCustomAttribute <WorkflowMethodAttribute>();

                    if (workflowMethodAttribute == null)
                    {
                        continue;
                    }

                    var signature = method.ToString();

                    if (methodSignatureToAttribute.ContainsKey(signature))
                    {
                        throw new NotSupportedException($"Workflow type [{workflowType.FullName}] cannot implement the [{signature}] method from two different interfaces.");
                    }

                    methodSignatureToAttribute.Add(signature, workflowMethodAttribute);
                }
            }

            // Next, we need to register the workflow methods that implement the
            // workflow interface.

            foreach (var method in workflowType.GetMethods())
            {
                if (!methodSignatureToAttribute.TryGetValue(method.ToString(), out var workflowMethodAttribute))
                {
                    continue;
                }

                var workflowTypeName = TemporalHelper.GetWorkflowTypeName(workflowType, workflowMethodAttribute);

                lock (nameToWorkflowRegistration)
                {
                    if (nameToWorkflowRegistration.TryGetValue(workflowTypeName, out var existingRegistration))
                    {
                        if (!object.ReferenceEquals(existingRegistration.WorkflowType, workflowType))
                        {
                            throw new InvalidOperationException($"Conflicting workflow interface registration: Workflow interface [{workflowType.FullName}] is already registered for workflow type name [{workflowTypeName}].");
                        }
                    }
                    else
                    {
                        nameToWorkflowRegistration[workflowTypeName] =
                            new WorkflowRegistration()
                        {
                            WorkflowType   = workflowType,
                            WorkflowMethod = method,
                            WorkflowMethodParameterTypes = method.GetParameterTypes(),
                            MethodMap = methodMap
                        };
                    }
                }

                var reply = (WorkflowRegisterReply)await Client.CallProxyAsync(
                    new WorkflowRegisterRequest()
                {
                    WorkerId = WorkerId,
                    Name     = workflowTypeName,
                });

                reply.ThrowOnError();
            }
        }
예제 #29
0
        //---------------------------------------------------------------------
        // Static members

        /// <summary>
        /// <b>INTERNAL USE ONLY:</b> 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 Temporal client.</param>
        /// <param name="options">The input options or <c>null</c>.</param>
        /// <param name="workflowInterface">Optionally specifies the workflow interface definition.</param>
        /// /// <param name="method">Optionally specifies the target workflow method.</param>
        /// <returns>The normalized options.</returns>
        /// <exception cref="ArgumentNullException">Thrown if a valid task queue is not specified.</exception>
        public static ChildWorkflowOptions Normalize(TemporalClient client, ChildWorkflowOptions options, Type workflowInterface = null, MethodInfo method = null)
        {
            Covenant.Requires <ArgumentNullException>(client != null, nameof(client));

            WorkflowInterfaceAttribute interfaceAttribute = null;
            WorkflowMethodAttribute    methodAttribute    = null;

            if (options == null)
            {
                options = new ChildWorkflowOptions();
            }
            else
            {
                options = options.Clone();
            }

            if (workflowInterface != null)
            {
                TemporalHelper.ValidateWorkflowInterface(workflowInterface);

                interfaceAttribute = workflowInterface.GetCustomAttribute <WorkflowInterfaceAttribute>();
            }

            if (method != null)
            {
                methodAttribute = method.GetCustomAttribute <WorkflowMethodAttribute>();
            }

            if (string.IsNullOrEmpty(options.Namespace))
            {
                if (!string.IsNullOrEmpty(methodAttribute?.Namespace))
                {
                    options.Namespace = methodAttribute.Namespace;
                }

                if (string.IsNullOrEmpty(options.Namespace) && !string.IsNullOrEmpty(interfaceAttribute?.Namespace))
                {
                    options.Namespace = interfaceAttribute.Namespace;
                }
            }

            if (string.IsNullOrEmpty(options.TaskQueue))
            {
                if (!string.IsNullOrEmpty(methodAttribute?.TaskQueue))
                {
                    options.TaskQueue = methodAttribute.TaskQueue;
                }

                if (string.IsNullOrEmpty(options.TaskQueue) && !string.IsNullOrEmpty(interfaceAttribute?.TaskQueue))
                {
                    options.TaskQueue = interfaceAttribute.TaskQueue;
                }

                if (string.IsNullOrEmpty(options.TaskQueue))
                {
                    options.TaskQueue = client.Settings.TaskQueue;
                }
            }

            if (options.WorkflowExecutionTimeout <= TimeSpan.Zero)
            {
                if (methodAttribute != null && methodAttribute.WorkflowExecutionTimeoutSeconds > 0)
                {
                    options.WorkflowExecutionTimeout = TimeSpan.FromSeconds(methodAttribute.WorkflowExecutionTimeoutSeconds);
                }

                if (options.WorkflowExecutionTimeout <= TimeSpan.Zero)
                {
                    options.WorkflowExecutionTimeout = client.Settings.WorkflowExecutionTimeout;
                }
            }

            if (options.WorkflowRunTimeout <= TimeSpan.Zero)
            {
                if (methodAttribute != null && methodAttribute.WorkflowRunTimeoutSeconds > 0)
                {
                    options.WorkflowRunTimeout = TimeSpan.FromSeconds(methodAttribute.WorkflowRunTimeoutSeconds);
                }

                if (options.WorkflowRunTimeout <= TimeSpan.Zero)
                {
                    options.WorkflowRunTimeout = client.Settings.WorkflowRunTimeout;
                }
            }

            if (options.WorkflowTaskTimeout <= TimeSpan.Zero)
            {
                if (methodAttribute != null && methodAttribute.WorkflowTaskTimeoutSeconds > 0)
                {
                    options.WorkflowTaskTimeout = TimeSpan.FromSeconds(methodAttribute.WorkflowTaskTimeoutSeconds);
                }

                if (options.WorkflowTaskTimeout <= TimeSpan.Zero)
                {
                    options.WorkflowTaskTimeout = client.Settings.WorkflowTaskTimeout;
                }
            }

            if (options.WorkflowIdReusePolicy == Temporal.WorkflowIdReusePolicy.UseDefault)
            {
                if (methodAttribute != null && methodAttribute.WorkflowIdReusePolicy != WorkflowIdReusePolicy.UseDefault)
                {
                    options.WorkflowIdReusePolicy = methodAttribute.WorkflowIdReusePolicy;
                }

                if (options.WorkflowIdReusePolicy == Temporal.WorkflowIdReusePolicy.UseDefault)
                {
                    options.WorkflowIdReusePolicy = client.Settings.WorkflowIdReusePolicy;
                }
            }

            return(options);
        }
예제 #30
0
        /// <summary>
        /// Handles workflow signals.
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        internal async Task <WorkflowSignalInvokeReply> OnSignalAsync(WorkflowSignalInvokeRequest request)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(request != null, nameof(request));

            // Handle synchronous signals in a specialized method.

            if (request.SignalName == TemporalClient.SignalSync)
            {
                return(await OnSyncSignalAsync(request));
            }

            try
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.Signal;

                var workflow = GetWorkflow(request.ContextId);

                if (workflow != null)
                {
                    Workflow.Current = workflow.Workflow;   // Initialize the ambient workflow information.

                    var method = workflow.Workflow.MethodMap.GetSignalMethod(request.SignalName);

                    if (method != null)
                    {
                        await(Task)(method.Invoke(workflow, TemporalHelper.BytesToArgs(Client.DataConverter, request.SignalArgs, method.GetParameterTypes())));

                        return(new WorkflowSignalInvokeReply()
                        {
                            RequestId = request.RequestId
                        });
                    }
                    else
                    {
                        return(new WorkflowSignalInvokeReply()
                        {
                            Error = new EntityNotExistsException($"Workflow type [{workflow.GetType().FullName}] does not define a signal handler for [signalName={request.SignalName}].").ToTemporalError()
                        });
                    }
                }
                else
                {
                    // I don't believe we'll ever land here because that would mean that
                    // Temporal sends signals to a workflow that hasn't started running
                    // on a worker (which wouldn't make sense).
                    //
                    // We're going go ahead and send a reply, just in case.

                    return(new WorkflowSignalInvokeReply());
                }
            }
            catch (Exception e)
            {
                log.LogError(e);

                return(new WorkflowSignalInvokeReply()
                {
                    Error = new TemporalError(e)
                });
            }
            finally
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.None;
            }
        }