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