Ejemplo n.º 1
0
        /// <summary>
        /// Executes any pending workflows that also have a matching worker running.
        /// </summary>
        private async Task ExecutePendingWorkflowsAsync()
        {
            var startedWorkflows = new List <EmulatedWorkflow>();

            foreach (var workflow in emulatedPendingWorkflows)
            {
                var worker = GetWorkflowWorker(workflow.Domain, workflow.TaskList);

                if (worker != null)
                {
                    startedWorkflows.Add(workflow);

                    _ = Task.Run(
                        async() =>
                    {
                        var workflowInvokeRequest =
                            new WorkflowInvokeRequest()
                        {
                            Args      = workflow.Args,
                            Name      = workflow.Name,
                            ContextId = workflow.ContextId
                        };

                        var workflowInvokeReply = (WorkflowInvokeReply)await CallClientAsync(workflowInvokeRequest);

                        workflow.Result     = workflowInvokeReply.Result;
                        workflow.Error      = workflowInvokeReply.Error;
                        workflow.IsComplete = true;

                        workflow.CompletedEvent.Set();
                    });
                }
            }

            foreach (var workflow in startedWorkflows)
            {
                emulatedWorkflows.Add(workflow.RunId, workflow);
                emulatedPendingWorkflows.Remove(workflow);
            }

            await Task.CompletedTask;
        }
Ejemplo n.º 2
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);
            Covenant.Requires <ArgumentNullException>(request != null);
            Covenant.Requires <ArgumentException>(request.ReplayStatus != InternalReplayStatus.Unspecified);

            IWorkflowBase        workflow;
            WorkflowRegistration registration;

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

            lock (syncLock)
            {
                if (request.ReplayStatus != InternalReplayStatus.Unspecified)
                {
                    return(new WorkflowInvokeReply()
                    {
                        Error = new CadenceError($"[{nameof(WorkflowInvokeRequest)}] did not specify Workflow type name [Type={request.WorkflowType}] is not registered for this worker.")
                    });
                }

                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          = (IWorkflowBase)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 handlers 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
            {
                var workflowMethod   = registration.WorkflowMethod;
                var resultType       = workflowMethod.ReturnType;
                var args             = client.DataConverter.FromDataArray(request.Args, registration.WorkflowMethodParameterTypes);
                var serializedResult = emptyBytes;

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

                    var result = await(Task <object>) workflowMethod.Invoke(workflow, args);

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

                    await(Task <object>) workflowMethod.Invoke(workflow, args);
                    serializedResult = emptyBytes;
                }

                return(new WorkflowInvokeReply()
                {
                    Result = serializedResult
                });
            }
            catch (CadenceWorkflowRestartException e)
            {
                return(new WorkflowInvokeReply()
                {
                    ContinueAsNew = true,
                    ContinueAsNewArgs = e.Args,
                    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)
            {
                return(new WorkflowInvokeReply()
                {
                    Error = e.ToCadenceError()
                });
            }
            catch (Exception e)
            {
                return(new WorkflowInvokeReply()
                {
                    Error = new CadenceError(e)
                });
            }
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Handles workflow invocation.
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <returns>The reply message.</returns>
        internal async Task <WorkflowInvokeReply> OnInvokeAsync(WorkflowInvokeRequest request)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(request != null, nameof(request));
            Covenant.Requires <ArgumentException>(request.ReplayStatus != InternalReplayStatus.Unspecified, nameof(request));

            WorkflowBase         workflow;
            WorkflowRegistration registration;

            var contextId = request.ContextId;

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

            registration = GetWorkflowRegistration(request.WorkflowType);

            if (registration == null)
            {
                return(new WorkflowInvokeReply()
                {
                    Error = new TemporalError($"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,
                    worker:             this,
                    contextId:          contextId,
                    workflowTypeName:   request.WorkflowType,
                    @namespace:         request.Namespace,
                    taskQueue:          request.TaskQueue,
                    workflowId:         request.WorkflowId,
                    runId:              request.RunId,
                    isReplaying:        request.ReplayStatus == InternalReplayStatus.Replaying,
                    methodMap:          registration.MethodMap);

            Workflow.Current = workflow.Workflow;   // Initialize the ambient workflow information.

            lock (idToWorkflow)
            {
                idToWorkflow.Add(contextId, workflow);
            }

            // Register any workflow signal and/or query methods with [temporal-proxy].
            // Note that synchronous signals need special handling further below.

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

                reply.ThrowOnError();
            }

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

                reply.ThrowOnError();
            }

            // $hack(jefflill):
            //
            // We registered synchronous signal names above even though we probably
            // shouldn't have.  This shouldn't really cause any trouble.
            //
            // If the workflow has any synchronous signals, we need to register the
            // special synchronous signal dispatcher as well as the synchronous signal
            // query handler.

            if (registration.MethodMap.HasSynchronousSignals)
            {
                workflow.HasSynchronousSignals = true;

                var signalSubscribeReply = (WorkflowSignalSubscribeReply)await Client.CallProxyAsync(
                    new WorkflowSignalSubscribeRequest()
                {
                    ContextId  = contextId,
                    SignalName = TemporalClient.SignalSync,
                    WorkerId   = this.WorkerId
                });

                signalSubscribeReply.ThrowOnError();

                var querySubscribeReply = (WorkflowSetQueryHandlerReply)await Client.CallProxyAsync(
                    new WorkflowSetQueryHandlerRequest()
                {
                    ContextId = contextId,
                    QueryName = TemporalClient.QuerySyncSignal,
                    WorkerId  = this.WorkerId
                });

                querySubscribeReply.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 [ContinuAsNewAsync()] 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             = TemporalHelper.BytesToArgs(Client.DataConverter, request.Args, registration.WorkflowMethodParameterTypes);
                var serializedResult = Array.Empty <byte>();

                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);
                }

                await workflow.WaitForPendingWorkflowOperationsAsync();

                return(new WorkflowInvokeReply()
                {
                    Result = serializedResult
                });
            }
            catch (ForceReplayException)
            {
                return(new WorkflowInvokeReply()
                {
                    ForceReplay = true
                });
            }
            catch (ContinueAsNewException e)
            {
                await workflow.WaitForPendingWorkflowOperationsAsync();

                return(new WorkflowInvokeReply()
                {
                    ContinueAsNew = true,
                    ContinueAsNewArgs = e.Args,
                    ContinueAsNewWorkflow = e.Workflow,
                    ContinueAsNewNamespace = e.Namespace,
                    ContinueAsNewTaskQueue = e.TaskQueue,
                    ContinueAsNewExecutionStartToCloseTimeout = TemporalHelper.ToTemporal(e.StartToCloseTimeout),
                    ContinueAsNewScheduleToCloseTimeout = TemporalHelper.ToTemporal(e.ScheduleToCloseTimeout),
                    ContinueAsNewScheduleToStartTimeout = TemporalHelper.ToTemporal(e.ScheduleToStartTimeout),
                    ContinueAsNewStartToCloseTimeout = TemporalHelper.ToTemporal(e.DecisionTaskTimeout),
                });
            }
            catch (TemporalException e)
            {
                log.LogError(e);

                await workflow.WaitForPendingWorkflowOperationsAsync();

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

                await workflow.WaitForPendingWorkflowOperationsAsync();

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