public virtual async Task WrongEtags()
        {
            var stateStorage = await this.stateStorageFactory();

            // load first time
            var loadresponse = await stateStorage.Load();

            // store with wrong e-tag, must fail
            try
            {
                var etag1 = await stateStorage.Store("wrong-etag", loadresponse.Metadata, emptyPendingStates, null, null);

                throw new Exception("storage did not catch e-tag mismatch");
            }
            catch (Exception) { }

            // load again
            loadresponse = await stateStorage.Load();

            loadresponse.Should().NotBeNull();
            loadresponse.Metadata.TimeStamp.Should().Be(default(DateTime));
            loadresponse.Metadata.CommitRecords.Should().BeEmpty();
            loadresponse.ETag.Should().BeNull();
            loadresponse.CommittedSequenceId.Should().Be(0);
            loadresponse.PendingStates.Should().BeEmpty();

            // update timestamp in metadata, then write back with correct e-tag
            var now      = DateTime.UtcNow;
            var cr       = MakeCommitRecords(2, 2);
            var metadata = new TransactionalStateMetaData()
            {
                TimeStamp = now, CommitRecords = cr
            };
            var etag2 = await stateStorage.Store(null, metadata, emptyPendingStates, null, null);

            // update timestamp in metadata, then write back with wrong e-tag, must fail
            try
            {
                var now2      = DateTime.UtcNow;
                var metadata2 = new TransactionalStateMetaData()
                {
                    TimeStamp = now2, CommitRecords = MakeCommitRecords(3, 3)
                };
                await stateStorage.Store(null, metadata, emptyPendingStates, null, null);

                throw new Exception("storage did not catch e-tag mismatch");
            }
            catch (Exception) { }

            // load again, check content
            loadresponse = await stateStorage.Load();

            loadresponse.Should().NotBeNull();
            loadresponse.Metadata.Should().NotBeNull();
            loadresponse.Metadata.TimeStamp.Should().Be(now);
            loadresponse.Metadata.CommitRecords.Count.Should().Be(cr.Count);
            loadresponse.ETag.Should().Be(etag2);
            loadresponse.CommittedSequenceId.Should().Be(0);
            loadresponse.PendingStates.Should().BeEmpty();
        }
Ejemplo n.º 2
0
        public void JsonConcertCanSerializeMetaData()
        {
            ITransactionTestGrain testGrain = this.RandomTestGrain(TransactionTestConstants.SingleStateTransactionalGrain);
            GrainReference        reference = (GrainReference)testGrain;
            var metaData = new TransactionalStateMetaData();

            metaData.TimeStamp = DateTime.UtcNow;
            metaData.CommitRecords.Add(Guid.NewGuid(), new CommitRecord()
            {
                Timestamp         = DateTime.UtcNow,
                WriteParticipants = new List <ParticipantId>()
                {
                    new ParticipantId("bob", reference, ParticipantId.Role.Resource | ParticipantId.Role.Manager)
                }
            });
            JsonSerializerSettings serializerSettings = TransactionalStateFactory.GetJsonSerializerSettings(
                this.fixture.Client.ServiceProvider.GetService <ITypeResolver>(),
                this.grainFactory);
            //should be able to serialize it
            string jsonMetaData = JsonConvert.SerializeObject(metaData, serializerSettings);

            TransactionalStateMetaData deseriliazedMetaData = JsonConvert.DeserializeObject <TransactionalStateMetaData>(jsonMetaData, serializerSettings);

            Assert.Equal(metaData.TimeStamp, deseriliazedMetaData.TimeStamp);
        }
Ejemplo n.º 3
0
 public StorageBatch(TransactionalStateMetaData metaData, string etag, long confirmUpTo, long cancelAbove)
 {
     this.MetaData         = metaData ?? throw new ArgumentNullException(nameof(metaData));
     this.ETag             = etag;
     this.confirmUpTo      = confirmUpTo;
     this.cancelAbove      = cancelAbove;
     this.cancelAboveStart = cancelAbove;
     this.followUpActions  = new List <Action>();
     this.storeConditions  = new List <Func <Task <bool> > >();
     this.prepares         = new SortedDictionary <long, PendingTransactionState <TState> >();
 }
        public async Task <string> Store(string expectedETag, TransactionalStateMetaData metadata,
                                         List <PendingTransactionState <TState> > statesToPrepare, long?commitUpTo,
                                         long?abortAfter)
        {
            if (abortAfter.HasValue && _states.Any())
            {
                while (_states.Count > 0 && _states[_states.Count - 1].SequenceId > abortAfter)
                {
                    var entity = _states[_states.Count - 1];
                    await RemoveAbortedState(entity).ConfigureAwait(false);

                    _states.RemoveAt(_states.Count - 1);
                }
            }

            if (statesToPrepare != null)
            {
                foreach (var s in statesToPrepare)
                {
                    ITransactionStateEntity <TState> existingState = null;
                    if (FindState(s.SequenceId, out var pos))
                    {
                        existingState = _states[pos];
                    }

                    var persistedState = await PersistState(s, commitUpTo, existingState).ConfigureAwait(false);

                    if (existingState == null)
                    {
                        _states.Insert(pos, persistedState);
                    }
                    else
                    {
                        _states[pos] = persistedState;
                    }
                }
            }

            Metadata = await PersistMetadata(metadata, commitUpTo ?? Metadata.CommittedSequenceId)
                       .ConfigureAwait(false);
            await StoreFinalize(commitUpTo).ConfigureAwait(false);

            return(Metadata.ETag);
        }
        public virtual async Task StoreWithoutChanges()
        {
            var stateStorage = await this.stateStorageFactory();

            // load first time
            var loadresponse = await stateStorage.Load();

            // store without any changes
            var etag1 = await stateStorage.Store(loadresponse.ETag, loadresponse.Metadata, emptyPendingStates, null, null);

            // load again
            loadresponse = await stateStorage.Load();

            loadresponse.Should().NotBeNull();
            loadresponse.Metadata.Should().NotBeNull();
            loadresponse.Metadata.TimeStamp.Should().Be(default(DateTime));
            loadresponse.Metadata.CommitRecords.Should().BeEmpty();
            loadresponse.ETag.Should().Be(etag1);
            loadresponse.CommittedSequenceId.Should().Be(0);
            loadresponse.PendingStates.Should().BeEmpty();

            // update metadata, then write back
            var now      = DateTime.UtcNow;
            var cr       = MakeCommitRecords(2, 2);
            var metadata = new TransactionalStateMetaData()
            {
                TimeStamp = now, CommitRecords = cr
            };
            var etag2 = await stateStorage.Store(etag1, metadata, emptyPendingStates, null, null);

            // load again, check content
            loadresponse = await stateStorage.Load();

            loadresponse.Should().NotBeNull();
            loadresponse.Metadata.Should().NotBeNull();
            loadresponse.Metadata.TimeStamp.Should().Be(now);
            loadresponse.Metadata.CommitRecords.Count.Should().Be(cr.Count);
            loadresponse.ETag.Should().Be(etag2);
            loadresponse.CommittedSequenceId.Should().Be(0);
            loadresponse.PendingStates.Should().BeEmpty();
        }
Ejemplo n.º 6
0
        public async Task <string> Store(

            string expectedETag,
            TransactionalStateMetaData metadata,

            // a list of transactions to prepare.
            List <PendingTransactionState <TState> > statesToPrepare,

            // if non-null, commit all pending transaction up to and including this sequence number.
            long?commitUpTo,

            // if non-null, abort all pending transactions with sequence numbers strictly larger than this one.
            long?abortAfter
            )
        {
            faultInjector.BeforeStore();
            var result = await this.stateStorage.Store(expectedETag, metadata, statesToPrepare, commitUpTo, abortAfter);

            faultInjector.AfterStore();
            return(result);
        }
Ejemplo n.º 7
0
        protected override Task <ITransactionMetadataEntity> PersistMetadata(TransactionalStateMetaData value,
                                                                             long commitSequenceId) => ExecuteQuery <ITransactionMetadataEntity>(async db =>
        {
            var tableName = _options.MetadataTableName;

            var newEtag         = Guid.NewGuid().ToString();
            var serializedValue = JsonConvert.SerializeObject(value, _jsonSettings);

            if (Metadata.ETag == null)
            {
                await db.Query(tableName)
                .AsInsert(new[] { "state_id", "committed_sequence_id", "etag", "value" },
                          new object[]
                {
                    _stateId, commitSequenceId, newEtag, serializedValue
                }).FirstOrDefaultAsync().ConfigureAwait(false);
            }
            else
            {
                var rowsUpdated = await db.Query(tableName).Where("state_id", _stateId).Where("etag", Metadata.ETag)
                                  .UpdateAsync(new
                {
                    committed_sequence_id = commitSequenceId,
                    etag  = newEtag,
                    value = serializedValue
                }).ConfigureAwait(false);

                if (rowsUpdated != 1)
                {
                    throw new InvalidOperationException("Could not update metadata. Possible concurrency issue");
                }
            }

            return(new TransactionMetadata
            {
                Value = value,
                ETag = newEtag,
                CommittedSequenceId = commitSequenceId
            });
        });
Ejemplo n.º 8
0
        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;
            }
        }
Ejemplo n.º 9
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());
        }
Ejemplo n.º 10
0
        public async Task <string> Store(string expectedETag, TransactionalStateMetaData metadata, List <PendingTransactionState <TState> > statesToPrepare, long?commitUpTo, long?abortAfter)
        {
            if (this.StateStorage.Etag != expectedETag)
            {
                throw new ArgumentException(nameof(expectedETag), "Etag does not match");
            }
            stateStorage.State.Metadata = metadata;

            var pendinglist = stateStorage.State.PendingStates;

            // abort
            if (abortAfter.HasValue && pendinglist.Count != 0)
            {
                var pos = pendinglist.FindIndex(t => t.SequenceId > abortAfter.Value);
                if (pos != -1)
                {
                    pendinglist.RemoveRange(pos, pendinglist.Count - pos);
                }
            }

            // prepare
            if (statesToPrepare?.Count > 0)
            {
                foreach (var p in statesToPrepare)
                {
                    var pos = pendinglist.FindIndex(t => t.SequenceId >= p.SequenceId);
                    if (pos == -1)
                    {
                        pendinglist.Add(p); //append
                    }
                    else if (pendinglist[pos].SequenceId == p.SequenceId)
                    {
                        pendinglist[pos] = p;  //replace
                    }
                    else
                    {
                        pendinglist.Insert(pos, p); //insert
                    }
                }
            }

            // commit
            if (commitUpTo.HasValue && commitUpTo.Value > stateStorage.State.CommittedSequenceId)
            {
                var pos = pendinglist.FindIndex(t => t.SequenceId == commitUpTo.Value);
                if (pos != -1)
                {
                    var committedState = pendinglist[pos];
                    stateStorage.State.CommittedSequenceId = committedState.SequenceId;
                    stateStorage.State.CommittedState      = committedState.State;
                    pendinglist.RemoveRange(0, pos + 1);
                }
                else
                {
                    throw new InvalidOperationException($"Transactional state corrupted. Missing prepare record (SequenceId={commitUpTo.Value}) for committed transaction.");
                }
            }

            await stateStorage.WriteStateAsync();

            return(stateStorage.Etag);
        }
Ejemplo n.º 11
0
        public async Task <string> Store(string expectedETag, TransactionalStateMetaData metadata,
                                         List <PendingTransactionState <TState> > statesToPrepare, long?commitUpTo,
                                         long?abortAfter)
        {
            if (_metadata.ETag != expectedETag)
            {
                throw new ArgumentException(nameof(expectedETag), "Etag does not match");
            }

            var stateUpdater = new StateUpdater(_dbExecuter, _stateId, _options.StateTableName, _jsonSettings);

            // first, clean up aborted records
            if (abortAfter.HasValue && _states.Count != 0)
            {
                await stateUpdater.DeleteStates(afterSequenceId : abortAfter);

                while (_states.Count > 0 && _states[_states.Count - 1].SequenceId > abortAfter)
                {
                    _states.RemoveAt(_states.Count - 1);
                }
            }

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

            if (statesToPrepare != null && statesToPrepare.Count > 0)
            {
                foreach (var s in statesToPrepare)
                {
                    if (s.SequenceId >= obsoleteBefore)
                    {
                        var newState = new TransactionStateEntity
                        {
                            SequenceId         = s.SequenceId,
                            TransactionId      = s.TransactionId,
                            Timestamp          = s.TimeStamp,
                            TransactionManager = s.TransactionManager,
                            Value = s.State
                        };

                        if (FindState(s.SequenceId, out var pos))
                        {
                            // overwrite with new pending state
                            _states[pos] = newState;
                            await stateUpdater.UpdateState(newState).ConfigureAwait(false);
                        }
                        else
                        {
                            await stateUpdater.InsertState(newState).ConfigureAwait(false);

                            _states.Insert(pos, newState);
                        }

                        _logger.LogTrace($"{_stateId}.{newState.SequenceId} Update {newState.TransactionId}");
                    }
                }
            }

            await stateUpdater.EnsureInsertBufferFlushed();

            // third, persist metadata and commit position
            _metadata = await PersistMetadata(metadata, commitUpTo ?? _metadata.CommittedSequenceId);

            // fourth, remove obsolete records
            if (_states.Count > 0 && _states[0].SequenceId < obsoleteBefore)
            {
                FindState(obsoleteBefore, out var pos);
                await stateUpdater.DeleteStates(beforeSequenceId : obsoleteBefore);

                _states.RemoveRange(0, pos);
            }

            return(_metadata.ETag);
        }
 protected abstract Task <ITransactionMetadataEntity> PersistMetadata(TransactionalStateMetaData value,
                                                                      long commitSequenceId);
Ejemplo n.º 13
0
 public Task <string> Store(string expectedETag, TransactionalStateMetaData metadata, List <PendingTransactionState <TState> > statesToPrepare, long?commitUpTo, long?abortAfter)
 {
     throw new NotImplementedException();
 }