private Etag AddToTransactionState(string key,
                                           Etag etag,
                                           TransactionInformation transactionInformation,
                                           Etag committedEtag,
                                           DocumentInTransactionData item)
        {
            try
            {
                var state = transactionStates.GetOrAdd(transactionInformation.Id, id => new TransactionState());
                lock (state)
                {
                    state.LastSeen = new Reference <DateTime>
                    {
                        Value = SystemTime.UtcNow
                    };
                    state.Timeout = transactionInformation.Timeout;

                    var currentTxVal = state.Changes.LastOrDefault(x => string.Equals(x.Key, key, StringComparison.InvariantCultureIgnoreCase));
                    if (currentTxVal != null)
                    {
                        EnsureValidEtag(key, etag, committedEtag, currentTxVal);
                        state.Changes.Remove(currentTxVal);
                    }
                    var result = changedInTransaction.AddOrUpdate(key, s =>
                    {
                        EnsureValidEtag(key, etag, committedEtag, currentTxVal);

                        return(new ChangedDoc
                        {
                            transactionId = transactionInformation.Id,
                            committedEtag = committedEtag,
                            currentEtag = item.Etag
                        });
                    }, (_, existing) =>
                    {
                        if (existing.transactionId == transactionInformation.Id)
                        {
                            EnsureValidEtag(key, etag, committedEtag, currentTxVal);
                            existing.currentEtag = item.Etag;
                            return(existing);
                        }

                        throw new ConcurrencyException("Document " + key + " is being modified by another transaction: " + existing);
                    });

                    state.Changes.Add(item);

                    return(result.currentEtag);
                }
            }
            catch (Exception)
            {
                Rollback(transactionInformation.Id);
                throw;
            }
        }
        protected HashSet <string> RunOperationsInTransaction(string id, out List <DocumentInTransactionData> changes)
        {
            changes = null;
            TransactionState value;

            if (transactionStates.TryGetValue(id, out value) == false)
            {
                return(null);                // no transaction, cannot do anything to this
            }
            changes = value.Changes;
            lock (value)
            {
                value.LastSeen = new Reference <DateTime>
                {
                    Value = SystemTime.UtcNow
                };
                currentlyCommittingTransaction.Value = id;
                try
                {
                    var documentIdsToTouch = new HashSet <string>();
                    foreach (var change in value.Changes)
                    {
                        var doc = new DocumentInTransactionData
                        {
                            Metadata     = change.Metadata == null ? null : (RavenJObject)change.Metadata.CreateSnapshot(),
                            Data         = change.Data == null ? null : (RavenJObject)change.Data.CreateSnapshot(),
                            Delete       = change.Delete,
                            Etag         = change.Etag,
                            LastModified = change.LastModified,
                            Key          = change.Key
                        };

                        log.Debug("Prepare of txId {0}: {1} {2}", id, doc.Delete ? "DEL" : "PUT", doc.Key);

                        // doc.Etag - represent the _modified_ document etag, and we already
                        // checked etags on previous PUT/DELETE, so we don't pass it here
                        if (doc.Delete)
                        {
                            DatabaseDelete(doc.Key, null /* etag might have been changed by a touch */, null);
                            documentIdsToTouch.RemoveWhere(x => x.Equals(doc.Key));
                        }
                        else
                        {
                            DatabasePut(doc.Key, null /* etag might have been changed by a touch */, doc.Data, doc.Metadata, null);
                            documentIdsToTouch.Add(doc.Key);
                        }
                    }
                    return(documentIdsToTouch);
                }
                finally
                {
                    currentlyCommittingTransaction.Value = null;
                }
            }
        }
Exemple #3
0
        public void CompleteTransaction(Guid txId, Action <DocumentInTransactionData> perDocumentModified)
        {
            Api.JetSetCurrentIndex(session, Transactions, "by_tx_id");
            Api.MakeKey(session, Transactions, txId, MakeKeyGrbit.NewKey);
            if (Api.TrySeek(session, Transactions, SeekGrbit.SeekEQ))
            {
                Api.JetDelete(session, Transactions);
            }

            Api.JetSetCurrentIndex(session, DocumentsModifiedByTransactions, "by_tx");
            Api.MakeKey(session, DocumentsModifiedByTransactions, txId, MakeKeyGrbit.NewKey);
            if (Api.TrySeek(session, DocumentsModifiedByTransactions, SeekGrbit.SeekEQ) == false)
            {
                return;
            }
            Api.MakeKey(session, DocumentsModifiedByTransactions, txId, MakeKeyGrbit.NewKey);
            Api.JetSetIndexRange(session, DocumentsModifiedByTransactions, SetIndexRangeGrbit.RangeInclusive | SetIndexRangeGrbit.RangeUpperLimit);

            do
            {
                // esent index ranges are approximate, and we need to check them ourselves as well
                if (Api.RetrieveColumnAsGuid(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["locked_by_transaction"]) != txId)
                {
                    continue;
                }
                var data     = Api.RetrieveColumn(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["data"]);
                var metadata = Api.RetrieveColumn(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["metadata"]);
                var key      = Api.RetrieveColumnAsString(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["key"], Encoding.Unicode);

                if (data != null && metadata != null)
                {
                    var metadataAsJson = metadata.ToJObject();
                    data = documentCodecs.Aggregate(data, (bytes, codec) => codec.Decode(key, metadataAsJson, bytes));
                }

                var documentInTransactionData = new DocumentInTransactionData
                {
                    Data     = data,
                    Delete   = Api.RetrieveColumnAsBoolean(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["delete_document"]).Value,
                    Etag     = new Guid(Api.RetrieveColumn(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["etag"])),
                    Key      = key,
                    Metadata = metadata,
                };
                Api.JetDelete(session, DocumentsModifiedByTransactions);
                perDocumentModified(documentInTransactionData);
            } while (Api.TryMoveNext(session, DocumentsModifiedByTransactions));
        }
Exemple #4
0
        protected void RunOperationsInTransaction(string id)
        {
            TransactionState value;

            if (transactionStates.TryGetValue(id, out value) == false)
            {
                return;         // no transaction, cannot do anything to this
            }
            lock (value)
            {
                currentlyCommittingTransaction.Value = id;
                try
                {
                    foreach (var change in value.changes)
                    {
                        var doc = new DocumentInTransactionData
                        {
                            Metadata      = change.Metadata == null ? null : (RavenJObject)change.Metadata.CreateSnapshot(),
                            Data          = change.Data == null ? null : (RavenJObject)change.Data.CreateSnapshot(),
                            Delete        = change.Delete,
                            Etag          = change.Etag,
                            CommittedEtag = change.CommittedEtag,
                            LastModified  = change.LastModified,
                            Key           = change.Key
                        };

                        log.Debug("Commit of txId {0}: {1} {2}", id, doc.Delete ? "DEL" : "PUT", doc.Key);
                        // doc.Etag - represent the _modified_ document etag, and we already
                        // checked etags on previous PUT/DELETE, so we don't pass it here
                        if (doc.Delete)
                        {
                            databaseDelete(doc.Key, doc.CommittedEtag, null);
                        }
                        else
                        {
                            databasePut(doc.Key, doc.CommittedEtag, doc.Data, doc.Metadata, null);
                        }
                    }
                }
                finally
                {
                    currentlyCommittingTransaction.Value = null;
                }
            }
        }
        private static void EnsureValidEtag(string key, Etag etag, Etag committedEtag, DocumentInTransactionData currentTxVal)
        {
            if (etag == null)
            {
                return;
            }
            if (currentTxVal != null && currentTxVal.Delete)
            {
                if (etag != Etag.Empty)
                {
                    throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag (delete)");
                }
                return;
            }

            if (currentTxVal != null)
            {
                if (currentTxVal.Etag != etag)
                {
                    throw new ConcurrencyException("Transaction operation attempted on : " + key +
                                                   " using a non current etag");
                }
                return;
            }

            if (etag != committedEtag)
            {
                throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
            }
        }
        private Etag AddToTransactionState(string key,
                                           Etag etag,
                                           TransactionInformation transactionInformation,
                                           Etag committedEtag,
                                           DocumentInTransactionData item)
        {
            try
            {
                TransactionState state;

                if (transactionStates.TryGetValue(transactionInformation.Id, out state) == false)
                {
                    lock (modifyTransactionStates)
                    {
                        if (transactionStates.TryGetValue(transactionInformation.Id, out state) == false) // check it once again, after we retrieved the lock - could be added while we waited for the lock
                        {
                            state             = new TransactionState();
                            transactionStates = transactionStates.Add(transactionInformation.Id, state);
                        }
                    }
                }

                lock (state)
                {
                    state.LastSeen = new Reference <DateTime>
                    {
                        Value = SystemTime.UtcNow
                    };
                    state.Timeout = transactionInformation.Timeout;

                    var currentTxVal = state.Changes.LastOrDefault(x => string.Equals(x.Key, key, StringComparison.InvariantCultureIgnoreCase));
                    if (currentTxVal != null)
                    {
                        EnsureValidEtag(key, etag, committedEtag, currentTxVal);
                        state.Changes.Remove(currentTxVal);
                    }

                    ChangedDoc result;

                    lock (modifyChangedInTransaction)
                    {
                        if (changedInTransaction.TryGetValue(key, out result))
                        {
                            if (result.transactionId != transactionInformation.Id)
                            {
                                throw new ConcurrencyException("Document " + key + " is being modified by another transaction: " + result);
                            }

                            EnsureValidEtag(key, etag, committedEtag, currentTxVal);
                            result.currentEtag = item.Etag;
                        }
                        else
                        {
                            EnsureValidEtag(key, etag, committedEtag, currentTxVal);

                            result = new ChangedDoc
                            {
                                transactionId = transactionInformation.Id,
                                committedEtag = committedEtag,
                                currentEtag   = item.Etag
                            };

                            changedInTransaction = changedInTransaction.Add(key, result);
                        }
                    }

                    state.Changes.Add(item);

                    return(result.currentEtag);
                }
            }
            catch (Exception)
            {
                Rollback(transactionInformation.Id);
                throw;
            }
        }
        private Etag AddToTransactionState(string key,
                                           Etag etag,
                                           TransactionInformation transactionInformation,
                                           Etag committedEtag,
                                           DocumentInTransactionData item)
        {
            try
            {
                var state = transactionStates.GetOrAdd(transactionInformation.Id, id => new TransactionState());
                lock (state)
                {
                    state.lastSeen = new Reference <DateTime>
                    {
                        Value = SystemTime.UtcNow + transactionInformation.Timeout
                    };

                    var currentTxVal = state.changes.LastOrDefault(x => string.Equals(x.Key, key, StringComparison.InvariantCultureIgnoreCase));
                    if (currentTxVal != null)
                    {
                        if (etag != null && currentTxVal.Etag != etag)
                        {
                            throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
                        }
                        state.changes.Remove(currentTxVal);
                    }
                    var result = changedInTransaction.AddOrUpdate(key, s =>
                    {
                        if (etag != null && etag != committedEtag)
                        {
                            throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
                        }

                        return(new ChangedDoc
                        {
                            transactionId = transactionInformation.Id,
                            committedEtag = committedEtag,
                            currentEtag = item.Etag
                        });
                    }, (_, existing) =>
                    {
                        if (existing.transactionId == transactionInformation.Id)
                        {
                            if (etag != null && etag != existing.currentEtag)
                            {
                                throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
                            }

                            return(existing);
                        }

                        TransactionState transactionState;
                        if (transactionStates.TryGetValue(existing.transactionId, out transactionState) == false ||
                            SystemTime.UtcNow > transactionState.lastSeen.Value)
                        {
                            Rollback(existing.transactionId);

                            if (etag != null && etag != committedEtag)
                            {
                                throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
                            }

                            return(new ChangedDoc
                            {
                                transactionId = transactionInformation.Id,
                                committedEtag = committedEtag,
                                currentEtag = item.Etag
                            });
                        }

                        throw new ConcurrencyException("Document " + key + " is being modified by another transaction: " + existing);
                    });


                    state.changes.Add(item);

                    return(result.currentEtag);
                }
            }
            catch (Exception)
            {
                Rollback(transactionInformation.Id);
                throw;
            }
        }
Exemple #8
0
		public void CompleteTransaction(Guid txId, Action<DocumentInTransactionData> perDocumentModified)
		{
			Api.JetSetCurrentIndex(session, Transactions, "by_tx_id");
			Api.MakeKey(session, Transactions, txId, MakeKeyGrbit.NewKey);
			if (Api.TrySeek(session, Transactions, SeekGrbit.SeekEQ))
				Api.JetDelete(session, Transactions);

			Api.JetSetCurrentIndex(session, DocumentsModifiedByTransactions, "by_tx");
			Api.MakeKey(session, DocumentsModifiedByTransactions, txId, MakeKeyGrbit.NewKey);
			if (Api.TrySeek(session, DocumentsModifiedByTransactions, SeekGrbit.SeekEQ) == false)
				return;
			Api.MakeKey(session, DocumentsModifiedByTransactions, txId, MakeKeyGrbit.NewKey);
			Api.JetSetIndexRange(session, DocumentsModifiedByTransactions, SetIndexRangeGrbit.RangeInclusive | SetIndexRangeGrbit.RangeUpperLimit);

			do
			{
				// esent index ranges are approximate, and we need to check them ourselves as well
				if (Api.RetrieveColumnAsGuid(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["locked_by_transaction"]) != txId)
					continue;
				var documentInTransactionData = new DocumentInTransactionData
				{
					Data = Api.RetrieveColumn(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["data"]),
					Delete = Api.RetrieveColumnAsBoolean(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["delete_document"]).Value,
					Etag = new Guid(Api.RetrieveColumn(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["etag"])),
					Key = Api.RetrieveColumnAsString(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["key"], Encoding.Unicode),
					Metadata = Api.RetrieveColumn(session, DocumentsModifiedByTransactions, tableColumnsCache.DocumentsModifiedByTransactionsColumns["metadata"]),
				};
				Api.JetDelete(session, DocumentsModifiedByTransactions);
				perDocumentModified(documentInTransactionData);
			} while (Api.TryMoveNext(session, DocumentsModifiedByTransactions));
		}