├── .ensime ├── .gitignore ├── LICENSE ├── README.markdown ├── build.sbt ├── run ├── src ├── main │ └── scala │ │ ├── core │ │ ├── Backoff.scala │ │ ├── Channels.scala │ │ ├── Chemistry.scala │ │ ├── KCASRef.scala │ │ ├── Offer.scala │ │ ├── Pool.scala │ │ ├── Reaction.scala │ │ ├── Reagent.scala │ │ ├── Ref.scala │ │ ├── Stats.scala │ │ ├── ThreadLocal.scala │ │ └── Util.scala │ │ ├── data │ │ ├── Counter.scala │ │ ├── EliminationStack.scala │ │ ├── HMList.scala │ │ ├── LaggingQueue.scala │ │ ├── MSQueue.scala │ │ └── TreiberStack.scala │ │ └── sync │ │ ├── CountDownLatch.scala │ │ ├── Exchanger.scala │ │ ├── Lock.scala │ │ └── RWLock.scala └── test │ └── scala │ ├── BackoffSpec.scala │ ├── PoolSpec.scala │ ├── QueueSpec.scala │ ├── StackSpec.scala │ └── TestUtil.scala └── web ├── cs.png ├── index.html └── style.css /.ensime: -------------------------------------------------------------------------------- 1 | ;; This config was generated using ensime-config-gen. Feel free to customize its contents manually. 2 | 3 | ( 4 | 5 | :project-package "chemistry" 6 | 7 | :use-sbt t 8 | 9 | ) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | reports/ 3 | *#*# 4 | *~ 5 | lib_managed/ 6 | src_managed/ 7 | project/boot/ 8 | *.aux 9 | *.log 10 | *.pdf 11 | unlicensed/ 12 | .DS_Store 13 | lib/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Aaron Turon. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY AARON TURON ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL AARON TURON OR OTHER CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 22 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 24 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 25 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Chemistry Set 2 | 3 | The Scala Chemistry Set is an experiment in generalizing both the join 4 | calculus and CML into a single concurrency/parallelism library, 5 | incorporating ideas from scalable parallel implementations of each 6 | ([CML](http://people.cs.uchicago.edu/~jhr/papers/2009/icfp-parallel-cml.pdf), 7 | [Joins](http://www.ccs.neu.edu/home/turon/scalable-joins.pdf)). 8 | 9 | The library is based on the idea of "reagents", which are described in a 10 | [paper](http://www.ccs.neu.edu/home/turon/reagents.pdf) to appear in PLDI2012. 11 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "ChemistrySet" 2 | 3 | version := "0.1" 4 | 5 | scalacOptions += "-deprecation" 6 | 7 | scalacOptions += "-unchecked" 8 | 9 | scalacOptions += "-optimize" 10 | 11 | //scalacOptions += "-Yinline" 12 | 13 | //scalacOptions += "-Ydebug" 14 | 15 | //scalacOptions += "-Ylog:inliner" 16 | 17 | parallelExecution in Test := false 18 | 19 | // disable publishing of main docs 20 | publishArtifact in (Compile, packageDoc) := false 21 | 22 | scalaVersion := "2.9.1" 23 | 24 | resolvers += "repo.codahale.com" at "http://repo.codahale.com" 25 | 26 | libraryDependencies ++= Seq( 27 | "com.codahale" % "simplespec_2.9.0-1" % "0.4.1" 28 | ) 29 | 30 | fork := true 31 | 32 | javaOptions += "-server" 33 | 34 | javaOptions += "-XX:+DoEscapeAnalysis" 35 | 36 | javaOptions += "-Xmx2048M" -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | sbt publish-local && /home/turon/jdk1.6.0_27/bin/java -Xmx8000M -Xms128M -Xbootclasspath/a:/home/turon/scala-2.9.0.1/lib/jline.jar:/home/turon/scala-2.9.0.1/lib/scala-compiler.jar:/home/turon/scala-2.9.0.1/lib/scala-dbc.jar:/home/turon/scala-2.9.0.1/lib/scala-library.jar:/home/turon/scala-2.9.0.1/lib/scala-swing.jar:/home/turon/scala-2.9.0.1/lib/scalap.jar -XX:+DoEscapeAnalysis -XX:-UseLoopPredicate -jar /home/turon/ChemistrySet/target/scala-2.9.1.final/chemistryset_2.9.1-0.1.jar $@ -------------------------------------------------------------------------------- /src/main/scala/core/Backoff.scala: -------------------------------------------------------------------------------- 1 | // Exponential backoff. 2 | 3 | package chemistry 4 | 5 | import scala.util._ 6 | 7 | private object Backoff { 8 | val maxCount: Int = 14 9 | } 10 | final class Backoff { 11 | import Backoff._ 12 | 13 | // private val rand = new Random(Thread.currentThread.getId) 14 | var seed: Long = Thread.currentThread.getId 15 | var count = 0 16 | 17 | def once() { 18 | if (count == 0) 19 | count = 1 20 | else { 21 | seed = Random.nextSeed(seed) 22 | Util.noop(Random.scale(seed, (Chemistry.procs-1) << (count + 2))) 23 | if (count < maxCount) count += 1 24 | } 25 | } 26 | 27 | def flip(n: Int): Boolean = { 28 | seed = Random.nextSeed(seed) 29 | seed % n == 0 30 | } 31 | 32 | @inline def once(until: => Boolean, mult: Int) { 33 | // if (count == 0) 34 | // count = 1 35 | // else { 36 | // seed = Random.nextSeed(seed) 37 | val max = (Chemistry.procs-1) << (count + mult) 38 | var spins = max 39 | // var spins = Random.scale(seed, max) 40 | 41 | while (!until && spins > 0) spins -= 1 42 | if (count < maxCount) count += 1 43 | } 44 | // } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/core/Channels.scala: -------------------------------------------------------------------------------- 1 | // Message passing constructions: synchronous channels and swap channels. 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | import java.util.concurrent.locks._ 7 | 8 | final private class Message[A,B,C]( 9 | payload: A, // the actual content of the message 10 | senderRx: Reaction, // the checkpointed reaction of the sender 11 | senderK: Reagent[B,C], // the sender's reagent continuation 12 | val offer: Offer[C] // the sender's offer 13 | ) extends DeletionFlag { 14 | private case class CompleteExchange[D](receiverK: Reagent[A,D]) 15 | extends Reagent[C,D] { 16 | def tryReact(c: C, rx: Reaction, enclosingOffer: Offer[D]): Any = 17 | offer.consumeAndContinue( 18 | c, payload, rx ++ senderRx, receiverK, enclosingOffer) 19 | def composeI[E](next: Reagent[D,E]): Reagent[C,E] = 20 | CompleteExchange(receiverK >> next) 21 | def maySync = receiverK.maySync 22 | def alwaysCommits = false 23 | def snoop(c: C) = offer.isActive && receiverK.snoop(payload) 24 | } 25 | 26 | val exchange: Reagent[B, A] = senderK >> CompleteExchange(Commit[A]()) 27 | def isDeleted = !offer.isActive 28 | } 29 | 30 | private final case class Endpoint[A,B,C]( 31 | outgoing: Pool[Message[A,B,_]], 32 | incoming: Pool[Message[B,A,_]], 33 | k: Reagent[B,C] 34 | ) extends Reagent[A,C] { 35 | def tryReact(a: A, rx: Reaction, offer: Offer[C]): Any = { 36 | // todo: avoid attempts of claiming the same message twice 37 | @tailrec def tryFrom(n: incoming.Node, retry: Boolean): Any = 38 | if (n == null) { 39 | if (retry) Retry else Block 40 | } else if ((n.data.offer eq offer) || rx.hasOffer(n.data.offer)) { 41 | tryFrom(n.next, retry) 42 | } else n.data.exchange.compose(k).tryReact(a, 43 | rx.withOffer(n.data.offer), 44 | offer) match { 45 | case Retry => tryFrom(n.next, true) 46 | case Block => tryFrom(n.next, retry) 47 | case ans => ans 48 | } 49 | 50 | // send message if so requested. note that we send the message 51 | // *before* attempting to react with existing messages in the 52 | // other direction. 53 | // TODO: should combine !maysync with blocking check 54 | if (offer != null && !k.maySync) 55 | outgoing.put(new Message(a, rx, k, offer)) 56 | 57 | // now attempt an immediate reaction 58 | tryFrom(incoming.cursor, false) match { 59 | case Block => Block // todo: fall back on "multithreaded" reagents 60 | case ow => ow 61 | } 62 | } 63 | def snoop(a: A): Boolean = incoming.snoop 64 | @inline def composeI[D](next: Reagent[C,D]) = 65 | Endpoint(outgoing, incoming, k.compose(next)) 66 | @inline def maySync = true 67 | @inline def alwaysCommits = false 68 | } 69 | 70 | object SwapChan { 71 | @inline def apply[A,B](): (Reagent[A,B], Reagent[B,A]) = { 72 | val p1 = new CircularPool[Message[A,B,_]] 73 | val p2 = new CircularPool[Message[B,A,_]] 74 | (Endpoint(p1,p2,Commit[B]()), Endpoint(p2,p1,Commit[A]())) 75 | } 76 | } 77 | 78 | object Chan { 79 | @inline def apply[A](): (Reagent[Unit,A], Reagent[A,Unit]) = 80 | SwapChan[Unit,A]() 81 | } 82 | 83 | // is there a possibility of building the exchanger directly here? 84 | // also, can the endpoint logic be decomposed/parameterized more? 85 | -------------------------------------------------------------------------------- /src/main/scala/core/Chemistry.scala: -------------------------------------------------------------------------------- 1 | package chemistry 2 | 3 | object Chemistry { 4 | var procs = Runtime.getRuntime.availableProcessors 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/core/KCASRef.scala: -------------------------------------------------------------------------------- 1 | // an implementation of KCAS 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | import java.util.concurrent.atomic._ 7 | 8 | /* 9 | // a k-cas-able reference cell 10 | class KCASRef[A <: AnyRef](init: A) { 11 | private[chemistry] val data = new AtomicReference[AnyRef](init) 12 | 13 | private[chemistry] def afterCAS {} 14 | 15 | @inline private[chemistry] final def getI = (data.get match { 16 | case (owner: KCAS) => owner.curVal(this) 17 | case v => v 18 | }).asInstanceOf[A] 19 | 20 | @inline private [chemistry] final def casI(ov: A, nv: A): Boolean = { 21 | val success = data.compareAndSet(ov, nv) 22 | if (success) afterCAS 23 | success 24 | } 25 | } 26 | */ 27 | 28 | private final case class CAS[A <: AnyRef]( 29 | ref: Ref[A], ov: A, nv: A 30 | ) { 31 | def execAsSingle: Boolean = ref.data.compareAndSet(ov, nv) 32 | } 33 | 34 | /* 35 | private object KCAS { 36 | private sealed abstract class Status 37 | private final case object Pending extends Status 38 | private final case object Complete extends Status 39 | private final case object Aborted extends Status 40 | } 41 | private final class KCAS(val casList: List[CAS[_]]) { 42 | private val status = new AtomicReference[KCAS.Status](KCAS.Pending) 43 | private def isPending: Boolean = status.get == KCAS.Pending 44 | private def complete: Boolean = 45 | status.compareAndSet(KCAS.Pending, KCAS.Complete) 46 | private def abort: Boolean = 47 | status.compareAndSet(KCAS.Pending, KCAS.Aborted) 48 | 49 | def curVal(ref: KCASRef[_]): AnyRef = { 50 | casList find (_.ref == ref) match { 51 | case None => throw Util.Impossible // only happens if there's a bug 52 | case Some(CAS(_, ov, nv)) => 53 | if (status.get == KCAS.Complete) nv else ov 54 | } 55 | } 56 | 57 | def tryCommit: Boolean = { 58 | // attempt to place KCAS record in each CASed reference. returns null 59 | // if successful, and otherwise the point in the CAS list to rollback 60 | // from. 61 | @tailrec def acquire(casList: List[CAS[_]]): List[CAS[_]] = 62 | if (!isPending) casList 63 | else casList match { 64 | case Nil => null 65 | case CAS(ref, ov, nv) :: rest => ref.data.get match { 66 | case (owner: KCAS) => casList // blocking version 67 | // if (owner.curVal(ref) == ov) { 68 | // if (ref.compareAndSet(owner, kcas)) acquire(rest) 69 | // else false 70 | // } else false 71 | case curVal if (curVal == ov) => 72 | if (ref.data.compareAndSet(ov, this)) acquire(rest) 73 | else casList 74 | case _ => casList 75 | } 76 | } 77 | 78 | @tailrec def rollBackBetween(start: List[CAS[_]], end: List[CAS[_]]) { 79 | if (start != end) start match { 80 | case Nil => throw Util.Impossible // somehow went past the `end` 81 | case CAS(ref, ov, nv) :: rest => { 82 | ref.data.compareAndSet(this, ov) // roll back to old value 83 | rollBackBetween(rest, end) 84 | } 85 | } 86 | } 87 | 88 | def rollForward(casList: List[CAS[_]]): Unit = { 89 | // do all the CASes first, to propagate update as quickly as 90 | // possible 91 | casList.foreach { 92 | case CAS(ref, _, nv) => ref.data.compareAndSet(this, nv) 93 | } 94 | // now perform relevant post-CAS actions (e.g. waking up 95 | // waiters) 96 | casList.foreach { 97 | case CAS(ref, _, _) => ref.afterCAS 98 | } 99 | } 100 | 101 | acquire(casList) match { 102 | case null => 103 | if (complete) { rollForward(casList); true } 104 | else { rollBackBetween(casList, Nil); false } 105 | case end => { rollBackBetween(casList, end); false } 106 | } 107 | } 108 | } 109 | */ 110 | 111 | private object KCAS { 112 | def tryCommit(casList: List[CAS[_]]): Boolean = { 113 | // println("KCAS") 114 | 115 | // attempt to place KCAS record in each CASed reference. returns null 116 | // if successful, and otherwise the point in the CAS list to rollback 117 | // from. 118 | @tailrec def acquire(casList: List[CAS[_]]): List[CAS[_]] = 119 | casList match { 120 | case Nil => null 121 | case CAS(ref, ov, nv) :: rest => ref.data.get match { 122 | case null => casList // blocking version 123 | case curVal if (curVal == ov) => 124 | if (ref.data.compareAndSet(ov, null)) acquire(rest) 125 | else casList 126 | case _ => casList 127 | } 128 | } 129 | 130 | @tailrec def rollBackBetween(start: List[CAS[_]], end: List[CAS[_]]) { 131 | if (start != end) start match { 132 | case Nil => throw Util.Impossible // somehow went past the `end` 133 | case CAS(ref, ov, nv) :: rest => { 134 | ref.data.lazySet(ov) // roll back to old value 135 | //ref.data.compareAndSet(null, ov) 136 | rollBackBetween(rest, end) 137 | } 138 | } 139 | } 140 | 141 | def rollForward(casList: List[CAS[_]]): Unit = { 142 | // do all the CASes first, to propagate update as quickly as 143 | // possible 144 | casList.foreach { 145 | case CAS(ref, _, nv) => ref.data.lazySet(nv) 146 | } 147 | // now perform relevant post-CAS actions (e.g. waking up 148 | // waiters) 149 | casList.foreach { 150 | case CAS(ref, _, _) => ref.afterCAS 151 | } 152 | } 153 | 154 | acquire(casList) match { 155 | case null => { rollForward(casList); true } 156 | case end => { rollBackBetween(casList, end); false } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/scala/core/Offer.scala: -------------------------------------------------------------------------------- 1 | // Representation of reagents offering to react: 2 | // - used to discover catalysts 3 | // - used to discover (possibly blocked) partners for SwapChans 4 | // - used to discover reagents blocked on Ref cell values 5 | 6 | package chemistry 7 | 8 | import scala.annotation.tailrec 9 | import java.util.concurrent.locks._ 10 | import java.util.concurrent.atomic._ 11 | 12 | private abstract class Offer[-A] extends DeletionFlag { 13 | // is the Offer still available? 14 | def isActive: Boolean 15 | 16 | // consume the Offer within the current reaction, and continue on with the 17 | // rest of a reagent 18 | def consumeAndContinue[B,C]( 19 | completeWith: A, // the value yielded to the Offerer 20 | continueWith: B, // the value yielded to the continuation 21 | rx: Reaction, // the reaction so far 22 | k: Reagent[B, C], // the continuation 23 | enclosingOffer: Offer[C] // the offer, if any, that the enclosing 24 | // reagent is making 25 | ): Any 26 | 27 | // an alias used for Offers appearing in Pools 28 | def isDeleted = !isActive 29 | 30 | // used only when the Offer is enrolled in a Ref, and the value of the Ref 31 | // changes 32 | def abortAndWake: Unit 33 | } 34 | 35 | private class Catalyst[-A](dissolvent: Reagent[Unit,A]) extends Offer[A] { 36 | private val alive = new AtomicReference[Boolean](true) 37 | 38 | def isActive = alive.get 39 | def consumeAndContinue[B,C]( 40 | completeWith: A, continueWith: B, 41 | rx: Reaction, k: Reagent[B, C], enclosingOffer: Offer[C] 42 | ): Any = 43 | k.tryReact(continueWith, rx, enclosingOffer) 44 | 45 | def abortAndWake { 46 | if (alive.compareAndSet(true, false)) 47 | Reagent.dissolve(dissolvent) // reinstate the catalyst 48 | } 49 | } 50 | 51 | private object Waiter { 52 | abstract class WaiterStatus 53 | object Waiting extends WaiterStatus 54 | object Aborted extends WaiterStatus 55 | } 56 | private final class Waiter[-A](val blocking: Boolean) 57 | extends Offer[A] with DeletionFlag { 58 | import Waiter._ 59 | 60 | private[chemistry] val status = new Ref[AnyRef](Waiting) 61 | 62 | // the thread that *created* the Waiter 63 | private val waiterThread = Thread.currentThread() 64 | private def wake(u: Unit) { 65 | if (blocking) LockSupport.unpark(waiterThread) 66 | } 67 | 68 | @inline def isActive: Boolean = status.data.get == Waiting 69 | 70 | // Attempt to abort, returning 71 | // - None if abort succeeded 72 | // - Some(ans) if waiter already completed with ans 73 | @tailrec def tryAbort: Option[Any] = status.data.get match { 74 | case null => tryAbort 75 | case Aborted => None 76 | case Waiting => 77 | if (status.data.compareAndSet(Waiting, Aborted)) None 78 | else tryAbort 79 | case ans => Some(ans) 80 | } 81 | 82 | @inline def rxWithAbort(rx: Reaction): Reaction = 83 | rx.withCAS(status, Waiting, Aborted) 84 | 85 | def abortAndWake = if (tryAbort eq None) wake() 86 | 87 | @inline def tryComplete(a: A) = 88 | status.data.compareAndSet(Waiting, a.asInstanceOf[AnyRef]) 89 | @inline def rxWithCompletion(rx: Reaction, a: A): Reaction = 90 | rx.withCAS(status, Waiting, a.asInstanceOf[AnyRef]) 91 | 92 | def consumeAndContinue[B,C]( 93 | completeWith: A, continueWith: B, 94 | rx: Reaction, k: Reagent[B, C], enclosingOffer: Offer[C] 95 | ): Any = { 96 | val newRX = 97 | if (rx.canCASImmediate(k, enclosingOffer)) { 98 | if (!tryComplete(completeWith)) // attempt early, and 99 | return Retry // retry early on failure 100 | else rx 101 | } else rxWithCompletion(rx, completeWith) 102 | 103 | if (blocking) 104 | k.tryReact(continueWith, newRX.withPostCommit(wake), enclosingOffer) 105 | else 106 | k.tryReact(continueWith, newRX, enclosingOffer) 107 | } 108 | 109 | // def reset { status.set(Waiting) } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/main/scala/core/Pool.scala: -------------------------------------------------------------------------------- 1 | // A concurrent, unordered bag, used to represent channels 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | import java.util.concurrent.atomic._ 7 | import java.util.concurrent.locks._ 8 | 9 | trait DeletionFlag { 10 | def isDeleted: Boolean 11 | } 12 | 13 | trait Pool[A <: DeletionFlag] { 14 | type Node <: { 15 | def data: A 16 | def next: Node 17 | } 18 | def cursor: Node 19 | def put(a: A): Unit 20 | def snoop: Boolean 21 | } 22 | 23 | /* 24 | final class Pool[A <: DeletionFlag] { 25 | final case class Node(data: A, next: Cursor) 26 | val cursor = new Cursor(null) 27 | 28 | final class Cursor private[Pool](node: Node) { 29 | private[Pool] val ref = Ref(node) 30 | @tailrec def get: Node = ref.read ! () match { 31 | case null => null 32 | case n@Node(data, next) => 33 | if (data.isDeleted) { 34 | // ref.cas(n, next.ref.read ! ()) !? () 35 | ref.data.lazySet(next.ref.data.get) 36 | get 37 | } else n 38 | } 39 | } 40 | 41 | private val putRA: Reagent[A,Unit] = cursor.ref.updIn { 42 | (xs,x) => Node(x, new Cursor(xs)) 43 | } 44 | 45 | def put(a: A) { 46 | putRA ! a 47 | } 48 | } 49 | */ 50 | 51 | private final class PaddedAtomicReference[A](init:A) 52 | extends AtomicReference[A](init) { 53 | var q0: Long = 0 54 | var q1: Long = 0 55 | var q2: Long = 0 56 | var q3: Long = 0 57 | var q4: Long = 0 58 | var q5: Long = 0 59 | var q6: Long = 0 60 | var q7: Long = 0 61 | var q8: Long = 0 62 | var q9: Long = 0 63 | var qa: Long = 0 64 | var qb: Long = 0 65 | var qc: Long = 0 66 | var qd: Long = 0 67 | var qe: Long = 0 68 | } 69 | 70 | private object ArrayPool { 71 | //val size = math.max(1,Chemistry.procs / 2) 72 | val size = 8 73 | } 74 | final class ArrayPool[A >: Null <: DeletionFlag] { 75 | import ArrayPool._ 76 | 77 | private val arr = new Array[PaddedAtomicReference[A]](size) 78 | for (i <- (0 to size-1)) arr(i) = new PaddedAtomicReference[A](null) 79 | 80 | private def myStart = (Thread.currentThread.getId % size).toInt 81 | 82 | def tryPut(a: A): Boolean = { 83 | // val slot = myStart 84 | var slot = 0 85 | while (slot < size) { 86 | val cur = arr(slot).get 87 | if (cur != null && !cur.isDeleted) return false 88 | if (arr(slot).compareAndSet(cur, a)) return true 89 | slot += 1 90 | } 91 | false 92 | } 93 | 94 | def cursor = 0 95 | @tailrec def get(cursor: Int): A = { 96 | val cur = arr(cursor).get 97 | if (cur != null && !cur.isDeleted) 98 | cur 99 | else if (cursor + 1 < size) 100 | get(cursor+1) 101 | else 102 | null 103 | } 104 | def next(cursor: Int): A = 105 | if (cursor + 1 < size) get(cursor+1) else null 106 | } 107 | 108 | final class CircularPool[A <: DeletionFlag] extends Pool[A] { 109 | @tailrec private final def findNext(start: AbsNode): Node = start match { 110 | case (n: Node) => 111 | if (n.data.isDeleted) findNext(n.nextVar) else n 112 | case(l: LinkNode) => 113 | if (l == cursors(myStart)) null else findNext(l.nextRef.get) 114 | } 115 | 116 | abstract class AbsNode { 117 | def next: Node 118 | } 119 | final class Node private[CircularPool](val data: A) extends AbsNode { 120 | private[CircularPool] var nextVar: AbsNode = null 121 | def next = findNext(nextVar) 122 | } 123 | private final class LinkNode extends AbsNode { 124 | private[CircularPool] val nextRef = new PaddedAtomicReference[AbsNode](null) 125 | def next = findNext(nextRef.get) 126 | def nextInLane: AbsNode = { 127 | @tailrec def findNext(cur: AbsNode): AbsNode = cur match { 128 | case null => null 129 | case (n: Node) => 130 | if (n.data.isDeleted) findNext(n.nextVar) else n 131 | case(l: LinkNode) => l 132 | } 133 | findNext(nextRef.get) 134 | } 135 | } 136 | 137 | private def size = 8//math.max(1,Chemistry.procs) 138 | private val cursors = new Array[LinkNode](size) 139 | for (i <- 0 to size-1) cursors(i) = new LinkNode 140 | for (i <- 0 to size-1) cursors(i).nextRef.set(cursors((i+1) % size)) 141 | 142 | private def myStart = (Thread.currentThread.getId % size).toInt 143 | 144 | def cursor: Node = cursors(myStart).next 145 | 146 | /* 147 | private final def snoop(n: Int): Boolean = cursors(n).nextRef.get match { 148 | case (n: Node) => !n.data.isDeleted 149 | case _ => false 150 | } 151 | final def snoop: Boolean = { 152 | var i: Int = 0 153 | val start = myStart 154 | while (i < size) { 155 | if (snoop((start + i) % size)) return true 156 | i += 1 157 | } 158 | false 159 | } 160 | */ 161 | @inline final def snoop: Boolean = cursor != null 162 | 163 | def put(a: A) { 164 | val link = cursors(myStart) 165 | val ref = link.nextRef 166 | val node = new Node(a) 167 | while (true) { 168 | val oldHead = ref.get 169 | node.nextVar = link.nextInLane 170 | if (ref.compareAndSet(oldHead, node)) return 171 | } 172 | } 173 | 174 | /* 175 | def put(a: A) { 176 | val node = new Node(a) 177 | var i: Int = myStart 178 | while (true) { 179 | val link = cursors(i) 180 | val ref = link.nextRef 181 | 182 | val oldHead = ref.get 183 | node.nextVar = link.nextInLane 184 | if (ref.compareAndSet(oldHead, node)) return 185 | i = (i + 1) % size 186 | } 187 | } 188 | */ 189 | } 190 | 191 | /* 192 | private object BoundPool { 193 | val capacity = 32 194 | val full = math.max(0, math.min(capacity, Chemistry.procs / 2) - 1) 195 | } 196 | final class BoundPool[A <: DeletionFlag] { 197 | import BoundPool._ 198 | 199 | case class Node(data: A) //, next: Int) 200 | 201 | private val lock = new ReentrantLock 202 | @volatile private var arena = new Array[PaddedAtomicReference[Node]](capacity) 203 | private val max = new AtomicInteger 204 | 205 | def put(a: A) { 206 | val n = Node(a) 207 | var index = hashIndex 208 | var fails = 0 209 | 210 | while (fails < 50) { 211 | val slot = arena(index) 212 | if (slot == null) { 213 | val slot = new PaddedAtomicReference[Node](null) 214 | lock.lock 215 | if (arena(index) == null) 216 | arena(index) = slot 217 | lock.unlock 218 | } else { 219 | val cur = slot.get 220 | if (cur == null || cur.data.isDeleted) { 221 | if (slot.compareAndSet(cur, n)) return 222 | } 223 | 224 | fails +=1 225 | if (fails >= 2) { 226 | val m = max.get 227 | if (fails > 3 && m < full && max.compareAndSet(m, m+1)) 228 | index = m + 1 229 | else if (index == 0) 230 | index = m 231 | else index -= 1 232 | } 233 | } 234 | } 235 | 236 | throw OfferFail 237 | } 238 | 239 | @inline private final def hashIndex: Int = { 240 | val id = Thread.currentThread().getId() 241 | var hash = ((id ^ (id >>> 32)).toInt ^ 0x811c9dc5) * 0x01000193 242 | 243 | val m = max.get() 244 | val nbits = (((0xfffffc00 >> m) & 4) | // Compute ceil(log2(m+1)) 245 | ((0x000001f8 >>> m) & 2) | // The constants hold 246 | ((0xffff00f2 >>> m) & 1)) // a lookup table 247 | 248 | var index = hash & ((1 << nbits) - 1) 249 | while (index > m) { // May retry on 250 | hash = (hash >>> nbits) | (hash << (33 - nbits)) // non-power-2 m 251 | index = hash & ((1 << nbits) - 1) 252 | } 253 | index 254 | } 255 | 256 | def get: Node = { 257 | var i = 0 258 | val m = max.get 259 | while (i <= m) { 260 | arena(i) match { 261 | case null => {} 262 | case slot => { 263 | val n = slot.get 264 | if (n != null && !n.data.isDeleted) return n 265 | } 266 | } 267 | i += 1 268 | } 269 | return null 270 | } 271 | } 272 | */ 273 | -------------------------------------------------------------------------------- /src/main/scala/core/Reaction.scala: -------------------------------------------------------------------------------- 1 | // Internal representation of queued up actions making up a potential 2 | // reaction. 3 | 4 | package chemistry 5 | 6 | import scala.collection.immutable.HashSet 7 | 8 | private sealed class Reaction private ( 9 | val casList: List[CAS[_]], // k-cas built up so far 10 | val pcList: List[Unit => Unit], // post-commit actions 11 | val offerSet: HashSet[Offer[_]] // offers intended for consumption 12 | ) { 13 | import Reaction._ 14 | 15 | def casCount: Int = casList.size 16 | 17 | // is it safe to do a CAS *while creating the reaction*? generally, this is 18 | // fine as long as the whole reaction is guaranteed to be a 1-cas. 19 | def canCASImmediate[A,B](k: Reagent[A,B], offer: Offer[B]): Boolean = 20 | casCount == 0 && k.alwaysCommits && (offer match { 21 | case null => true 22 | case (_: Catalyst[_]) => true 23 | case (_: Waiter[_]) => false 24 | }) 25 | 26 | def withPostCommit(postCommit: Unit => Unit): Reaction = 27 | new Reaction(casList, postCommit +: pcList, offerSet) 28 | def withCAS[A <: AnyRef](ref: Ref[A], ov: A, nv: A): Reaction = 29 | new Reaction(CAS(ref, ov, nv) +: casList, pcList, offerSet) 30 | def withOffer(offer: Offer[_]): Reaction = 31 | new Reaction(casList, pcList, offerSet + offer) 32 | 33 | def ++(rx: Reaction): Reaction = 34 | new Reaction(casList ++ rx.casList, pcList ++ rx.pcList, offerSet union rx.offerSet) 35 | 36 | def hasOffer(offer: Offer[_]): Boolean = offerSet.contains(offer) 37 | 38 | def tryCommit: Boolean = { 39 | val success: Boolean = casCount match { 40 | case 0 => true 41 | case 1 => casList.head.execAsSingle 42 | case _ => KCAS.tryCommit(casList) 43 | } 44 | if (success) 45 | pcList.foreach(_.apply()) // perform the post-commit actions 46 | success 47 | } 48 | } 49 | private object Reaction { 50 | val inert = new Reaction(Nil, Nil, HashSet.empty) 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/core/Reagent.scala: -------------------------------------------------------------------------------- 1 | // The core reagent implementation and accompanying combinators 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | import java.util.concurrent.locks._ 7 | import chemistry.Util.Implicits._ 8 | 9 | private[chemistry] sealed abstract class BacktrackCommand { 10 | // what to do when the backtracking command runs out of choices 11 | // (i.e., hits bottom) 12 | def bottom[A](waiter: Waiter[A], backoff: Backoff, snoop: => Boolean): Unit 13 | def isBlock: Boolean 14 | } 15 | private[chemistry] case object Block extends BacktrackCommand { 16 | def bottom[A](waiter: Waiter[A], backoff: Backoff, snoop: => Boolean): Unit = 17 | LockSupport.park(waiter) 18 | def isBlock: Boolean = true 19 | } 20 | private[chemistry] case object Retry extends BacktrackCommand { 21 | def bottom[A](waiter: Waiter[A], backoff: Backoff, snoop: => Boolean): Unit = 22 | backoff.once(waiter.isActive && !snoop, 1) 23 | def isBlock: Boolean = false 24 | } 25 | 26 | abstract class Reagent[-A, +B] { 27 | // returns either a BacktrackCommand or a B 28 | private[chemistry] def tryReact(a: A, rx: Reaction, offer: Offer[B]): Any 29 | protected def composeI[C](next: Reagent[B,C]): Reagent[A,C] 30 | private[chemistry] def alwaysCommits: Boolean 31 | private[chemistry] def maySync: Boolean 32 | private[chemistry] def snoop(a: A): Boolean 33 | 34 | final def compose[C](next: Reagent[B,C]): Reagent[A,C] = next match { 35 | case Commit() => this.asInstanceOf[Reagent[A,C]] // B = C 36 | case _ => composeI(next) 37 | } 38 | 39 | final def !(a: A): B = tryReact(a, Reaction.inert, null) match { 40 | case (_: BacktrackCommand) => { 41 | val backoff = new Backoff 42 | val maySync = this.maySync // cache 43 | @tailrec def retryLoop(shouldBlock: Boolean): B = { 44 | // to think about: can a single waiter be reused? 45 | val wait = maySync || shouldBlock 46 | val waiter = if (wait) new Waiter[B](shouldBlock) else null 47 | 48 | tryReact(a, Reaction.inert, waiter) match { 49 | case (bc: BacktrackCommand) if wait => { 50 | bc.bottom(waiter, backoff, snoop(a)) 51 | waiter.tryAbort match { // rescind waiter, but check if already 52 | // completed 53 | case Some(ans) => ans.asInstanceOf[B] 54 | case None => retryLoop(bc.isBlock) 55 | } 56 | } 57 | case Retry => backoff.once; retryLoop(false) 58 | case Block => retryLoop(true) 59 | case ans => ans.asInstanceOf[B] 60 | } 61 | } 62 | backoff.once 63 | retryLoop(false) 64 | } 65 | case ans => ans.asInstanceOf[B] 66 | } 67 | 68 | @inline final def !?(a:A) : Option[B] = { 69 | tryReact(a, Reaction.inert, null) match { 70 | case Retry => None // should we actually retry here? if we do, more 71 | // informative: a failed attempt entails a 72 | // linearization where no match was possible. but 73 | // could diverge... 74 | case Block => None 75 | case ans => Some(ans.asInstanceOf[B]) 76 | } 77 | } 78 | 79 | @inline final def dissolve(a:A) = Reagent.dissolve(ret(a) >> this) 80 | 81 | @inline final def flatMap[C](k: B => Reagent[Unit,C]): Reagent[A,C] = 82 | compose(computed(k)) 83 | @inline final def map[C](f: B => C): Reagent[A,C] = 84 | compose(lift(f)) 85 | @inline final def mapFilter[C](f: PartialFunction[B, C]): Reagent[A,C] = 86 | compose(lift(f)) 87 | @inline final def withFilter(f: B => Boolean): Reagent[A,B] = 88 | compose(lift((_: B) match { case b if f(b) => b })) 89 | @inline final def +[C <: A, D >: B](that: Reagent[C,D]): Reagent[C,D] = 90 | choice(this, that) 91 | @inline final def >>[C](k: Reagent[B,C]): Reagent[A,C] = 92 | compose(k) 93 | } 94 | private object Reagent { 95 | def dissolve[A](reagent: Reagent[Unit, A]) { 96 | val cata = new Catalyst(reagent) 97 | reagent.tryReact((), Reaction.inert, cata) match { 98 | case Block => return 99 | case _ => throw Util.Impossible // something has gone awry... 100 | } 101 | } 102 | } 103 | 104 | private abstract class AutoContImpl[A,B,C](val k: Reagent[B, C]) 105 | extends Reagent[A,C] { 106 | def retValue(a: A): Any // BacktrackCommand or B 107 | def newRx(a: A, rx: Reaction): Reaction = rx 108 | 109 | final def snoop(a: A) = retValue(a) match { 110 | case (_: BacktrackCommand) => false 111 | case b => k.snoop(b.asInstanceOf[B]) 112 | } 113 | final def tryReact(a: A, rx: Reaction, offer: Offer[C]): Any = 114 | retValue(a) match { 115 | case (bc: BacktrackCommand) => bc 116 | case b => k.tryReact(b.asInstanceOf[B], newRx(a, rx), offer) 117 | } 118 | final def composeI[D](next: Reagent[C,D]) = 119 | new AutoContImpl[A,B,D](k >> next) { 120 | def retValue(a: A): Any = 121 | AutoContImpl.this.retValue(a) 122 | override def newRx(a: A, rx: Reaction): Reaction = 123 | AutoContImpl.this.newRx(a, rx) 124 | } 125 | final def alwaysCommits = k.alwaysCommits // this needs to be overridable! 126 | final def maySync = k.maySync 127 | } 128 | private abstract class AutoCont[A,B] extends AutoContImpl[A,B,B](Commit[B]()) 129 | 130 | object ret { 131 | @inline final def apply[A](pure: A): Reagent[Any,A] = new AutoCont[Any,A] { 132 | def retValue(a: Any): Any = pure 133 | } 134 | } 135 | 136 | // Not sure whether this should be available as a combinaor 137 | // object retry extends Reagent[Any,Nothing] { 138 | // final def tryReact[A](a: Any, rx: Reaction, k: K[Nothing,A]): A = 139 | // throw ShouldRetry 140 | // } 141 | 142 | private final case class Commit[A]() extends Reagent[A,A] { 143 | def tryReact(a: A, rx: Reaction, offer: Offer[A]): Any = { 144 | offer match { 145 | case null => if (rx.tryCommit) a else Retry 146 | // case (w: Waiter[_]) => if (w.rxWithAbort(rx).tryCommit) a else Retry 147 | case (w: Waiter[_]) => { 148 | w.tryAbort match { // rescind waiter, but check if already completed 149 | case Some(ans) => ans 150 | case None => if (rx.tryCommit) a else Retry 151 | } 152 | } 153 | case (_: Catalyst[_]) => { 154 | rx.tryCommit 155 | Block 156 | } 157 | } 158 | } 159 | def snoop(a: A) = true 160 | def makeOfferI(a: A, offer: Offer[A]) {} 161 | def composeI[B](next: Reagent[A,B]) = next 162 | def alwaysCommits = true 163 | def maySync = false 164 | } 165 | 166 | object never extends Reagent[Any, Nothing] { 167 | def tryReact(a: Any, rx: Reaction, offer: Offer[Nothing]): Any = Block 168 | def snoop(a: Any) = false 169 | def composeI[A](next: Reagent[Nothing, A]) = never 170 | def alwaysCommits = false 171 | def maySync = false 172 | } 173 | 174 | object computed { 175 | private final case class Computed[A,B,C](c: A => Reagent[Unit,B], 176 | k: Reagent[B,C]) 177 | extends Reagent[A,C] { 178 | def snoop(a: A) = false 179 | def tryReact(a: A, rx: Reaction, offer: Offer[C]): Any = 180 | c(a).compose(k).tryReact((), rx, offer) 181 | def composeI[D](next: Reagent[C,D]) = Computed(c, k.compose(next)) 182 | def alwaysCommits = false 183 | def maySync = true 184 | } 185 | @inline def apply[A,B](c: A => Reagent[Unit,B]): Reagent[A,B] = 186 | Computed(c, Commit[B]()) 187 | } 188 | 189 | object lift { 190 | // this is WRONG -- does NOT always commit 191 | @inline def apply[A,B](f: PartialFunction[A,B]): Reagent[A,B] = 192 | new AutoCont[A,B] { 193 | def retValue(a: A): Any = if (f.isDefinedAt(a)) f(a) else Block 194 | } 195 | } 196 | 197 | object choice { 198 | private final case class Choice[A,B](r1: Reagent[A,B], r2: Reagent[A,B]) 199 | extends Reagent[A,B] { 200 | def tryReact(a: A, rx: Reaction, offer: Offer[B]): Any = 201 | r1.tryReact(a, rx, offer) match { 202 | case Retry => 203 | r2.tryReact(a, rx, offer) match { 204 | case Retry => Retry 205 | case Block => Retry // must retry r1 206 | case ans => ans 207 | } 208 | case Block => r2.tryReact(a, rx, offer) 209 | case ans => ans 210 | } 211 | def composeI[C](next: Reagent[B,C]) = 212 | next match { 213 | case Choice(next1, next2) => 214 | Choice(r1 >> next1, 215 | Choice(r1 >> next2, 216 | Choice(r2 >> next1, 217 | r2 >> next2))) 218 | case _ => Choice(r1.compose(next), r2.compose(next)) 219 | } 220 | def alwaysCommits = r1.alwaysCommits && r2.alwaysCommits 221 | def maySync = r1.maySync || r2.maySync 222 | def snoop(a: A) = r2.snoop(a) || r1.snoop(a) 223 | } 224 | @inline def apply[A,B](r1: Reagent[A,B], r2: Reagent[A,B]): Reagent[A,B] = 225 | Choice(r1, r2) 226 | } 227 | 228 | object postCommit { 229 | @inline def apply[A](pc: A => Unit): Reagent[A,A] = new AutoCont[A,A] { 230 | def retValue(a: A): Any = a 231 | override def newRx(a: A, rx: Reaction): Reaction = 232 | rx.withPostCommit((_:Unit) => pc(a)) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/scala/core/Ref.scala: -------------------------------------------------------------------------------- 1 | // Atomically updateable reference cells 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | import java.util.concurrent.atomic._ 7 | 8 | final class Ref[A <: AnyRef](init: A) { 9 | private[chemistry] val data = new AtomicReference(init) 10 | private[chemistry] val offers = new CircularPool[Offer[_]] 11 | 12 | private[chemistry] def afterCAS { 13 | @tailrec def wakeFrom(n: offers.Node): Unit = if (n != null) { 14 | n.data.abortAndWake 15 | wakeFrom(n.next) 16 | } 17 | wakeFrom(offers.cursor) 18 | } 19 | 20 | private final case class Read[B](k: Reagent[A,B]) extends Reagent[Unit, B] { 21 | def tryReact(u: Unit, rx: Reaction, offer: Offer[B]): Any = { 22 | if (offer != null) offers.put(offer) 23 | data.get match { 24 | case null => Retry 25 | case ans => ans 26 | } 27 | } 28 | def composeI[C](next: Reagent[B,C]) = Read(k >> next) 29 | def maySync = k.maySync 30 | def alwaysCommits = k.alwaysCommits 31 | def snoop(u: Unit) = data.get match { 32 | case null => false 33 | case ans => k.snoop(ans) 34 | } 35 | } 36 | @inline def read: Reagent[Unit,A] = Read(Commit[A]()) 37 | 38 | private final case class CAS[B](expect: A, update: A, k: Reagent[Unit,B]) 39 | extends Reagent[Unit, B] { 40 | // CAS can ignore the "offer", because no information flows from the 41 | // ref cell to the continuation k. 42 | def tryReact(u: Unit, rx: Reaction, offer: Offer[B]): Any = 43 | if (rx.canCASImmediate(k, offer)) { 44 | if (data.compareAndSet(expect, update)) 45 | k.tryReact((), rx, offer) 46 | else Retry 47 | } else k.tryReact((), rx.withCAS(Ref.this, expect, update), offer) 48 | 49 | def composeI[C](next: Reagent[B,C]) = CAS(expect, update, k >> next) 50 | def maySync = k.maySync 51 | def alwaysCommits = false 52 | def snoop(u: Unit) = false 53 | } 54 | @inline def cas(ov:A,nv:A): Reagent[Unit,Unit] = CAS(ov, nv, Commit[Unit]()) 55 | 56 | abstract class InnerUpd[B,C,D] private[chemistry] (k: Reagent[C,D]) 57 | extends Reagent[B, D] { 58 | def tryReact(b: B, rx: Reaction, offer: Offer[D]): Any = { 59 | if (rx.canCASImmediate(k, offer)) { 60 | // no need to store offer here, as we will either succeed or retry 61 | // (never block) 62 | 63 | val ov = data.get 64 | if ((ov eq null) || !valid(ov,b)) return Retry 65 | val nv = newValue(ov, b) 66 | if (data.compareAndSet(ov, nv)) 67 | k.tryReact(retValue(ov, b), rx, offer) 68 | else Retry 69 | } else { 70 | if (offer != null) offers.put(offer) 71 | 72 | val ov = data.get 73 | if ((ov eq null) || !valid(ov,b)) return Retry 74 | val nv = newValue(ov, b) 75 | k.tryReact(retValue(ov, b), rx.withCAS(Ref.this, ov, nv), offer) 76 | } 77 | } 78 | def composeI[E](next: Reagent[D,E]) = 79 | new InnerUpd[B,C,E](k.compose(next)) { 80 | final def newValue(a: A, b: B): A = InnerUpd.this.newValue(a, b) 81 | final def retValue(a: A, b: B): C = InnerUpd.this.retValue(a, b) 82 | } 83 | def maySync = k.maySync 84 | def alwaysCommits = false 85 | def snoop(b: B) = false 86 | 87 | def valid(a: A, b: B): Boolean = true 88 | def newValue(a: A, b: B): A 89 | def retValue(a: A, b: B): C 90 | def retryValue(cur: A, lastAttempt: A, b: B): A = newValue(cur, b) 91 | } 92 | abstract class Upd[B,C] extends InnerUpd[B,C,C](Commit[C]()) 93 | 94 | @inline def upd[B,C](f: (A,B) => (A,C)): Reagent[B, C] = 95 | new Upd[B,C] { 96 | @inline def newValue(a: A, b: B): A = f(a,b)._1 97 | @inline def retValue(a: A, b: B): C = f(a,b)._2 98 | } 99 | 100 | @inline def upd[B](f: PartialFunction[A,(A,B)]): Reagent[Unit, B] = 101 | new Upd[Unit,B] { 102 | @inline override def valid(a: A, u: Unit): Boolean = f.isDefinedAt(a) 103 | @inline def newValue(a: A, u: Unit): A = f(a)._1 104 | @inline def retValue(a: A, u: Unit): B = f(a)._2 105 | } 106 | 107 | } 108 | object Ref { 109 | @inline def apply[A <: AnyRef](init: A): Ref[A] = new Ref(init) 110 | @inline def unapply[A <: AnyRef](r: Ref[A]): Option[A] = { 111 | while (true) r.data.get match { 112 | case null => {} 113 | case ans => return Some(ans) 114 | } 115 | throw Util.Impossible 116 | } 117 | } 118 | 119 | object upd { 120 | @inline def apply[A <: AnyRef,B,C](r: Ref[A])(f: (A,B) => (A,C)) = 121 | r.upd(f) 122 | @inline def apply[A <: AnyRef,B](r: Ref[A])(f: PartialFunction[A, (A,B)]) = 123 | r.upd(f) 124 | 125 | /* 126 | @inline def fast[A <: AnyRef,B,C](r: Ref[A],f: (A,B) => (A,C)) = 127 | new Reagent[B,C] { 128 | private val k = Commit[C]() 129 | def tryReact(b: B, rx: Reaction, offer: Offer[C]): Any = { 130 | if (rx.canCASImmediate(k, offer)) { 131 | // no need to store offer here, as we will either succeed or retry 132 | // (never block) 133 | 134 | val ov = r.getI 135 | val (nv, retVal) = f(ov, b) 136 | if (r.casI(ov, nv)) retVal 137 | else Retry 138 | } else { 139 | if (offer != null) r.offers.put(offer) 140 | 141 | val ov = r.getI 142 | val (nv, retVal) = f(ov, b) 143 | k.tryReact(retVal, rx.withCAS(r, ov, nv), offer) 144 | } 145 | } 146 | def composeI[D](next: Reagent[C,D]) = throw Util.Impossible/* 147 | new r.InnerUpd[B,C,D](next) { 148 | @inline def newValue(a: A, b: B): A = f(a,b)._1 149 | @inline def retValue(a: A, b: B): C = f(a,b)._2 150 | }*/ 151 | def maySync = false 152 | def alwaysCommits = false 153 | def snoop(b: B) = false 154 | } 155 | */ 156 | } 157 | -------------------------------------------------------------------------------- /src/main/scala/core/Stats.scala: -------------------------------------------------------------------------------- 1 | // Tracks basic performance data of the library 2 | 3 | package chemistry 4 | 5 | private object Stats { 6 | // todo, obviously... 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/core/ThreadLocal.scala: -------------------------------------------------------------------------------- 1 | package chemistry 2 | 3 | class ThreadLocal[T](init: => T) 4 | extends java.lang.ThreadLocal[T] with Function0[T] { 5 | override def initialValue:T = init 6 | def apply = get 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/core/Util.scala: -------------------------------------------------------------------------------- 1 | // Misc utilities used throughout the Chemistry Set. Not exported. 2 | 3 | package chemistry 4 | 5 | import scala.util._ 6 | import scala.annotation.tailrec 7 | import scala.concurrent._ 8 | import scala.concurrent.ops._ 9 | import scala.math._ 10 | import java.lang.Thread 11 | 12 | private object Util { 13 | def undef[A]: A = throw new Exception() 14 | 15 | @inline def nanoToMilli(nano: Long): Double = nano / 1000000 16 | 17 | def time(thunk: => Unit): Double = { 18 | val t1 = System.nanoTime 19 | thunk 20 | val t2 = System.nanoTime 21 | nanoToMilli(t2 - t1) 22 | } 23 | 24 | @tailrec def untilSome[A](thunk: => Option[A]): Unit = 25 | thunk match { 26 | case None => untilSome(thunk) 27 | case Some(_) => () 28 | } 29 | 30 | @tailrec def whileNull[A <: AnyRef](thunk: => A): Unit = 31 | thunk match { 32 | case null => whileNull(thunk) 33 | case _ => () 34 | } 35 | 36 | def fork(code: => Unit) { 37 | val runnable = new Runnable { def run() { code } } 38 | (new Thread(runnable)).start() 39 | } 40 | 41 | def mean(ds: Seq[Double]): Double = ds.sum/ds.length 42 | def stddev(ds: Seq[Double]): Double = { 43 | val m = mean(ds) 44 | sqrt(ds.map(d => (d-m) * (d-m)).sum/ds.length) 45 | } 46 | def cov(ds: Seq[Double]): Double = 100 * (stddev(ds) / abs(mean(ds))) 47 | 48 | // compute6 from j.u.c. 49 | def noop(times: Int = 1): Int = { 50 | var seed: Int = 1 51 | var n: Int = times 52 | while (seed == 1 || n > 0) { // need to inspect seed or is optimized away 53 | seed = seed ^ (seed << 1) 54 | seed = seed ^ (seed >>> 3) 55 | seed = seed ^ (seed << 10) 56 | n -= 1 57 | } 58 | seed 59 | } 60 | 61 | // Handy exception to throw at unreachable code locations 62 | object Impossible extends Exception 63 | 64 | object Implicits { 65 | implicit def functionToPartialFunction[A,B](f: A => B): 66 | PartialFunction[A,B] = 67 | new PartialFunction[A,B] { 68 | def isDefinedAt(x:A) = true 69 | def apply(x:A) = f(x) 70 | } 71 | } 72 | 73 | // Untagged unions, due to Miles Sabin 74 | // see www.chuusai.com/2011/06/09/scala-union-types-curry-howard/ 75 | type Not[A] = A => Nothing 76 | type NotNot[A] = Not[Not[A]] 77 | type UntaggedSum[A,B] = { type F[X] = NotNot[X] <:< Not[Not[A] with Not[B]] } 78 | } 79 | 80 | // an unsynchronized, but thread-varying RNG 81 | private final class Random(var seed: Long = 1) { 82 | def nextSeed { 83 | seed = Random.nextSeed(seed) 84 | } 85 | 86 | def next(max: Int): Int = { 87 | nextSeed 88 | Random.scale(seed, max) 89 | } 90 | 91 | def fuzz(around: Int, percent: Int = 50): Int = { 92 | val max = (around * percent) / 100 93 | math.max(around + next(max) - (max >> 1), 0) 94 | } 95 | } 96 | private object Random { 97 | def nextSeed(oldSeed: Long): Long = { 98 | var seed = oldSeed 99 | seed = seed ^ (seed << 13) 100 | seed = seed ^ (seed >>> 7) 101 | seed = seed ^ (seed << 17) 102 | seed 103 | } 104 | def scale(seed: Long, max: Int): Int = { 105 | if (max <= 0) max else { 106 | val r = (seed % max).toInt 107 | if (r < 0) -r else r 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/scala/data/Counter.scala: -------------------------------------------------------------------------------- 1 | // A simple nonblocking counter. Can be used to implement semaphores, 2 | // or the equivalent to asynchronous unit channels in the join calculus. 3 | 4 | package chemistry 5 | 6 | final class Counter(init: Int = 0) { 7 | private val state = Ref[java.lang.Integer](init) 8 | 9 | val get = state.upd[Int] { case i => (i, i) } 10 | val inc = state.upd[Int] { case i => (i+1, i) } 11 | val dec = state.upd[Int] { case n if (n > 0) => (n-1, n) } 12 | val tryDec = state.upd[Option[Int]] { 13 | case n if (n == 0) => (0, None) 14 | case n => (n-1, Some(n)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/data/EliminationStack.scala: -------------------------------------------------------------------------------- 1 | // The Treiber stack with elimination-backoff, via reagents 2 | 3 | package chemistry 4 | 5 | final class EliminationStack[A >: Null] { 6 | private val stack = new TreiberStack[A] 7 | private val (elimPop, elimPush) = Chan[A]() 8 | 9 | val push: Reagent[A,Unit] = stack.push + elimPush 10 | val tryPop: Reagent[Unit,Option[A]] = stack.tryPop + elimPop.map(Some(_)) 11 | def pop: Reagent[Unit,A] = stack.pop + elimPop 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/data/HMList.scala: -------------------------------------------------------------------------------- 1 | // An implementation of the Harris-Michael linked list via reagents 2 | 3 | package chemistry 4 | 5 | /* 6 | 7 | // invariant: if a node n was ever reachable from head, and a node m 8 | // is reachable from n, then all keys >= m are reachable from n. 9 | sealed class Set[A] { 10 | private abstract class Node 11 | private abstract class PNode extends Node { 12 | def next: Ref[Node] 13 | def retryIfDel: Reagent[Unit] 14 | } 15 | private object PNode { 16 | def unapply(pn: PNode): Option[Ref[Node]] = Some(pn.next) 17 | } 18 | private case object Tail extends Node 19 | private case object Head extends PNode { 20 | val next: Ref[Node] = Ref(Tail) 21 | val retryIfDel: Reagent[Unit] = Return(()) 22 | } 23 | private case class INode( 24 | next: Ref[Node], 25 | data: A, 26 | deleted: Ref[Boolean] = Ref(false) 27 | ) extends PNode { 28 | val retryIfDel: Reagent[Unit] = deleted.cas(false, false) 29 | } 30 | 31 | private abstract class FindResult 32 | private case class Found(pred: PNode, node: INode) extends FindResult 33 | private case class NotFound(pred: PNode, succ: Node) extends FindResult 34 | 35 | private @inline final def find(key: Int): FindResult = { 36 | @tailrec def walk(c: PNode): (PNode, Node) = c match { 37 | case PNode(Ref(Tail)) => 38 | (c, Tail) 39 | case PNode(r@Ref(n@INode(Ref(m), _, Ref(true)))) => 40 | r.cas(n, m) !?; walk(c) 41 | case PNode(Ref(n@INode(_, data, Ref(false)))) => 42 | if (key == data.hashCode()) Found(c, n) 43 | else if (key < data.hashCode()) NotFound(c, n) 44 | else walk(n) 45 | } 46 | walk(Head) 47 | } 48 | 49 | final def add(item: A): Reagent[Boolean] = Loop { 50 | find(item.hashCode()) match { 51 | case Found(_, node) => // blocking would be *here* 52 | node.retryIfDel >> Return(false) 53 | case NotFound(pred, succ) => 54 | (pred.next.cas(succ, INode(Ref(succ), item)) >> 55 | pred.retryIfDel >> Return(true)) 56 | } 57 | } 58 | 59 | final def remove(item: A): Reagent[Boolean] = Loop { 60 | find(item.hashCode()) match { 61 | case NotFound(pred, _) => // blocking would be *here* 62 | pred.retryIfDel >> Return(false) 63 | case Found(pred, node) => 64 | (node.deleted.cas(false, true) >> Return(true)) commitThen 65 | pred.next.cas(node, node.next.get) !? 66 | } 67 | } 68 | 69 | // def contains: Reagent[A, Boolean] 70 | } 71 | 72 | */ 73 | -------------------------------------------------------------------------------- /src/main/scala/data/LaggingQueue.scala: -------------------------------------------------------------------------------- 1 | // An implementation of the Michael-Scott queue via reagents which 2 | // allows the tail pointer to lag arbitrarily 3 | 4 | package chemistry 5 | 6 | import scala.annotation.tailrec 7 | 8 | final class LaggingQueue[A >: Null] { 9 | private final case class Node(data: A, next: Ref[Node] = Ref(null)) 10 | private val head = Ref(Node(null)) 11 | private var tail = head.read!() 12 | 13 | val enq: Reagent[A, Unit] = computed { (x:A) => 14 | @tailrec def search: Reagent[Unit,Unit] = tail match { 15 | case Node(_, r@Ref(null)) => r.cas(null, Node(x)) 16 | case Node(_, Ref(nv)) => tail = nv; search 17 | } 18 | search 19 | } 20 | val tryDeq: Reagent[Unit, Option[A]] = head.upd[Option[A]] { 21 | case Node(_, Ref(n@Node(x, _))) => (n, Some(x)) 22 | case emp => (emp, None) 23 | } 24 | val deq: Reagent[Unit, A] = head.upd[A] { 25 | case Node(_, Ref(n@Node(x, _))) => (n, x) 26 | } 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/scala/data/MSQueue.scala: -------------------------------------------------------------------------------- 1 | // An implementation of the classic Michael-Scott queue via reagents 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | 7 | final class MSQueue[A >: Null] { 8 | private abstract class Q 9 | private final case class Node(data: A, next: Ref[Q] = Ref(Emp)) extends Q 10 | private final case object Emp extends Q 11 | private val head = Ref[Node](Node(null)) 12 | // private val tail = Ref(head.read!()) 13 | private var tail = head.read!() 14 | 15 | val enq: Reagent[A, Unit] = computed { (x:A) => 16 | val newNode = Node(x) 17 | @tailrec def search: Reagent[Unit,Unit] = { 18 | val nextRef = tail.next 19 | val next = nextRef.data.get 20 | if (next eq null) search 21 | else if (next eq Emp) nextRef.cas(Emp, newNode) 22 | else { 23 | tail = next.asInstanceOf[Node] 24 | search 25 | } 26 | } 27 | search 28 | } 29 | 30 | val tryDeq: Reagent[Unit, Option[A]] = head.upd[Option[A]] { 31 | case Node(_, Ref(n@Node(x, _))) => (n, Some(x)) 32 | case emp => (emp, None) 33 | } 34 | val deq: Reagent[Unit, A] = head.upd[A] { 35 | case Node(_, Ref(n@Node(x, _))) => (n, x) 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/scala/data/TreiberStack.scala: -------------------------------------------------------------------------------- 1 | // An implementation of the classic Treiber stack via reagents 2 | 3 | package chemistry 4 | 5 | import scala.annotation.tailrec 6 | import java.util.concurrent.atomic._ 7 | import scala.collection.immutable._ 8 | 9 | final class TreiberStack[A >: Null] { 10 | private val head = new Ref[List[A]](Nil) 11 | 12 | val push: Reagent[A,Unit] = head.upd[A,Unit] { 13 | case (xs,x) => (x::xs, ()) 14 | } 15 | 16 | val tryPop: Reagent[Unit,Option[A]] = head.upd[Option[A]] { 17 | case x::xs => (xs, Some(x)) 18 | case Nil => (Nil, None) 19 | } 20 | 21 | val pop: Reagent[Unit,A] = head.upd[A] { 22 | case x::xs => (xs, x) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/sync/CountDownLatch.scala: -------------------------------------------------------------------------------- 1 | // One-time use count-down latch 2 | 3 | package chemistry 4 | 5 | final class CountDownLatch(count: Int) { 6 | private val state = new Counter(count) 7 | val getCount = state.get 8 | val countDown = state.tryDec.map(_ => ()) 9 | val await = state.get.withFilter(_ == 0) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/sync/Exchanger.scala: -------------------------------------------------------------------------------- 1 | // A two-way exchanger: unlike channels, exchangers do not distinguish 2 | // one side from another 3 | 4 | package chemistry 5 | 6 | final class Exchanger[A] { 7 | private val (c, d) = SwapChan[A,A]() 8 | 9 | // could randomize order based on threadid -- or even build that in 10 | // to reagents. note that this trick *only* works for 2-way 11 | // exchange! 12 | val exchange: Reagent[A,A] = c + d 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/sync/Lock.scala: -------------------------------------------------------------------------------- 1 | // Simple boolean flag lock implementation; nonreentrant 2 | 3 | package chemistry 4 | 5 | class IllegalRelease extends Exception 6 | 7 | final class Lock { 8 | private sealed abstract class LockStatus 9 | private case object Locked extends LockStatus 10 | private case object Unlocked extends LockStatus 11 | 12 | private val status = Ref[LockStatus](Unlocked) 13 | 14 | final class Condition { 15 | def await { 16 | } 17 | def signal { 18 | } 19 | def signalAll { 20 | } 21 | } 22 | 23 | val tryAcq: Reagent[Unit,Boolean] = status.upd[Boolean] { 24 | case Locked => (Locked, false) 25 | case Unlocked => (Locked, true) 26 | } 27 | 28 | val acq: Reagent[Unit,Unit] = status.upd[Unit] { 29 | case Unlocked => (Locked, ()) 30 | } 31 | 32 | val rel: Reagent[Unit,Unit] = status.upd[Unit] { 33 | case Locked => (Unlocked, ()) 34 | case Unlocked => throw new IllegalRelease 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/sync/RWLock.scala: -------------------------------------------------------------------------------- 1 | // Simple nonreentrant rw-lock implementation 2 | 3 | package chemistry 4 | 5 | //class IllegalRelease extends Exception 6 | 7 | final class RWLock { 8 | private sealed abstract class LockStatus 9 | private case object Exclusive extends LockStatus 10 | private case class Shared(count: Ref[java.lang.Integer]) extends LockStatus 11 | private case object Unlocked extends LockStatus 12 | 13 | private val status = Ref[LockStatus](Unlocked) 14 | 15 | /* 16 | val tryAcqRead: Reagent[Unit,Boolean] = status.upd[Boolean] { 17 | case Exclusive => (Locked, false) 18 | case Unlocked => (Locked, true) 19 | } 20 | 21 | val acq: Reagent[Unit,Unit] = status.upd[Unit] { 22 | case Unlocked => (Locked, ()) 23 | } 24 | 25 | val rel: Reagent[Unit,Unit] = status.upd[Unit] { 26 | case Locked => (Unlocked, ()) 27 | case Unlocked => throw new IllegalRelease 28 | } 29 | */ 30 | } 31 | -------------------------------------------------------------------------------- /src/test/scala/BackoffSpec.scala: -------------------------------------------------------------------------------- 1 | import System.out._ 2 | import com.codahale.simplespec.Spec 3 | 4 | object BackoffSpec extends Spec { 5 | class `exponential backoff` { 6 | // def `should take time` { 7 | // val b = new Backoff() 8 | // val start = System.currentTimeMillis 9 | // for (i <- 1 to 12) b.spin 10 | // val end = System.currentTimeMillis 11 | // (end - start > 100) must be(true) 12 | // } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/PoolSpec.scala: -------------------------------------------------------------------------------- 1 | import scala.annotation.tailrec 2 | import System.out._ 3 | import java.util.concurrent.atomic._ 4 | import scala.concurrent.ops._ 5 | import org.specs2.mutable._ 6 | import chemistry._ 7 | 8 | trait PoolTests extends Specification { 9 | private abstract class DelStatus 10 | private final case object Deleted extends DelStatus 11 | private final case object Active extends DelStatus 12 | private case class TestItem(i: Int) extends DeletionFlag { 13 | val deletedFlag = new AtomicReference[DelStatus](Active) 14 | def isDeleted = deletedFlag.get == Deleted 15 | def delete = deletedFlag.compareAndSet(Active, Deleted) 16 | } 17 | 18 | protected def newPool[A <: DeletionFlag](): Pool[A] 19 | protected def title: String 20 | 21 | private def np = newPool[TestItem]() 22 | 23 | title should { 24 | "return a null cursor when empty" in { 25 | np.cursor === null 26 | } 27 | "return a nonnull cursor when nonempty" in { 28 | val p = np 29 | p.put(TestItem(2)) 30 | p.cursor !== null 31 | } 32 | "when a singleton, contain the single item" in { 33 | val p = np 34 | p.put(TestItem(2)) 35 | p.cursor.data.i === 2 36 | } 37 | "return a null cursor after removing all items" in { 38 | val p = np 39 | val ti = TestItem(2) 40 | p.put(ti) 41 | ti.delete 42 | p.cursor === null 43 | } 44 | "iterate through all inserted items (in any order)" in { 45 | val p = np 46 | p.put(TestItem(1)) 47 | p.put(TestItem(2)) 48 | val t1 = p.cursor 49 | val t2 = t1.next 50 | List(t1.data.i, t2.data.i) must contain(1,2).only 51 | } 52 | } 53 | } 54 | 55 | object CircularPoolSpec extends PoolTests { 56 | def title = "a CircularPool" 57 | def newPool[A <: DeletionFlag]() = new CircularPool[A] 58 | } 59 | -------------------------------------------------------------------------------- /src/test/scala/QueueSpec.scala: -------------------------------------------------------------------------------- 1 | import scala.annotation.tailrec 2 | import System.out._ 3 | import scala.concurrent.ops._ 4 | import com.codahale.simplespec.Spec 5 | import com.codahale.simplespec.annotation.test 6 | import chemistry._ 7 | 8 | trait QueueTests { 9 | import org.specs2.matcher.MustMatchers._ 10 | 11 | type queue[A >: Null] <: { 12 | val tryDeq: Reagent[Unit,Option[A]] 13 | val enq: Reagent[A,Unit] 14 | val deq: Reagent[Unit,A] 15 | } 16 | protected def newQueue[A >: Null](): queue[A] 17 | 18 | @test def `should tryDeq as None when empty` { 19 | var q = newQueue[java.lang.Integer]() 20 | q.tryDeq ! () must beNone 21 | } 22 | @test def `should tryDeq as Some _ when full` { 23 | var q = newQueue[java.lang.Integer]() 24 | q.enq ! 1; 25 | q.tryDeq ! () must beSome 26 | } 27 | @test def `should tryDeq as None after tryDequeuing` { 28 | var q = newQueue[java.lang.Integer]() 29 | q.enq!1; 30 | q.tryDeq!(); 31 | q.tryDeq !() must beNone 32 | } 33 | @test def `should tryDeq in order` { 34 | var q = newQueue[java.lang.Integer]() 35 | q.enq! 1; 36 | q.enq! 2; 37 | (q.tryDeq! (), q.tryDeq! ()) must beEqualTo(Some(1), Some(2)) 38 | } 39 | @test def `should enqueue from multiple threads in locally-ordered way` { 40 | val max = 100000 41 | 42 | var s = newQueue[java.lang.Integer]() 43 | TestUtil.spawnAndJoin (List( 44 | () => for (i <- 1 to max) s.enq ! i, 45 | () => for (i <- max+1 to 2*max) s.enq ! i)) 46 | 47 | val outcome = new Traversable[Int] { 48 | def foreach[U](f: Int => U) { 49 | s.tryDeq ! () match { 50 | case None => () 51 | case Some(i) => f(i.intValue); foreach(f) 52 | } 53 | } 54 | } 55 | 56 | val left = for (i <- outcome if i <= max) yield i 57 | val right = for (i <- outcome if i > max) yield i 58 | val comp = left.toSeq ++ right.toSeq 59 | val eqs = for ((i,j) <- comp zip (1 to 2*max)) yield i == j 60 | 61 | (true /: eqs)(_ && _) must beTrue 62 | } 63 | } 64 | 65 | object QueueSpec extends Spec { 66 | /* 67 | class `an MSQueue` extends QueueTests { 68 | type queue[A >: Null] = MSQueue[A] 69 | protected def newQueue[A >: Null]() = new MSQueue[A]() 70 | } 71 | class `a LaggingQueue` extends QueueTests { 72 | type queue[A >: Null] = LaggingQueue[A] 73 | protected def newQueue[A >: Null]() = new LaggingQueue[A]() 74 | } 75 | */ 76 | } 77 | -------------------------------------------------------------------------------- /src/test/scala/StackSpec.scala: -------------------------------------------------------------------------------- 1 | import scala.annotation.tailrec 2 | import System.out._ 3 | import scala.concurrent.ops._ 4 | import com.codahale.simplespec.Spec 5 | import com.codahale.simplespec.annotation.test 6 | import chemistry._ 7 | 8 | trait StackTests { 9 | import org.specs2.matcher.MustMatchers._ 10 | 11 | type stack[A >: Null] <: { 12 | val tryPop: Reagent[Unit,Option[A]] 13 | val push: Reagent[A,Unit] 14 | val pop: Reagent[Unit,A] 15 | } 16 | protected def newStack[A >: Null](): stack[A] 17 | 18 | @test def `should tryPop as None when empty` { 19 | val s = newStack[java.lang.Integer]() 20 | s.tryPop ! () must beNone 21 | } 22 | @test def `should tryPop as Some _ when full` { 23 | val s = newStack[java.lang.Integer]() 24 | s.push ! 1; 25 | s.tryPop ! () must beSome 26 | } 27 | @test def `should tryPop as None after emptying` { 28 | val s = newStack[java.lang.Integer]() 29 | s.push ! 1; 30 | s.tryPop ! (); 31 | s.tryPop ! () must beNone 32 | } 33 | @test def `should tryPop in reverse order` { 34 | val s = newStack[java.lang.Integer]() 35 | s.push ! 1; 36 | s.push ! 2; 37 | (s.tryPop!(), s.tryPop!()) must beEqualTo(Some(2), Some(1)) 38 | } 39 | 40 | def stackToTrav[A >: Null](s: stack[A]) = new Traversable[A] { 41 | def foreach[U](f: A => U) { 42 | while (true) s.tryPop ! () match { 43 | case None => return () 44 | case Some(a) => f(a) 45 | } 46 | } 47 | } 48 | 49 | def concTest: Boolean = { 50 | val max = 100000 51 | val s = newStack[java.lang.Integer]() 52 | 53 | TestUtil.spawnAndJoin (List( 54 | () => for (i <- 1 to max) s.push ! i, 55 | () => for (i <- max+1 to 2*max) s.push ! i)) 56 | 57 | val outcome = stackToTrav(s).map(_.intValue) 58 | val left = for (i <- outcome if i <= max) yield i 59 | val right = for (i <- outcome if i > max) yield i 60 | val comp = left.toSeq.reverse ++ right.toSeq.reverse 61 | val eqs = for ((i,j) <- comp zip (1 to 2*max)) yield i == j 62 | 63 | (true /: eqs)(_ && _) 64 | } 65 | 66 | @test def `should push from multiple threads in locally-ordered way` { 67 | val testResults = for (_ <- 1 to 10) yield concTest 68 | (true /: testResults)(_ && _) must beTrue 69 | } 70 | } 71 | 72 | object StackSpec extends Spec { 73 | class `a TreiberStack` extends StackTests { 74 | type stack[A >: Null] = TreiberStack[A] 75 | protected def newStack[A >: Null]() = new TreiberStack[A]() 76 | } 77 | class `an EliminationStack` extends StackTests { 78 | type stack[A >: Null] = EliminationStack[A] 79 | protected def newStack[A >: Null]() = new EliminationStack[A]() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/scala/TestUtil.scala: -------------------------------------------------------------------------------- 1 | import scala.concurrent.ops._ 2 | import scala.concurrent._ 3 | 4 | object TestUtil { 5 | // launch a list of threads in parallel, and wait till they all finish 6 | def spawnAndJoin(bodies: Seq[() => Unit]): Unit = 7 | (for (body <- bodies; 8 | done = new SyncVar[Unit]; 9 | _ = spawn { body(); done set () }) 10 | yield done) foreach (_.get) 11 | } 12 | -------------------------------------------------------------------------------- /web/cs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aturon/ChemistrySet/30b19a2a169e1fa1d266aa01afec158166720e61/web/cs.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |33 |
implicit def string2toInt(s: String): { def toInt: Int } = 34 | new { 35 | def toInt: Int = Integer.parseInt(s) 36 | } 37 |38 | 39 |
40 | Ut at dui ipsum, non feugiat metus. Sed sit amet lacus odio. Nam dolor urna, accumsan nec rhoncus eget, imperdiet sit amet lorem. Vivamus luctus placerat lacus non pretium. Sed tellus enim, scelerisque non dapibus et, ultrices ac massa. Maecenas vestibulum molestie felis vel porta. Praesent varius feugiat nulla, consequat luctus nulla vulputate vitae. Sed sagittis ultricies nulla eu fringilla. Suspendisse eget condimentum justo. Vestibulum vel enim quis est luctus mattis tristique sed enim. Curabitur tincidunt vehicula viverra. 41 | 42 |
43 | Vestibulum molestie blandit convallis. In diam justo, sodales id dapibus nec, tristique placerat mauris. Maecenas lobortis metus in risus viverra ac vulputate lacus viverra. Vestibulum odio ipsum, vehicula non sodales a, egestas et leo. Vestibulum nec eleifend velit. Ut diam ante, faucibus nec convallis id, pellentesque vitae metus. Nunc et quam eget nibh fermentum rutrum. Nullam urna neque, volutpat non malesuada eu, placerat placerat libero. Pellentesque sed lorem tellus, eget consequat metus. Sed rutrum aliquam pellentesque. Nam id pulvinar dui. Integer pharetra faucibus eleifend. In fringilla turpis adipiscing urna dapibus mattis. 44 | 45 |