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