PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/light_reports/light_reports/lib/clojure_src/java/clojure/lang/LockingTransaction.java

https://github.com/berlinbrown/lightreports
Java | 583 lines | 351 code | 47 blank | 185 comment | 78 complexity | 92135a73dc3dc7c48082e020559b1638 MD5 | raw file
  1. /**
  2. * Copyright (c) Rich Hickey. All rights reserved.
  3. * The use and distribution terms for this software are covered by the
  4. * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
  5. * which can be found in the file epl-v10.html at the root of this distribution.
  6. * By using this software in any fashion, you are agreeing to be bound by
  7. * the terms of this license.
  8. * You must not remove this notice, or any other, from this software.
  9. **/
  10. /* rich Jul 26, 2007 */
  11. package clojure.lang;
  12. import java.util.*;
  13. import java.util.concurrent.atomic.AtomicInteger;
  14. import java.util.concurrent.atomic.AtomicLong;
  15. import java.util.concurrent.Callable;
  16. @SuppressWarnings({"SynchronizeOnNonFinalField"})
  17. public class LockingTransaction{
  18. public static final int RETRY_LIMIT = 10000;
  19. public static final int LOCK_WAIT_MSECS = 100;
  20. public static final long BARGE_WAIT_NANOS = 10 * 1000000;
  21. //public static int COMMUTE_RETRY_LIMIT = 10;
  22. static final int RUNNING = 0;
  23. static final int COMMITTING = 1;
  24. static final int RETRY = 2;
  25. static final int KILLED = 3;
  26. static final int COMMITTED = 4;
  27. final static ThreadLocal<LockingTransaction> transaction = new ThreadLocal<LockingTransaction>();
  28. static class RetryEx extends Error{
  29. }
  30. static class AbortException extends Exception{
  31. }
  32. public static class Info{
  33. final AtomicInteger status;
  34. final long startPoint;
  35. public Info(int status, long startPoint){
  36. this.status = new AtomicInteger(status);
  37. this.startPoint = startPoint;
  38. }
  39. public boolean running(){
  40. int s = status.get();
  41. return s == RUNNING || s == COMMITTING;
  42. }
  43. }
  44. static class CFn{
  45. final IFn fn;
  46. final ISeq args;
  47. public CFn(IFn fn, ISeq args){
  48. this.fn = fn;
  49. this.args = args;
  50. }
  51. }
  52. //total order on transactions
  53. //transactions will consume a point for init, for each retry, and on commit if writing
  54. final private static AtomicLong lastPoint = new AtomicLong();
  55. void getReadPoint(){
  56. readPoint = lastPoint.incrementAndGet();
  57. }
  58. long getCommitPoint(){
  59. return lastPoint.incrementAndGet();
  60. }
  61. void stop(int status){
  62. if(info != null)
  63. {
  64. synchronized(info)
  65. {
  66. info.status.set(status);
  67. info.notifyAll();
  68. }
  69. info = null;
  70. vals.clear();
  71. sets.clear();
  72. commutes.clear();
  73. //actions.clear();
  74. }
  75. }
  76. Info info;
  77. long readPoint;
  78. long startPoint;
  79. long startTime;
  80. final RetryEx retryex = new RetryEx();
  81. final ArrayList<Agent.Action> actions = new ArrayList<Agent.Action>();
  82. final HashMap<Ref, Object> vals = new HashMap<Ref, Object>();
  83. final HashSet<Ref> sets = new HashSet<Ref>();
  84. final TreeMap<Ref, ArrayList<CFn>> commutes = new TreeMap<Ref, ArrayList<CFn>>();
  85. //returns the most recent val
  86. Object lock(Ref ref){
  87. boolean unlocked = false;
  88. try
  89. {
  90. ref.lock.writeLock().lock();
  91. if(ref.tvals != null && ref.tvals.point > readPoint)
  92. throw retryex;
  93. Info refinfo = ref.tinfo;
  94. //write lock conflict
  95. if(refinfo != null && refinfo != info && refinfo.running())
  96. {
  97. if(!barge(refinfo))
  98. {
  99. ref.lock.writeLock().unlock();
  100. unlocked = true;
  101. //stop prior to blocking
  102. stop(RETRY);
  103. synchronized(refinfo)
  104. {
  105. if(refinfo.running())
  106. {
  107. try
  108. {
  109. refinfo.wait(LOCK_WAIT_MSECS);
  110. }
  111. catch(InterruptedException e)
  112. {
  113. }
  114. }
  115. }
  116. throw retryex;
  117. }
  118. }
  119. ref.tinfo = info;
  120. return ref.tvals == null ? null : ref.tvals.val;
  121. }
  122. finally
  123. {
  124. if(!unlocked)
  125. ref.lock.writeLock().unlock();
  126. }
  127. }
  128. void abort() throws AbortException{
  129. stop(KILLED);
  130. throw new AbortException();
  131. }
  132. private boolean bargeTimeElapsed(){
  133. return System.nanoTime() - startTime > BARGE_WAIT_NANOS;
  134. }
  135. private boolean barge(Info refinfo){
  136. boolean barged = false;
  137. //if this transaction is older
  138. // try to abort the other
  139. if(bargeTimeElapsed() && startPoint < refinfo.startPoint)
  140. {
  141. synchronized(refinfo)
  142. {
  143. barged = refinfo.status.compareAndSet(RUNNING, KILLED);
  144. if(barged)
  145. refinfo.notifyAll();
  146. }
  147. }
  148. return barged;
  149. }
  150. static LockingTransaction getEx(){
  151. LockingTransaction t = transaction.get();
  152. if(t == null || t.info == null)
  153. throw new IllegalStateException("No transaction running");
  154. return t;
  155. }
  156. static public boolean isRunning(){
  157. return getRunning() != null;
  158. }
  159. static LockingTransaction getRunning(){
  160. LockingTransaction t = transaction.get();
  161. if(t == null || t.info == null)
  162. return null;
  163. return t;
  164. }
  165. static public Object runInTransaction(Callable fn) throws Exception{
  166. LockingTransaction t = transaction.get();
  167. if(t == null)
  168. transaction.set(t = new LockingTransaction());
  169. if(t.info != null)
  170. return fn.call();
  171. return t.run(fn);
  172. }
  173. static class Notify{
  174. final public Ref ref;
  175. final public Object oldval;
  176. final public Object newval;
  177. Notify(Ref ref, Object oldval, Object newval){
  178. this.ref = ref;
  179. this.oldval = oldval;
  180. this.newval = newval;
  181. }
  182. }
  183. Object run(Callable fn) throws Exception{
  184. boolean done = false;
  185. Object ret = null;
  186. ArrayList<Ref> locked = new ArrayList<Ref>();
  187. ArrayList<Notify> notify = new ArrayList<Notify>();
  188. for(int i = 0; !done && i < RETRY_LIMIT; i++)
  189. {
  190. try
  191. {
  192. getReadPoint();
  193. if(i == 0)
  194. {
  195. startPoint = readPoint;
  196. startTime = System.nanoTime();
  197. }
  198. info = new Info(RUNNING, startPoint);
  199. ret = fn.call();
  200. //make sure no one has killed us before this point, and can't from now on
  201. if(info.status.compareAndSet(RUNNING, COMMITTING))
  202. {
  203. for(Map.Entry<Ref, ArrayList<CFn>> e : commutes.entrySet())
  204. {
  205. Ref ref = e.getKey();
  206. ref.lock.writeLock().lock();
  207. locked.add(ref);
  208. Info refinfo = ref.tinfo;
  209. if(refinfo != null && refinfo != info && refinfo.running())
  210. {
  211. if(!barge(refinfo))
  212. throw retryex;
  213. }
  214. Object val = ref.tvals == null ? null : ref.tvals.val;
  215. if(!sets.contains(ref))
  216. vals.put(ref, val);
  217. for(CFn f : e.getValue())
  218. {
  219. vals.put(ref, f.fn.applyTo(RT.cons(vals.get(ref), f.args)));
  220. }
  221. }
  222. for(Ref ref : sets)
  223. {
  224. if(!commutes.containsKey(ref))
  225. {
  226. ref.lock.writeLock().lock();
  227. locked.add(ref);
  228. }
  229. }
  230. //validate and enqueue notifications
  231. for(Map.Entry<Ref, Object> e : vals.entrySet())
  232. {
  233. Ref ref = e.getKey();
  234. ref.validate(ref.getValidator(), e.getValue());
  235. }
  236. //at this point, all values calced, all refs to be written locked
  237. //no more client code to be called
  238. long msecs = System.currentTimeMillis();
  239. long commitPoint = getCommitPoint();
  240. for(Map.Entry<Ref, Object> e : vals.entrySet())
  241. {
  242. Ref ref = e.getKey();
  243. Object oldval = ref.tvals == null ? null : ref.tvals.val;
  244. Object newval = e.getValue();
  245. if(ref.tvals == null)
  246. {
  247. ref.tvals = new Ref.TVal(newval, commitPoint, msecs);
  248. }
  249. else if(ref.faults.get() > 0)
  250. {
  251. ref.tvals = new Ref.TVal(newval, commitPoint, msecs, ref.tvals);
  252. ref.faults.set(0);
  253. }
  254. else
  255. {
  256. ref.tvals = ref.tvals.next;
  257. ref.tvals.val = newval;
  258. ref.tvals.point = commitPoint;
  259. ref.tvals.msecs = msecs;
  260. }
  261. if(ref.getWatches().count() > 0)
  262. notify.add(new Notify(ref, oldval, newval));
  263. }
  264. done = true;
  265. info.status.set(COMMITTED);
  266. }
  267. }
  268. catch(RetryEx retry)
  269. {
  270. //eat this so we retry rather than fall out
  271. }
  272. finally
  273. {
  274. for(int k = locked.size() - 1; k >= 0; --k)
  275. {
  276. locked.get(k).lock.writeLock().unlock();
  277. }
  278. locked.clear();
  279. stop(done ? COMMITTED : RETRY);
  280. try
  281. {
  282. if(done) //re-dispatch out of transaction
  283. {
  284. for(Notify n : notify)
  285. {
  286. n.ref.notifyWatches(n.oldval, n.newval);
  287. }
  288. for(Agent.Action action : actions)
  289. {
  290. Agent.dispatchAction(action);
  291. }
  292. }
  293. }
  294. finally
  295. {
  296. notify.clear();
  297. actions.clear();
  298. }
  299. }
  300. }
  301. if(!done)
  302. throw new Exception("Transaction failed after reaching retry limit");
  303. return ret;
  304. }
  305. public void enqueue(Agent.Action action){
  306. actions.add(action);
  307. }
  308. Object doGet(Ref ref){
  309. if(!info.running())
  310. throw retryex;
  311. if(vals.containsKey(ref))
  312. return vals.get(ref);
  313. try
  314. {
  315. ref.lock.readLock().lock();
  316. if(ref.tvals == null)
  317. throw new IllegalStateException(ref.toString() + " is unbound.");
  318. Ref.TVal ver = ref.tvals;
  319. do
  320. {
  321. if(ver.point <= readPoint)
  322. return ver.val;
  323. } while((ver = ver.prior) != ref.tvals);
  324. }
  325. finally
  326. {
  327. ref.lock.readLock().unlock();
  328. }
  329. //no version of val precedes the read point
  330. ref.faults.incrementAndGet();
  331. throw retryex;
  332. }
  333. Object doSet(Ref ref, Object val){
  334. if(!info.running())
  335. throw retryex;
  336. if(commutes.containsKey(ref))
  337. throw new IllegalStateException("Can't set after commute");
  338. if(!sets.contains(ref))
  339. {
  340. sets.add(ref);
  341. lock(ref);
  342. }
  343. vals.put(ref, val);
  344. return val;
  345. }
  346. void doTouch(Ref ref){
  347. if(!info.running())
  348. throw retryex;
  349. lock(ref);
  350. }
  351. Object doCommute(Ref ref, IFn fn, ISeq args) throws Exception{
  352. if(!info.running())
  353. throw retryex;
  354. if(!vals.containsKey(ref))
  355. {
  356. Object val = null;
  357. try
  358. {
  359. ref.lock.readLock().lock();
  360. val = ref.tvals == null ? null : ref.tvals.val;
  361. }
  362. finally
  363. {
  364. ref.lock.readLock().unlock();
  365. }
  366. vals.put(ref, val);
  367. }
  368. ArrayList<CFn> fns = commutes.get(ref);
  369. if(fns == null)
  370. commutes.put(ref, fns = new ArrayList<CFn>());
  371. fns.add(new CFn(fn, args));
  372. Object ret = fn.applyTo(RT.cons(vals.get(ref), args));
  373. vals.put(ref, ret);
  374. return ret;
  375. }
  376. /*
  377. //for test
  378. static CyclicBarrier barrier;
  379. static ArrayList<Ref> items;
  380. public static void main(String[] args){
  381. try
  382. {
  383. if(args.length != 4)
  384. System.err.println("Usage: LockingTransaction nthreads nitems niters ninstances");
  385. int nthreads = Integer.parseInt(args[0]);
  386. int nitems = Integer.parseInt(args[1]);
  387. int niters = Integer.parseInt(args[2]);
  388. int ninstances = Integer.parseInt(args[3]);
  389. if(items == null)
  390. {
  391. ArrayList<Ref> temp = new ArrayList(nitems);
  392. for(int i = 0; i < nitems; i++)
  393. temp.add(new Ref(0));
  394. items = temp;
  395. }
  396. class Incr extends AFn{
  397. public Object invoke(Object arg1) throws Exception{
  398. Integer i = (Integer) arg1;
  399. return i + 1;
  400. }
  401. public Obj withMeta(IPersistentMap meta){
  402. throw new UnsupportedOperationException();
  403. }
  404. }
  405. class Commuter extends AFn implements Callable{
  406. int niters;
  407. List<Ref> items;
  408. Incr incr;
  409. public Commuter(int niters, List<Ref> items){
  410. this.niters = niters;
  411. this.items = items;
  412. this.incr = new Incr();
  413. }
  414. public Object call() throws Exception{
  415. long nanos = 0;
  416. for(int i = 0; i < niters; i++)
  417. {
  418. long start = System.nanoTime();
  419. LockingTransaction.runInTransaction(this);
  420. nanos += System.nanoTime() - start;
  421. }
  422. return nanos;
  423. }
  424. public Object invoke() throws Exception{
  425. for(Ref tref : items)
  426. {
  427. LockingTransaction.getEx().doCommute(tref, incr);
  428. }
  429. return null;
  430. }
  431. public Obj withMeta(IPersistentMap meta){
  432. throw new UnsupportedOperationException();
  433. }
  434. }
  435. class Incrementer extends AFn implements Callable{
  436. int niters;
  437. List<Ref> items;
  438. public Incrementer(int niters, List<Ref> items){
  439. this.niters = niters;
  440. this.items = items;
  441. }
  442. public Object call() throws Exception{
  443. long nanos = 0;
  444. for(int i = 0; i < niters; i++)
  445. {
  446. long start = System.nanoTime();
  447. LockingTransaction.runInTransaction(this);
  448. nanos += System.nanoTime() - start;
  449. }
  450. return nanos;
  451. }
  452. public Object invoke() throws Exception{
  453. for(Ref tref : items)
  454. {
  455. //Transaction.get().doTouch(tref);
  456. // LockingTransaction t = LockingTransaction.getEx();
  457. // int val = (Integer) t.doGet(tref);
  458. // t.doSet(tref, val + 1);
  459. int val = (Integer) tref.get();
  460. tref.set(val + 1);
  461. }
  462. return null;
  463. }
  464. public Obj withMeta(IPersistentMap meta){
  465. throw new UnsupportedOperationException();
  466. }
  467. }
  468. ArrayList<Callable<Long>> tasks = new ArrayList(nthreads);
  469. for(int i = 0; i < nthreads; i++)
  470. {
  471. ArrayList<Ref> si;
  472. synchronized(items)
  473. {
  474. si = (ArrayList<Ref>) items.clone();
  475. }
  476. Collections.shuffle(si);
  477. tasks.add(new Incrementer(niters, si));
  478. //tasks.add(new Commuter(niters, si));
  479. }
  480. ExecutorService e = Executors.newFixedThreadPool(nthreads);
  481. if(barrier == null)
  482. barrier = new CyclicBarrier(ninstances);
  483. System.out.println("waiting for other instances...");
  484. barrier.await();
  485. System.out.println("starting");
  486. long start = System.nanoTime();
  487. List<Future<Long>> results = e.invokeAll(tasks);
  488. long estimatedTime = System.nanoTime() - start;
  489. System.out.printf("nthreads: %d, nitems: %d, niters: %d, time: %d%n", nthreads, nitems, niters,
  490. estimatedTime / 1000000);
  491. e.shutdown();
  492. for(Future<Long> result : results)
  493. {
  494. System.out.printf("%d, ", result.get() / 1000000);
  495. }
  496. System.out.println();
  497. System.out.println("waiting for other instances...");
  498. barrier.await();
  499. synchronized(items)
  500. {
  501. for(Ref item : items)
  502. {
  503. System.out.printf("%d, ", (Integer) item.currentVal());
  504. }
  505. }
  506. System.out.println("\ndone");
  507. System.out.flush();
  508. }
  509. catch(Exception ex)
  510. {
  511. ex.printStackTrace();
  512. }
  513. }
  514. */
  515. }