Example #1
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>
        /// <param name="method">Optionally specifies the target workflow method.</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, MethodInfo method = null)
        {
            Covenant.Requires <ArgumentNullException>(client != null, nameof(client));

            ActivityInterfaceAttribute interfaceAttribute = null;
            ActivityMethodAttribute    methodAttribute    = null;

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

            if (activityInterface != null)
            {
                CadenceHelper.ValidateActivityInterface(activityInterface);

                interfaceAttribute = activityInterface.GetCustomAttribute <ActivityInterfaceAttribute>();
            }

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

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

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

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

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

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

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

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

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

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

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

            return(options);
        }
Example #2
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="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 list is not specified.</exception>
        internal static WorkflowOptions Normalize(CadenceClient client, WorkflowOptions 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 WorkflowOptions();
            }
            else
            {
                options = options.Clone();
            }

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

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

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

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

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

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

                if (string.IsNullOrEmpty(options.Domain))
                {
                    throw new ArgumentNullException(nameof(options), "You must specify a valid domain explicitly in [CadenceSettings], [ActivityOptions] or via an [ActivityInterface] or [ActivityMethod] attribute on the target activity interface or method.");
                }
            }

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

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

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

                if (string.IsNullOrEmpty(options.TaskList))
                {
                    throw new ArgumentNullException(nameof(options), "You must specify a valid task list explicitly via [WorkflowOptions] or using an [WorkflowInterface] or [WorkflowMethod] attribute on the target workflow interface or method.");
                }
            }

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

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

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

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

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

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

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

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

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

            return(options);
        }
Example #3
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 Cadence 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 list is not specified.</exception>
        public static ChildWorkflowOptions Normalize(CadenceClient 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)
            {
                CadenceHelper.ValidateWorkflowInterface(workflowInterface);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return(options);
        }
Example #4
0
 public WorkflowInstanceKey(CadenceClient client, long contextId)
 {
     this.clientId  = client.ClientId;
     this.contextId = contextId;
 }
Example #5
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="client">The associated client.</param>
 /// <param name="execution">The workflow execution.</param>
 /// <param name="domain">Optionally specifies the target domain.  This defaults to the default client domain.</param>
 internal ExternalWorkflowFuture(CadenceClient client, WorkflowExecution execution, string domain = null)
 {
     this.client    = client;
     this.Execution = execution;
     this.domain    = client.ResolveDomain(domain);
 }
Example #6
0
        /// <summary>
        /// Handles workflow invocation.
        /// </summary>
        /// <param name="client">The associated Cadence client.</param>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        internal static async Task<WorkflowInvokeReply> OnInvokeAsync(CadenceClient client, WorkflowInvokeRequest request)
        {
            Covenant.Requires<ArgumentNullException>(client != null, nameof(client));
            Covenant.Requires<ArgumentNullException>(request != null, nameof(request));
            Covenant.Requires<ArgumentException>(request.ReplayStatus != InternalReplayStatus.Unspecified, nameof(request));

            WorkflowBase            workflow;
            WorkflowRegistration    registration;

            var contextId   = request.ContextId;
            var workflowKey = new WorkflowInstanceKey(client, contextId);

            lock (syncLock)
            {
                if (idToWorkflow.TryGetValue(workflowKey, out workflow))
                {
                    return new WorkflowInvokeReply()
                    {
                        Error = new CadenceError($"A workflow with [ID={workflowKey}] is already running on this worker.")
                    };
                }

                registration = GetWorkflowRegistration(client, request.WorkflowType);

                if (registration == null)
                {
                    return new WorkflowInvokeReply()
                    {
                        Error = new CadenceError($"Workflow type name [Type={request.WorkflowType}] is not registered for this worker.")
                    };
                }
            }

            workflow = (WorkflowBase)Activator.CreateInstance(registration.WorkflowType);
            workflow.Workflow = 
                new Workflow(
                    parent:             (WorkflowBase)workflow,
                    client:             client, 
                    contextId:          contextId,
                    workflowTypeName:   request.WorkflowType,
                    domain:             request.Domain,
                    taskList:           request.TaskList,
                    workflowId:         request.WorkflowId,
                    runId:              request.RunId,
                    isReplaying:        request.ReplayStatus == InternalReplayStatus.Replaying,
                    methodMap:          registration.MethodMap);

            lock (syncLock)
            {
                idToWorkflow.Add(workflowKey, workflow);
            }

            // Register any workflow signal and/or query methods with cadence-proxy.

            foreach (var signalName in registration.MethodMap.GetSignalNames())
            {
                var reply = (WorkflowSignalSubscribeReply)await client.CallProxyAsync(
                    new WorkflowSignalSubscribeRequest()
                    {
                        ContextId  = contextId,
                        SignalName = signalName
                    });

                reply.ThrowOnError();
            }

            foreach (var queryType in registration.MethodMap.GetQueryTypes())
            {
                var reply = (WorkflowSetQueryHandlerReply)await client.CallProxyAsync(
                    new WorkflowSetQueryHandlerRequest()
                    {
                        ContextId = contextId,
                        QueryName = queryType
                    });

                reply.ThrowOnError();
            }

            // Start the workflow by calling its workflow entry point method.
            // This method will indicate that it has completed via one of these 
            // techniques:
            //
            //      1. The method returns normally with the workflow result.
            //
            //      2. The method calls [RestartAsync(result, args)] which throws an
            //         [InternalWorkflowRestartException] which will be caught and
            //         handled here.
            //
            //      3. The method throws another exception which will be caught
            //         and be used to indicate that the workflow failed.

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

                var workflowMethod   = registration.WorkflowMethod;
                var resultType       = workflowMethod.ReturnType;
                var args             = client.DataConverter.FromDataArray(request.Args, registration.WorkflowMethodParameterTypes);
                var serializedResult = emptyBytes;

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

                    var result = await NeonHelper.GetTaskResultAsObjectAsync((Task)workflowMethod.Invoke(workflow, args));

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

                    await (Task)workflowMethod.Invoke(workflow, args);
                }

                return new WorkflowInvokeReply()
                {
                    Result = serializedResult
                };
            }
            catch (ForceReplayException)
            {
                return new WorkflowInvokeReply()
                {
                    ForceReplay = true
                };
            }
            catch (ContinueAsNewException e)
            {
                return new WorkflowInvokeReply()
                {
                    ContinueAsNew                             = true,
                    ContinueAsNewArgs                         = e.Args,
                    ContinueAsNewWorkflow                     = e.Workflow,
                    ContinueAsNewDomain                       = e.Domain,
                    ContinueAsNewTaskList                     = e.TaskList,
                    ContinueAsNewExecutionStartToCloseTimeout = CadenceHelper.ToCadence(e.ExecutionStartToCloseTimeout),
                    ContinueAsNewScheduleToCloseTimeout       = CadenceHelper.ToCadence(e.ScheduleToCloseTimeout),
                    ContinueAsNewScheduleToStartTimeout       = CadenceHelper.ToCadence(e.ScheduleToStartTimeout),
                    ContinueAsNewStartToCloseTimeout          = CadenceHelper.ToCadence(e.TaskStartToCloseTimeout),
                };
            }
            catch (CadenceException e)
            {
                log.LogError(e);

                return new WorkflowInvokeReply()
                {
                    Error = e.ToCadenceError()
                };
            }
            catch (Exception e)
            {
                log.LogError(e);

                return new WorkflowInvokeReply()
                {
                    Error = new CadenceError(e)
                };
            }
            finally
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.None;
            }
        }
Example #7
0
        /// <summary>
        /// Handles workflow queries.
        /// </summary>
        /// <param name="client">The Cadence client.</param>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        internal static async Task<WorkflowQueryInvokeReply> OnQueryAsync(CadenceClient client, WorkflowQueryInvokeRequest request)
        {
            Covenant.Requires<ArgumentNullException>(client != null, nameof(client));
            Covenant.Requires<ArgumentNullException>(request != null, nameof(request));

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

                var workflow = GetWorkflow(client, request.ContextId);

                if (workflow != null)
                {
                    // Handle built-in queries.

                    if (request.QueryName == "__stack_trace")
                    {
                        var trace = string.Empty;

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

                        return new WorkflowQueryInvokeReply()
                        {
                            RequestId = request.RequestId,
                            Result    = NeonHelper.JsonSerializeToBytes(trace)
                        };
                    }

                    // Handle user queries.

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

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

                        var serializedResult = emptyBytes;

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

                            var result = await NeonHelper.GetTaskResultAsObjectAsync((Task)method.Invoke(workflow, client.DataConverter.FromDataArray(request.QueryArgs, methodParameterTypes)));

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

                            await (Task)method.Invoke(workflow, client.DataConverter.FromDataArray(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}].").ToCadenceError()
                        };
                    }
                }
                else
                {
                    return new WorkflowQueryInvokeReply()
                    {
                        Error = new EntityNotExistsException($"Workflow with [contextID={request.ContextId}] does not exist.").ToCadenceError()
                    };
                }
            }
            catch (Exception e)
            {
                log.LogError(e);

                return new WorkflowQueryInvokeReply()
                {
                    Error = new CadenceError(e)
                };
            }
            finally
            {
                WorkflowBase.CallContext.Value = WorkflowCallContext.None;
            }
        }
Example #8
0
        /// <summary>
        /// Registers a workflow implementation.
        /// </summary>
        /// <param name="client">The associated client.</param>
        /// <param name="workflowType">The workflow implementation type.</param>
        /// <param name="workflowTypeName">The name used to identify the implementation.</param>
        /// <param name="domain">Specifies the target domain.</param>
        /// <exception cref="InvalidOperationException">Thrown if a different workflow class has already been registered for <paramref name="workflowTypeName"/>.</exception>
        internal static async Task RegisterAsync(CadenceClient client, Type workflowType, string workflowTypeName, string domain)
        {
            Covenant.Requires<ArgumentNullException>(client != null, nameof(client));
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(domain), nameof(domain));
            CadenceHelper.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 workflowTypeKey = GetWorkflowTypeKey(client, workflowTypeName, workflowMethodAttribute);

                lock (syncLock)
                {
                    if (nameToRegistration.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
                    {
                        nameToRegistration[workflowTypeKey] =
                            new WorkflowRegistration()
                            {
                                WorkflowType                 = workflowType,
                                WorkflowMethod               = method,
                                WorkflowMethodParameterTypes = method.GetParameterTypes(),
                                MethodMap                    = methodMap
                            };
                    }
                }

                var reply = (WorkflowRegisterReply)await client.CallProxyAsync(
                    new WorkflowRegisterRequest()
                    {
                        Name   = GetWorkflowTypeNameFromKey(workflowTypeKey),
                        Domain = client.ResolveDomain(domain)
                    });

                // $hack(jefflill): 
                //
                // We're going to ignore any errors here to handle:
                //
                //      https://github.com/nforgeio/neonKUBE/issues/668

                // reply.ThrowOnError();
            }
        }
Example #9
0
 public ActivityKey(CadenceClient client, long contextId)
 {
     this.clientId  = client.ClientId;
     this.contextId = contextId;
 }
Example #10
0
        /// <summary>
        /// Registers an activity type.
        /// </summary>
        /// <param name="client">The associated client.</param>
        /// <param name="activityType">The activity type.</param>
        /// <param name="activityTypeName">The name used to identify the implementation.</param>
        /// <param name="domain">Specifies the target domain.</param>
        /// <returns><c>true</c> if the activity was already registered.</returns>
        /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception>
        internal async static Task RegisterAsync(CadenceClient client, Type activityType, string activityTypeName, string domain)
        {
            Covenant.Requires <ArgumentNullException>(client != null, nameof(client));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(domain), nameof(domain));
            CadenceHelper.ValidateActivityImplementation(activityType);

            var constructor = activityType.GetConstructor(Type.EmptyTypes);

            if (constructor == null)
            {
                throw new ArgumentException($"Activity type [{activityType.FullName}] does not have a default constructor.", nameof(activityType));
            }

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

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

            foreach (var interfaceType in activityType.GetInterfaces())
            {
                foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
                {
                    var activityMethodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>();

                    if (activityMethodAttribute == null)
                    {
                        continue;
                    }

                    var signature = method.ToString();

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

                    methodSignatureToAttribute.Add(signature, activityMethodAttribute);
                }
            }

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

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

                var activityTypeKey = GetActivityTypeKey(client, activityTypeName, activityMethodAttribute);

                lock (syncLock)
                {
                    if (nameToRegistration.TryGetValue(activityTypeKey, out var registration))
                    {
                        if (!object.ReferenceEquals(registration.ActivityType, registration.ActivityType))
                        {
                            throw new InvalidOperationException($"Conflicting activity type registration: Activity type [{activityType.FullName}] is already registered for activity type name [{activityTypeKey}].");
                        }
                    }
                    else
                    {
                        nameToRegistration[activityTypeKey] =
                            new ActivityRegistration()
                        {
                            ActivityType                 = activityType,
                            ActivityConstructor          = constructor,
                            ActivityMethod               = method,
                            ActivityMethodParamaterTypes = method.GetParameterTypes()
                        };
                    }
                }

                var reply = (ActivityRegisterReply)await client.CallProxyAsync(
                    new ActivityRegisterRequest()
                {
                    Name   = GetActivityTypeNameFromKey(activityTypeKey),
                    Domain = client.ResolveDomain(domain)
                });

                // $hack(jefflill):
                //
                // We're going to ignore any errors here to handle:
                //
                //      https://github.com/nforgeio/neonKUBE/issues/668

                // reply.ThrowOnError();
            }
        }
Example #11
0
        /// <summary>
        /// Handles received <see cref="ActivityInvokeRequest"/> messages.
        /// </summary>
        /// <param name="client">The receiving Cadence client.</param>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        private static async Task <ActivityInvokeReply> OnActivityInvokeRequest(CadenceClient client, ActivityInvokeRequest request)
        {
            await SyncContext.Clear;

            ActivityRegistration invokeInfo;

            lock (syncLock)
            {
                if (!nameToRegistration.TryGetValue(GetActivityTypeKey(client, request.Activity), out invokeInfo))
                {
                    throw new KeyNotFoundException($"Cannot resolve [activityTypeName = {request.Activity}] to a registered activity type and activity method.");
                }
            }

            var activity = CreateNormal(client, invokeInfo, request.ContextId);

            try
            {
                var result = await activity.OnInvokeAsync(client, request.Args);

                if (activity.CompleteExternally)
                {
                    return(new ActivityInvokeReply()
                    {
                        Pending = true
                    });
                }
                else
                {
                    return(new ActivityInvokeReply()
                    {
                        Result = result,
                    });
                }
            }
            catch (CadenceException e)
            {
                activity.logger.LogError(e);

                return(new ActivityInvokeReply()
                {
                    Error = e.ToCadenceError()
                });
            }
            catch (TaskCanceledException e)
            {
                return(new ActivityInvokeReply()
                {
                    Error = new CancelledException(e.Message).ToCadenceError()
                });
            }
            catch (Exception e)
            {
                activity.logger.LogError(e);

                return(new ActivityInvokeReply()
                {
                    Error = new CadenceError(e)
                });
            }
        }
Example #12
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="client">The associated client.</param>
 /// <param name="execution">The workflow execution.</param>
 internal ExternalWorkflowFuture(CadenceClient client, WorkflowExecution execution)
 {
     this.client    = client;
     this.Execution = execution;
 }