예제 #1
0
        private async Task <MongoDbTransaction> GetLastTransactionInternal()
        {
            // look for potential last transaction
            var res = await TransactionCollection.Find(x => true)
                      .SortByDescending(x => x.Timestamp)
                      .FirstOrDefaultAsync();

            if (res == null)
            {
                return(null);
            }

            // look if a pending transaction
            var resp = await PendingTransactionCollection.Find(x => true).SortByDescending(x => x.Timestamp).FirstOrDefaultAsync();

            if (resp == null) // no pending transaction
            {
                // return last transaction
                return(res);
            }
            else
            {
                // else return last transaction no younger than pending transaction
                res = await TransactionCollection.Find(x => x.Timestamp < resp.Timestamp)
                      .SortByDescending(x => x.Timestamp)
                      .FirstOrDefaultAsync();

                return(res);
            }
        }
예제 #2
0
        public async Task RollbackAllPendingTransactions(DateTime limit)
        {
            var res = await PendingTransactionCollection.Find(x => x.LockTimestamp < limit).ToListAsync();

            foreach (var t in res)
            {
                await RollbackTransaction(t.TransactionHash);
            }
        }
예제 #3
0
        private async Task RollbackTransaction(byte[] hash)
        {
            // Rollback is idempotent && reentrant : may be call twice even at the same time
            try
            {
#if DEBUG
                Logger.LogDebug($"Rollbacking transaction {new ByteString(hash)}");
#endif
                // get affected records
                var trn = await PendingTransactionCollection.Find(x => x.TransactionHash.Equals(hash)).SingleOrDefaultAsync();

                if (trn != null)
                {
                    // revert records values & version
                    foreach (var r in trn.InitialRecords)
                    {
                        await RecordCollection.FindOneAndUpdateAsync(
                            x => x.Key.Equals(r.Key) && x.TransactionLock.Equals(trn.LockToken),
                            Builders <MongoDbRecord> .Update.Set(x => x.Value, r.Value).Set(x => x.Version, r.Version).Unset(x => x.TransactionLock)
                            );
                    }

                    foreach (var r in trn.AddedRecords)
                    {
                        await RecordCollection.FindOneAndDeleteAsync(
                            x => x.Key.Equals(r) && x.TransactionLock.Equals(trn.LockToken)
                            );
                    }

                    // remove transaction
                    await TransactionCollection.DeleteOneAsync(x => x.TransactionHash.Equals(hash));

                    await RecordCollection.UpdateOneAsync(
                        x => x.TransactionLock == trn.LockToken,
                        Builders <MongoDbRecord> .Update
                        .Unset(x => x.TransactionLock)
                        );

                    // remove pending transaction
                    await PendingTransactionCollection.DeleteOneAsync(x => x.TransactionHash.Equals(hash));

                    Logger.LogInformation($"Transaction {new ByteString(hash)} rollbacked");
                }
            }
            catch (Exception ex)
            {
                var msg = "Error rollbacking transaction : " + new ByteString(hash).ToString();
                Logger.LogCritical(msg, ex);
                throw new Exception(msg, ex);
            }
        }
예제 #4
0
        public async Task AddTransactions(IEnumerable <ByteString> transactions)
        {
            List <byte[]> transactionHashes = new List <byte[]>();
            List <Record> lockedRecords     = new List <Record>();

            byte[] lockToken = Guid.NewGuid().ToByteArray();
            try {
                foreach (ByteString rawTransaction in transactions)
                {
                    byte[]        rawTransactionBuffer = rawTransaction.ToByteArray();
                    Transaction   transaction          = MessageSerializer.DeserializeTransaction(rawTransaction);
                    byte[]        transactionHash      = MessageSerializer.ComputeHash(rawTransactionBuffer);
                    byte[]        mutationHash         = MessageSerializer.ComputeHash(transaction.Mutation.ToByteArray());
                    Mutation      mutation             = MessageSerializer.DeserializeMutation(transaction.Mutation);
                    List <byte[]> records = new List <byte[]>();

#if DEBUG
                    Logger.LogDebug($"Add transaction {new ByteString(transactionHash)} token {new ByteString(lockToken)}");
#endif
                    transactionHashes.Add(transactionHash);

                    // add pending transaction
                    var ptr = new MongoDbPendingTransaction
                    {
                        MutationHash    = mutationHash,
                        TransactionHash = transactionHash,
                        RawData         = rawTransactionBuffer,
                        LockTimestamp   = DateTime.UtcNow,
                        InitialRecords  = new List <MongoDbRecord>(),
                        AddedRecords    = new List <byte[]>(),
                        LockToken       = lockToken
                    };
                    await PendingTransactionCollection.InsertOneAsync(ptr);

                    // lock records
                    foreach (var r in mutation.Records)
                    {
                        var previous = await LockRecord(lockToken, r);

                        if (previous != null)
                        {
                            ptr.InitialRecords.Add(previous);
                            lockedRecords.Add(r);
                        }
                        else
                        if (r.Value != null)
                        {
                            ptr.AddedRecords.Add(r.Key.ToByteArray());
                            lockedRecords.Add(r);
                        }
                    }

                    // save original records
                    await PendingTransactionCollection.UpdateOneAsync(
                        x => x.TransactionHash.Equals(transactionHash),
                        Builders <MongoDbPendingTransaction> .Update
                        .Set(x => x.InitialRecords, ptr.InitialRecords)
                        .Set(x => x.AddedRecords, ptr.AddedRecords)
                        );

                    // update records
                    foreach (var rec in mutation.Records)
                    {
                        MongoDbRecord r = BuildMongoDbRecord(rec);
                        if (r.Value == null)
                        {
                            if (r.Version.Length == 0) // No record expected
                            {
                                var res = await RecordCollection.CountAsync(x => x.Key.Equals(r.Key));

                                if (res != 0) // a record exists
                                {
                                    throw new ConcurrentMutationException(rec);
                                }
                            }
                            else
                            {   // specific version expected
                                var res = await RecordCollection.CountAsync(x => x.Key.Equals(r.Key) && x.Version.Equals(r.Version));

                                if (res != 1) // expected version not found
                                {
                                    throw new ConcurrentMutationException(rec);
                                }
                            }
                        }
                        else
                        {
                            if (r.Version.Length == 0)
                            {
                                r.Version         = mutationHash;
                                r.TransactionLock = lockToken;
                                try {
                                    await RecordCollection.InsertOneAsync(r);
                                } catch (MongoWriteException ex) when(ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
                                {
                                    throw new ConcurrentMutationException(rec);
                                }
                            }
                            else
                            {
                                var res = await RecordCollection.UpdateOneAsync(
                                    x => x.Key.Equals(r.Key) && x.Version.Equals(r.Version) && x.TransactionLock.Equals(lockToken),
                                    Builders <MongoDbRecord> .Update
                                    .Set(x => x.Value, r.Value)
                                    .Set(x => x.Version, mutationHash)
                                    );

                                if (res.MatchedCount != 1 || res.ModifiedCount != 1)
                                {
                                    throw new ConcurrentMutationException(rec);
                                }
                            }
                            records.Add(r.Key);
                        }
                    }

                    // add transaction
                    var tr = new MongoDbTransaction {
                        MutationHash    = mutationHash,
                        TransactionHash = transactionHash,
                        RawData         = rawTransactionBuffer,
                        Records         = records
                    };
                    await TransactionCollection.InsertOneAsync(tr);
                }

                // unlock records
                List <ByteString> l = new List <ByteString>();
                foreach (var r in lockedRecords)
                {
                    if (!l.Contains(r.Key))
                    {
                        await UnlockRecord(lockToken, r);

                        l.Add(r.Key);
                    }
                }

                // remove pending transaction
                foreach (var hash in transactionHashes)
                {
                    await PendingTransactionCollection.DeleteOneAsync(x => x.TransactionHash.Equals(hash));
                }

#if DEBUG
                Logger.LogDebug($"Transaction committed token {new ByteString(lockToken)}");
#endif
            }
            catch (Exception ex1)
            {
#if DEBUG
                Logger.LogDebug($"Error committing transaction batch {ex1.Message} token {new ByteString(lockToken)}");
#endif
                foreach (var hash in transactionHashes)
                {
#if DEBUG
                    Logger.LogDebug($"Rollbacking transaction 0 {new ByteString(hash)}");
#endif
                    try
                    {
                        await RollbackTransaction(hash);
                    } catch (Exception ex2)
                    {
                        throw new AggregateException(ex2, ex1);
                    }
                }
                throw;
            }
        }