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); } }
internal void SendOutbox(OrchestrationContext innerContext, bool writeBackSuccessful, ResponseMessage serializationErrorMessage) { lock (this.outbox) { foreach (var message in this.outbox) { if (message is LockMessage lockMessage) { this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, lockMessage.Target.InstanceId, lockMessage.EventName, lockMessage.EventContent); innerContext.SendEvent(lockMessage.Target, lockMessage.EventName, lockMessage.EventContent); } else if (message is ResultMessage resultMessage) { // non-error result messages are replaced with the writeback failed response if (!writeBackSuccessful && !resultMessage.IsError) { resultMessage.EventContent = serializationErrorMessage; } this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, resultMessage.Target.InstanceId, resultMessage.EventName, resultMessage.EventContent); innerContext.SendEvent(resultMessage.Target, resultMessage.EventName, resultMessage.EventContent); } else if (!writeBackSuccessful) { // all other messages (signals and fire-and-forget) are suppressed if the writeback failed // this helps to keep the observer pattern correct, for example. } else if (message is OperationMessage operationMessage) { this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, operationMessage.Target.InstanceId, operationMessage.EventName, operationMessage.EventContent); innerContext.SendEvent(operationMessage.Target, operationMessage.EventName, operationMessage.EventContent); } else if (message is FireAndForgetMessage fireAndForgetMessage) { var dummyTask = innerContext.CreateSubOrchestrationInstance <object>( fireAndForgetMessage.FunctionName, DurableOrchestrationContext.DefaultVersion, fireAndForgetMessage.InstanceId, fireAndForgetMessage.Input, new Dictionary <string, string>() { { OrchestrationTags.FireAndForget, "" } }); System.Diagnostics.Debug.Assert(dummyTask.IsCompleted, "task should be fire-and-forget"); } } this.outbox.Clear(); } }
public override async Task <string> Execute(OrchestrationContext innerContext, string serializedInput) { #if !FUNCTIONS_V1 // Adding "Tags" to activity allows using App Insights to query current state of entities var activity = Activity.Current; OrchestrationRuntimeStatus status = OrchestrationRuntimeStatus.Running; DurableTaskExtension.TagActivityWithOrchestrationStatus(status, this.context.InstanceId, true); #endif if (this.operationBatch.Count == 0 && this.lockRequest == null && (this.toBeRescheduled == null || this.toBeRescheduled.Count == 0)) { // we are idle after a ContinueAsNew - the batch is empty. // Wait for more messages to get here (via extended sessions) await this.doneProcessingMessages.Task; } if (!this.messageDataConverter.IsDefault) { innerContext.MessageDataConverter = this.messageDataConverter; } if (!this.errorDataConverter.IsDefault) { innerContext.ErrorDataConverter = this.errorDataConverter; } this.Config.TraceHelper.FunctionStarting( this.context.HubName, this.context.Name, this.context.InstanceId, this.Config.GetIntputOutputTrace(serializedInput), FunctionType.Entity, isReplay: false); if (this.NumberEventsToReceive > 0) { await this.doneProcessingMessages.Task; } // Commit the effects of this batch, if // we have not already run into an internal error // (in which case we will abort the batch instead of committing it) if (this.context.InternalError == null) { bool writeBackSuccessful = true; ResponseMessage serializationErrorMessage = null; if (this.RollbackFailedOperations) { // the state has already been written back, since it is // done right after each operation. } else { // we are writing back the state here, after executing // the entire batch of operations. writeBackSuccessful = this.context.TryWriteback(out serializationErrorMessage); } // Reschedule all signals that were received before their time this.context.RescheduleMessages(innerContext, this.toBeRescheduled); // Send all buffered outgoing messages this.context.SendOutbox(innerContext, writeBackSuccessful, serializationErrorMessage); var jstate = JToken.FromObject(this.context.State); // continue as new innerContext.ContinueAsNew(jstate); } if (this.context.ErrorsPresent(out var description)) { this.Config.TraceHelper.FunctionFailed( this.context.HubName, this.context.Name, this.context.InstanceId, description, functionType: FunctionType.Entity, isReplay: false); } else { this.Config.TraceHelper.FunctionCompleted( this.context.HubName, this.context.Name, this.context.InstanceId, this.Config.GetIntputOutputTrace(this.context.State.EntityState), continuedAsNew: true, functionType: FunctionType.Entity, isReplay: false); } // The return value is not used. return(string.Empty); }
internal void SendOutbox(OrchestrationContext innerContext, bool writeBackSuccessful, ResponseMessage serializationErrorMessage) { lock (this.outbox) { foreach (var message in this.outbox) { if (message is LockMessage lockMessage) { this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, lockMessage.Target.InstanceId, lockMessage.EventName, lockMessage.EventContent); innerContext.SendEvent(lockMessage.Target, lockMessage.EventName, lockMessage.EventContent); } else if (message is ResultMessage resultMessage) { // Since for all of the operations in the batch, their effect on the entity state // is lost, we don't want the calling orchestrations to think everything is o.k. // They should be notified, so we replace all non-error operation results // with an exception result. if (!writeBackSuccessful && !resultMessage.IsError) { resultMessage.EventContent = serializationErrorMessage; } this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, resultMessage.Target.InstanceId, resultMessage.EventName, resultMessage.EventContent); innerContext.SendEvent(resultMessage.Target, resultMessage.EventName, resultMessage.EventContent); } else if (!writeBackSuccessful) { // all other messages (signals and fire-and-forget) are suppressed if the writeback failed // this helps to keep the observer pattern correct, for example. } else if (message is OperationMessage operationMessage) { if (!operationMessage.EventContent.ScheduledTime.HasValue) { this.State.MessageSorter.LabelOutgoingMessage(operationMessage.EventContent, operationMessage.Target.InstanceId, DateTime.UtcNow, this.EntityMessageReorderWindow); } this.Config.TraceHelper.SendingEntityMessage( this.InstanceId, this.ExecutionId, operationMessage.Target.InstanceId, operationMessage.EventName, operationMessage.EventContent); innerContext.SendEvent(operationMessage.Target, operationMessage.EventName, operationMessage.EventContent); } else if (message is FireAndForgetMessage fireAndForgetMessage) { var dummyTask = innerContext.CreateSubOrchestrationInstance <object>( fireAndForgetMessage.FunctionName, DurableOrchestrationContext.DefaultVersion, fireAndForgetMessage.InstanceId, fireAndForgetMessage.Input, new Dictionary <string, string>() { { OrchestrationTags.FireAndForget, "" } }); System.Diagnostics.Debug.Assert(dummyTask.IsCompleted, "task should be fire-and-forget"); } } this.outbox.Clear(); } }