public void ProcessLockRequest(RequestMessage request)
        {
            this.Config.TraceHelper.EntityLockAcquired(
                this.context.HubName,
                this.context.Name,
                this.context.InstanceId,
                request.ParentInstanceId,
                request.ParentExecutionId,
                request.Id.ToString(),
                isReplay: false);

            System.Diagnostics.Debug.Assert(this.context.State.LockedBy == request.ParentInstanceId, "Lock was set.");

            System.Diagnostics.Debug.Assert(request.LockSet[request.Position].Equals(this.EntityId), "position is correct");
            request.Position++;

            if (request.Position < request.LockSet.Length)
            {
                // send lock request to next entity in the lock set
                var target = new OrchestrationInstance()
                {
                    InstanceId = EntityId.GetSchedulerIdFromEntityId(request.LockSet[request.Position])
                };
                this.context.SendLockRequestMessage(target, request);
            }
            else
            {
                // send lock acquisition completed response back to originating orchestration instance
                var target = new OrchestrationInstance()
                {
                    InstanceId = request.ParentInstanceId, ExecutionId = request.ParentExecutionId
                };
                this.context.SendLockResponseMessage(target, request.Id);
            }
        }
        /// <inheritdoc/>
        public async override Task <string> RetrieveSerializedEntityState(EntityId entityId, JsonSerializerSettings serializerSettings)
        {
            var instanceId = EntityId.GetSchedulerIdFromEntityId(entityId);
            IList <OrchestrationState> stateList = await this.serviceClient.GetOrchestrationStateAsync(instanceId, false);

            OrchestrationState state = stateList?.FirstOrDefault();

            if (state != null &&
                state.OrchestrationInstance != null &&
                state.Input != null)
            {
                string serializedState;

                if (state.Input.StartsWith("http"))
                {
                    serializedState = await this.serviceClient.DownloadBlobAsync(state.Input);
                }
                else
                {
                    serializedState = state.Input;
                }

                var schedulerState = JsonConvert.DeserializeObject <SchedulerState>(serializedState, serializerSettings);

                if (schedulerState.EntityExists)
                {
                    return(schedulerState.EntityState);
                }
            }

            return(null);
        }
        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);
        }
        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);
        }
Beispiel #5
0
        private void SignalEntityInternal(EntityId entity, DateTime?scheduledTimeUtc, string operation, object input)
        {
            this.ThrowIfInvalidAccess();
            if (operation == null)
            {
                throw new ArgumentNullException(nameof(operation));
            }

            string functionName = entity.EntityName;

            this.Config.ThrowIfFunctionDoesNotExist(functionName, FunctionType.Entity);

            var target = new OrchestrationInstance()
            {
                InstanceId = EntityId.GetSchedulerIdFromEntityId(entity),
            };
            var request = new RequestMessage()
            {
                ParentInstanceId  = this.InstanceId,
                ParentExecutionId = null, // for entities, message sorter persists across executions
                Id            = Guid.NewGuid(),
                IsSignal      = true,
                Operation     = operation,
                ScheduledTime = scheduledTimeUtc,
            };

            if (input != null)
            {
                request.SetInput(input, this.messageDataConverter);
            }

            this.SendOperationMessage(target, request);

            this.Config.TraceHelper.FunctionScheduled(
                this.Config.Options.HubName,
                functionName,
                this.InstanceId,
                reason: this.FunctionName,
                functionType: FunctionType.Entity,
                isReplay: false);
        }
Beispiel #6
0
        void IDurableEntityContext.SignalEntity(EntityId entity, string operation, object input)
        {
            this.ThrowIfInvalidAccess();
            if (operation == null)
            {
                throw new ArgumentNullException(nameof(operation));
            }

            string functionName = entity.EntityName;

            this.Config.ThrowIfFunctionDoesNotExist(functionName, FunctionType.Entity);

            var target = new OrchestrationInstance()
            {
                InstanceId = EntityId.GetSchedulerIdFromEntityId(entity),
            };
            var request = new RequestMessage()
            {
                ParentInstanceId = this.InstanceId,
                Id        = Guid.NewGuid(),
                IsSignal  = true,
                Operation = operation,
            };

            if (input != null)
            {
                request.SetInput(input);
            }

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

            this.Config.TraceHelper.FunctionScheduled(
                this.Config.Options.HubName,
                functionName,
                this.InstanceId,
                reason: this.FunctionName,
                functionType: FunctionType.Entity,
                isReplay: false);
        }
        private async Task SignalEntityAsync(TaskHubClient client, string hubName, EntityId entityId, string operationName, object operationInput)
        {
            if (operationName == null)
            {
                throw new ArgumentNullException(nameof(operationName));
            }

            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,
                Id        = guid,
                IsSignal  = true,
                Operation = operationName,
            };

            if (operationInput != null)
            {
                request.SetInput(operationInput);
            }

            var jrequest = JToken.FromObject(request, MessagePayloadDataConverter.DefaultSerializer);
            await client.RaiseEventAsync(instance, "op", jrequest);

            this.traceHelper.FunctionScheduled(
                hubName,
                entityId.EntityName,
                EntityId.GetSchedulerIdFromEntityId(entityId),
                reason: $"EntitySignal:{operationName}",
                functionType: FunctionType.Entity,
                isReplay: false);
        }
Beispiel #8
0
        public void ReleaseLocks()
        {
            if (this.ContextLocks != null)
            {
                foreach (var entityId in this.ContextLocks)
                {
                    var instance = new OrchestrationInstance()
                    {
                        InstanceId = EntityId.GetSchedulerIdFromEntityId(entityId)
                    };
                    var message = new ReleaseMessage()
                    {
                        ParentInstanceId = this.InstanceId,
                        LockRequestId    = this.LockRequestId,
                    };
                    this.SendEntityMessage(instance, "release", message);
                }

                this.ContextLocks  = null;
                this.lockReleaser  = null;
                this.LockRequestId = null;
            }
        }
Beispiel #9
0
        /// <inheritdoc/>
        async Task <IDisposable> IDurableOrchestrationContext.LockAsync(params EntityId[] entities)
        {
            this.ThrowIfInvalidAccess();
            if (this.ContextLocks != null)
            {
                throw new LockingRulesViolationException("Cannot acquire more locks when already holding some locks.");
            }

            if (entities == null || entities.Length == 0)
            {
                throw new ArgumentException("The list of entities to lock must not be null or empty.", nameof(entities));
            }

            // acquire the locks in a globally fixed order to avoid deadlocks
            Array.Sort(entities);

            // remove duplicates if necessary. Probably quite rare, so no need to optimize more.
            for (int i = 0; i < entities.Length - 1; i++)
            {
                if (entities[i].Equals(entities[i + 1]))
                {
                    entities = entities.Distinct().ToArray();
                    break;
                }
            }

            // use a deterministically replayable unique ID for this lock request, and to receive the response
            var lockRequestId = this.NewGuid();

            // All the entities in entity[] need to be locked, but to avoid deadlock, the locks have to be acquired
            // sequentially, in order. So, we send the lock request to the first entity; when the first lock
            // is granted by the first entity, the first entity will forward the lock request to the second entity,
            // and so on; after the last entity grants the last lock, a response is sent back here.

            // send lock request to first entity in the lock set
            var target = new OrchestrationInstance()
            {
                InstanceId = EntityId.GetSchedulerIdFromEntityId(entities[0])
            };
            var request = new RequestMessage()
            {
                Id = lockRequestId,
                ParentInstanceId = this.InstanceId,
                LockSet          = entities,
                Position         = 0,
            };

            this.LockRequestId = lockRequestId.ToString();

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

            // wait for the response from the last entity in the lock set
            await this.WaitForExternalEvent <ResponseMessage>(this.LockRequestId, "LockAcquisitionCompleted");

            this.ContextLocks = new List <EntityId>(entities);

            // return an IDisposable that releases the lock
            this.lockReleaser = new LockReleaser(this);

            return(this.lockReleaser);
        }
Beispiel #10
0
 /// <inheritdoc/>
 Task IDurableOrchestrationContext.CallEntityAsync(EntityId entityId, string operationName, object operationInput)
 {
     this.ThrowIfInvalidAccess();
     return(this.CallDurableTaskFunctionAsync <object>(entityId.EntityName, FunctionType.Entity, false, EntityId.GetSchedulerIdFromEntityId(entityId), operationName, null, operationInput));
 }
Beispiel #11
0
        /// <inheritdoc/>
        void IDurableOrchestrationContext.SignalEntity(EntityId entity, string operationName, object operationInput)
        {
            this.ThrowIfInvalidAccess();
            if (operationName == null)
            {
                throw new ArgumentNullException(nameof(operationName));
            }

            var alreadyCompletedTask = this.CallDurableTaskFunctionAsync <object>(entity.EntityName, FunctionType.Entity, true, EntityId.GetSchedulerIdFromEntityId(entity), operationName, null, operationInput);

            System.Diagnostics.Debug.Assert(alreadyCompletedTask.IsCompleted, "signaling entities is synchronous");
            alreadyCompletedTask.Wait(); // just so we see exceptions during testing
        }
        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);
            }
        }
 /// <summary>
 /// Returns the instance id of the entity scheduler for a given entity id.
 /// </summary>
 /// <param name="entityId">The entity id.</param>
 /// <returns>The instance id of the scheduler.</returns>
 public static string GetSchedulerIdFromEntityId(EntityId entityId)
 {
     return(EntityId.GetSchedulerIdFromEntityId(entityId));
 }