public async ValueTask Add(TableTransactionAction operation) { if (!BatchHasKey && operation.Entity.RowKey == key.RowKey && operation.Entity.PartitionKey == key.PartitionKey) { key = (KeyEntity)operation.Entity; keyIndex = batchOperation.Count; } batchOperation.Add(operation); if (batchOperation.Count == AzureTableConstants.MaxBatchSize - (BatchHasKey ? 0 : 1)) { // the key serves as a synchronizer, to prevent modification by multiple grains under edge conditions, // like duplicate activations or deployments.Every batch write needs to include the key, // even if the key values don't change. if (!BatchHasKey) { keyIndex = batchOperation.Count; if (string.IsNullOrEmpty(key.ETag.ToString())) { batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Add, key)); } else { batchOperation.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, key, key.ETag)); } } await Flush().ConfigureAwait(false); } }
public async Task <TransactionalStorageLoadResponse <TState> > Load() { try { Task <KeyEntity> keyTask = ReadKey(); Task <List <StateEntity> > statesTask = ReadStates(); this.key = await keyTask.ConfigureAwait(false); this.states = await statesTask.ConfigureAwait(false); if (string.IsNullOrEmpty(this.key.ETag)) { return(new TransactionalStorageLoadResponse <TState>()); } TState commitedState = (!string.IsNullOrEmpty(this.key.CommittedTransactionId)) ? FindState(this.key.CommittedTransactionId) : new TState(); if (commitedState == null) { this.logger.LogCritical("Transactional state non-recoverable error. Commited state for transaction {TransactionId} not found.", this.key.CommittedTransactionId); throw new InvalidOperationException($"Transactional state non-recoverable error. Commited state for transaction {this.key.CommittedTransactionId} not found."); } var pendingStates = states.Select(s => new PendingTransactionState <TState>(s.TransactionId, s.SequenceId, s.GetState <TState>(this.jsonSettings))).ToList(); return(new TransactionalStorageLoadResponse <TState>(this.key.ETag, commitedState, this.key.Metadata, pendingStates)); } catch (Exception ex) { this.logger.LogError("Transactional state load failed {Exception}.", ex); throw; } }
public BatchOperation(ILogger logger, KeyEntity key, TableClient table) { this.batchOperation = new(); this.logger = logger; this.key = key; this.table = table; }
public BatchOperation(ILogger logger, KeyEntity key, CloudTable table) { this.batchOperation = new TableBatchOperation(); this.logger = logger; this.key = key; this.table = table; }
public async Task <TransactionalStorageLoadResponse <TState> > Load() { try { var keyTask = ReadKey(); var statesTask = ReadStates(); key = await keyTask.ConfigureAwait(false); states = await statesTask.ConfigureAwait(false); if (string.IsNullOrEmpty(key.ETag.ToString())) { if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug($"{partition} Loaded v0, fresh"); } // first time load return(new TransactionalStorageLoadResponse <TState>()); } else { TState committedState; if (this.key.CommittedSequenceId == 0) { committedState = new TState(); } else { if (!FindState(this.key.CommittedSequenceId, out var pos)) { var error = $"Storage state corrupted: no record for committed state v{this.key.CommittedSequenceId}"; logger.LogCritical($"{partition} {error}"); throw new InvalidOperationException(error); } committedState = states[pos].Value.GetState <TState>(this.jsonSettings); } var PrepareRecordsToRecover = new List <PendingTransactionState <TState> >(); for (int i = 0; i < states.Count; i++) { var kvp = states[i]; // pending states for already committed transactions can be ignored if (kvp.Key <= key.CommittedSequenceId) { continue; } // upon recovery, local non-committed transactions are considered aborted if (kvp.Value.TransactionManager == null) { break; } ParticipantId tm = JsonConvert.DeserializeObject <ParticipantId>(kvp.Value.TransactionManager, this.jsonSettings); PrepareRecordsToRecover.Add(new PendingTransactionState <TState>() { SequenceId = kvp.Key, State = kvp.Value.GetState <TState>(this.jsonSettings), TimeStamp = kvp.Value.TransactionTimestamp, TransactionId = kvp.Value.TransactionId, TransactionManager = tm }); } // clear the state strings... no longer needed, ok to GC now for (int i = 0; i < states.Count; i++) { var entity = states[i].Value; entity.StateJson = null; } if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug($"{partition} Loaded v{this.key.CommittedSequenceId} rows={string.Join(",", states.Select(s => s.Key.ToString("x16")))}"); } TransactionalStateMetaData metadata = JsonConvert.DeserializeObject <TransactionalStateMetaData>(this.key.Metadata, this.jsonSettings); return(new TransactionalStorageLoadResponse <TState>(this.key.ETag.ToString(), committedState, this.key.CommittedSequenceId, metadata, PrepareRecordsToRecover)); } } catch (Exception ex) { this.logger.LogError("Transactional state load failed {Exception}.", ex); throw; } }