/// <summary> /// Post a commute on a ref in this transaction. /// </summary> /// <param name="r">The ref.</param> /// <param name="fn">The commuting function.</param> /// <param name="args">Additional arguments to the function.</param> /// <returns>The computed value.</returns> internal object DoCommute(Ref r, IFn fn, ISeq args) { if (!_info.IsRunning) { throw _retryex; } if (!_vals.ContainsKey(r)) { object val = null; try { r.EnterReadLock(); val = r.TryGetVal(); } finally { r.ExitReadLock(); } _vals[r] = val; } List <CFn> fns; if (!_commutes.TryGetValue(r, out fns)) { _commutes[r] = fns = new List <CFn>(); } fns.Add(new CFn(fn, args)); object ret = fn.applyTo(RT.cons(_vals[r], args)); _vals[r] = ret; return(ret); }
/// <summary> /// Lock a ref. /// </summary> /// <param name="r">The ref to lock.</param> /// <returns>The most recent value of the ref.</returns> object Lock(Ref r) { // can't upgrade read lock, so release it. ReleaseIfEnsured(r); bool unlocked = true; try { TryWriteLock(r); unlocked = false; if (r.CurrentValPoint() > _readPoint) { throw _retryex; } Info refinfo = r.TInfo; // write lock conflict if (refinfo != null && refinfo != _info && refinfo.IsRunning) { if (!Barge(refinfo)) { r.ExitWriteLock(); unlocked = true; return(BlockAndBail(refinfo)); } } r.TInfo = _info; return(r.TryGetVal()); } finally { if (!unlocked) { r.ExitWriteLock(); } } }
/// <summary> /// Start a transaction and invoke a function. /// </summary> /// <param name="fn">The function to invoke.</param> /// <returns>The value computed by the function.</returns> object Run(IFn fn) { // TODO: Define an overload called on ThreadStartDelegate or something equivalent. bool done = false; object ret = null; List <Ref> locked = new List <Ref>(); List <Notify> notify = new List <Notify>(); for (int i = 0; !done && i < RetryLimit; i++) { try { GetReadPoint(); if (i == 0) { _startPoint = _readPoint; _startTime = Environment.TickCount; } _info = new Info(RUNNING, _startPoint); ret = fn.invoke(); // make sure no one has killed us before this point, // and can't from now on if (_info.Status.compareAndSet(RUNNING, COMMITTING)) { foreach (KeyValuePair <Ref, List <CFn> > pair in _commutes) { Ref r = pair.Key; if (_sets.Contains(r)) { continue; } bool wasEnsured = _ensures.Contains(r); // can't upgrade read lock, so release ReleaseIfEnsured(r); TryWriteLock(r); locked.Add(r); if (wasEnsured && r.CurrentValPoint() > _readPoint) { throw _retryex; } Info refinfo = r.TInfo; if (refinfo != null && refinfo != _info && refinfo.IsRunning) { if (!Barge(refinfo)) { throw _retryex; } } object val = r.TryGetVal(); _vals[r] = val; foreach (CFn f in pair.Value) { _vals[r] = f.Fn.applyTo(RT.cons(_vals[r], f.Args)); } } foreach (Ref r in _sets) { TryWriteLock(r); locked.Add(r); } // validate and enqueue notifications foreach (KeyValuePair <Ref, object> pair in _vals) { Ref r = pair.Key; r.Validate(pair.Value); } // at this point, all values calced, all refs to be written locked // no more client code to be called int msecs = System.Environment.TickCount; long commitPoint = GetCommitPoint(); foreach (KeyValuePair <Ref, object> pair in _vals) { Ref r = pair.Key; object oldval = r.TryGetVal(); object newval = pair.Value; r.SetValue(newval, commitPoint, msecs); if (r.getWatches().count() > 0) { notify.Add(new Notify(r, oldval, newval)); } } done = true; _info.Status.set(COMMITTED); } } catch (RetryEx) { // eat this so we retry rather than fall out } catch (Exception ex) { if (ContainsNestedRetryEx(ex)) { // Wrapped exception, eat it. } else { throw; } } finally { for (int k = locked.Count - 1; k >= 0; --k) { locked[k].ExitWriteLock(); } locked.Clear(); foreach (Ref r in _ensures) { r.ExitReadLock(); } _ensures.Clear(); Stop(done ? COMMITTED : RETRY); try { if (done) // re-dispatch out of transaction { foreach (Notify n in notify) { n._ref.NotifyWatches(n._oldval, n._newval); } foreach (Agent.Action action in _actions) { Agent.DispatchAction(action); } } } finally { notify.Clear(); _actions.Clear(); } } } if (!done) { throw new InvalidOperationException("Transaction failed after reaching retry limit"); } return(ret); }