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