Beispiel #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);
                    }
                }
Beispiel #2
0
            public Task <TransitionDescriptor> GetTransitionDescriptorAsync(CancellationToken ct)
            {
                TransitionDescriptor result;

                if (_eventEnvelope.EventType == DasyncCloudEventsTypes.InvokeRoutine.Name)
                {
                    result = new TransitionDescriptor
                    {
                        Type = TransitionType.InvokeRoutine,
                        ETag = _eventEnvelope.ETag
                    };
                }
                else if (_eventEnvelope.EventType == DasyncCloudEventsTypes.ContinueRoutine.Name)
                {
                    result = new TransitionDescriptor
                    {
                        Type = TransitionType.ContinueRoutine,
                        ETag = _eventEnvelope.ETag
                    };
                }
                else
                {
                    throw new InvalidOperationException($"Unknown event type '{_eventEnvelope.EventType}'.");
                }
                return(Task.FromResult(result));
            }
Beispiel #3
0
        public Task <ActiveRoutineInfo> ScheduleContinuationAsync(
            ContinueRoutineIntent intent, CancellationToken ct)
        {
            var transitionDescriptor = new TransitionDescriptor
            {
                Type = TransitionType.ContinueRoutine,
                ETag = intent.Continuation.Routine.ETag
            };

            var message = new Message
            {
                //["IntentId"] = _serializer.Serialize(intent.Id),
                [nameof(TransitionDescriptor)]    = _serializer.SerializeToString(transitionDescriptor),
                [nameof(ServiceId)]               = _serializer.SerializeToString(intent.Continuation.ServiceId),
                [nameof(RoutineDescriptor)]       = _serializer.SerializeToString(intent.Continuation.Routine),
                [nameof(RoutineResultDescriptor)] = _serializer.SerializeToString(intent.Result),
                DeliverAt = intent.Continuation.ContinueAt?.ToUniversalTime()
            };

            _dataStore.ScheduleMessage(message);

            var info = new ActiveRoutineInfo
            {
                RoutineId = intent.Continuation.Routine.RoutineId
            };

            return(Task.FromResult(info));
        }
        public IDisposable Enter(TransitionDescriptor transitionDescriptor)
        {
            if (IsActive)
            {
                throw new InvalidOperationException(
                          "Cannot start transition of while another one is still running.");
            }

            var context = new TransitionContext
            {
                TransitionDescriptor = transitionDescriptor
            };

            var monitor = _transitionMonitorFactory.Create(context);

            var scopeData = new ScopeData
            {
                Context = context,
                Monitor = monitor
            };

            _currentScope.Value = scopeData;

            return(new DisposeTarget(this, scopeData));
        }
        public Task SubscribeToTriggerAsync(SubscribeToTriggerIntent intent, CancellationToken ct)
        {
            _dataStore.SubscribeToTrigger(
                intent.TriggerId,
                taskResult =>
            {
                var transitionDescriptor = new TransitionDescriptor
                {
                    Type = TransitionType.ContinueRoutine,
                    ETag = intent.Continuation.Routine.ETag
                };

                var resultDescriptor = new ResultDescriptor
                {
                    CorrelationId = intent.TriggerId,
                    Result        = taskResult
                };

                var message = new Message
                {
                    [nameof(TransitionDescriptor)] = _serializer.SerializeToString(transitionDescriptor),
                    [nameof(ServiceId)]            = _serializer.SerializeToString(intent.Continuation.ServiceId),
                    [nameof(RoutineDescriptor)]    = _serializer.SerializeToString(intent.Continuation.Routine),
                    [nameof(ResultDescriptor)]     = _serializer.SerializeToString(resultDescriptor),
                    DeliverAt = intent.Continuation.ContinueAt?.ToUniversalTime()
                };

                _dataStore.ScheduleMessage(message);
            });

            return(Task.FromResult(0));
        }
Beispiel #6
0
        /// <summary>
        /// Reinforces the model from the new item and reward.
        /// </summary>
        /// <param name="state">Initial State object or features with action label.</param>
        /// <param name="stateP">New State object or features with reward label.</param>
        public void Learn(object state, object stateP)
        {
            var doubles1 = Descriptor.Convert(state, true);
            var tuple1   = new double[][] { doubles1.ToArray() }.ToExamples();

            var doubles2 = TransitionDescriptor.Convert(stateP, true);
            var tuple2   = new double[][] { doubles2.ToArray() }.ToExamples();

            this.Learn(tuple1.X[0], tuple1.Y[0], tuple2.X[0], tuple2.Y[0]);
        }
Beispiel #7
0
        public Task <ActiveRoutineInfo> ScheduleRoutineAsync(
            ExecuteRoutineIntent intent, CancellationToken ct)
        {
#warning Need to send message first, then create routine record.

            var routineId = Interlocked.Increment(ref _dataStore.RoutineCounter);

            var routineRecord = new RoutineStateRecord
            {
                ETag         = DateTime.UtcNow.Ticks.ToString("X16"),
                Id           = routineId.ToString(),
                Completion   = new TaskCompletionSource <string>(),
                Continuation = intent.Continuation == null ? null : _serializer.SerializeToString(intent.Continuation)
            };

            lock (_dataStore.Routines)
            {
                _dataStore.Routines.Add(routineRecord.Id, routineRecord);
            }

            var transitionDescriptor = new TransitionDescriptor
            {
                Type = TransitionType.InvokeRoutine,
                ETag = routineRecord.ETag
            };

            var routineDescriptor = new RoutineDescriptor
            {
                MethodId  = intent.MethodId,
                IntentId  = intent.Id,
                RoutineId = routineRecord.Id,
                ETag      = routineRecord.ETag
            };

            var message = new Message
            {
                //["IntentId"] = _serializer.Serialize(intent.Id),
                [nameof(TransitionDescriptor)] = _serializer.SerializeToString(transitionDescriptor),
                [nameof(ServiceId)]            = _serializer.SerializeToString(intent.ServiceId),
                [nameof(RoutineDescriptor)]    = _serializer.SerializeToString(routineDescriptor),
                ["Parameters"] = _serializer.SerializeToString(intent.Parameters)
            };

            _dataStore.ScheduleMessage(message);

            var info = new ActiveRoutineInfo
            {
                RoutineId = routineRecord.Id
            };

            return(Task.FromResult(info));
        }
Beispiel #8
0
        public IDisposable Enter(TransitionDescriptor transitionDescriptor)
        {
            var context = new TransitionContext
            {
                TransitionDescriptor = transitionDescriptor
            };

            var monitor = _transitionMonitorFactory.Create(context);

            var scopeData = new ScopeData
            {
                Context = context,
                Monitor = monitor
            };

            _currentScope.Value = scopeData;

            return(new DisposeTarget(this, scopeData));
        }
Beispiel #9
0
        public async Task <InvokeRoutineResult> RunAsync(MethodInvocationData data, ICommunicatorMessage message)
        {
            var serviceReference = _serviceResolver.Resolve(data.Service);
            var methodReference  = _methodResolver.Resolve(serviceReference.Definition, data.Method);

            var behaviorSettings = _communicationSettingsProvider.GetMethodSettings(methodReference.Definition);

            //-------------------------------------------------------------------------------
            // MESSGE DE-DUPLICATION
            //-------------------------------------------------------------------------------

            bool needsDeduplication = behaviorSettings.Deduplicate;

            if (methodReference.Definition.IsQuery)
            {
                // NOTE: you can run queries using message passing (non-volatile),
                // or the volatile method uses a callback instead of re-trying.
                if (message.CommunicatorTraits.HasFlag(CommunicationTraits.Volatile) && data.Continuation == null)
                {
                    needsDeduplication = false;
                }
            }
            if (needsDeduplication &&
                !message.CommunicatorTraits.HasFlag(CommunicationTraits.MessageDeduplication) &&
                (message.IsRetry != false || string.IsNullOrEmpty(message.RequestId)))
            {
                // TODO: if has message de-dup'er, check if a dedup
                // return new InvokeRoutineResult { Outcome = InvocationOutcome.Deduplicated };
            }

            //-------------------------------------------------------------------------------
            // UNIT OF WORK
            //-------------------------------------------------------------------------------

            // TODO: Unit of Work - check if there is cached/stored data
            if (message.IsRetry != false)
            {
                // TODO: If has entities in transitions and they have been already committed,
                // skip method transition and re-try sending commands and events.
            }

            //-------------------------------------------------------------------------------
            // DELEGATION TO RESILIENT COMMUNICATOR
            //-------------------------------------------------------------------------------

            IMessageHandle messageHandle = null;

            if (behaviorSettings.Persistent && message.CommunicatorTraits.HasFlag(CommunicationTraits.Volatile))
            {
                // TODO: check if can poll for result! (think about continuation and sync invocation)

                var preferredCommunicator = _communicatorProvider.GetCommunicator(data.Service, data.Method);
                if (message.CommunicatorType != preferredCommunicator.Type &&
                    !preferredCommunicator.Traits.HasFlag(CommunicationTraits.Volatile))
                {
                    var resultValueType = methodReference.Definition.MethodInfo.ReturnType;
                    if (resultValueType != typeof(void))
                    {
                        resultValueType = TaskAccessor.GetTaskResultType(resultValueType);
                        if (resultValueType == TaskAccessor.VoidTaskResultType)
                        {
                            resultValueType = typeof(void);
                        }
                    }

                    var preferences = new InvocationPreferences
                    {
                        LockMessage = behaviorSettings.RunInPlace &&
                                      preferredCommunicator.Traits.HasFlag(CommunicationTraits.MessageLockOnPublish),
                        ResultValueType = resultValueType
                    };

                    var invocationResult = await preferredCommunicator.InvokeAsync(data, preferences);

                    if (invocationResult.Outcome == InvocationOutcome.Complete && !string.IsNullOrEmpty(data.IntentId))
                    {
                        _routineCompletionSink.OnRoutineCompleted(data.Service, data.Method, data.IntentId, invocationResult.Result);
                    }

                    if (invocationResult.Outcome == InvocationOutcome.Scheduled && invocationResult.MessageHandle != null)
                    {
                        // NOTE: will run synchronously below
                        messageHandle = invocationResult.MessageHandle;
                    }
                    else
                    {
                        return(invocationResult);
                    }
                }
            }

            //-------------------------------------------------------------------------------
            // RUN METHOD TRANSITION
            //-------------------------------------------------------------------------------

            try
            {
                var adapter = new TransitionCarrier(data, _valueContainerCopier, message);
                var transitionDescriptor = new TransitionDescriptor {
                    Type = TransitionType.InvokeRoutine
                };
                var result = await RunRoutineAsync(adapter, transitionDescriptor, default);

                if (result.Outcome == InvocationOutcome.Complete && !string.IsNullOrEmpty(data.IntentId))
                {
                    _routineCompletionSink.OnRoutineCompleted(data.Service, data.Method, data.IntentId, result.Result);
                }
                if (messageHandle != null)
                {
                    await messageHandle.Complete();
                }
                return(result);
            }
            catch
            {
                messageHandle?.ReleaseLock();
                throw;
            }
        }
Beispiel #10
0
        public async Task <ContinueRoutineResult> ContinueAsync(MethodContinuationData data, ICommunicatorMessage message)
        {
            var serviceReference = _serviceResolver.Resolve(data.Service);
            var methodReference  = _methodResolver.Resolve(serviceReference.Definition, data.Method);

            var behaviorSettings = _communicationSettingsProvider.GetMethodSettings(methodReference.Definition);

            //-------------------------------------------------------------------------------
            // MESSGE DE-DUPLICATION
            //-------------------------------------------------------------------------------

            if (behaviorSettings.Deduplicate &&
                !message.CommunicatorTraits.HasFlag(CommunicationTraits.MessageDeduplication) &&
                (message.IsRetry != false || string.IsNullOrEmpty(message.RequestId)))
            {
                // TODO: if has message de-dup'er, check if a dedup
                // return new InvokeRoutineResult { Outcome = InvocationOutcome.Deduplicated };
            }

            //-------------------------------------------------------------------------------
            // UNIT OF WORK
            //-------------------------------------------------------------------------------

            // TODO: Unit of Work - check if there is cached/stored data
            if (message.IsRetry != false)
            {
                // TODO: If has entities in transitions and they have been already committed,
                // skip method transition and re-try sending commands and events.
            }

            //-------------------------------------------------------------------------------
            // DELEGATION TO RESILIENT COMMUNICATOR
            //-------------------------------------------------------------------------------

            IMessageHandle messageHandle = null;

            if (behaviorSettings.Persistent && message.CommunicatorTraits.HasFlag(CommunicationTraits.Volatile))
            {
                // TODO: check if can poll for result! (think about continuation and sync invocation)

                var preferredCommunicator = _communicatorProvider.GetCommunicator(data.Service, data.Method);
                if (message.CommunicatorType != preferredCommunicator.Type &&
                    !preferredCommunicator.Traits.HasFlag(CommunicationTraits.Volatile))
                {
                    var preferences = new InvocationPreferences
                    {
                        LockMessage = behaviorSettings.RunInPlace &&
                                      preferredCommunicator.Traits.HasFlag(CommunicationTraits.MessageLockOnPublish)
                    };

                    var invocationResult = await preferredCommunicator.ContinueAsync(data, preferences);

                    if (!preferences.LockMessage || invocationResult.MessageHandle == null)
                    {
                        return(new ContinueRoutineResult {
                        });
                    }

                    // NOTE: will run synchronously below
                    messageHandle = invocationResult.MessageHandle;
                }
            }

            //-------------------------------------------------------------------------------
            // RUN METHOD TRANSITION
            //-------------------------------------------------------------------------------

            // A communication method may not support a scheduled message delivery.
            if (data.ContinueAt.HasValue && data.ContinueAt.Value > DateTimeOffset.Now)
            {
                var delay = data.ContinueAt.Value - DateTimeOffset.Now;
                if (delay > TimeSpan.Zero)
                {
                    await Task.Delay(delay);
                }
            }

            try
            {
@TryRun:
                var adapter = new TransitionCarrier(data, message);

                MethodExecutionState methodState = DecodeContinuationData(data.State);
                if (methodState == null)
                {
                    var stateStorage = _methodStateStorageProvider.GetStorage(data.Service, data.Method, returnNullIfNotFound: true);
                    if (stateStorage == null)
                    {
                        throw new InvalidOperationException($"Cannot resume method '{data.Service}'.{data.Method} due to absence of a persistence mechanism.");
                    }

                    // TODO: a method can be already completed or transitioned (duplicate messages?) - discard transition?
                    methodState = await stateStorage.ReadStateAsync(data.Service, data.Method, default);
                }

                adapter.SetMethodExecutionState(methodState, _valueContainerCopier);

                InvokeRoutineResult result;
                try
                {
                    var transitionDescriptor = new TransitionDescriptor {
                        Type = TransitionType.ContinueRoutine
                    };
                    result = await RunRoutineAsync(adapter, transitionDescriptor, default);
                }
                catch (ConcurrentTransitionException)
                {
                    goto TryRun;
                }

                if (result.Outcome == InvocationOutcome.Complete && !string.IsNullOrEmpty(data.Method.IntentId))
                {
                    _routineCompletionSink.OnRoutineCompleted(data.Service, data.Method, data.Method.IntentId, result.Result);
                }

                if (messageHandle != null)
                {
                    await messageHandle.Complete();
                }

                return(new ContinueRoutineResult
                {
                });
            }
            catch
            {
                messageHandle?.ReleaseLock();
                throw;
            }
        }
Beispiel #11
0
        private async void RunMessageInBackground(Message message)
        {
            if (message.DeliverAt.HasValue && message.DeliverAt > DateTime.UtcNow)
            {
                await Task.Delay(message.DeliverAt.Value - DateTime.UtcNow);
            }
            else
            {
                await Task.Yield();
            }

            var ct = CancellationToken.None;

            if (message.IsEvent)
            {
                var serviceId = Serializer.Deserialize <ServiceId>(message[nameof(ServiceId)]);
                var eventId   = Serializer.Deserialize <EventId>(message[nameof(EventId)]);
                var eventDesc = new EventDescriptor {
                    EventId = eventId, ServiceId = serviceId
                };
                var subscribers = DataStore.GetEventSubscribers(eventDesc);

                foreach (var subscriber in subscribers)
                {
                    var routineId = Interlocked.Increment(ref DataStore.RoutineCounter);

                    var routineRecord = new RoutineStateRecord
                    {
                        ETag       = DateTime.UtcNow.Ticks.ToString("X16"),
                        Id         = routineId.ToString(),
                        Completion = new TaskCompletionSource <string>()
                    };

                    lock (DataStore.Routines)
                    {
                        DataStore.Routines.Add(routineRecord.Id, routineRecord);
                    }

                    var transitionDescriptor = new TransitionDescriptor
                    {
                        Type = TransitionType.InvokeRoutine,
                        ETag = routineRecord.ETag
                    };

                    var routineDescriptor = new RoutineDescriptor
                    {
                        MethodId  = subscriber.MethodId,
                        IntentId  = _numericIdGenerator.NewId(),
                        RoutineId = routineRecord.Id,
                        ETag      = routineRecord.ETag
                    };

                    var invokeRoutineMessage = new Message
                    {
                        //["IntentId"] = _serializer.Serialize(intent.Id),
                        [nameof(TransitionDescriptor)] = Serializer.SerializeToString(transitionDescriptor),
                        [nameof(ServiceId)]            = Serializer.SerializeToString(subscriber.ServiceId),
                        [nameof(RoutineDescriptor)]    = Serializer.SerializeToString(routineDescriptor),
                        ["Parameters"] = message["Parameters"]
                    };

                    DataStore.ScheduleMessage(invokeRoutineMessage);
                }
            }
            else
            {
                for (; ;)
                {
                    var carrier = new TransitionCarrier(this, message);
                    carrier.Initialize();

                    //var transitionInfo = await data.GetTransitionDescriptorAsync(ct);
                    //if (transitionInfo.Type == TransitionType.InvokeRoutine ||
                    //    transitionInfo.Type == TransitionType.ContinueRoutine)
                    //{
                    //    var routineDescriptor = await data.GetRoutineDescriptorAsync(ct);

                    //    if (!string.IsNullOrEmpty(transitionInfo.ETag) &&
                    //        transitionInfo.ETag != routineDescriptor.ETag)
                    //    {
                    //        // Ignore - stale duplicate message
                    //        return;
                    //    }
                    //}

                    try
                    {
                        await _transitionRunner.RunAsync(carrier, ct);

                        break;
                    }
                    catch (ConcurrentRoutineExecutionException)
                    {
                        // re-try
                        continue;
                    }
                }
            }
        }
        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);
                    }
                }
Beispiel #13
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);
                    }
                }