/System.Xronos/Language/Threading/Transaction.cs
C# | 296 lines | 228 code | 37 blank | 31 comment | 36 complexity | 5b1d8d8aed1a185e13906954697fc176 MD5 | raw file
- /* ****************************************************************************
- *
- * Copyright (c) 2008 Stefan Rusek and Benjamin Pollack
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * ***************************************************************************/
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading;
- using System.Collections;
-
- namespace System.Xronos.Language.Threading
- {
- public enum TransactionState
- {
- Idle,
- Running,
- Commiting,
- Retry,
- Killed,
- Committed,
- }
-
- public class Transaction
- {
- static readonly int RetryLimit = 10000;
- static readonly TimeSpan OtherTransactionWaitTime = TimeSpan.FromMilliseconds(100);
- static readonly TimeSpan MinAgeToBarge = TimeSpan.FromMilliseconds(10);
- static readonly object RetryState = new object();
-
- [ThreadStatic]
- static Transaction _current;
- public static Transaction Current { get { return _current ?? (_current = new Transaction()); } }
-
- internal class RefState
- {
- public Ref Ref;
- public bool HasValue;
- public object Value;
- public List<Func<object, object>> Commutes;
- }
-
- DateTime startTime;
- public int StartPoint;
- public int ReadPoint;
- public int CommitPoint;
- int _state;
- IPersistentStack agentActions = null;
- object sync = new object();
- Thread thread = Thread.CurrentThread;
-
- public TransactionState State { get { return (TransactionState)_state; } }
- public bool IsRunning { get { return State == TransactionState.Running || State == TransactionState.Commiting; } }
-
- private Dictionary<Guid, RefState> refValues = new Dictionary<Guid, RefState>();
-
- internal bool TryGetRefValue(Guid id, out object result)
- {
- RefState state;
- bool rval;
- if (rval = refValues.TryGetValue(id, out state) && state.HasValue)
- result = state.Value;
- else
- result = default(object);
- return rval;
- }
-
- internal void SetValue(Guid id, Ref r, object value)
- {
- AssertNotIdle();
-
- RefState state;
- if (!refValues.TryGetValue(id, out state))
- {
- r.Enlist(this);
- refValues[id] = state = new RefState { Ref = r };
- }
- state.HasValue = true;
- state.Value = value;
- }
-
- internal void EnqueueCommute(Guid id, Ref r, object fn, ISequence args)
- {
- AssertNotIdle();
-
- RefState state;
- if (!refValues.TryGetValue(id, out state))
- refValues[id] = state = new RefState { Ref = r };
-
- Func<object, object> commute = old => FunctionHelper.Invoke<object>(fn, RT.Cons(old, args));
-
- Interlocked.CompareExchange(ref state.Commutes, new List<Func<object, object>>(), null);
- lock (state.Commutes)
- state.Commutes.Add(commute);
-
- state.Value = commute(state.HasValue ? state.Value : r.Value);
- state.HasValue = true;
- }
-
- private void AssertNotIdle()
- {
- if (State == TransactionState.Idle)
- throw new InvalidProgramException("No transaction running");
- }
-
- internal void Retry()
- {
- thread.Abort(RetryState);
- }
-
- internal void Abort()
- {
- Stop(TransactionState.Killed);
- thread.Abort();
- }
-
- internal static Transaction ResolveConflict(Action beforeStop, Transaction currentOwner, Transaction requestedOwner)
- {
- if (BargeIfNewer(currentOwner, requestedOwner))
- return requestedOwner;
-
- beforeStop();
- requestedOwner.Stop(TransactionState.Retry);
-
- lock (currentOwner.sync)
- Monitor.Wait(currentOwner.sync, OtherTransactionWaitTime);
- requestedOwner.Retry();
-
- return currentOwner;
- }
-
- private void Stop(TransactionState transactionState)
- {
- if (State != TransactionState.Idle)
- {
- lock (sync)
- {
- _state = (int)transactionState;
- Monitor.PulseAll(sync);
- }
- // right here all the waits that we just pulsed
- // will be able to read the new value of _state
- // when they are finished, then the code below
- // will execute.
- lock (sync)
- {
- _state = 0;
- refValues.Clear();
- }
- }
- }
-
- private static bool BargeIfNewer(Transaction current, Transaction requested)
- {
- if (DateTime.Now - requested.startTime < MinAgeToBarge || requested.StartPoint > current.StartPoint)
- return false;
-
- lock (current.sync)
- {
- var state = (TransactionState)Interlocked.CompareExchange(ref current._state, (int)TransactionState.Killed, (int)TransactionState.Running);
- if (state != TransactionState.Running)
- return false;
-
- Monitor.PulseAll(current.sync);
- return true;
- }
- }
-
- public static object Run(object action){
- if (Current.IsRunning)
- return FunctionHelper.Invoke<object>(action);
- else
- return Current.InternalRun(action);
- }
-
- private object InternalRun(object action)
- {
- object result = null;
- bool success = false;
- List<RefState> lockedRefs = new List<RefState>();
- for (int i = 0; i < RetryLimit && !success; i++)
- {
- try
- {
- ReadPoint = NewPoint();
- if (i == 0)
- {
- StartPoint = ReadPoint;
- startTime = DateTime.Now;
- }
- _state = (int)TransactionState.Running;
- refValues.Clear();
- result = Run(action);
-
- if ((int)TransactionState.Running != Interlocked.CompareExchange(ref _state, (int)TransactionState.Commiting, (int)TransactionState.Running))
- continue;
-
- success = Commit(lockedRefs);
- }
- catch (ThreadAbortException taex)
- {
- if (taex.ExceptionState != RetryState)
- throw;
- Thread.ResetAbort();
- }
- finally
- {
- foreach (var item in lockedRefs)
- item.Ref.Release();
- lockedRefs.Clear();
- Stop(success ? TransactionState.Committed : TransactionState.Retry);
- if (success && agentActions != null)
- foreach (IAgentItem item in (IEnumerable)agentActions)
- item.Enqueue();
- agentActions = null;
- }
- }
- _state = (int)TransactionState.Idle;
- if (!success)
- throw new InvalidOperationException("Transaction failed after reaching retry limit");
- return result;
- }
-
- private bool Commit(List<RefState> lockedRefs)
- {
- // lock all refs first
- List<RefState> refs = refValues.Values.ToList();
- foreach (var item in refs)
- {
- item.Ref.LockForCommit();
- lockedRefs.Add(item);
- }
- // apply commutes
- foreach (var item in refs)
- if (item.Commutes != null)
- ApplyCommutes(item);
- // validate new values
- foreach (var item in refs)
- {
- item.Ref.Validate(item.Value);
- }
- // All of the above could fail, but from here on out we are cool
- foreach (var item in refs)
- item.Ref.Commit(this, item.Value);
-
- _state = (int)TransactionState.Committed;
- return true;
- }
-
- private void ApplyCommutes(RefState item)
- {
- if (item.Ref.CurrentOwner != this && item.Ref.CurrentOwner.IsRunning)
- if (!BargeIfNewer(item.Ref.CurrentOwner, this))
- Retry();
-
- if (!item.HasValue)
- {
- item.Value = item.Ref.Value;
- item.HasValue = true;
- }
-
- foreach (var fn in item.Commutes)
- item.Value = fn(item.Value);
- }
-
- static int _lastPoint = 0;
- private static int NewPoint()
- {
- return Interlocked.Increment(ref _lastPoint);
- }
-
- internal void EnqueueAgent(IAgentItem item)
- {
- agentActions = (IPersistentStack)(agentActions ?? PersistentQueue.Empty).cons(item);
- }
- }
- }