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); } } }
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}'."); } }
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); } } }
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); } }
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); } }
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 ||