示例#1
0
        private async Task <InvokeRoutineResult> RunRoutineAsync(
            ITransitionCarrier transitionCarrier,
            TransitionDescriptor transitionDescriptor,
            CancellationToken ct)
        {
            var invocationResult = new InvokeRoutineResult();

            using (_transitionScope.Enter(transitionDescriptor))
            {
                var transitionMonitor = _transitionScope.CurrentMonitor;

                var serviceId = await transitionCarrier.GetServiceIdAsync(ct);

                var methodId = await transitionCarrier.GetRoutineDescriptorAsync(ct);

                var serviceReference = _serviceResolver.Resolve(serviceId);
                var methodReference  = _methodResolver.Resolve(serviceReference.Definition, methodId);

                object serviceInstance = serviceReference.GetInstance();

                //var serviceStateContainer = _serviceStateValueContainerProvider.CreateContainer(serviceInstance);
                //var isStatefullService = serviceStateContainer.GetCount() > 0;
                //if (isStatefullService)
                //    await transitionCarrier.ReadServiceStateAsync(serviceStateContainer, ct);

                Type taskResultType =
                    methodReference.Definition.MethodInfo.ReturnType == typeof(void)
                    ? TaskAccessor.VoidTaskResultType
                    : TaskAccessor.GetTaskResultType(methodReference.Definition.MethodInfo.ReturnType);

                Task            completionTask;
                IValueContainer asmValueContainer = null;

                if (TryCreateAsyncStateMachine(methodReference.Definition.MethodInfo, methodId.IntentId, out var asmInstance, out var asmMetadata))
                {
                    var isContinuation = transitionDescriptor.Type == TransitionType.ContinueRoutine;
                    asmValueContainer = await LoadRoutineStateAsync(transitionCarrier, asmInstance, asmMetadata, isContinuation, ct);

                    asmMetadata.Owner.FieldInfo?.SetValue(asmInstance, serviceInstance);

                    transitionMonitor.OnRoutineStart(
                        serviceReference,
                        methodReference,
                        methodId,
                        serviceInstance,
                        asmInstance,
                        (transitionCarrier as TransitionCarrier)?.Caller);

                    try
                    {
                        asmInstance.MoveNext();
                        completionTask = GetCompletionTask(asmInstance, asmMetadata);
                    }
                    catch (Exception ex)
                    {
                        // The MoveNext() must not throw, but instead complete the task with an error.
                        // try-catch is added just in case for a non-compiler-generated state machine.
                        completionTask = TaskAccessor.FromException(taskResultType, ex);
                    }
                }
        public async Task CommitAsync(ScheduledActions actions, ITransitionCarrier transitionCarrier, TransitionCommitOptions options, CancellationToken ct)
        {
            if (actions.ExecuteRoutineIntents?.Count > 0)
            {
                foreach (var intent in actions.ExecuteRoutineIntents)
                {
                    var serviceDefinition = GetServiceDefinition(intent.ServiceId);

                    if (serviceDefinition.Type == ServiceType.Local)
                    {
#pragma warning disable CS4014
                        Task.Run(() => RunRoutineInBackground(serviceDefinition, intent));
#pragma warning restore CS4014
                    }
                    else
                    {
                        var platformHttpClient = _platformHttpClientProvider.GetClient(serviceDefinition);
                        var routineInfo        = await platformHttpClient.ScheduleRoutineAsync(intent, _transitionUserContext.Current, ct);

                        if (routineInfo.Result != null)
                        {
                            _routineCompletionSink.OnRoutineCompleted(intent.Id, routineInfo.Result);
                        }
                    }
                }
            }

            if (actions.RaiseEventIntents?.Count > 0)
            {
                foreach (var intent in actions.RaiseEventIntents)
                {
                    await _eventDispatcher.PublishEvent(intent);
                }
            }
        }
示例#3
0
        public async Task RunAsync(
            ITransitionCarrier transitionCarrier,
            CancellationToken ct)
        {
            var transitionDescriptor = await transitionCarrier.GetTransitionDescriptorAsync(ct);

            if (transitionDescriptor.Type == TransitionType.InvokeRoutine ||
                transitionDescriptor.Type == TransitionType.ContinueRoutine)
            {
                await RunRoutineAsync(transitionCarrier, transitionDescriptor, ct);
            }
            else
            {
                throw new InvalidOperationException($"Unknown transition type '{transitionDescriptor.Type}'.");
            }
        }
示例#4
0
        public async Task CommitAsync(
            ITransitionCarrier transitionCarrier,
            ScheduledActions actions,
            CancellationToken ct)
        {
#warning This need deep thinking on how to achieve transictionality

            if (actions.SaveStateIntent != null)
            {
#warning Make sure that saving service and routine state is transactional - you don't want to re-run routine on failure after service state was saved only.
                var intent = actions.SaveStateIntent;
                await transitionCarrier.SaveStateAsync(intent, ct);
            }

            if (actions.ExecuteRoutineIntents?.Count > 0)
            {
                foreach (var intent in actions.ExecuteRoutineIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.ServiceId);
#warning TODO: try to pre-generate routine ID - needed for transactionality.
#warning TODO: check if target fabric can route back the continuation. If not, come up with another strategy, e.g. polling, or gateway?
                    var info = await connector.ScheduleRoutineAsync(intent, ct);

#warning TODO: check if routine is already done - it's possible on retry to run the transition, or under some special circumstances.
#warning TODO: save scheduled routine info into current routine's state - needed for dynamic subscription.
                }
            }

            if (actions.ResumeRoutineIntent != null)
            {
#warning need ability to overwrite existing message instead of creating a new one (if supported)
                var intent    = actions.ResumeRoutineIntent;
                var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                var info      = await connector.ScheduleContinuationAsync(intent, ct);
            }

            if (actions.ContinuationIntents?.Count > 0)
            {
                foreach (var intent in actions.ContinuationIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                    var info      = await connector.ScheduleContinuationAsync(intent, ct);
                }
            }
        }
        public async Task CommitAsync(
            ScheduledActions actions,
            // Carrier is NULL when call is made outside of a transition scope, e.g. from entry point of a console app.
            ITransitionCarrier transitionCarrier,
            TransitionCommitOptions options,
            CancellationToken ct)
        {
#warning This need deep thinking on how to achieve consistency

            if (actions.SaveStateIntent != null)
            {
#warning Make sure that saving service and routine state is transactional - you don't want to re-run routine on failure after service state was saved only.
                var intent = actions.SaveStateIntent;
                await((ITransitionStateSaver)transitionCarrier).SaveStateAsync(intent, ct);
            }

            if (actions.ExecuteRoutineIntents?.Count > 0)
            {
                foreach (var intent in actions.ExecuteRoutineIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.ServiceId);
#warning TODO: try to pre-generate routine ID - needed for transactionality.
#warning TODO: check if target fabric can route back the continuation. If not, come up with another strategy, e.g. polling, or gateway?
                    var info = await connector.ScheduleRoutineAsync(intent, ct);

#warning TODO: check if routine is already done - it's possible on retry to run the transition, or under some special circumstances.
#warning TODO: save scheduled routine info into current routine's state - needed for dynamic subscription.
                    if (options.NotifyOnRoutineCompletion)
                    {
                        ((IInternalRoutineCompletionNotifier)_routineCompletionNotifier).RegisterComittedRoutine(intent.Id, connector, info);
                    }
                }
            }

            if (actions.ResumeRoutineIntent != null)
            {
#warning need ability to overwrite existing message instead of creating a new one (if supported)
                var intent    = actions.ResumeRoutineIntent;
                var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                var info      = await connector.ScheduleContinuationAsync(intent, ct);
            }

            if (actions.ContinuationIntents?.Count > 0)
            {
                foreach (var intent in actions.ContinuationIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                    var info      = await connector.ScheduleContinuationAsync(intent, ct);
                }
            }

            if (actions.RaiseEventIntents?.Count > 0)
            {
                foreach (var intent in actions.RaiseEventIntents)
                {
                    if (intent.ServiceId == null)
                    {
                        throw new NotSupportedException();
                    }

                    var connector = _fabricConnectorSelector.Select(intent.ServiceId);
                    await connector.PublishEventAsync(intent, ct);
                }
            }

            if (actions.RegisterTriggerIntents?.Count > 0)
            {
                var connector = ((ICurrentConnectorProvider)transitionCarrier).Connector;
                foreach (var intent in actions.RegisterTriggerIntents)
                {
                    await connector.RegisterTriggerAsync(intent, ct);
                }
            }

            if (actions.ActivateTriggerIntents?.Count > 0)
            {
                var connector = ((ICurrentConnectorProvider)transitionCarrier).Connector;
                foreach (var intent in actions.ActivateTriggerIntents)
                {
                    await connector.ActivateTriggerAsync(intent, ct);
                }
            }

            if (actions.SubscribeToTriggerIntents?.Count > 0)
            {
                var connector = ((ICurrentConnectorProvider)transitionCarrier).Connector;
                foreach (var intent in actions.SubscribeToTriggerIntents)
                {
                    await connector.SubscribeToTriggerAsync(intent, ct);
                }
            }
        }
示例#6
0
        private async Task RunRoutineAsync(
            ITransitionCarrier transitionCarrier,
            TransitionDescriptor transitionDescriptor,
            CancellationToken ct)
        {
            using (_transitionScope.Enter(transitionDescriptor))
            {
                var transitionMonitor = _transitionScope.CurrentMonitor;

                var serviceId = await transitionCarrier.GetServiceIdAsync(ct);

                var routineDescriptor = await transitionCarrier.GetRoutineDescriptorAsync(ct);

                object             serviceInstance;
                IServiceDefinition serviceDefinition;

#warning IntrinsicRoutines must be registered in the service registry, but it needs the engine IoC to resolve.
                if (serviceId.ProxyName == nameof(IntrinsicRoutines))
                {
                    serviceInstance   = _intrinsicRoutines;
                    serviceDefinition = IntrinsicCommunicationModel.IntrinsicRoutinesServiceDefinition;
                }
                else
                {
                    serviceInstance   = _serviceProxyBuilder.Build(serviceId);
                    serviceDefinition = ((ServiceProxyContext)((IProxy)serviceInstance).Context).Definition;
                }

                var routineMethod = _routineMethodResolver.Resolve(serviceDefinition, routineDescriptor.MethodId);

                //var serviceStateContainer = _serviceStateValueContainerProvider.CreateContainer(serviceInstance);
                //var isStatefullService = serviceStateContainer.GetCount() > 0;
                //if (isStatefullService)
                //    await transitionCarrier.ReadServiceStateAsync(serviceStateContainer, ct);

                Task            completionTask;
                IValueContainer asmValueContainer = null;

                if (TryCreateAsyncStateMachine(routineMethod, out var asmInstance, out var asmMetadata))
                {
                    var isContinuation = transitionDescriptor.Type == TransitionType.ContinueRoutine;
                    asmValueContainer = await LoadRoutineStateAsync(transitionCarrier, asmInstance, asmMetadata, isContinuation, ct);

                    asmMetadata.Owner.FieldInfo?.SetValue(asmInstance, serviceInstance);

                    transitionMonitor.OnRoutineStart(
                        serviceId,
                        routineDescriptor,
                        serviceInstance,
                        routineMethod,
                        asmInstance);

                    try
                    {
#warning possibly need to create a proxy? on a sealed ASM class? How to capture Task.Delay if it's not immediate after first MoveNext?
                        asmInstance.MoveNext();
                        completionTask = GetCompletionTask(asmInstance, asmMetadata);
                    }
                    catch (Exception ex)
                    {
                        // The MoveNext() must not throw, but instead complete the task with an error.
                        // try-catch is added just in case for a non-compiler-generated state machine.
                        var taskResultType = TaskAccessor.GetTaskResultType(routineMethod.ReturnType);
                        completionTask = TaskAccessor.FromException(taskResultType, ex);
                    }
                }
示例#7
0
        private async Task RunRoutineAsync(
            ITransitionCarrier transitionCarrier,
            ITransitionData transitionData,
            TransitionDescriptor transitionDescriptor,
            CancellationToken ct)
        {
            using (_transitionScope.Enter(transitionDescriptor))
            {
                var transitionMonitor = _transitionScope.CurrentMonitor;

                var serviceId = await transitionData.GetServiceIdAsync(ct);

                var routineDescriptor = await transitionData.GetRoutineDescriptorAsync(ct);

                var serviceInstance =
#warning IntrinsicRoutines must be registered in the service registry, but it needs the engine IoC to resolve.
                    serviceId.ProxyName == nameof(IntrinsicRoutines)
                    ? _intrinsicRoutines
                    : _serviceProxyBuilder.Build(serviceId);
#warning check if the serviceInstance proxy is an actual non-abstract class with implementation

                // Need exact underlying type of the service implementation type to call
                // the routine method directly without using the virtual method table.
                var serviceType   = (serviceInstance as IProxy)?.ObjectType ?? serviceInstance.GetType();
                var routineMethod = _routineMethodResolver.Resolve(serviceType, routineDescriptor.MethodId);

                var serviceStateContainer = _serviceStateValueContainerProvider.CreateContainer(serviceInstance);
                var isStatefullService    = serviceStateContainer.GetCount() > 0;
                if (isStatefullService)
                {
                    await transitionData.ReadServiceStateAsync(serviceStateContainer, ct);
                }

                Task            completionTask;
                IValueContainer asmValueContainer = null;

                if (TryCreateAsyncStateMachine(routineMethod, out var asmInstance, out var asmMetadata, out completionTask))
                {
                    var isContinuation = transitionDescriptor.Type == TransitionType.ContinueRoutine;
                    asmValueContainer = await LoadRoutineStateAsync(transitionData, asmInstance, asmMetadata, isContinuation, ct);

                    asmMetadata.Owner.FieldInfo?.SetValue(asmInstance, serviceInstance);

                    transitionMonitor.OnRoutineStart(
                        serviceId,
                        routineDescriptor,
                        serviceInstance,
                        routineMethod,
                        asmInstance);

                    try
                    {
#warning possibly need to create a proxy? on a sealed ASM class? How to capture Task.Delay if it's not immediate after first MoveNext?
                        asmInstance.MoveNext();
                    }
                    catch (Exception ex)
                    {
                        // The MoveNext() must not throw, but instead complete the task with an error.
                        // try-catch is added just in case for a non-compiler-generated state machine.
                        var taskResultType = TaskAccessor.GetTaskResultType(routineMethod.ReturnType);
                        completionTask = TaskAccessor.FromException(taskResultType, ex);
                    }
                }
示例#8
0
        public async Task CommitAsync(
            ScheduledActions actions,
            ITransitionCarrier transitionCarrier,
            TransitionCommitOptions options,
            CancellationToken ct)
        {
            ITransitionContext context = _transitionScope.CurrentMonitor.Context;
            SerializedMethodContinuationState continuationState = null;

            if (actions.SaveStateIntent != null)
            {
                var intent           = actions.SaveStateIntent;
                var serviceRef       = _serviceResolver.Resolve(context.Service);
                var methodRef        = _methodResolver.Resolve(serviceRef.Definition, context.Method);
                var behaviorSettings = _communicationSettingsProvider.GetMethodSettings(methodRef.Definition);

                IMethodStateStorage stateStorage = null;

                if (intent.RoutineState != null && intent.RoutineResult == null)
                {
                    var roamState = behaviorSettings.RoamingState;

                    if (!roamState)
                    {
                        stateStorage = _methodStateStorageProvider.GetStorage(context.Service, context.Method, returnNullIfNotFound: true);
                        if (stateStorage == null)
                        {
                            // Fallback: try to roam the state if possible
                            roamState = true;
                        }
                    }

                    if (roamState)
                    {
                        // Aggregate methods (WhenAll, WhenAny) must have a concurrency control
                        // using a persistence mechanism to aggregate multiple results.
                        roamState = methodRef.Definition.FindProperty("aggregate")?.Value != (object)true;
                    }

                    if (!roamState && stateStorage == null)
                    {
                        throw new InvalidOperationException($"Invoking method '{methodRef.Id}' on '{serviceRef.Id}' requires persistence for aggregating result.");
                    }

                    if (roamState)
                    {
                        continuationState = EncodeContinuationState(intent, transitionCarrier, context);
                    }
                    else
                    {
                        try
                        {
                            var executionState = GetMethodExecutionState(
                                actions.SaveStateIntent, transitionCarrier, context);

                            var etag = await stateStorage.WriteStateAsync(
                                actions.SaveStateIntent.Service,
                                actions.SaveStateIntent.Method,
                                executionState);

                            // NOTE: assume that ContinuationDescriptor instances refer to this instance of
                            // PersistedMethodId and the ETag gets automatically propagated to the invoke intents.
                            actions.SaveStateIntent.Method.ETag = etag;
                        }
                        catch (ETagMismatchException ex)
                        {
                            // The record in the storage has changed since the beginning of the transition.
                            // There must have been a concurrent transition.
                            // Discard current results and try again.
                            throw new ConcurrentTransitionException(ex);
                        }
                    }
                }
                else if (intent.RoutineResult != null)
                {
                    var expectsSyncReply = (transitionCarrier as TransitionCarrier)?.Message.CommunicatorTraits.HasFlag(CommunicationTraits.SyncReplies) == true;

                    // TODO: make this behavior optional if a continuation is present (no polling expected).
                    // TODO: for the event sourcing style, the result must be written by the receiver of the response.
                    var writeResult = !expectsSyncReply;

                    if (writeResult && context.Caller?.Event != null)
                    {
                        writeResult = false;
                    }

                    if (writeResult && stateStorage == null)
                    {
                        stateStorage = _methodStateStorageProvider.GetStorage(context.Service, context.Method, returnNullIfNotFound: true);
                    }

                    if (stateStorage == null)
                    {
                        // Fallback (A): if the method has a continuation, assume that no polling is expected,
                        // so there is no need to write the result into a persisted storage.
                        // This does not cover 'fire and forget' scenarios.
                        // Fallback (B): This method must be an event handler - no need
                        // to write result because nothing should poll for the result.
                        if (expectsSyncReply ||
                            transitionCarrier.GetContinuationsAsync(default).Result?.Count > 0 ||