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