Beispiel #1
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;
            }
        }