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); }
internal void SendResponseMessage(OrchestrationInstance target, Guid requestId, object message, bool isException) { lock (this.outbox) { this.outbox.Add(new ResultMessage() { Target = target, EventName = EntityMessageEventNames.ResponseMessageEventName(requestId), EventContent = message, IsError = isException, }); } }
internal void SendLockResponseMessage(OrchestrationInstance target, Guid requestId) { lock (this.outbox) { this.outbox.Add(new LockMessage() { Target = target, EventName = EntityMessageEventNames.ResponseMessageEventName(requestId), EventContent = new ResponseMessage() { Result = "Lock Acquisition Completed", // ignored by receiver but shows up in traces }, }); } }
internal void RescheduleMessages(OrchestrationContext innerContext, List <RequestMessage> messages) { if (messages != null) { foreach (var message in messages) { var instance = new OrchestrationInstance { InstanceId = this.InstanceId }; DateTime adjustedDeliveryTime = message.GetAdjustedDeliveryTime(this.durabilityProvider); var eventName = EntityMessageEventNames.ScheduledRequestMessageEventName(adjustedDeliveryTime); innerContext.SendEvent(instance, eventName, message); } messages.Clear(); } }
private async Task SignalEntityAsyncInternal(TaskHubClient client, string hubName, EntityId entityId, DateTime?scheduledTimeUtc, string operationName, object operationInput) { if (operationName == null) { throw new ArgumentNullException(nameof(operationName)); } if (this.client.Equals(client)) { 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(scheduledTimeUtc.Value) : EntityMessageEventNames.RequestMessageEventName; await client.RaiseEventAsync(instance, eventName, jrequest); this.traceHelper.FunctionScheduled( hubName, entityId.EntityName, EntityId.GetSchedulerIdFromEntityId(entityId), reason: $"EntitySignal:{operationName}", functionType: FunctionType.Entity, isReplay: false); }
internal void SendResponseMessage(OrchestrationInstance target, Guid requestId, object message, bool isException) { lock (this.outbox) { if (message is RequestMessage requestMessage) { this.State.MessageSorter.LabelOutgoingMessage(requestMessage, target.InstanceId, DateTime.UtcNow, this.EntityMessageReorderWindow); } this.outbox.Add(new ResultMessage() { Target = target, EventName = EntityMessageEventNames.ResponseMessageEventName(requestId), EventContent = message, IsError = isException, }); } }
internal void SendEntityMessage(OrchestrationInstance target, object eventContent) { string eventName; if (eventContent is RequestMessage requestMessage) { if (requestMessage.ScheduledTime.HasValue) { eventName = EntityMessageEventNames.ScheduledRequestMessageEventName(requestMessage.ScheduledTime.Value); } else { this.MessageSorter.LabelOutgoingMessage( requestMessage, target.InstanceId, this.InnerContext.CurrentUtcDateTime, TimeSpan.FromMinutes(this.Config.Options.EntityMessageReorderWindowInMinutes)); eventName = EntityMessageEventNames.RequestMessageEventName; } } else { eventName = EntityMessageEventNames.ReleaseMessageEventName; } if (!this.IsReplaying) { this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, target.InstanceId, eventName, eventContent); } this.IncrementActionsOrThrowException(); this.InnerContext.SendEvent(target, eventName, eventContent); }
internal void SendOperationMessage(OrchestrationInstance target, RequestMessage requestMessage) { lock (this.outbox) { string eventName; if (requestMessage.ScheduledTime.HasValue) { eventName = EntityMessageEventNames.ScheduledRequestMessageEventName(requestMessage.ScheduledTime.Value); } else { eventName = EntityMessageEventNames.RequestMessageEventName; } this.outbox.Add(new OperationMessage() { Target = target, EventName = eventName, EventContent = requestMessage, }); } }
internal void SendOperationMessage(OrchestrationInstance target, RequestMessage requestMessage) { lock (this.outbox) { string eventName; if (requestMessage.ScheduledTime.HasValue) { DateTime adjustedDeliveryTime = requestMessage.GetAdjustedDeliveryTime(this.durabilityProvider); eventName = EntityMessageEventNames.ScheduledRequestMessageEventName(adjustedDeliveryTime); } else { eventName = EntityMessageEventNames.RequestMessageEventName; } this.outbox.Add(new OperationMessage() { Target = target, EventName = eventName, EventContent = requestMessage, }); } }
internal void SendOperationMessage(OrchestrationInstance target, RequestMessage requestMessage) { lock (this.outbox) { string eventName; if (requestMessage.ScheduledTime.HasValue) { eventName = EntityMessageEventNames.ScheduledRequestMessageEventName(requestMessage.ScheduledTime.Value); } else { this.State.MessageSorter.LabelOutgoingMessage(requestMessage, target.InstanceId, DateTime.UtcNow, this.EntityMessageReorderWindow); eventName = EntityMessageEventNames.RequestMessageEventName; } this.outbox.Add(new OperationMessage() { Target = target, EventName = eventName, EventContent = requestMessage, }); } }
private async Task EntityMiddleware(DispatchMiddlewareContext dispatchContext, Func <Task> next) { var entityShim = dispatchContext.GetProperty <TaskOrchestration>() as TaskEntityShim; if (entityShim == null) { // This is not an entity - skip. await next(); return; } OrchestrationRuntimeState runtimeState = dispatchContext.GetProperty <OrchestrationRuntimeState>(); DurableEntityContext entityContext = (DurableEntityContext)entityShim.Context; entityContext.InstanceId = runtimeState.OrchestrationInstance.InstanceId; entityContext.ExecutionId = runtimeState.OrchestrationInstance.ExecutionId; entityContext.History = runtimeState.Events; entityContext.RawInput = runtimeState.Input; try { // 1. First time through the history // we count events, add any under-lock op to the batch, and process lock releases foreach (HistoryEvent e in runtimeState.Events) { switch (e.EventType) { case EventType.ExecutionStarted: entityShim.Rehydrate(runtimeState.Input); break; case EventType.EventRaised: EventRaisedEvent eventRaisedEvent = (EventRaisedEvent)e; this.TraceHelper.DeliveringEntityMessage( entityContext.InstanceId, entityContext.ExecutionId, e.EventId, eventRaisedEvent.Name, eventRaisedEvent.Input); entityShim.NumberEventsToReceive++; if (EntityMessageEventNames.IsRequestMessage(eventRaisedEvent.Name)) { // we are receiving an operation request or a lock request var requestMessage = this.DataConverter.Deserialize <RequestMessage>(eventRaisedEvent.Input); IEnumerable <RequestMessage> deliverNow; if (requestMessage.ScheduledTime.HasValue) { // messages with a scheduled time are always delivered immediately deliverNow = new RequestMessage[] { requestMessage }; } else { // run this through the message sorter to help with reordering and duplicate filtering deliverNow = entityContext.State.MessageSorter.ReceiveInOrder(requestMessage, entityContext.EntityMessageReorderWindow); } foreach (var message in deliverNow) { if (entityContext.State.LockedBy == message.ParentInstanceId) { // operation requests from the lock holder are processed immediately entityShim.AddOperationToBatch(message); } else { // others go to the back of the queue entityContext.State.Enqueue(message); } } } else { // we are receiving a lock release var message = this.DataConverter.Deserialize <ReleaseMessage>(eventRaisedEvent.Input); if (entityContext.State.LockedBy == message.ParentInstanceId) { this.TraceHelper.EntityLockReleased( entityContext.HubName, entityContext.Name, entityContext.InstanceId, message.ParentInstanceId, message.LockRequestId, isReplay: false); entityContext.State.LockedBy = null; } } break; } } // 2. We add as many requests from the queue to the batch as possible (stopping at lock requests) while (entityContext.State.LockedBy == null && entityContext.State.TryDequeue(out var request)) { if (request.IsLockRequest) { entityShim.AddLockRequestToBatch(request); entityContext.State.LockedBy = request.ParentInstanceId; } else { entityShim.AddOperationToBatch(request); } } } catch (Exception e) { entityContext.CaptureInternalError(e); } // 3. Start the functions invocation pipeline (billing, logging, bindings, and timeout tracking). FunctionResult result = await entityShim.GetFunctionInfo().Executor.TryExecuteAsync( new TriggeredFunctionData { TriggerValue = entityShim.Context, #pragma warning disable CS0618 // Approved for use by this extension InvokeHandler = async userCodeInvoker => { entityShim.SetFunctionInvocationCallback(userCodeInvoker); // 3. Run all the operations in the batch if (entityContext.InternalError == null) { try { await entityShim.ExecuteBatch(); } catch (Exception e) { entityContext.CaptureInternalError(e); } } // 4. Run the DTFx orchestration to persist the effects, // send the outbox, and continue as new await next(); // 5. If there were internal or application errors, indicate to the functions host entityContext.ThrowInternalExceptionIfAny(); entityContext.ThrowApplicationExceptionsIfAny(); }, #pragma warning restore CS0618 }, CancellationToken.None); await entityContext.RunDeferredTasks(); // If there were internal errors, do not commit the batch, but instead rethrow // here so DTFx can abort the batch and back off the work item entityContext.ThrowInternalExceptionIfAny(); }