Esempio n. 1
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;
            }
        }