PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/System.Xronos/Language/Threading/Transaction.cs

https://bitbucket.org/stefanrusek/xronos
C# | 296 lines | 228 code | 37 blank | 31 comment | 36 complexity | 5b1d8d8aed1a185e13906954697fc176 MD5 | raw file
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) 2008 Stefan Rusek and Benjamin Pollack
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. *
  23. * ***************************************************************************/
  24. using System;
  25. using System.Collections.Generic;
  26. using System.Linq;
  27. using System.Text;
  28. using System.Threading;
  29. using System.Collections;
  30. namespace System.Xronos.Language.Threading
  31. {
  32. public enum TransactionState
  33. {
  34. Idle,
  35. Running,
  36. Commiting,
  37. Retry,
  38. Killed,
  39. Committed,
  40. }
  41. public class Transaction
  42. {
  43. static readonly int RetryLimit = 10000;
  44. static readonly TimeSpan OtherTransactionWaitTime = TimeSpan.FromMilliseconds(100);
  45. static readonly TimeSpan MinAgeToBarge = TimeSpan.FromMilliseconds(10);
  46. static readonly object RetryState = new object();
  47. [ThreadStatic]
  48. static Transaction _current;
  49. public static Transaction Current { get { return _current ?? (_current = new Transaction()); } }
  50. internal class RefState
  51. {
  52. public Ref Ref;
  53. public bool HasValue;
  54. public object Value;
  55. public List<Func<object, object>> Commutes;
  56. }
  57. DateTime startTime;
  58. public int StartPoint;
  59. public int ReadPoint;
  60. public int CommitPoint;
  61. int _state;
  62. IPersistentStack agentActions = null;
  63. object sync = new object();
  64. Thread thread = Thread.CurrentThread;
  65. public TransactionState State { get { return (TransactionState)_state; } }
  66. public bool IsRunning { get { return State == TransactionState.Running || State == TransactionState.Commiting; } }
  67. private Dictionary<Guid, RefState> refValues = new Dictionary<Guid, RefState>();
  68. internal bool TryGetRefValue(Guid id, out object result)
  69. {
  70. RefState state;
  71. bool rval;
  72. if (rval = refValues.TryGetValue(id, out state) && state.HasValue)
  73. result = state.Value;
  74. else
  75. result = default(object);
  76. return rval;
  77. }
  78. internal void SetValue(Guid id, Ref r, object value)
  79. {
  80. AssertNotIdle();
  81. RefState state;
  82. if (!refValues.TryGetValue(id, out state))
  83. {
  84. r.Enlist(this);
  85. refValues[id] = state = new RefState { Ref = r };
  86. }
  87. state.HasValue = true;
  88. state.Value = value;
  89. }
  90. internal void EnqueueCommute(Guid id, Ref r, object fn, ISequence args)
  91. {
  92. AssertNotIdle();
  93. RefState state;
  94. if (!refValues.TryGetValue(id, out state))
  95. refValues[id] = state = new RefState { Ref = r };
  96. Func<object, object> commute = old => FunctionHelper.Invoke<object>(fn, RT.Cons(old, args));
  97. Interlocked.CompareExchange(ref state.Commutes, new List<Func<object, object>>(), null);
  98. lock (state.Commutes)
  99. state.Commutes.Add(commute);
  100. state.Value = commute(state.HasValue ? state.Value : r.Value);
  101. state.HasValue = true;
  102. }
  103. private void AssertNotIdle()
  104. {
  105. if (State == TransactionState.Idle)
  106. throw new InvalidProgramException("No transaction running");
  107. }
  108. internal void Retry()
  109. {
  110. thread.Abort(RetryState);
  111. }
  112. internal void Abort()
  113. {
  114. Stop(TransactionState.Killed);
  115. thread.Abort();
  116. }
  117. internal static Transaction ResolveConflict(Action beforeStop, Transaction currentOwner, Transaction requestedOwner)
  118. {
  119. if (BargeIfNewer(currentOwner, requestedOwner))
  120. return requestedOwner;
  121. beforeStop();
  122. requestedOwner.Stop(TransactionState.Retry);
  123. lock (currentOwner.sync)
  124. Monitor.Wait(currentOwner.sync, OtherTransactionWaitTime);
  125. requestedOwner.Retry();
  126. return currentOwner;
  127. }
  128. private void Stop(TransactionState transactionState)
  129. {
  130. if (State != TransactionState.Idle)
  131. {
  132. lock (sync)
  133. {
  134. _state = (int)transactionState;
  135. Monitor.PulseAll(sync);
  136. }
  137. // right here all the waits that we just pulsed
  138. // will be able to read the new value of _state
  139. // when they are finished, then the code below
  140. // will execute.
  141. lock (sync)
  142. {
  143. _state = 0;
  144. refValues.Clear();
  145. }
  146. }
  147. }
  148. private static bool BargeIfNewer(Transaction current, Transaction requested)
  149. {
  150. if (DateTime.Now - requested.startTime < MinAgeToBarge || requested.StartPoint > current.StartPoint)
  151. return false;
  152. lock (current.sync)
  153. {
  154. var state = (TransactionState)Interlocked.CompareExchange(ref current._state, (int)TransactionState.Killed, (int)TransactionState.Running);
  155. if (state != TransactionState.Running)
  156. return false;
  157. Monitor.PulseAll(current.sync);
  158. return true;
  159. }
  160. }
  161. public static object Run(object action){
  162. if (Current.IsRunning)
  163. return FunctionHelper.Invoke<object>(action);
  164. else
  165. return Current.InternalRun(action);
  166. }
  167. private object InternalRun(object action)
  168. {
  169. object result = null;
  170. bool success = false;
  171. List<RefState> lockedRefs = new List<RefState>();
  172. for (int i = 0; i < RetryLimit && !success; i++)
  173. {
  174. try
  175. {
  176. ReadPoint = NewPoint();
  177. if (i == 0)
  178. {
  179. StartPoint = ReadPoint;
  180. startTime = DateTime.Now;
  181. }
  182. _state = (int)TransactionState.Running;
  183. refValues.Clear();
  184. result = Run(action);
  185. if ((int)TransactionState.Running != Interlocked.CompareExchange(ref _state, (int)TransactionState.Commiting, (int)TransactionState.Running))
  186. continue;
  187. success = Commit(lockedRefs);
  188. }
  189. catch (ThreadAbortException taex)
  190. {
  191. if (taex.ExceptionState != RetryState)
  192. throw;
  193. Thread.ResetAbort();
  194. }
  195. finally
  196. {
  197. foreach (var item in lockedRefs)
  198. item.Ref.Release();
  199. lockedRefs.Clear();
  200. Stop(success ? TransactionState.Committed : TransactionState.Retry);
  201. if (success && agentActions != null)
  202. foreach (IAgentItem item in (IEnumerable)agentActions)
  203. item.Enqueue();
  204. agentActions = null;
  205. }
  206. }
  207. _state = (int)TransactionState.Idle;
  208. if (!success)
  209. throw new InvalidOperationException("Transaction failed after reaching retry limit");
  210. return result;
  211. }
  212. private bool Commit(List<RefState> lockedRefs)
  213. {
  214. // lock all refs first
  215. List<RefState> refs = refValues.Values.ToList();
  216. foreach (var item in refs)
  217. {
  218. item.Ref.LockForCommit();
  219. lockedRefs.Add(item);
  220. }
  221. // apply commutes
  222. foreach (var item in refs)
  223. if (item.Commutes != null)
  224. ApplyCommutes(item);
  225. // validate new values
  226. foreach (var item in refs)
  227. {
  228. item.Ref.Validate(item.Value);
  229. }
  230. // All of the above could fail, but from here on out we are cool
  231. foreach (var item in refs)
  232. item.Ref.Commit(this, item.Value);
  233. _state = (int)TransactionState.Committed;
  234. return true;
  235. }
  236. private void ApplyCommutes(RefState item)
  237. {
  238. if (item.Ref.CurrentOwner != this && item.Ref.CurrentOwner.IsRunning)
  239. if (!BargeIfNewer(item.Ref.CurrentOwner, this))
  240. Retry();
  241. if (!item.HasValue)
  242. {
  243. item.Value = item.Ref.Value;
  244. item.HasValue = true;
  245. }
  246. foreach (var fn in item.Commutes)
  247. item.Value = fn(item.Value);
  248. }
  249. static int _lastPoint = 0;
  250. private static int NewPoint()
  251. {
  252. return Interlocked.Increment(ref _lastPoint);
  253. }
  254. internal void EnqueueAgent(IAgentItem item)
  255. {
  256. agentActions = (IPersistentStack)(agentActions ?? PersistentQueue.Empty).cons(item);
  257. }
  258. }
  259. }