public async Task <HttpResponseMessage> HandleRequestAsync(HttpRequestMessage request) { try { string basePath; if (this.localHttpListener.IsListening && request.RequestUri.IsLoopback && request.RequestUri.Port == this.localHttpListener.InternalRpcUri.Port) { basePath = this.localHttpListener.InternalRpcUri.AbsolutePath; } else if (this.config.Options.NotificationUrl != null) { basePath = this.config.Options.NotificationUrl.AbsolutePath; } else { throw new InvalidOperationException($"Don't know how to handle request to {request.RequestUri}."); } string path = "/" + request.RequestUri.AbsolutePath.Substring(basePath.Length).Trim('/'); var routeValues = new RouteValueDictionary(); if (StartOrchestrationRoute.TryMatch(path, routeValues)) { string functionName = (string)routeValues[FunctionNameRouteParameter]; string instanceId = (string)routeValues[InstanceIdRouteParameter]; if (request.Method == HttpMethod.Post) { return(await this.HandleStartOrchestratorRequestAsync(request, functionName, instanceId)); } else { return(request.CreateResponse(HttpStatusCode.NotFound)); } } if (EntityRoute.TryMatch(path, routeValues)) { try { string entityName = (string)routeValues[EntityNameRouteParameter]; string entityKey = (string)routeValues[EntityKeyRouteParameter]; if (request.Method == HttpMethod.Get) { if (!string.IsNullOrEmpty(entityKey)) { EntityId entityId = new EntityId(entityName, entityKey); return(await this.HandleGetEntityRequestAsync(request, entityId)); } else { return(await this.HandleListEntitiesRequestAsync(request, entityName)); } } else if (request.Method == HttpMethod.Post || request.Method == HttpMethod.Delete) { EntityId entityId = new EntityId(entityName, entityKey); return(await this.HandlePostEntityOperationRequestAsync(request, entityId)); } else { return(request.CreateResponse(HttpStatusCode.NotFound)); } } catch (ArgumentException e) { return(request.CreateErrorResponse(HttpStatusCode.BadRequest, e.Message)); } } if (InstancesRoute.TryMatch(path, routeValues)) { routeValues.TryGetValue(InstanceIdRouteParameter, out object instanceIdValue); routeValues.TryGetValue(OperationRouteParameter, out object operationValue); var instanceId = instanceIdValue as string; var operation = operationValue as string; if (instanceId == null) { // Retrieve All Status or conditional query in case of the request URL ends e.g. /instances/ if (request.Method == HttpMethod.Get) { return(await this.HandleGetStatusRequestAsync(request)); } else if (request.Method == HttpMethod.Delete) { return(await this.HandleDeleteHistoryWithFiltersRequestAsync(request)); } else { return(request.CreateResponse(HttpStatusCode.NotFound)); } } else if (instanceId != null && operation == null) { if (request.Method == HttpMethod.Get) { return(await this.HandleGetStatusRequestAsync(request, instanceId)); } else if (request.Method == HttpMethod.Delete) { return(await this.HandleDeleteHistoryByIdRequestAsync(request, instanceId)); } else { return(request.CreateResponse(HttpStatusCode.NotFound)); } } else { if (string.Equals(operation, TerminateOperation, StringComparison.OrdinalIgnoreCase)) { return(await this.HandleTerminateInstanceRequestAsync(request, instanceId)); } else if (string.Equals(operation, RewindOperation, StringComparison.OrdinalIgnoreCase)) { return(await this.HandleRewindInstanceRequestAsync(request, instanceId)); } else { return(request.CreateResponse(HttpStatusCode.NotFound)); } } } if (InstanceRaiseEventRoute.TryMatch(path, routeValues)) { string instanceId = (string)routeValues[InstanceIdRouteParameter]; string eventName = (string)routeValues[EventNameRouteParameter]; if (request.Method == HttpMethod.Post) { return(await this.HandleRaiseEventRequestAsync(request, instanceId, eventName)); } else { return(request.CreateResponse(HttpStatusCode.NotFound)); } } return(request.CreateResponse(HttpStatusCode.NotFound)); } /* Some handler methods throw ArgumentExceptions in specialized cases which should be returned to the client, such as when: * - the function name is not found (starting a new function) * - the orchestration instance is not in a Failed state (rewinding an orchestration instance) */ catch (ArgumentException e) { return(request.CreateErrorResponse(HttpStatusCode.BadRequest, "One or more of the arguments submitted is incorrect", e)); } catch (Exception e) { return(request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Something went wrong while processing your request", e)); } }
public Task <TResult> CallAsync <TResult>(EntityId entityId, string operationName, object operationInput) { this.SignalAndStoreTask(entityId, operationName, operationInput); return(Task.FromResult <TResult>(default(TResult))); }
public void Signal(EntityId entityId, string operationName, object operationInput) { this.SignalAndStoreTask(entityId, operationName, operationInput); }
void IDurableEntityContext.SignalEntity <TEntityInterface>(EntityId entityId, DateTime scheduledTimeUtc, Action <TEntityInterface> operation) { operation(EntityProxyFactory.Create <TEntityInterface>(new EntityContextProxy(this, scheduledTimeUtc), entityId)); }
public Task CallAsync(EntityId entityId, string operationName, object operationInput) { this.SignalAndStoreTask(entityId, operationName, operationInput); return(Task.CompletedTask); }
Task <EntityStateResponse <T> > IDurableEntityClient.ReadEntityStateAsync <T>(EntityId entityId, string taskHubName, string connectionName) { if (string.IsNullOrEmpty(taskHubName)) { return(this.ReadEntityStateAsync <T>(this.DurabilityProvider, entityId)); } else { if (string.IsNullOrEmpty(connectionName)) { connectionName = this.attribute.ConnectionName; } var attribute = new DurableClientAttribute { TaskHub = taskHubName, ConnectionName = connectionName, }; DurabilityProvider durabilityProvider = ((DurableClient)this.config.GetClient(attribute)).DurabilityProvider; return(this.ReadEntityStateAsync <T>(durabilityProvider, entityId)); } }
private async Task ExecuteOutOfProcBatch() { object outOfProcResults = null; Task invokeTask = this.FunctionInvocationCallback(); if (invokeTask is Task <object> resultTask) { outOfProcResults = await resultTask; } else { throw new InvalidOperationException("The WebJobs runtime returned a invocation task that does not support return values!"); } var jObj = outOfProcResults as JObject; if (jObj == null) { throw new ArgumentException("Out of proc orchestrators must return a valid JSON schema."); } var outOfProcResult = jObj.ToObject <OutOfProcResult>(); // update the state this.context.State.EntityExists = outOfProcResult.EntityExists; this.context.State.EntityState = outOfProcResult.EntityState; // for each operation, emit trace and send response message (if not a signal) for (int i = 0; i < this.OperationBatch.Count; i++) { var request = this.OperationBatch[i]; var result = outOfProcResult.Results[i]; if (!result.IsError) { this.Config.TraceHelper.OperationCompleted( this.context.HubName, this.context.Name, this.context.InstanceId, request.Id.ToString(), request.Operation, this.Config.GetIntputOutputTrace(request.Input), this.Config.GetIntputOutputTrace(result.Result), result.DurationInMilliseconds, isReplay: false); } else { this.context.CaptureApplicationError(new OperationErrorException( $"Error in operation '{request.Operation}': {result}")); this.Config.TraceHelper.OperationFailed( this.context.HubName, this.context.Name, this.context.InstanceId, request.Id.ToString(), request.Operation, this.Config.GetIntputOutputTrace(request.Input), this.Config.GetIntputOutputTrace(result.Result), result.DurationInMilliseconds, isReplay: false); } if (!request.IsSignal) { var target = new OrchestrationInstance() { InstanceId = request.ParentInstanceId, ExecutionId = request.ParentExecutionId, }; var responseMessage = new ResponseMessage() { Result = result.Result, ExceptionType = result.IsError ? "Error" : null, }; this.context.SendResponseMessage(target, request.Id, responseMessage, !result.IsError); } } // send signal messages foreach (var signal in outOfProcResult.Signals) { var request = new RequestMessage() { ParentInstanceId = this.context.InstanceId, ParentExecutionId = null, // for entities, message sorter persists across executions Id = Guid.NewGuid(), IsSignal = true, Operation = signal.Name, Input = signal.Input, }; var target = new OrchestrationInstance() { InstanceId = EntityId.GetSchedulerIdFromEntityId(signal.Target), }; this.context.SendOperationMessage(target, request); } }
public DurableEntityContext(DurableTaskExtension config, DurabilityProvider durabilityProvider, EntityId entity, TaskEntityShim shim) : base(config, entity.EntityName) { this.messageDataConverter = config.MessageDataConverter; this.errorDataConverter = config.ErrorDataConverter; this.durabilityProvider = durabilityProvider; this.self = entity; this.shim = shim; }
void IDurableEntityContext.SignalEntity(EntityId entity, string operation, object input) { this.SignalEntityInternal(entity, null, operation, input); }
/// <summary> /// Create an entity proxy. /// </summary> /// <param name="context">context.</param> /// <param name="entityId">Entity id.</param> protected EntityProxy(IEntityProxyContext context, EntityId entityId) { this.context = context; this.entityId = entityId; }
internal static TEntityInterface Create <TEntityInterface>(IEntityProxyContext context, EntityId entityId) { var type = TypeMappings.GetOrAdd(typeof(TEntityInterface), CreateProxyType); return((TEntityInterface)Activator.CreateInstance(type, context, entityId)); }
/// <summary> /// Calls an operation on an entity and waits for it to complete. /// </summary> /// <param name="context">The context object.</param> /// <param name="entityId">The target entity.</param> /// <param name="operationName">The name of the operation.</param> /// <returns>A task representing the completion of the operation on the entity.</returns> public static Task CallEntityAsync(this IDurableOrchestrationContext context, EntityId entityId, string operationName) { return(context.CallEntityAsync <object>(entityId, operationName, null)); }
/// <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); }
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()) { DurableOrchestrationContext ctx = this.context as DurableOrchestrationContext; if (ctx != null) { tasks.Add(ctx.OutOfProcCreateTimer(ctx, action.FireAt, cts.Token)); } else { 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.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); 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); 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); } } }
public DurableEntityContext(DurableTaskExtension config, EntityId entity, TaskEntityShim shim) : base(config, entity.EntityName) { this.self = entity; this.shim = shim; }
void IDurableEntityContext.SignalEntity(EntityId entity, DateTime scheduledTimeUtc, string operation, object input) { this.SignalEntityInternal(entity, scheduledTimeUtc, operation, input); }
private async Task SignalEntityAsyncInternal(DurableClient durableClient, string hubName, EntityId entityId, DateTime?scheduledTimeUtc, string operationName, object operationInput) { var entityKey = entityId.EntityKey; if (entityKey.Any(IsInvalidCharacter)) { throw new ArgumentException(nameof(entityKey), "Entity keys must not contain /, \\, #, ?, or control characters."); } if (operationName == null) { throw new ArgumentNullException(nameof(operationName)); } if (scheduledTimeUtc.HasValue) { scheduledTimeUtc = scheduledTimeUtc.Value.ToUniversalTime(); } if (this.ClientReferencesCurrentApp(durableClient)) { this.config.ThrowIfFunctionDoesNotExist(entityId.EntityName, FunctionType.Entity); } var guid = Guid.NewGuid(); // unique id for this request var instanceId = EntityId.GetSchedulerIdFromEntityId(entityId); var instance = new OrchestrationInstance() { InstanceId = instanceId }; var request = new RequestMessage() { ParentInstanceId = null, // means this was sent by a client ParentExecutionId = null, Id = guid, IsSignal = true, Operation = operationName, ScheduledTime = scheduledTimeUtc, }; if (operationInput != null) { request.SetInput(operationInput, this.messageDataConverter); } var jrequest = JToken.FromObject(request, this.messageDataConverter.JsonSerializer); var eventName = scheduledTimeUtc.HasValue ? EntityMessageEventNames.ScheduledRequestMessageEventName(request.GetAdjustedDeliveryTime(this.durabilityProvider)) : EntityMessageEventNames.RequestMessageEventName; await durableClient.client.RaiseEventAsync(instance, eventName, jrequest); this.traceHelper.FunctionScheduled( hubName, entityId.EntityName, EntityId.GetSchedulerIdFromEntityId(entityId), reason: $"EntitySignal:{operationName}", functionType: FunctionType.Entity, isReplay: false); }
/// <inheritdoc/> void IDurableEntityContext.SignalEntity <TEntityInterface>(EntityId entityId, Action <TEntityInterface> operation) { operation(EntityProxyFactory.Create <TEntityInterface>(new EntityContextProxy(this), entityId)); }
private async Task <EntityStateResponse <T> > ReadEntityStateAsync <T>(DurabilityProvider provider, EntityId entityId) { string entityState = await provider.RetrieveSerializedEntityState(entityId, this.messageDataConverter.JsonSettings); return(new EntityStateResponse <T>() { EntityExists = entityState != null, EntityState = this.messageDataConverter.Deserialize <T>(entityState), }); }
public Task <TResult> CallAsync <TResult>(EntityId entityId, string operationName, object operationInput) { return(this.context.CallEntityAsync <TResult>(entityId, operationName, operationInput)); }