// 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 doSet(Ref ref, Object val) { if (!info.running()) throw retryex; if (commutes.containsKey(ref)) throw new IllegalStateException("Can't set after commute"); if (!sets.contains(ref)) { sets.add(ref); lock(ref); } vals.put(ref, val); return val; }
void doEnsure(Ref ref) { if (!info.running()) throw retryex; if (ensures.contains(ref)) return; ref.lock.readLock().lock(); // someone completed a write after our snapshot if (ref.tvals != null && ref.tvals.point > readPoint) { ref.lock.readLock().unlock(); throw retryex; } Info refinfo = ref.tinfo; // writer exists if (refinfo != null && refinfo.running()) { ref.lock.readLock().unlock(); if (refinfo != info) // not us, ensure is doomed { blockAndBail(refinfo); } } else ensures.add(ref); }
Object doGet(Ref ref) { if (!info.running()) throw retryex; if (vals.containsKey(ref)) return vals.get(ref); try { ref.lock.readLock().lock(); if (ref.tvals == null) throw new IllegalStateException(ref.toString() + " is unbound."); Ref.TVal ver = ref.tvals; do { if (ver.point <= readPoint) return ver.val; } while ((ver = ver.prior) != ref.tvals); } finally { ref.lock.readLock().unlock(); } // no version of val precedes the read point ref.faults.incrementAndGet(); throw retryex; }
Object doCommute(Ref ref, IFn fn, ISeq args) { if (!info.running()) throw retryex; if (!vals.containsKey(ref)) { Object val = null; try { ref.lock.readLock().lock(); val = ref.tvals == null ? null : ref.tvals.val; } finally { ref.lock.readLock().unlock(); } vals.put(ref, val); } ArrayList<CFn> fns = commutes.get(ref); if (fns == null) commutes.put(ref, fns = new ArrayList<CFn>()); fns.add(new CFn(fn, args)); Object ret = fn.applyTo(RT.cons(vals.get(ref), args)); vals.put(ref, ret); return ret; }
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; }