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