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 ||
public InMemoryPersistenceMethod(IDefaultSerializerProvider defaultSerializerProvider) { _singleStorage = new InMemoryMethodStateStorage(defaultSerializerProvider.DefaultSerializer); }