public Task <string> WriteStateAsync( ServiceId serviceId, PersistedMethodId methodId, MethodExecutionState state) { var serializedState = _serializer.SerializeToString(state.MethodState); var serializedContinuation = state.Continuation != null?_serializer.SerializeToString(state.Continuation) : null; var serializedFlowContext = state.FlowContext?.Count > 0 ? _serializer.SerializeToString(state.FlowContext) : null; var expectedETag = methodId.ETag; var intentId = methodId.IntentId; lock (_entryMap) { if (!_entryMap.TryGetValue(intentId, out var entry)) { entry = new StorageEntry(); _entryMap.Add(intentId, entry); } else if (!string.IsNullOrEmpty(expectedETag) && entry.ETag != expectedETag) { throw new ETagMismatchException(expectedETag, entry.ETag); } entry.ETag = DateTimeOffset.UtcNow.Ticks.ToString(); entry["ServiceId"] = state.Service.Clone(); entry["MethodId"] = state.Method.Clone(); entry["Caller"] = state.Caller?.Clone(); entry["State"] = serializedState; entry["Continuation"] = serializedContinuation; entry["FlowContext"] = serializedFlowContext; if (state.ContinuationState != null) { entry["Continuation:Format"] = state.ContinuationState.Format; entry["Continuation:State"] = state.ContinuationState.State; } return(Task.FromResult(entry.ETag)); } }
public async Task <string> WriteStateAsync( ServiceId serviceId, PersistedMethodId methodId, MethodExecutionState state) { var fileName = GetStateFileName(serviceId, methodId); var filePath = Path.Combine(_stateDirectory, fileName); var data = _serializer.SerializeToBytes(state); EnsureDirectoryExists(_stateDirectory); @TryWrite: var tryCount = 10; try { using (var fileStream = new FileStream( filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, FileBufferSize, FileOptions.Asynchronous | FileOptions.WriteThrough)) { var etag = TryGetETag(filePath); if (fileStream.Length > 0 && !string.IsNullOrEmpty(methodId.ETag) && methodId.ETag != etag) { throw new ETagMismatchException(methodId.ETag, etag); } await fileStream.WriteAsync(data, 0, data.Length); fileStream.SetLength(fileStream.Position); return(etag); } } catch (IOException) when(tryCount > 0) { await Task.Yield(); tryCount--; goto @TryWrite; } }
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; } }
public void SetMethodExecutionState(MethodExecutionState methodExecutionState, IValueContainerCopier valueContainerCopier) { _methodExecutionState = methodExecutionState; Caller = _methodExecutionState.Caller; _valueContainerCopier = valueContainerCopier; }
public async Task <string> WriteStateAsync(ServiceId serviceId, PersistedMethodId methodId, MethodExecutionState state) { var tableName = GetTableQualifiedName(serviceId, methodId); var key = new StorageRecord { service = serviceId.Name, method = methodId.Name, intent_id = methodId.IntentId }; var record = new StorageRecord { etag = DateTimeOffset.UtcNow.Ticks, status = (int)Statuses.Paused, updated_at = DateTimeOffset.Now, caller_service = state.Caller?.Service?.Name, caller_proxy = state.Caller?.Service?.Proxy, caller_method = state.Caller?.Method?.Name, caller_event = state.Caller?.Event?.Name, caller_intent_id = state.Caller?.IntentId, format = _serializer.Format, execution_state = _serializer.SerializeToBytes(state), }; var query = new StringBuilder("UPDATE ").Append(tableName); query.Append(" SET "); WriteValues(query, record, delimiter: ", "); query.Append(" WHERE "); WriteValues(query, key, delimiter: " AND "); if (!string.IsNullOrEmpty(methodId.ETag)) { query.Append(" IF etag = ").AppendCqlText(methodId.ETag); } var result = await ExecuteQueryAsync(serviceId, methodId, query.ToString()); if (result != null) { var row = result.FirstOrDefault(); if (row != null && !row.GetValue <bool>("[applied]")) { var actualETag = row.GetValueOrDefault <long>("etag"); throw new ETagMismatchException(methodId.ETag, actualETag.ToString()); } } return(record.etag.ToString()); }