예제 #1
0
 public TaskEntityShim(DurableTaskExtension config, string schedulerId)
     : base(config)
 {
     this.SchedulerId = schedulerId;
     this.EntityId    = EntityId.GetEntityIdFromSchedulerId(schedulerId);
     this.context     = new DurableEntityContext(config, this.EntityId, this);
 }
 public TaskEntityShim(DurableTaskExtension config, DurabilityProvider durabilityProvider, string schedulerId)
     : base(config)
 {
     this.messageDataConverter = config.MessageDataConverter;
     this.errorDataConverter   = config.ErrorDataConverter;
     this.SchedulerId          = schedulerId;
     this.EntityId             = EntityId.GetEntityIdFromSchedulerId(schedulerId);
     this.context = new DurableEntityContext(config, durabilityProvider, this.EntityId, this);
 }
        internal DurableEntityStatus(DurableOrchestrationStatus orchestrationStatus)
        {
            this.EntityId          = EntityId.GetEntityIdFromSchedulerId(orchestrationStatus.InstanceId);
            this.LastOperationTime = orchestrationStatus.LastUpdatedTime;

            if (orchestrationStatus?.Input is JObject input)
            {
                SchedulerState state = input.ToObject <SchedulerState>();
                if (state?.EntityState != null)
                {
                    try
                    {
                        // Entity state is expected to be JSON-compatible
                        this.State = JToken.Parse(state.EntityState);
                    }
                    catch (JsonException)
                    {
                        // Just in case the above assumption is ever wrong, fallback to a raw string
                        this.State = state.EntityState;
                    }
                }
            }
        }
예제 #4
0
        internal async Task <TResult> CallDurableTaskFunctionAsync <TResult>(
            string functionName,
            FunctionType functionType,
            bool oneWay,
            string instanceId,
            string operation,
            RetryOptions retryOptions,
            object input)
        {
            this.ThrowIfInvalidAccess();

            if (retryOptions != null)
            {
                if (!this.durabilityProvider.ValidateDelayTime(retryOptions.MaxRetryInterval, out string errorMessage))
                {
                    throw new ArgumentException(errorMessage, nameof(retryOptions.MaxRetryInterval));
                }

                if (!this.durabilityProvider.ValidateDelayTime(retryOptions.FirstRetryInterval, out errorMessage))
                {
                    throw new ArgumentException(errorMessage, nameof(retryOptions.FirstRetryInterval));
                }
            }

            // TODO: Support for versioning
            string version = DefaultVersion;

            this.Config.ThrowIfFunctionDoesNotExist(functionName, functionType);

            Task <TResult> callTask      = null;
            EntityId?      lockToUse     = null;
            string         operationId   = string.Empty;
            string         operationName = string.Empty;

            switch (functionType)
            {
            case FunctionType.Activity:
                System.Diagnostics.Debug.Assert(instanceId == null, "The instanceId parameter should not be used for activity functions.");
                System.Diagnostics.Debug.Assert(operation == null, "The operation parameter should not be used for activity functions.");
                System.Diagnostics.Debug.Assert(!oneWay, "The oneWay parameter should not be used for activity functions.");
                if (retryOptions == null)
                {
                    this.IncrementActionsOrThrowException();
                    callTask = this.InnerContext.ScheduleTask <TResult>(functionName, version, input);
                }
                else
                {
                    this.IncrementActionsOrThrowException();
                    callTask = this.InnerContext.ScheduleWithRetry <TResult>(
                        functionName,
                        version,
                        retryOptions.GetRetryOptions(),
                        input);
                }

                break;

            case FunctionType.Orchestrator:
                // Instance IDs should not be reused when creating sub-orchestrations. This is a best-effort
                // check. We cannot easily check the full hierarchy, so we just look at the current orchestration
                // and the immediate parent.
                if (string.Equals(instanceId, this.InstanceId, StringComparison.OrdinalIgnoreCase) ||
                    (this.ParentInstanceId != null && string.Equals(instanceId, this.ParentInstanceId, StringComparison.OrdinalIgnoreCase)))
                {
                    throw new ArgumentException("The instance ID of a sub-orchestration must be different than the instance ID of a parent orchestration.");
                }

                System.Diagnostics.Debug.Assert(operation == null, "The operation parameter should not be used for activity functions.");
                if (instanceId != null && instanceId.StartsWith("@"))
                {
                    throw new ArgumentException(nameof(instanceId), "Orchestration instance ids must not start with @");
                }

                if (oneWay)
                {
                    this.IncrementActionsOrThrowException();
                    var dummyTask = this.InnerContext.CreateSubOrchestrationInstance <TResult>(
                        functionName,
                        version,
                        instanceId,
                        input,
                        new Dictionary <string, string>()
                    {
                        { OrchestrationTags.FireAndForget, "" }
                    });

                    System.Diagnostics.Debug.Assert(dummyTask.IsCompleted, "task should be fire-and-forget");
                }
                else
                {
                    if (this.ContextLocks != null)
                    {
                        throw new LockingRulesViolationException("While holding locks, cannot call suborchestrators.");
                    }

                    if (retryOptions == null)
                    {
                        this.IncrementActionsOrThrowException();
                        callTask = this.InnerContext.CreateSubOrchestrationInstance <TResult>(
                            functionName,
                            version,
                            instanceId,
                            input);
                    }
                    else
                    {
                        this.IncrementActionsOrThrowException();
                        callTask = this.InnerContext.CreateSubOrchestrationInstanceWithRetry <TResult>(
                            functionName,
                            version,
                            instanceId,
                            retryOptions.GetRetryOptions(),
                            input);
                    }
                }

                break;

            case FunctionType.Entity:
                System.Diagnostics.Debug.Assert(operation != null, "The operation parameter is required.");
                System.Diagnostics.Debug.Assert(retryOptions == null, "Retries are not supported for entity calls.");
                System.Diagnostics.Debug.Assert(instanceId != null, "Entity calls need to specify the target entity.");

                if (this.ContextLocks != null)
                {
                    lockToUse = EntityId.GetEntityIdFromSchedulerId(instanceId);
                    if (oneWay)
                    {
                        if (this.ContextLocks.Contains(lockToUse.Value))
                        {
                            throw new LockingRulesViolationException("While holding locks, cannot signal entities whose lock is held.");
                        }
                    }
                    else
                    {
                        if (!this.ContextLocks.Remove(lockToUse.Value))
                        {
                            throw new LockingRulesViolationException("While holding locks, cannot call entities whose lock is not held.");
                        }
                    }
                }

                var guid   = this.NewGuid();   // deterministically replayable unique id for this request
                var target = new OrchestrationInstance()
                {
                    InstanceId = instanceId
                };
                operationId   = guid.ToString();
                operationName = operation;
                var request = new RequestMessage()
                {
                    ParentInstanceId = this.InstanceId,
                    Id        = guid,
                    IsSignal  = oneWay,
                    Operation = operation,
                };
                if (input != null)
                {
                    request.SetInput(input);
                }

                this.SendEntityMessage(target, "op", request);

                if (!oneWay)
                {
                    callTask = this.WaitForEntityResponse <TResult>(guid, lockToUse);
                }

                break;

            default:
                throw new InvalidOperationException($"Unexpected function type '{functionType}'.");
            }

            string sourceFunctionId = this.FunctionName;

            this.Config.TraceHelper.FunctionScheduled(
                this.Config.Options.HubName,
                functionName,
                this.InstanceId,
                reason: sourceFunctionId,
                functionType: functionType,
                isReplay: this.IsReplaying);

            TResult   output;
            Exception exception = null;

            if (oneWay)
            {
                return(default(TResult));
            }

            System.Diagnostics.Debug.Assert(callTask != null, "Two-way operations are asynchronous, so callTask must not be null.");

            try
            {
                output = await callTask;
            }
            catch (TaskFailedException e)
            {
                exception = e;
                string message = string.Format(
                    "The {0} function '{1}' failed: \"{2}\". See the function execution logs for additional details.",
                    functionType.ToString().ToLowerInvariant(),
                    functionName,
                    e.InnerException?.Message);
                throw new FunctionFailedException(message, e.InnerException);
            }
            catch (SubOrchestrationFailedException e)
            {
                exception = e;
                string message = string.Format(
                    "The {0} function '{1}' failed: \"{2}\". See the function execution logs for additional details.",
                    functionType.ToString().ToLowerInvariant(),
                    functionName,
                    e.InnerException?.Message);
                throw new FunctionFailedException(message, e.InnerException);
            }
            catch (Exception e)
            {
                exception = e;
                throw;
            }
            finally
            {
                if (exception != null && this.IsReplaying)
                {
                    // If this were not a replay, then the orchestrator/activity/entity function trigger would have already
                    // emitted a FunctionFailed trace with the full exception details.

                    if (functionType == FunctionType.Entity)
                    {
                        this.Config.TraceHelper.OperationFailed(
                            this.Config.Options.HubName,
                            functionName,
                            this.InstanceId,
                            operationId,
                            operationName,
                            input: "(replayed)",
                            exception: "(replayed)",
                            duration: 0,
                            isReplay: true);
                    }
                    else
                    {
                        this.Config.TraceHelper.FunctionFailed(
                            this.Config.Options.HubName,
                            functionName,
                            this.InstanceId,
                            reason: $"(replayed {exception.GetType().Name})",
                            functionType: functionType,
                            isReplay: true);
                    }
                }
            }

            if (this.IsReplaying)
            {
                // If this were not a replay, then the orchestrator/activity/entity function trigger would have already
                // emitted a FunctionCompleted trace with the actual output details.

                if (functionType == FunctionType.Entity)
                {
                    this.Config.TraceHelper.OperationCompleted(
                        this.Config.Options.HubName,
                        functionName,
                        this.InstanceId,
                        operationId,
                        operationName,
                        input: "(replayed)",
                        output: "(replayed)",
                        duration: 0,
                        isReplay: true);
                }
                else
                {
                    this.Config.TraceHelper.FunctionCompleted(
                        this.Config.Options.HubName,
                        functionName,
                        this.InstanceId,
                        output: "(replayed)",
                        continuedAsNew: false,
                        functionType: functionType,
                        isReplay: true);
                }
            }

            return(output);
        }
예제 #5
0
        private async Task ProcessAsyncActions(AsyncAction[][] actions)
        {
            if (actions == null)
            {
                throw new ArgumentNullException("Out-of-proc orchestrator schema must have a non-null actions property.");
            }

            // Each actionSet represents a particular execution of the orchestration.
            foreach (AsyncAction[] actionSet in actions)
            {
                var tasks = new List <Task>(actions.Length);

                // An actionSet represents all actions that were scheduled within that execution.
                foreach (AsyncAction action in actionSet)
                {
                    switch (action.ActionType)
                    {
                    case AsyncActionType.CallActivity:
                        tasks.Add(this.context.CallActivityAsync(action.FunctionName, action.Input));
                        break;

                    case AsyncActionType.CreateTimer:
                        using (var cts = new CancellationTokenSource())
                        {
                            tasks.Add(this.context.CreateTimer(action.FireAt, cts.Token));
                            if (action.IsCanceled)
                            {
                                cts.Cancel();
                            }
                        }

                        break;

                    case AsyncActionType.CallActivityWithRetry:
                        tasks.Add(this.context.CallActivityWithRetryAsync(action.FunctionName, action.RetryOptions, action.Input));
                        break;

                    case AsyncActionType.CallSubOrchestrator:
                        tasks.Add(this.context.CallSubOrchestratorAsync(action.FunctionName, action.InstanceId, action.Input));
                        break;

                    case AsyncActionType.CallSubOrchestratorWithRetry:
                        tasks.Add(this.context.CallSubOrchestratorWithRetryAsync(action.FunctionName, action.RetryOptions, action.InstanceId, action.Input));
                        break;

                    case AsyncActionType.CallEntity:
                        var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                        tasks.Add(this.context.CallEntityAsync(entityId, action.EntityOperation, action.Input));
                        break;

                    case AsyncActionType.ContinueAsNew:
                        this.context.ContinueAsNew(action.Input);
                        break;

                    case AsyncActionType.WaitForExternalEvent:
                        tasks.Add(this.context.WaitForExternalEvent <object>(action.ExternalEventName));
                        break;

                    case AsyncActionType.CallHttp:
                        tasks.Add(this.context.CallHttpAsync(action.HttpRequest));
                        break;

                    default:
                        break;
                    }
                }

                if (tasks.Count > 0)
                {
                    await Task.WhenAny(tasks);
                }
            }
        }
        /// <summary>
        /// Invokes a DF API based on the input action object.
        /// </summary>
        /// <param name="action">An OOProc action object representing a DF task.</param>
        /// <returns>If the API returns a task, the DF task corresponding to the input action. Else, null.</returns>
        private Task InvokeAPIFromAction(AsyncAction action)
        {
            Task fireAndForgetTask = Task.CompletedTask;
            Task task = null;

            switch (action.ActionType)
            {
            case AsyncActionType.CallActivity:
                task = this.context.CallActivityAsync(action.FunctionName, action.Input);
                break;

            case AsyncActionType.CreateTimer:
                DurableOrchestrationContext ctx = this.context as DurableOrchestrationContext;
                using (var cts = new CancellationTokenSource())
                {
                    if (ctx != null)
                    {
                        ctx.ThrowIfInvalidTimerLengthForStorageProvider(action.FireAt);
                    }

                    task = this.context.CreateTimer(action.FireAt, cts.Token);

                    if (action.IsCanceled)
                    {
                        cts.Cancel();
                    }
                }

                break;

            case AsyncActionType.CallActivityWithRetry:
                task = this.context.CallActivityWithRetryAsync(action.FunctionName, action.RetryOptions, action.Input);
                break;

            case AsyncActionType.CallSubOrchestrator:
                task = this.context.CallSubOrchestratorAsync(action.FunctionName, action.InstanceId, action.Input);
                break;

            case AsyncActionType.CallSubOrchestratorWithRetry:
                task = this.context.CallSubOrchestratorWithRetryAsync(action.FunctionName, action.RetryOptions, action.InstanceId, action.Input);
                break;

            case AsyncActionType.CallEntity:
            {
                var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                task = this.context.CallEntityAsync(entityId, action.EntityOperation, action.Input);
                break;
            }

            case AsyncActionType.SignalEntity:
            {
                // We do not add a task because this is 'fire and forget'
                var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                this.context.SignalEntity(entityId, action.EntityOperation, action.Input);
                task = fireAndForgetTask;
                break;
            }

            case AsyncActionType.ScheduledSignalEntity:
            {
                // We do not add a task because this is 'fire and forget'
                var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                this.context.SignalEntity(entityId, action.FireAt, action.EntityOperation, action.Input);
                task = fireAndForgetTask;
                break;
            }

            case AsyncActionType.ContinueAsNew:
                this.context.ContinueAsNew(action.Input);
                task = fireAndForgetTask;
                break;

            case AsyncActionType.WaitForExternalEvent:
                task = this.context.WaitForExternalEvent <object>(action.ExternalEventName);
                break;

            case AsyncActionType.CallHttp:
                task = this.context.CallHttpAsync(action.HttpRequest);
                break;

            case AsyncActionType.WhenAll:
                task = Task.WhenAll(action.CompoundActions.Select(x => this.InvokeAPIFromAction(x)));
                break;

            case AsyncActionType.WhenAny:
                task = Task.WhenAny(action.CompoundActions.Select(x => this.InvokeAPIFromAction(x)));
                break;

            default:
                throw new Exception($"Received an unexpected action type from the out-of-proc function: '${action.ActionType}'.");
            }

            return(task);
        }