/Raven.Database/Impl/InFlightTransactionalState.cs

https://github.com/jalchr/ravendb · C# · 305 lines · 270 code · 30 blank · 5 comment · 48 complexity · a58465b4a503fb587201d1dc00058034 MD5 · raw file

  1. // -----------------------------------------------------------------------
  2. // <copyright file="InFlightTransactionalState.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. // -----------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Concurrent;
  8. using System.Collections.Generic;
  9. using System.Threading;
  10. using Raven.Abstractions;
  11. using Raven.Abstractions.Data;
  12. using Raven.Abstractions.Exceptions;
  13. using Raven.Abstractions.Extensions;
  14. using Raven.Database.Storage;
  15. using Raven.Database.Util;
  16. using Raven.Json.Linq;
  17. using System.Linq;
  18. namespace Raven.Database.Impl
  19. {
  20. public class InFlightTransactionalState
  21. {
  22. private class TransactionState
  23. {
  24. public readonly List<DocumentInTransactionData> changes = new List<DocumentInTransactionData>();
  25. public volatile Reference<DateTime> lastSeen = new Reference<DateTime>();
  26. }
  27. private class ChangedDoc
  28. {
  29. public Guid transactionId;
  30. public Etag currentEtag;
  31. public Etag committedEtag;
  32. }
  33. readonly ConcurrentDictionary<string, ChangedDoc> changedInTransaction = new ConcurrentDictionary<string, ChangedDoc>();
  34. private readonly ConcurrentDictionary<Guid, TransactionState> transactionStates =
  35. new ConcurrentDictionary<Guid, TransactionState>();
  36. public Etag AddDocumentInTransaction(
  37. string key,
  38. Etag etag,
  39. RavenJObject data,
  40. RavenJObject metadata,
  41. TransactionInformation transactionInformation,
  42. Etag committedEtag,
  43. SequentialUuidGenerator uuidGenerator)
  44. {
  45. metadata.EnsureCannotBeChangeAndEnableSnapshotting();
  46. data.EnsureCannotBeChangeAndEnableSnapshotting();
  47. return AddToTransactionState(key, etag,
  48. transactionInformation,
  49. committedEtag,
  50. new DocumentInTransactionData
  51. {
  52. Metadata = metadata,
  53. Data = data,
  54. Delete = false,
  55. Key = key,
  56. LastModified = SystemTime.UtcNow,
  57. Etag = uuidGenerator.CreateSequentialUuid(UuidType.DocumentTransactions)
  58. });
  59. }
  60. public void DeleteDocumentInTransaction(
  61. TransactionInformation transactionInformation,
  62. string key,
  63. Etag etag,
  64. Etag committedEtag,
  65. SequentialUuidGenerator uuidGenerator)
  66. {
  67. AddToTransactionState(key, etag, transactionInformation, committedEtag, new DocumentInTransactionData
  68. {
  69. Delete = true,
  70. Key = key,
  71. LastModified = SystemTime.UtcNow
  72. });
  73. }
  74. public bool IsModified(string key)
  75. {
  76. var value = currentlyCommittingTransaction.Value;
  77. if (value == Guid.Empty)
  78. return changedInTransaction.ContainsKey(key);
  79. ChangedDoc doc;
  80. if (changedInTransaction.TryGetValue(key, out doc) == false)
  81. return false;
  82. return doc.transactionId != value;
  83. }
  84. public Func<TDocument, TDocument> GetNonAuthoritativeInformationBehavior<TDocument>(TransactionInformation tx, string key) where TDocument : class, IJsonDocumentMetadata, new()
  85. {
  86. ChangedDoc existing;
  87. if (changedInTransaction.TryGetValue(key, out existing) == false || (tx != null && tx.Id == existing.transactionId))
  88. return null;
  89. TransactionState value;
  90. if (transactionStates.TryGetValue(existing.transactionId, out value) == false ||
  91. SystemTime.UtcNow > value.lastSeen.Value)
  92. {
  93. Rollback(existing.transactionId);
  94. return null;
  95. }
  96. return document =>
  97. {
  98. if (document == null)
  99. {
  100. return new TDocument
  101. {
  102. Key = key,
  103. Metadata = new RavenJObject {{Constants.RavenDocumentDoesNotExists, true}},
  104. LastModified = DateTime.MinValue,
  105. NonAuthoritativeInformation = true,
  106. Etag = Etag.Empty
  107. };
  108. }
  109. document.NonAuthoritativeInformation = true;
  110. return document;
  111. };
  112. }
  113. public void Rollback(Guid id)
  114. {
  115. TransactionState value;
  116. if (transactionStates.TryGetValue(id, out value) == false)
  117. return;
  118. lock (value)
  119. {
  120. foreach (var change in value.changes)
  121. {
  122. ChangedDoc guid;
  123. changedInTransaction.TryRemove(change.Key, out guid);
  124. }
  125. }
  126. transactionStates.TryRemove(id, out value);
  127. }
  128. private readonly ThreadLocal<Guid> currentlyCommittingTransaction = new ThreadLocal<Guid>();
  129. public void Commit(Guid id, Action<DocumentInTransactionData> action)
  130. {
  131. TransactionState value;
  132. if (transactionStates.TryGetValue(id, out value) == false)
  133. throw new InvalidOperationException("There is no transaction with id: " + id);
  134. lock (value)
  135. {
  136. currentlyCommittingTransaction.Value = id;
  137. try
  138. {
  139. foreach (var change in value.changes)
  140. {
  141. action(new DocumentInTransactionData
  142. {
  143. Metadata = change.Metadata == null ? null : (RavenJObject)change.Metadata.CreateSnapshot(),
  144. Data = change.Data == null ? null : (RavenJObject)change.Data.CreateSnapshot(),
  145. Delete = change.Delete,
  146. Etag = change.Etag,
  147. LastModified = change.LastModified,
  148. Key = change.Key
  149. });
  150. }
  151. }
  152. finally
  153. {
  154. currentlyCommittingTransaction.Value = Guid.Empty;
  155. }
  156. }
  157. }
  158. private Etag AddToTransactionState(string key,
  159. Etag etag,
  160. TransactionInformation transactionInformation,
  161. Etag committedEtag,
  162. DocumentInTransactionData item)
  163. {
  164. try
  165. {
  166. var state = transactionStates.GetOrAdd(transactionInformation.Id, id => new TransactionState());
  167. lock (state)
  168. {
  169. state.lastSeen = new Reference<DateTime>
  170. {
  171. Value = SystemTime.UtcNow + transactionInformation.Timeout
  172. };
  173. var currentTxVal = state.changes.LastOrDefault(x => string.Equals(x.Key, key, StringComparison.InvariantCultureIgnoreCase));
  174. if (currentTxVal != null)
  175. {
  176. if (etag != null && currentTxVal.Etag != etag)
  177. throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
  178. state.changes.Remove(currentTxVal);
  179. }
  180. var result = changedInTransaction.AddOrUpdate(key, s =>
  181. {
  182. if (etag != null && etag != committedEtag)
  183. throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
  184. return new ChangedDoc
  185. {
  186. transactionId = transactionInformation.Id,
  187. committedEtag = committedEtag,
  188. currentEtag = item.Etag
  189. };
  190. }, (_, existing) =>
  191. {
  192. if (existing.transactionId == transactionInformation.Id)
  193. {
  194. if (etag != null && etag != existing.currentEtag)
  195. throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
  196. return existing;
  197. }
  198. TransactionState transactionState;
  199. if (transactionStates.TryGetValue(existing.transactionId, out transactionState) == false ||
  200. SystemTime.UtcNow > transactionState.lastSeen.Value)
  201. {
  202. Rollback(existing.transactionId);
  203. if (etag != null && etag != committedEtag)
  204. throw new ConcurrencyException("Transaction operation attempted on : " + key + " using a non current etag");
  205. return new ChangedDoc
  206. {
  207. transactionId = transactionInformation.Id,
  208. committedEtag = committedEtag,
  209. currentEtag = item.Etag
  210. };
  211. }
  212. throw new ConcurrencyException("Document " + key + " is being modified by another transaction: " + existing);
  213. });
  214. state.changes.Add(item);
  215. return result.currentEtag;
  216. }
  217. }
  218. catch (Exception)
  219. {
  220. Rollback(transactionInformation.Id);
  221. throw;
  222. }
  223. }
  224. public bool TryGet(string key, TransactionInformation transactionInformation, out JsonDocument document)
  225. {
  226. return TryGetInternal(key, transactionInformation, (theKey, change) => new JsonDocument
  227. {
  228. DataAsJson = (RavenJObject)change.Data.CreateSnapshot(),
  229. Metadata = (RavenJObject)change.Metadata.CreateSnapshot(),
  230. Key = theKey,
  231. Etag = change.Etag,
  232. NonAuthoritativeInformation = false,
  233. LastModified = change.LastModified
  234. }, out document);
  235. }
  236. public bool TryGet(string key, TransactionInformation transactionInformation, out JsonDocumentMetadata document)
  237. {
  238. return TryGetInternal(key, transactionInformation, (theKey, change) => new JsonDocumentMetadata
  239. {
  240. Metadata = (RavenJObject)change.Metadata.CreateSnapshot(),
  241. Key = theKey,
  242. Etag = change.Etag,
  243. NonAuthoritativeInformation = false,
  244. LastModified = change.LastModified
  245. }, out document);
  246. }
  247. private bool TryGetInternal<T>(string key, TransactionInformation transactionInformation, Func<string, DocumentInTransactionData, T> createDoc, out T document)
  248. where T : class
  249. {
  250. TransactionState state;
  251. if (transactionStates.TryGetValue(transactionInformation.Id, out state) == false)
  252. {
  253. document = null;
  254. return false;
  255. }
  256. var change = state.changes.LastOrDefault(x => string.Equals(x.Key, key, StringComparison.InvariantCultureIgnoreCase));
  257. if (change == null)
  258. {
  259. document = null;
  260. return false;
  261. }
  262. if (change.Delete)
  263. {
  264. document = null;
  265. return true;
  266. }
  267. document = createDoc(key, change);
  268. return true;
  269. }
  270. public bool HasTransaction(Guid txId)
  271. {
  272. return transactionStates.ContainsKey(txId);
  273. }
  274. }
  275. }