// returns the most recent val
  Object lock(Ref ref) {
    // can't upgrade readLock, so release it
    releaseIfEnsured(ref);

    boolean unlocked = true;
    try {
      tryWriteLock(ref);
      unlocked = false;

      if (ref.tvals != null && ref.tvals.point > readPoint) throw retryex;
      Info refinfo = ref.tinfo;

      // write lock conflict
      if (refinfo != null && refinfo != info && refinfo.running()) {
        if (!barge(refinfo)) {
          ref.lock.writeLock().unlock();
          unlocked = true;
          return blockAndBail(refinfo);
        }
      }
      ref.tinfo = info;
      return ref.tvals == null ? null : ref.tvals.val;
    } finally {
      if (!unlocked) ref.lock.writeLock().unlock();
    }
  }
  Object run(Callable fn) throws Exception {
    boolean done = false;
    Object ret = null;
    ArrayList<Ref> locked = new ArrayList<Ref>();
    ArrayList<Notify> notify = new ArrayList<Notify>();

    for (int i = 0; !done && i < RETRY_LIMIT; i++) {
      try {
        getReadPoint();
        if (i == 0) {
          startPoint = readPoint;
          startTime = System.nanoTime();
        }
        info = new Info(RUNNING, startPoint);
        ret = fn.call();
        // make sure no one has killed us before this point, and can't from now on
        if (info.status.compareAndSet(RUNNING, COMMITTING)) {
          for (Map.Entry<Ref, ArrayList<CFn>> e : commutes.entrySet()) {
            Ref ref = e.getKey();
            if (sets.contains(ref)) continue;

            boolean wasEnsured = ensures.contains(ref);
            // can't upgrade readLock, so release it
            releaseIfEnsured(ref);
            tryWriteLock(ref);
            locked.add(ref);
            if (wasEnsured && ref.tvals != null && ref.tvals.point > readPoint) throw retryex;

            Info refinfo = ref.tinfo;
            if (refinfo != null && refinfo != info && refinfo.running()) {
              if (!barge(refinfo)) throw retryex;
            }
            Object val = ref.tvals == null ? null : ref.tvals.val;
            vals.put(ref, val);
            for (CFn f : e.getValue()) {
              vals.put(ref, f.fn.applyTo(RT.cons(vals.get(ref), f.args)));
            }
          }
          for (Ref ref : sets) {
            tryWriteLock(ref);
            locked.add(ref);
          }

          // validate and enqueue notifications
          for (Map.Entry<Ref, Object> e : vals.entrySet()) {
            Ref ref = e.getKey();
            ref.validate(ref.getValidator(), e.getValue());
          }

          // at this point, all values calced, all refs to be written locked
          // no more client code to be called
          long commitPoint = getCommitPoint();
          for (Map.Entry<Ref, Object> e : vals.entrySet()) {
            Ref ref = e.getKey();
            Object oldval = ref.tvals == null ? null : ref.tvals.val;
            Object newval = e.getValue();
            int hcount = ref.histCount();

            if (ref.tvals == null) {
              ref.tvals = new Ref.TVal(newval, commitPoint);
            } else if ((ref.faults.get() > 0 && hcount < ref.maxHistory)
                || hcount < ref.minHistory) {
              ref.tvals = new Ref.TVal(newval, commitPoint, ref.tvals);
              ref.faults.set(0);
            } else {
              ref.tvals = ref.tvals.next;
              ref.tvals.val = newval;
              ref.tvals.point = commitPoint;
            }
            if (ref.getWatches().count() > 0) notify.add(new Notify(ref, oldval, newval));
          }

          done = true;
          info.status.set(COMMITTED);
        }
      } catch (RetryEx retry) {
        // eat this so we retry rather than fall out
      } finally {
        for (int k = locked.size() - 1; k >= 0; --k) {
          locked.get(k).lock.writeLock().unlock();
        }
        locked.clear();
        for (Ref r : ensures) {
          r.lock.readLock().unlock();
        }
        ensures.clear();
        stop(done ? COMMITTED : RETRY);
        try {
          if (done) // re-dispatch out of transaction
          {
            for (Notify n : notify) {
              n.ref.notifyWatches(n.oldval, n.newval);
            }
            for (Agent.Action action : actions) {
              Agent.dispatchAction(action);
            }
          }
        } finally {
          notify.clear();
          actions.clear();
        }
      }
    }
    if (!done) throw Util.runtimeException("Transaction failed after reaching retry limit");
    return ret;
  }