Example #1
0
        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));
            }
        }
Example #2
0
        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;
            }
        }
Example #3
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;
            }
        }
Example #4
0
 public void SetMethodExecutionState(MethodExecutionState methodExecutionState, IValueContainerCopier valueContainerCopier)
 {
     _methodExecutionState = methodExecutionState;
     Caller = _methodExecutionState.Caller;
     _valueContainerCopier = valueContainerCopier;
 }
Example #5
0
        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());
        }