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; } }
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)); }