Exemple #1
0
        private async Task <List <KeyValuePair <long, StateEntity> > > ReadStates()
        {
            var query       = AzureTableUtils.RangeQuery(this.partition, StateEntity.RK_MIN, StateEntity.RK_MAX);
            var results     = new List <KeyValuePair <long, StateEntity> >();
            var queryResult = table.QueryAsync <TableEntity>(query).ConfigureAwait(false);

            await foreach (var entity in queryResult)
            {
                var state = new StateEntity(entity);
                results.Add(new KeyValuePair <long, StateEntity>(state.SequenceId, state));
            }
            ;
            return(results);
        }
        public async Task <string> Persist(string expectedETag, string metadata, List <PendingTransactionState <TState> > statesToPrepare)
        {
            try
            {
                var batchOperation = new TableBatchOperation();

                this.key.ETag     = expectedETag;
                this.key.Metadata = metadata;
                if (string.IsNullOrEmpty(this.key.ETag))
                {
                    batchOperation.Insert(this.key);
                }
                else
                {
                    batchOperation.Replace(this.key);
                }

                // add new states
                List <Tuple <string, long> > stored    = this.states.Select(s => Tuple.Create(s.TransactionId, s.SequenceId)).ToList();
                List <StateEntity>           newStates = new List <StateEntity>();
                foreach (PendingTransactionState <TState> pendingState in statesToPrepare.Where(p => !stored.Contains(Tuple.Create(p.TransactionId, p.SequenceId))))
                {
                    var newState = StateEntity.Create(this.jsonSettings, this.partition, pendingState);
                    newStates.Add(newState);
                    batchOperation.Insert(newState);
                }

                if (batchOperation.Count > AzureTableConstants.MaxBatchSize)
                {
                    this.logger.LogError("Too many pending states. PendingStateCount {PendingStateCount}.", batchOperation.Count);
                    throw new InvalidOperationException($"Too many pending states. PendingStateCount {batchOperation.Count}");
                }

                await table.ExecuteBatchAsync(batchOperation).ConfigureAwait(false);

                this.states.AddRange(newStates);
                return(this.key.ETag);
            }
            catch (Exception ex)
            {
                this.logger.LogError("Transactional state persist failed {Exception}.", ex);
                throw;
            }
        }
Exemple #3
0
        public async Task <string> Store(string expectedETag, TransactionalStateMetaData metadata, List <PendingTransactionState <TState> > statesToPrepare, long?commitUpTo, long?abortAfter)
        {
            var keyETag = key.ETag.ToString();

            if ((!string.IsNullOrWhiteSpace(keyETag) || !string.IsNullOrWhiteSpace(expectedETag)) && keyETag != expectedETag)
            {
                throw new ArgumentException(nameof(expectedETag), "Etag does not match");
            }

            // assemble all storage operations into a single batch
            // these operations must commit in sequence, but not necessarily atomically
            // so we can split this up if needed
            var batchOperation = new BatchOperation(logger, key, table);

            // first, clean up aborted records
            if (abortAfter.HasValue && states.Count != 0)
            {
                while (states.Count > 0 && states[states.Count - 1].Key > abortAfter)
                {
                    var entity = states[states.Count - 1].Value;
                    await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Delete, entity.Entity, entity.ETag)).ConfigureAwait(false);

                    key.ETag = batchOperation.KeyETag;
                    states.RemoveAt(states.Count - 1);

                    if (logger.IsEnabled(LogLevel.Trace))
                    {
                        logger.LogTrace($"{partition}.{entity.RowKey} Delete {entity.TransactionId}");
                    }
                }
            }

            // second, persist non-obsolete prepare records
            var obsoleteBefore = commitUpTo.HasValue ? commitUpTo.Value : key.CommittedSequenceId;

            if (statesToPrepare != null)
            {
                foreach (var s in statesToPrepare)
                {
                    if (s.SequenceId >= obsoleteBefore)
                    {
                        if (FindState(s.SequenceId, out var pos))
                        {
                            // overwrite with new pending state
                            StateEntity existing = states[pos].Value;
                            existing.TransactionId        = s.TransactionId;
                            existing.TransactionTimestamp = s.TimeStamp;
                            existing.TransactionManager   = JsonConvert.SerializeObject(s.TransactionManager, this.jsonSettings);
                            existing.SetState(s.State, this.jsonSettings);
                            await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, existing.Entity, existing.ETag)).ConfigureAwait(false);

                            key.ETag = batchOperation.KeyETag;

                            if (logger.IsEnabled(LogLevel.Trace))
                            {
                                logger.LogTrace($"{partition}.{existing.RowKey} Update {existing.TransactionId}");
                            }
                        }
                        else
                        {
                            var entity = StateEntity.Create(this.jsonSettings, this.partition, s);
                            await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Add, entity.Entity)).ConfigureAwait(false);

                            key.ETag = batchOperation.KeyETag;
                            states.Insert(pos, new KeyValuePair <long, StateEntity>(s.SequenceId, entity));

                            if (logger.IsEnabled(LogLevel.Trace))
                            {
                                logger.LogTrace($"{partition}.{entity.RowKey} Insert {entity.TransactionId}");
                            }
                        }
                    }
                }
            }

            // third, persist metadata and commit position
            key.Metadata = JsonConvert.SerializeObject(metadata, this.jsonSettings);
            if (commitUpTo.HasValue && commitUpTo.Value > key.CommittedSequenceId)
            {
                key.CommittedSequenceId = commitUpTo.Value;
            }
            if (string.IsNullOrEmpty(this.key.ETag.ToString()))
            {
                await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Add, key)).ConfigureAwait(false);

                key.ETag = batchOperation.KeyETag;

                if (logger.IsEnabled(LogLevel.Trace))
                {
                    logger.LogTrace($"{partition}.{KeyEntity.RK} Insert. v{this.key.CommittedSequenceId}, {metadata.CommitRecords.Count}c");
                }
            }
            else
            {
                await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, key, key.ETag)).ConfigureAwait(false);

                key.ETag = batchOperation.KeyETag;

                if (logger.IsEnabled(LogLevel.Trace))
                {
                    logger.LogTrace($"{partition}.{KeyEntity.RK} Update. v{this.key.CommittedSequenceId}, {metadata.CommitRecords.Count}c");
                }
            }

            // fourth, remove obsolete records
            if (states.Count > 0 && states[0].Key < obsoleteBefore)
            {
                FindState(obsoleteBefore, out var pos);
                for (int i = 0; i < pos; i++)
                {
                    await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Delete, states[i].Value.Entity, states[i].Value.ETag)).ConfigureAwait(false);

                    key.ETag = batchOperation.KeyETag;

                    if (logger.IsEnabled(LogLevel.Trace))
                    {
                        logger.LogTrace($"{partition}.{states[i].Value.RowKey} Delete {states[i].Value.TransactionId}");
                    }
                }
                states.RemoveRange(0, pos);
            }

            await batchOperation.Flush().ConfigureAwait(false);

            if (logger.IsEnabled(LogLevel.Debug))
            {
                logger.LogDebug($"{partition} Stored v{this.key.CommittedSequenceId} eTag={key.ETag}");
            }

            return(key.ETag.ToString());
        }
        private TState FindState(string transactionId)
        {
            StateEntity entity = this.states.FirstOrDefault(s => s.TransactionId == transactionId);

            return(entity?.GetState <TState>(this.jsonSettings));
        }