├── .gitignore ├── BUILDING.txt ├── LICENSE.txt ├── README ├── RELEASE-NOTES.txt ├── bin ├── all_tests ├── fix_copyrights ├── perf_sample └── test_coverage ├── build.sbt ├── dep_tests ├── PUBLISHING.txt ├── check_central ├── maven │ ├── RUN_MAVEN_HERE_TO_TEST_DEPS │ ├── pom.xml │ └── src │ │ └── main │ │ └── scala │ │ └── HelloWorld.scala └── sbt │ ├── RUN_SBT_HERE_TO_TEST_DEPS │ ├── build.sbt │ └── src │ └── main │ └── scala │ └── HelloWorld.scala ├── disabled └── stmbench7 │ └── scalastm │ ├── AssemblyImpl.scala │ ├── AtomicPartImpl.scala │ ├── BaseAssemblyImpl.scala │ ├── ComplexAssemblyImpl.scala │ ├── CompositePartImpl.scala │ ├── DesignObjImpl.scala │ ├── DocumentImpl.scala │ ├── IdPoolImpl.scala │ ├── ImmutableSeqImpl.scala │ ├── ImmutableSetImpl.scala │ ├── IndexImpl.scala │ ├── LargeSetImpl.scala │ ├── ManualImpl.scala │ ├── ModuleImpl.scala │ └── ScalaSTMInitializer.scala ├── lib ├── sb7_java-v1.2.tgz ├── stmbench7-VELOX-1.2.LICENSE └── stmbench7-VELOX-1.2.jar ├── project ├── build.properties └── plugins.sbt └── src ├── main └── scala │ └── scala │ └── concurrent │ └── stm │ ├── CommitBarrier.scala │ ├── InTxn.scala │ ├── InTxnEnd.scala │ ├── MaybeTxn.scala │ ├── NestingLevel.scala │ ├── PendingAtomicBlock.scala │ ├── Ref.scala │ ├── RefLike.scala │ ├── Sink.scala │ ├── SinkLike.scala │ ├── Source.scala │ ├── SourceLike.scala │ ├── TArray.scala │ ├── TMap.scala │ ├── TSet.scala │ ├── Txn.scala │ ├── TxnDebuggable.scala │ ├── TxnExecutor.scala │ ├── TxnLocal.scala │ ├── TxnUnknown.scala │ ├── ccstm │ ├── AccessHistory.scala │ ├── CCSTM.scala │ ├── CCSTMExecutor.scala │ ├── CCSTMRefs.scala │ ├── CommitBarrierImpl.scala │ ├── Counter.scala │ ├── GV6.scala │ ├── Handle.scala │ ├── InTxnImpl.scala │ ├── InTxnRefOps.scala │ ├── NonTxn.scala │ ├── RefOps.scala │ ├── RetrySet.scala │ ├── RetrySetBuilder.scala │ ├── Stats.scala │ ├── TArrayImpl.scala │ ├── TxnLevelImpl.scala │ ├── TxnLocalImpl.scala │ ├── TxnSlotManager.scala │ ├── UnrecordedRead.scala │ ├── ViewOps.scala │ └── WakeupManager.scala │ ├── impl │ ├── AlternativeResult.scala │ ├── RefFactory.scala │ ├── STMImpl.scala │ └── TxnContext.scala │ ├── japi │ └── STM.scala │ ├── package.scala │ └── skel │ ├── AbstractInTxn.scala │ ├── AbstractNestingLevel.scala │ ├── AtomicArray.scala │ ├── AtomicArrayBuilder.scala │ ├── CallbackList.scala │ ├── HashTrieTMap.scala │ ├── HashTrieTSet.scala │ ├── RollbackError.scala │ ├── SimpleRandom.scala │ ├── StubInTxn.scala │ ├── StubSTMImpl.scala │ ├── TMapViaClone.scala │ ├── TSetViaClone.scala │ └── TxnHashTrie.scala └── test ├── java └── scala │ └── concurrent │ └── stm │ ├── JavaAPITests.java │ └── TestException.java └── scala └── scala └── concurrent └── stm ├── CallbackSuite.scala ├── CommitBarrierSuite.scala ├── ContentionSuite.scala ├── FlipperSuite.scala ├── HistogramSuite.scala ├── InterruptSuite.scala ├── IsolatedRefSuite.scala ├── JavaAPISuite.scala ├── MaybeTxnSuite.scala ├── RelaxedValidationSuite.scala ├── RetrySuite.scala ├── Slow.scala ├── TMapSuite.scala ├── TSetSuite.scala ├── TokenRingSuite.scala ├── TxnLocalSuite.scala ├── TxnSuite.scala ├── UnrecordedTxnSuite.scala ├── WriteSkewSuite.scala ├── examples ├── BasicSyntax.scala ├── ConcurrentIntList.scala ├── DiningPhilosophers.scala ├── IndexedMap.scala ├── RealityShowPhilosophers.scala └── SyntaxCheatSheet.scala ├── impl └── RefFactorySuite.scala └── skel ├── AtomicArraySuite.scala └── SimpleRandomSuite.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.iws 2 | .*.swp 3 | .idea/ 4 | out/ 5 | target 6 | lib_managed/ 7 | src_managed/ 8 | project/boot/ 9 | dep_tests/sbt/project/boot/ 10 | cover/ 11 | -------------------------------------------------------------------------------- /BUILDING.txt: -------------------------------------------------------------------------------- 1 | BUILDING FROM SOURCE 2 | 3 | scala-stm is built using sbt: http://www.scala-sbt.org/ 4 | Once you've installed sbt you can download scala-stm's compile dependency 5 | (ScalaTest), build, and run the unit tests with 6 | 7 | sbt update test 8 | 9 | You can get a list of sbt build targets with "sbt tasks". If you 10 | compile, generate documentation or build a JAR, look in target/scala_* for 11 | the results. The Scala version is configured in ./build.sbt. To run 12 | a build target for all of the versions prefix the action with a "+", 13 | for example "sbt +test". 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2012 Stanford University, unless otherwise specified. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Stanford University nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR 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 STANFORD UNIVERSITY BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ScalaSTM is a lightweight software transactional memory for Scala, 2 | inspired by the STMs in Haskell and Clojure. 3 | 4 | The current release is 0.8. There is not currently a published 5 | snapshot release. For download info and documentation see 6 | http://scala-stm.org 7 | -------------------------------------------------------------------------------- /RELEASE-NOTES.txt: -------------------------------------------------------------------------------- 1 | ScalaSTM - 0.8-SNAPSHOT RELEASE NOTES 2 | 3 | Changes between 0.7 and 0.8-SNAPSHOT: 4 | 5 | * correctness fix for TArray[Long] and AtomicArray.ofLong. 6 | 7 | * small improvement to TxnLocal interface. 8 | 9 | * add 2.12 build and remove 2.10 build. 10 | 11 | * add deprecated message about incomplete deadlock detection for 12 | CommitBarrier. 13 | 14 | Snapshot releases deployed to the oss.sonatype.org repository are tested 15 | and functional, but may have changing APIs. 16 | 17 | ---- 18 | 19 | Changes between 0.6 and 0.7: 20 | 21 | * better support for interactive debuggers, via TxnDebuggable and 22 | atomic.unrecorded. IntelliJ IDEA and Eclipse can now watch Ref 23 | values inside a transaction without affecting the read or write sets. 24 | 25 | * ScalaSTM cooperates with 2.10's scala.concurrent.BlockingContext. 26 | 27 | * added transformAndExtract, which allows an arbitrary value to be 28 | returned from the transformation function. 29 | 30 | * added transformAndGet and getAndTransform to Ref and TxnLocal, 31 | previously these were only defined for Ref.View. 32 | 33 | ---- 34 | 35 | Changes between 0.5 and 0.6: 36 | 37 | * retry and retryFor added to the Java compatibility interface. 38 | 39 | * uses of scala.actor.threadpool.TimeUnit in the interface replaced with 40 | java.util.concurrent.TimeUnit, to avoid making ScalaSTM depend on the 41 | separate scala-actors jar in Scala 2.10. 42 | 43 | ---- 44 | 45 | Changes between 0.4 and 0.5: 46 | 47 | * Added scala.concurrent.stm.japi.STM, which makes it much cleaner to 48 | access ScalaSTM functionality from Java. 49 | 50 | ---- 51 | 52 | Changes between 0.3 and 0.4: 53 | 54 | * CommitBarrier added, which allows multiple atomic blocks (each on its 55 | own thread) to commit together. 56 | 57 | * Small performance improvements. 58 | 59 | * STMBench7 benchmark support added. 60 | 61 | * Automatic selection of STMImpl in most cases. 62 | 63 | ---- 64 | 65 | Changes between 0.2 and 0.3: 66 | 67 | * Support for Scala 2.9.0.RC1. 68 | 69 | * Bug fixes (see https://github.com/nbronson/scala-stm/issues/closed ). 70 | 71 | * Timeouts for modular blocking. Set timeouts at the atomic block using 72 | atomic.withRetryTimeout, or at the retry site using retryFor. 73 | 74 | ---- 75 | 76 | Changes between 0.1 and 0.2: 77 | 78 | * Substantial performance improvements, especially for nested atomic 79 | blocks. 80 | 81 | * TSet.View and TMap.View are integrated into the Scala collection 82 | class hierarchy, with factory companion objects and Builder and 83 | CanBuildFrom instances. 84 | 85 | * A fix for whileCommitting handlers (issue #3). 86 | 87 | * TxnLocal can now be read and written from while-preparing and while- 88 | committing handlers. Combining TxnLocal and life-cycle handlers is 89 | now more concise. 90 | 91 | * Transaction statistics can be enabled for the default algorithm 92 | with the VM argument -Dccstm.stats=1 (details in the ScalaDoc for 93 | scala.concurrent.stm.ccstm.CCSTM). 94 | 95 | -------------------------------------------------------------------------------- /bin/all_tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | set -e 5 | 6 | BASE="`dirname $0`/.." 7 | cd $BASE 8 | 9 | if [ "x$SCALA_VERSIONS" = "x" ]; then 10 | SCALA_VERSIONS="`awk -F= '$1==\"build.scala.versions\" {print $2}' project/build.properties`" 11 | fi 12 | 13 | for v in $SCALA_VERSIONS; do 14 | TARGET_BASE="target/scala_$v" 15 | SCALATEST=$(echo lib_managed/scala_$v/compile/scalatest*.jar) 16 | sbt ++$v test-compile 17 | scala -Dccstm.stats=1 -cp ${SCALATEST}:$TARGET_BASE/classes org.scalatest.tools.Runner -l slow -oW -p $TARGET_BASE/test-classes 18 | scala -cp ${SCALATEST}:$TARGET_BASE/classes org.scalatest.tools.Runner -oW -p $TARGET_BASE/test-classes 19 | done 20 | -------------------------------------------------------------------------------- /bin/fix_copyrights: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd `dirname $0`/.. 4 | for f in `grep -l -r '(c) 2009' src`; do 5 | d=`git log -1 --date=short --no-notes -- $f | awk 'NR==3 {print $2}'` 6 | y=`echo $d | sed 's/-.*//'` 7 | sed -i "s/(c) 2009-201./(c) 2009-$y/" $f 8 | done 9 | -------------------------------------------------------------------------------- /bin/perf_sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | BASE="`dirname $0`/.." 6 | BRANCH="`git status | awk '/^# On branch/ {print $NF}'`" 7 | 8 | cd $BASE 9 | WHEN=`date '+%Y%m%d-%H%M%S'` 10 | DIR="perf/`hostname`/perf-$BRANCH-$WHEN" 11 | 12 | if [ "x$VERSIONS" = "x" ]; then 13 | VERSIONS="`awk -F= '$1==\"build.scala.versions\" {print $2}' project/build.properties | sed 's/,/ /g'`" 14 | fi 15 | 16 | mkdir -p "$DIR" 17 | git show --quiet HEAD > "$DIR"/git.hash 18 | 19 | for v in $VERSIONS; do 20 | sbt ++$v test-compile > /dev/null 2>&1 21 | sbt ++$v test test > "$DIR"/.out 2>&1 22 | ( 23 | cd "$DIR" 24 | sed 's/[^m]*m//g' .out | uniq | gawk ' 25 | /== test-start ==/ {x=x+1;y=0} 26 | /== test-finish ==/ {y=1} 27 | {z=1} 28 | /^.info/ {z=0} 29 | x>0 && (y || z) {print >> ("." x)}' 30 | rm .out 31 | mv .1 test-first-$v.log 32 | mv .2 test-second-$v.log 33 | ) 34 | done 35 | -------------------------------------------------------------------------------- /bin/test_coverage: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | COBERTURA_DIR=$HOME/cobertura/cobertura-1.9.4.1 6 | SCALATEST_VERSION=1.2 7 | 8 | BASE="`dirname $0`/.." 9 | cd $BASE 10 | 11 | WHEN=`date '+%Y%m%d-%H%M%S'` 12 | DIR="cover/cover-$WHEN" 13 | SER="$DIR/cobertura.ser" 14 | 15 | if [ "x$SCALA_VERSION" = "x" ]; then 16 | SCALA_VERSION="`awk -F= '$1==\"build.scala.versions\" {print $2}' project/build.properties | sed 's/ .*//g'`" 17 | fi 18 | TARGET_BASE="target/scala_$SCALA_VERSION" 19 | 20 | SCALATEST="lib_managed/scala_$SCALA_VERSION/compile/scalatest-$SCALATEST_VERSION.jar" 21 | 22 | mkdir -p $DIR 23 | git show --quiet HEAD > $DIR/git.hash 24 | 25 | sbt ++$SCALA_VERSION test-compile > "$DIR"/log 2>&1 26 | 27 | rm -rf $TARGET_BASE/classes-instr 28 | sh $COBERTURA_DIR/cobertura-instrument.sh \ 29 | --basedir $TARGET_BASE/classes \ 30 | --datafile $SER \ 31 | --destination $TARGET_BASE/classes-instr \ 32 | scala 33 | 34 | # we have to include classes because cobertura skips those with no methods 35 | scala -cp ${SCALATEST}:$COBERTURA_DIR/cobertura.jar:$TARGET_BASE/classes-instr:$TARGET_BASE/classes \ 36 | -Dnet.sourceforge.cobertura.datafile=$SER \ 37 | -Dccstm.stats=1 \ 38 | org.scalatest.tools.Runner \ 39 | -oW -l slow -p $TARGET_BASE/test-classes 40 | 41 | sh $COBERTURA_DIR/cobertura-report.sh \ 42 | --datafile $SER \ 43 | --destination $DIR \ 44 | $BASE/src/main/scala 45 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | name := "scala-stm" 3 | organization := "org.scala-stm" 4 | version := "0.8-SNAPSHOT" 5 | def mimaVersion = "0.8" 6 | scalaVersion := "2.12.4" 7 | crossScalaVersions := Seq("2.11.12", "2.12.4", "2.13.0-M3") 8 | scalacOptions ++= Seq("-deprecation", "-feature") 9 | 10 | javacOptions in (Compile, compile) ++= { 11 | val javaVersion = if (scalaVersion.value.startsWith("2.11")) "1.6" else "1.8" 12 | Seq("-source", javaVersion, "-target", javaVersion) 13 | } 14 | 15 | libraryDependencies += { 16 | val v = if (scalaVersion.value == "2.13.0-M3") "3.0.5-M1" else "3.0.5" 17 | "org.scalatest" %% "scalatest" % v % "test" 18 | } 19 | 20 | libraryDependencies += ("junit" % "junit" % "4.12" % "test") 21 | 22 | mimaPreviousArtifacts := Set(organization.value %% name.value % mimaVersion) 23 | 24 | // skip exhaustive tests 25 | testOptions += Tests.Argument("-l", "slow") 26 | 27 | // test of TxnExecutor.transformDefault must be run by itself 28 | parallelExecution in Test := false 29 | 30 | //////////////////// 31 | // publishing 32 | 33 | pomExtra := { 34 | http://nbronson.github.com/scala-stm/ 35 | 36 | 37 | BSD 38 | https://github.com/nbronson/scala-stm/blob/master/LICENSE.txt 39 | repo 40 | 41 | 42 | 43 | scm:git:git@github.com:nbronson/scala-stm.git 44 | git@github.com:nbronson/scala-stm.git 45 | 46 | 47 | 48 | nbronson 49 | Nathan Bronson 50 | ngbronson@gmail.com 51 | 52 | 53 | } 54 | 55 | publishMavenStyle := true 56 | 57 | publishTo := { 58 | val base = "https://oss.sonatype.org/" 59 | if (version.value.trim.endsWith("SNAPSHOT")) 60 | Some("snapshots" at base + "content/repositories/snapshots/") 61 | else 62 | Some("releases" at base + "service/local/staging/deploy/maven2/") 63 | } 64 | 65 | // exclude scalatest from the Maven POM 66 | pomPostProcess := { xi: scala.xml.Node => 67 | import scala.xml._ 68 | val badDeps = (xi \\ "dependency").filter { 69 | x => (x \ "artifactId").text != "scala-library" 70 | } .toSet 71 | def filt(root: Node): Node = root match { 72 | case x: Elem => 73 | val ch = x.child.filter(!badDeps(_)).map(filt) 74 | Elem(x.prefix, x.label, x.attributes, x.scope, ch.isEmpty, ch: _*) 75 | 76 | case x => x 77 | } 78 | filt(xi) 79 | } 80 | 81 | credentials += Credentials(Path.userHome / ".ivy2" / ".credentials") 82 | -------------------------------------------------------------------------------- /dep_tests/PUBLISHING.txt: -------------------------------------------------------------------------------- 1 | DEPLOYING THE SCALA-STM ARTIFACT 2 | 3 | With a proper credentials file publishing a snapshot or release artifact is 4 | accomplished with 5 | 6 | sbt +test (or bin/all_tests) 7 | sbt +publish 8 | 9 | TESTING DEPLOYMENT AND ARTIFACT USAGE 10 | 11 | To test using published artifacts via sbt, select the desired scala-stm 12 | artifact in dep_tests/sbt/build.sbt . Then 13 | 14 | cd dep_tests/sbt 15 | sbt +run 16 | 17 | To test building using maven2, edit dep_tests/maven/pom.xml, then 18 | 19 | cd dep_tests/maven 20 | mvn -U compile 21 | -------------------------------------------------------------------------------- /dep_tests/check_central: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # http://search.maven.org/remotecontent?filepath=org/scala-tools/scala-stm_2.10.0-M7/0.6/scala-stm_2.10.0-M7-0.6.pom 4 | 5 | BUILD_SBT=$(dirname $0)/../build.sbt 6 | GROUP_ID="scala-stm" 7 | 8 | echo "Checking groupId [[$GROUP_ID]]" 9 | echo 10 | 11 | sed 's/[":=(,)]/ /g' "$BUILD_SBT" | \ 12 | awk '$1=="version" {stm=$2} 13 | $1=="crossScalaVersions" {for(i=3;i<=NF;++i) {print stm,$i}}' | \ 14 | while read x y; do 15 | /bin/echo -en "stm $x scala $y \t" 16 | # URL="http://search.maven.org/remotecontent?filepath=org/scala-stm/scala-stm_$y/$x/scala-stm_$y-$x.pom" 17 | URL="http://repo1.maven.org/maven2/org/$GROUP_ID/scala-stm_$y/$x/scala-stm_$y-$x.pom" 18 | curl -s "$URL" | grep -q "scala-stm_$y" 19 | if [ $? -eq 0 ]; then 20 | echo "synced" 21 | else 22 | echo "NOT SYNCED" 23 | fi 24 | done 25 | 26 | 27 | -------------------------------------------------------------------------------- /dep_tests/maven/RUN_MAVEN_HERE_TO_TEST_DEPS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbronson/scala-stm/150408e695684cb0df034bf870860361a434e327/dep_tests/maven/RUN_MAVEN_HERE_TO_TEST_DEPS -------------------------------------------------------------------------------- /dep_tests/maven/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.12.0 4 | 2.12 5 | 0.8-SNAPSHOT 6 | 7 | 8 | 4.0.0 9 | org.scala-stm 10 | scala-stm-dep-tests-maven 11 | 0.1-SNAPSHOT 12 | jar 13 | 14 | 15 | 16 | oss.sonatype.org 17 | OSS Sonatype Release Repository 18 | https://oss.sonatype.org/content/repositories/releases 19 | 20 | 21 | oss.sonatype.org.snapshots 22 | OSS Sonatype Snapshot Repository 23 | https://oss.sonatype.org/content/repositories/snapshots 24 | 25 | 26 | 27 | 28 | 29 | 30 | oss.sonatype.org 31 | OSS Sonatype Release Repository 32 | https://oss.sonatype.org/content/repositories/releases 33 | 34 | 35 | oss.sonatype.org.snapshots 36 | OSS Sonatype Snapshot Repository 37 | https://oss.sonatype.org/content/repositories/snapshots 38 | 39 | 40 | 41 | 42 | 43 | org.scala-lang 44 | scala-compiler 45 | ${scala.version} 46 | 47 | 48 | org.scala-lang 49 | scala-library 50 | ${scala.version} 51 | 52 | 53 | org.scala-stm 54 | scala-stm_${scala.major.version} 55 | ${scala.stm.version} 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 2.0.2 65 | 66 | 1.6 67 | 1.6 68 | 69 | 70 | 71 | org.scala-tools 72 | maven-scala-plugin 73 | 2.14 74 | 75 | 76 | 77 | compile 78 | testCompile 79 | 80 | 81 | 82 | 83 | ${scala.version} 84 | 85 | -target:jvm-1.5 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /dep_tests/maven/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | import scala.concurrent.stm._ 2 | 3 | object HelloWorld { 4 | def main(args: Array[String]) { 5 | val x = Ref("hello world!") 6 | println(x.single()) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dep_tests/sbt/RUN_SBT_HERE_TO_TEST_DEPS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbronson/scala-stm/150408e695684cb0df034bf870860361a434e327/dep_tests/sbt/RUN_SBT_HERE_TO_TEST_DEPS -------------------------------------------------------------------------------- /dep_tests/sbt/build.sbt: -------------------------------------------------------------------------------- 1 | 2 | name := "scala-stm-dep-tests-sbt" 3 | 4 | organization := "org.scala-stm" 5 | 6 | version := "0.1-SNAPSHOT" 7 | 8 | scalaVersion := "2.12.0" 9 | 10 | crossScalaVersions := Seq("2.12.0", "2.11.6") 11 | 12 | resolvers += ("releases" at "https://oss.sonatype.org/content/repositories/releases") 13 | 14 | resolvers += ("snapshots" at "https://oss.sonatype.org/content/repositories/snapshots") 15 | 16 | libraryDependencies += ("org.scala-stm" %% "scala-stm" % "0.8-SNAPSHOT") 17 | -------------------------------------------------------------------------------- /dep_tests/sbt/src/main/scala/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | import scala.concurrent.stm._ 2 | 3 | object HelloWorld { 4 | def main(args: Array[String]) { 5 | val x = Ref("hello world!") 6 | println(x.single()) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/AssemblyImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | 8 | abstract class AssemblyImpl(id: Int, typ: String, buildDate: Int, module0: Module, superAssembly0: ComplexAssembly 9 | ) extends DesignObjImpl(id, typ, buildDate) with Assembly { 10 | 11 | private val module = Ref(module0).single 12 | private val superAssembly = Ref(superAssembly0).single 13 | 14 | def getSuperAssembly = superAssembly() 15 | def getModule = module() 16 | 17 | def clearPointers() { 18 | superAssembly() = null 19 | module() = null 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/AtomicPartImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | import stmbench7.impl.core.ConnectionImpl 8 | 9 | class AtomicPartImpl(id0: Int, typ0: String, bd0: Int, x0: Int, y0: Int) extends DesignObjImpl(id0, typ0, bd0) with AtomicPart { 10 | val x = Ref(x0) 11 | val y = Ref(y0) 12 | val partOf = Ref(null : CompositePart).single 13 | val from = TSet.empty[Connection].single // this is the equivant of SmallSetImpl 14 | val to = TSet.empty[Connection].single 15 | 16 | def connectTo(dest: AtomicPart, typ: String, length: Int) { 17 | val c = new ConnectionImpl(this, dest, typ, length) 18 | to += c 19 | dest.addConnectionFromOtherPart(c.getReversed) 20 | } 21 | def addConnectionFromOtherPart(c: Connection) { from += c } 22 | def setCompositePart(po: CompositePart) { partOf() = po } 23 | def getNumToConnections = to.size 24 | def getToConnections = new ImmutableSetImpl[Connection](to) 25 | def getFromConnections = new ImmutableSetImpl[Connection](from) 26 | def getPartOf = partOf() 27 | def swapXY() { 28 | atomic { implicit t => 29 | y() = x.swap(y()) 30 | } 31 | } 32 | def getX = x.single() 33 | def getY = y.single() 34 | def clearPointers() { 35 | atomic { implicit t => 36 | x() = 0 37 | y() = 0 38 | to.clear() 39 | from.clear() 40 | partOf() = null 41 | } 42 | } 43 | 44 | // Comparable[AtomicPart] 45 | def compareTo(rhs: AtomicPart) = getId - rhs.getId // subtraction is faithful to reference impl 46 | } 47 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/BaseAssemblyImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | 8 | class BaseAssemblyImpl(id: Int, typ: String, buildDate: Int, module: Module, superAssembly: ComplexAssembly 9 | ) extends AssemblyImpl(id, typ, buildDate, module, superAssembly) with BaseAssembly { 10 | 11 | val components = Ref(List.empty[CompositePart]).single // the original BagImpl was just an ArrayList 12 | 13 | def addComponent(component: CompositePart) { 14 | components.transform(component :: _) 15 | component.addAssembly(this) 16 | } 17 | 18 | def removeComponent(component: CompositePart) = { 19 | val f = components transformIfDefined { 20 | case x if x contains component => x filterNot { _ == component } 21 | } 22 | if (f) component.removeAssembly(this) 23 | f 24 | } 25 | 26 | def getComponents = new ImmutableSeqImpl[CompositePart](components()) 27 | 28 | override def clearPointers() { 29 | super.clearPointers() 30 | components() = null 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/ComplexAssemblyImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | import stmbench7.Parameters 8 | 9 | class ComplexAssemblyImpl(id: Int, typ: String, buildDate: Int, module: Module, superAssembly: ComplexAssembly 10 | ) extends AssemblyImpl(id, typ, buildDate, module, superAssembly) with ComplexAssembly { 11 | 12 | val subAssemblies = TSet.empty[Assembly].single 13 | val level = if (superAssembly == null) Parameters.NumAssmLevels else superAssembly.getLevel - 1 14 | 15 | def addSubAssembly(assembly: Assembly) = { 16 | if(assembly.isInstanceOf[BaseAssembly] && level != 2) 17 | throw new RuntimeError("ComplexAssembly.addAssembly: BaseAssembly at wrong level!"); 18 | 19 | subAssemblies.add(assembly) 20 | } 21 | 22 | def removeSubAssembly(assembly: Assembly) = subAssemblies.remove(assembly) 23 | 24 | def getSubAssemblies = new ImmutableSetImpl[Assembly](subAssemblies) 25 | 26 | def getLevel = level.asInstanceOf[Short] 27 | 28 | override def clearPointers() { 29 | super.clearPointers() 30 | subAssemblies.clear() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/CompositePartImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | import stmbench7.backend.BackendFactory 8 | 9 | class CompositePartImpl(id: Int, typ: String, buildDate: Int, documentation0: Document 10 | ) extends DesignObjImpl(id, typ, buildDate) with CompositePart { 11 | val documentation = Ref(documentation0).single 12 | val usedIn = Ref(List.empty[BaseAssembly]).single 13 | val parts = Ref(BackendFactory.instance.createLargeSet[AtomicPart]).single 14 | val rootPart = Ref(null : AtomicPart).single 15 | documentation0.setPart(this) 16 | 17 | def addAssembly(assembly: BaseAssembly) { usedIn.transform(assembly :: _) } 18 | 19 | def addPart(part: AtomicPart) = { 20 | val f = parts().add(part) 21 | if (f) { 22 | part.setCompositePart(this) 23 | if(rootPart() == null) rootPart() = part 24 | } 25 | f 26 | } 27 | 28 | def getRootPart = rootPart() 29 | def setRootPart(part: AtomicPart) { rootPart() = part } 30 | 31 | def getDocumentation = documentation() 32 | 33 | def getParts = parts() 34 | 35 | def removeAssembly(assembly: BaseAssembly ) { 36 | usedIn.transform { _.filterNot(_ == assembly) } 37 | } 38 | 39 | def getUsedIn = new ImmutableSeqImpl[BaseAssembly](usedIn()) 40 | 41 | def clearPointers() { 42 | documentation() = null 43 | parts() = null 44 | usedIn() = null 45 | rootPart() = null 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/DesignObjImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | 8 | class DesignObjImpl(id: Int, typ: String, buildDate0: Int) extends DesignObj { 9 | val bd = Ref(buildDate0).single 10 | 11 | def getId = id 12 | def getType = typ 13 | def getBuildDate = bd() 14 | def updateBuildDate() { if (bd() % 2 == 0) bd -= 1 else bd += 1 } 15 | def nullOperation() {} 16 | } 17 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/DocumentImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.annotation.tailrec 6 | import scala.concurrent.stm._ 7 | import stmbench7.core._ 8 | 9 | class DocumentImpl(id: Int, title: String, text0: String) extends Document { 10 | val text = Ref(text0).single 11 | val part = Ref(null : CompositePart).single 12 | 13 | def getDocumentId = id 14 | def getTitle = title 15 | 16 | def getCompositePart = part() 17 | def setPart(v: CompositePart) { part() = v } 18 | 19 | def nullOperation() {} 20 | 21 | def getText = text() 22 | def searchText(symbol: Char): Int = searchText(text(), symbol, 0, 0) 23 | @tailrec private def searchText(s: String, c: Char, pos: Int, n: Int): Int = { 24 | val i = s.indexOf(c, pos) 25 | if (i < 0) n else searchText(s, c, i + 1, n + 1) 26 | } 27 | def replaceText(from: String, to: String) = { 28 | val f = text transformIfDefined { 29 | case s if s.startsWith(from) => s.replaceFirst(from, to) 30 | } 31 | if (f) 1 else 0 32 | } 33 | def textBeginsWith(prefix: String) = text().startsWith(prefix) 34 | } 35 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/IdPoolImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | import stmbench7.backend.IdPool 8 | 9 | object IdPoolImpl { 10 | class BoxedList(n: Int) extends IdPool { 11 | val underlying = Ref(List.range(1, n + 1)).single 12 | 13 | def putUnusedId(id: Int) { underlying.transform(id :: _) } 14 | 15 | def getId = { 16 | underlying.getAndTransform(_.drop(1)) match { 17 | case head :: _ => head 18 | case _ => throw new OperationFailedException 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/ImmutableSeqImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.collection.JavaConversions 6 | import stmbench7.backend.ImmutableCollection 7 | 8 | class ImmutableSeqImpl[A](contents: Seq[A]) extends ImmutableCollection[A] { 9 | override def clone = this 10 | def contains(element: A) = contents.contains(element) 11 | def size = contents.size 12 | def iterator = JavaConversions.asJavaIterator(contents.iterator) 13 | } 14 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/ImmutableSetImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.collection.JavaConversions 6 | import scala.concurrent.stm._ 7 | import stmbench7.backend.ImmutableCollection 8 | 9 | /** Read-only wrapper */ 10 | class ImmutableSetImpl[A](contents: TSet.View[A], shared: Boolean = true) extends ImmutableCollection[A] { 11 | override def clone: ImmutableSetImpl[A] = if (!shared) this else new ImmutableSetImpl(contents.clone(), false) 12 | def contains(element: A) = contents.contains(element) 13 | def size = contents.size 14 | def iterator = JavaConversions.asJavaIterator(contents.iterator) 15 | } 16 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/IndexImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.annotation._ 6 | import scala.collection.immutable.{RedBlack, TreeMap} 7 | import scala.collection.mutable.ArrayBuffer 8 | import scala.collection.JavaConversions 9 | import scala.concurrent.stm._ 10 | import stmbench7.backend.Index 11 | 12 | object IndexImpl { 13 | class BoxedImmutable[A <: Comparable[A], B] extends Index[A,B] { 14 | // needed for 2.8, harmless in 2.9 15 | implicit val order = new Ordering[A] { 16 | def compare(lhs: A, rhs: A) = lhs compareTo rhs 17 | } 18 | 19 | val underlying = Ref(TreeMap.empty[A,B]).single 20 | 21 | def get(key: A) = underlying().getOrElse(key, null.asInstanceOf[B]) 22 | 23 | def put(key: A, value: B) { underlying.transform(_ + (key -> value)) } 24 | 25 | def putIfAbsent(key: A, value: B): B = { 26 | underlying.getAndTransform({ m => if (m.contains(key)) m else m + (key -> value) }).getOrElse(key, null.asInstanceOf[B]) 27 | } 28 | 29 | def remove(key: A) = underlying transformIfDefined { 30 | case m if m.contains(key) => m - key 31 | } 32 | 33 | def iterator = makeValuesIterator(underlying()) 34 | 35 | def getKeys = JavaConversions.asJavaSet(underlying().keySet) 36 | 37 | def getRange(minKey: A, maxKey: A) = new java.lang.Iterable[B] { 38 | val range = underlying().range(minKey, maxKey) 39 | def iterator = makeValuesIterator(range) 40 | } 41 | 42 | private def makeValuesIterator(m: TreeMap[A, B]) = { 43 | if (treeMapTreeField == null) 44 | JavaConversions.asJavaIterator(m.values.iterator) 45 | else 46 | makeCustomValuesIterator(m) 47 | } 48 | 49 | // We implement our own iterator because the Scala one generates 50 | // a large quantity of garbage pre 2.10. 51 | private def makeCustomValuesIterator(m: TreeMap[A, B]) = { 52 | val root = treeMapTreeField.get(m).asInstanceOf[RedBlack[A]#Tree[B]] 53 | new java.util.Iterator[B] { 54 | val avail = new ArrayBuffer[RedBlack[A]#NonEmpty[B]] // values ready to return 55 | pushAll(root) 56 | 57 | @tailrec final def pushAll(n: RedBlack[A]#Tree[B]) { 58 | n match { 59 | case ne: RedBlack[A]#NonEmpty[B] => { 60 | avail += ne 61 | pushAll(ne.left) 62 | } 63 | case _ => 64 | } 65 | } 66 | 67 | def hasNext = !avail.isEmpty 68 | 69 | def next(): B = { 70 | val n = avail.remove(avail.size - 1) 71 | pushAll(n.right) 72 | n.value 73 | } 74 | 75 | def remove() = throw new UnsupportedOperationException 76 | } 77 | } 78 | } 79 | 80 | private val treeMapTreeField = { 81 | val f = classOf[TreeMap[String, String]].getDeclaredField("tree") 82 | if (f.getType == classOf[RedBlack[String]#Tree[String]]) { 83 | // pre Scala 2.10, we need the custom iterator hack 84 | f.setAccessible(true) 85 | f 86 | } else { 87 | // Scala 2.10 or later uses RedBlackTree, which array-based iter 88 | null 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/LargeSetImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.collection.JavaConversions 6 | import scala.concurrent.stm._ 7 | import stmbench7.backend.LargeSet 8 | 9 | // LargeSetImpl 10 | 11 | class LargeSetImpl[A <: Comparable[A]] extends LargeSet[A] { 12 | val underlying = TMap.empty[A,Unit].single 13 | 14 | def add(e: A) = underlying.put(e, ()).isEmpty 15 | def remove(e: A) = !underlying.remove(e).isEmpty 16 | def contains(e: A) = underlying.contains(e) 17 | def size = underlying.size 18 | def iterator = JavaConversions.asJavaIterator(underlying.keysIterator) 19 | } 20 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/ManualImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import scala.collection.immutable.WrappedString 7 | import scala.annotation.tailrec 8 | import stmbench7.core._ 9 | 10 | class ManualImpl(id: Int, title0: String, text0: String) extends Manual { 11 | val title = Ref(title0).single 12 | val text = Ref(text0).single 13 | val module = Ref(null : Module).single 14 | 15 | def getId = id 16 | def getTitle = title() 17 | def getText = text() 18 | def getModule = module() 19 | 20 | def setModule(v: Module) { module() = v } 21 | 22 | def countOccurences(ch: Char): Int = countOccurrences(ch) 23 | def countOccurrences(ch: Char): Int = countOccurrences(text(), ch, 0, 0) 24 | @tailrec private def countOccurrences(s: String, c: Char, pos: Int, n: Int): Int = { 25 | val i = s.indexOf(c, pos) 26 | if (i < 0) n else countOccurrences(s, c, i + 1, n + 1) 27 | } 28 | 29 | def checkFirstLastCharTheSame = { 30 | val t = text() 31 | if (t.charAt(0) == t.charAt(t.length - 1)) 1 else 0 32 | } 33 | 34 | def startsWith(ch: Char) = text().charAt(0) == ch 35 | 36 | def replaceChar(from: Char, to: Char) = { 37 | text.transform(_.replace(from, to)) 38 | countOccurences(to) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/ModuleImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.core._ 7 | 8 | class ModuleImpl(id: Int, typ: String, buildDate: Int, man: Manual 9 | ) extends DesignObjImpl(id, typ, buildDate) with Module { 10 | 11 | val designRoot = Ref(null : ComplexAssembly).single 12 | 13 | man.setModule(this) 14 | 15 | def setDesignRoot(v: ComplexAssembly ) { designRoot() = v } 16 | def getDesignRoot = designRoot() 17 | def getManual = man 18 | } 19 | -------------------------------------------------------------------------------- /disabled/stmbench7/scalastm/ScalaSTMInitializer.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package stmbench7.scalastm 4 | 5 | import scala.concurrent.stm._ 6 | import stmbench7.backend._ 7 | import stmbench7.core._ 8 | import stmbench7.impl.core.ConnectionImpl 9 | import stmbench7._ 10 | 11 | class ScalaSTMInitializer extends SynchMethodInitializer { 12 | def createOperationExecutorFactory() = new OperationExecutorFactory { 13 | 14 | val timestamp = Ref(0) 15 | 16 | def createOperationExecutor(op: Operation) = new OperationExecutor { 17 | var lastTS = 0 18 | 19 | def execute(): Int = { 20 | atomic { implicit t => 21 | val z = op.performOperation() 22 | if (Parameters.sequentialReplayEnabled) { 23 | timestamp += 1 24 | lastTS = timestamp() 25 | } 26 | z 27 | } 28 | } 29 | 30 | def getLastOperationTimestamp = lastTS 31 | } 32 | } 33 | 34 | def createBackendFactory() = new BackendFactory { 35 | 36 | // a couple hundred, not ordered, except for huge numbers of discarded 37 | // sets with exactly 1 element 38 | def createLargeSet[E <: Comparable[E]](): LargeSet[E] = new LargeSetImpl[E] 39 | 40 | // ordered 41 | def createIndex[K <: Comparable[K],V](): Index[K,V] = new IndexImpl.BoxedImmutable[K,V] 42 | 43 | def createIdPool(maxNumberOfIds: Int): IdPool = new IdPoolImpl.BoxedList(maxNumberOfIds) 44 | } 45 | 46 | def createDesignObjFactory() = new DesignObjFactory { 47 | 48 | def createAtomicPart(id: Int, typ: String, bd: Int, x: Int, y: Int) = 49 | new AtomicPartImpl(id, typ, bd, x, y) 50 | 51 | def createConnection(from: AtomicPart, to: AtomicPart, typ: String, length: Int) = 52 | new ConnectionImpl(from, to, typ, length) 53 | 54 | def createBaseAssembly(id: Int, typ: String, buildDate: Int, module: Module, superAssembly: ComplexAssembly) = 55 | new BaseAssemblyImpl(id, typ, buildDate, module, superAssembly) 56 | 57 | def createComplexAssembly(id: Int, typ: String, buildDate: Int, module: Module, superAssembly: ComplexAssembly) = 58 | new ComplexAssemblyImpl(id, typ, buildDate, module, superAssembly) 59 | 60 | def createCompositePart(id: Int, typ: String, buildDate: Int, documentation: Document) = 61 | new CompositePartImpl(id, typ, buildDate, documentation) 62 | 63 | def createDocument(id: Int, title: String, text: String) = 64 | new DocumentImpl(id, title, text) 65 | 66 | def createManual(id: Int, title: String, text: String) = 67 | new ManualImpl(id, title, text) 68 | 69 | def createModule(id: Int, typ: String, buildDate: Int, man: Manual) = 70 | new ModuleImpl(id, typ, buildDate, man) 71 | } 72 | 73 | def createThreadFactory() = new stmbench7.ThreadFactory { 74 | def createThread(body: Runnable) = new Thread(body) 75 | } 76 | } -------------------------------------------------------------------------------- /lib/sb7_java-v1.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbronson/scala-stm/150408e695684cb0df034bf870860361a434e327/lib/sb7_java-v1.2.tgz -------------------------------------------------------------------------------- /lib/stmbench7-VELOX-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbronson/scala-stm/150408e695684cb0df034bf870860361a434e327/lib/stmbench7-VELOX-1.2.jar -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.18") // test binary compatibility between minor versions 2 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/InTxn.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | /** The presence of an implicit `InTxn` instance grants the caller permission 6 | * to perform transactional reads and writes on `Ref` instances, as well as 7 | * permission to call `object Txn` methods that require an `InTxnEnd`. 8 | * `InTxn` instances themselves might be reused by the STM, use 9 | * `NestingLevel.current` or `NestingLevel.root` to get a `NestingLevel` if 10 | * you need to track an individual execution attempt. 11 | * 12 | * @author Nathan Bronson 13 | */ 14 | trait InTxn extends InTxnEnd 15 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/InTxnEnd.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | /** The presence of an implicit `InTxnEnd` instance inside a transaction 6 | * life-cycle handler grants permission to call methods in `object Txn` that 7 | * locate nesting levels or register additional handlers. This functionality 8 | * is separated from that granted by `InTxn` because `Ref` operations are not 9 | * allowed from handlers after commit has begun. 10 | * 11 | * @author Nathan Bronson 12 | */ 13 | trait InTxnEnd extends MaybeTxn { 14 | import Txn._ 15 | 16 | // The user-visible versions of these methods are in the Txn object. 17 | 18 | protected[stm] def status: Status 19 | protected[stm] def rootLevel: NestingLevel 20 | protected[stm] def currentLevel: NestingLevel 21 | protected[stm] def rollback(cause: RollbackCause): Nothing 22 | protected[stm] def retry(): Nothing 23 | protected[stm] def retryFor(timeoutNanos: Long): Unit 24 | protected[stm] def beforeCommit(handler: InTxn => Unit): Unit 25 | protected[stm] def whilePreparing(handler: InTxnEnd => Unit): Unit 26 | protected[stm] def whileCommitting(handler: InTxnEnd => Unit): Unit 27 | protected[stm] def afterCommit(handler: Status => Unit): Unit 28 | protected[stm] def afterRollback(handler: Status => Unit): Unit 29 | protected[stm] def afterCompletion(handler: Status => Unit): Unit 30 | protected[stm] def setExternalDecider(decider: ExternalDecider): Unit 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/MaybeTxn.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | object MaybeTxn { 6 | implicit val unknown = TxnUnknown 7 | } 8 | 9 | /** `MaybeTxn` allows lookup of the implicit `InTxn` instance without failing 10 | * if the `InTxn` is not known at compile time. `implicitly[MaybeTxn]` will 11 | * bind to an implicit `InTxn` if one is available, otherwise it will bind to 12 | * the object `TxnUnkown`. A `MaybeTxn` of `TxnUnknown` should trigger a 13 | * dynamically-scoped `InTxn` search using `Txn.findCurrent`. 14 | * 15 | * @author Nathan Bronson 16 | */ 17 | trait MaybeTxn 18 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/NestingLevel.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | object NestingLevel { 6 | 7 | /** Returns the `NestingLevel` that represents the current atomic block 8 | * execution attempt. 9 | */ 10 | def current(implicit txn: InTxnEnd): NestingLevel = txn.currentLevel 11 | 12 | /** Returns the `NestingLevel` that represents the outermost atomic block 13 | * that is currently being attempted. 14 | */ 15 | def root(implicit txn: InTxnEnd): NestingLevel = txn.rootLevel 16 | } 17 | 18 | /** A `NestingLevel` instance describes a single attempt to execute an atomic 19 | * block inside a transaction. Reads and writes performed by a transaction 20 | * will only be made visible to other threads after (if) the root nesting 21 | * level commits. 22 | * 23 | * Methods on this class may be called from any thread, and may be called 24 | * after the corresponding execution attempt has been completed. 25 | * 26 | * @author Nathan Bronson 27 | */ 28 | trait NestingLevel { 29 | import Txn._ 30 | 31 | /** Returns the `TxnExecutor` in which this attempt is executing. */ 32 | def executor: TxnExecutor 33 | 34 | /** Returns the nearest enclosing nesting level, if any. */ 35 | def parent: Option[NestingLevel] 36 | 37 | /** Returns the outermost enclosing nested transaction context, or this 38 | * instance if it is the outermost nesting level. It is always true that 39 | * `a.parent.isEmpty == (a.root == a)`. 40 | */ 41 | def root: NestingLevel 42 | 43 | /** Returns a snapshot of this nesting level's current status. The status 44 | * may change to `Txn.RolledBack` due to the actions of a concurrent 45 | * thread. This method may be called from any thread. 46 | */ 47 | def status: Status 48 | 49 | /** Requests that a transaction attempt be marked for rollback, possibly 50 | * also rolling back some or all of the enclosing nesting levels. Returns 51 | * the resulting status, which will be one of `Prepared`, `Committed` or 52 | * `RolledBack`. Regardless of the status, this method does not throw an 53 | * exception. 54 | * 55 | * Unlike `Txn.rollback(cause)`, this method may be called from any thread. 56 | * Note that there is no facility for remotely triggering a rollback during 57 | * the `Prepared` state, as the `ExplicitDecider` is given the final choice. 58 | */ 59 | def requestRollback(cause: RollbackCause): Status 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/PendingAtomicBlock.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | /** Instances of `PendingAtomicBlock` defer the execution of an atomic block 6 | * until all of the alternatives can be gathered from the user. There is an 7 | * implicit conversion in the `stm` package object from any type `A` to a 8 | * `PendingAtomicBlock[A]`, which will kick in if there is an attempt to call 9 | * `.orAtomic` on a value. 10 | * 11 | * @author Nathan Bronson 12 | */ 13 | class PendingAtomicBlock[A](above: => A) { 14 | 15 | /** See `atomic.oneOf`. */ 16 | def orAtomic[B >: A](below: InTxn => B)(implicit mt: MaybeTxn): B = { 17 | // Execution of Delayed.orAtomic proceeds bottom to top, with the upper 18 | // portions being captured by-name in `above`. The actual transactional 19 | // execution is all performed inside the top-most block (the one that 20 | // used atomic rather than orAtomic). If a block other than the top one 21 | // is the one that eventually succeeds, we must tunnel the value out with 22 | // an exception because the alternatives may have a wider type. We only 23 | // catch the exception if we are the bottom-most alternative, because 24 | // only it is guaranteed to have been fully widened. 25 | if (!atomic.pushAlternative(mt, below)) { 26 | // we're not the bottom 27 | above 28 | } else { 29 | // we're the bottom, this is the end of the result tunnel 30 | try { 31 | above 32 | } catch { 33 | case impl.AlternativeResult(x) => x.asInstanceOf[B] 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/RefLike.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | 6 | /** Provides all of the operations of a `Ref[A]`, without the ability to get a 7 | * `Ref.View`. 8 | * 9 | * @author Nathan Bronson 10 | */ 11 | trait RefLike[A, Context] extends SourceLike[A, Context] with SinkLike[A, Context] { 12 | 13 | // read-only operations (covariant) are in SourceLike 14 | // write-only operations (contravariant) are in SinkLike 15 | // read+write operations go here 16 | 17 | /** Works like `set(v)`, but returns the old value. 18 | * @return the previous value of this `Ref`, as observed by `txn`. 19 | * @throws IllegalStateException if `txn` is not active. 20 | */ 21 | def swap(v: A)(implicit txn: Context): A 22 | 23 | /** Transforms the value referenced by this `Ref` by applying the function 24 | * `f`. Acts like `ref.set(f(ref.get))`, but the execution of `f` may be 25 | * deferred or repeated by the STM to reduce transaction conflicts. 26 | * @param f a function that is safe to call multiple times, and safe to 27 | * call later during the transaction. 28 | * @throws IllegalStateException if `txn` is not active. 29 | */ 30 | def transform(f: A => A)(implicit txn: Context): Unit 31 | 32 | /** Transforms the value referenced by this `Ref` by applying the function 33 | * `f`, and returns the new value. 34 | * @param f a function that is safe to call multiple times. 35 | * @return the new value of this `Ref` (the value returned from `f`). 36 | * @throws IllegalStateException if `txn` is not active. 37 | */ 38 | def transformAndGet(f: A => A)(implicit txn: Context): A = { val z = f(get) ; set(z) ; z } 39 | 40 | /** Transforms the value referenced by this `Ref` by applying the function 41 | * `f`, and returns the previous value. 42 | * @param f a function that is safe to call multiple times. 43 | * @return the previous value of this `Ref` (the value passed to `f`). 44 | * @throws IllegalStateException if `txn` is not active. 45 | */ 46 | def getAndTransform(f: A => A)(implicit txn: Context): A = { val z = get ; set(f(z)) ; z } 47 | 48 | /** Transforms the value referenced by this `Ref` from ''v'' to 49 | * `f`(''v'')`._1`, and returns `f`(''v'')`._2`. 50 | * @param f a function that is safe to call multiple times. 51 | * @return the second element of the pair returned by `f`. 52 | * @throws IllegalStateException if `txn` is not active. 53 | */ 54 | def transformAndExtract[B](f: A => (A,B))(implicit txn: Context): B = { val p = f(get) ; set(p._1) ; p._2 } 55 | 56 | /** Transforms the value ''v'' referenced by this `Ref` by to 57 | * `pf.apply`(''v''), but only if `pf.isDefinedAt`(''v''). Returns true if 58 | * a transformation was performed, false otherwise. `pf.apply` and 59 | * `pf.isDefinedAt` may be deferred or repeated by the STM to reduce 60 | * transaction conflicts. 61 | * @param pf a partial function that is safe to call multiple times, and 62 | * safe to call later in the transaction. 63 | * @return `pf.isDefinedAt(v)`, where `v` was the value of this `Ref` 64 | * before transformation (if any). 65 | * @throws IllegalStateException if `txn` is not active. 66 | */ 67 | def transformIfDefined(pf: PartialFunction[A,A])(implicit txn: Context): Boolean 68 | 69 | /** Transforms the value stored in the `Ref` by incrementing it. 70 | * 71 | * '''Note: Implementations may choose to ignore the provided `Numeric[A]` 72 | * instance if `A` is a primitive type.''' 73 | * 74 | * @param rhs the quantity by which to increment the value of this `Ref`. 75 | * @throws IllegalStateException if `txn` is not active. */ 76 | def += (rhs: A)(implicit txn: Context, num: Numeric[A]): Unit = transform { v => num.plus(v, rhs) } 77 | 78 | /** Transforms the value stored in the `Ref` by decrementing it. 79 | * 80 | * '''Note: Implementations may choose to ignore the provided `Numeric[A]` 81 | * instance if `A` is a primitive type.''' 82 | * 83 | * @param rhs the quantity by which to decrement the value of this `Ref`. 84 | * @throws IllegalStateException if `txn` is not active. */ 85 | def -= (rhs: A)(implicit txn: Context, num: Numeric[A]): Unit = transform { v => num.minus(v, rhs) } 86 | 87 | /** Transforms the value stored in the `Ref` by multiplying it. 88 | * 89 | * '''Note: Implementations may choose to ignore the provided `Numeric[A]` 90 | * instance if `A` is a primitive type.''' 91 | * 92 | * @param rhs the quantity by which to multiply the value of this `Ref`. 93 | * @throws IllegalStateException if `txn` is not active. 94 | */ 95 | def *= (rhs: A)(implicit txn: Context, num: Numeric[A]): Unit = transform { v => num.times(v, rhs) } 96 | 97 | /** Transforms the value stored the `Ref` by performing a division on it, 98 | * throwing away the remainder if division is not exact for instances of 99 | * type `A`. The careful reader will note that division is actually 100 | * provided by `Fractional[A]` or `Integral[A]`, it is not defined on 101 | * `Numeric[A]`. To avoid compile-time ambiguity this method accepts a 102 | * `Numeric[A]` and assumes that it can be converted at runtime into 103 | * either a `Fractional[A]` or an `Integral[A]`. 104 | * 105 | * '''Note: Implementations may choose to ignore the provided `Numeric[A]` 106 | * instance if `A` is a primitive type.''' 107 | * 108 | * @param rhs the quantity by which to divide the value of this `Ref`. 109 | * @throws IllegalStateException if `txn` is not active. 110 | */ 111 | def /= (rhs: A)(implicit txn: Context, num: Numeric[A]): Unit = 112 | num match { 113 | //case numF: Fractional[A] => transform { v => numF.div(v, rhs) } 114 | case numF: Fractional[_] => transform { v => numF.asInstanceOf[Fractional[A]].div(v, rhs) } 115 | //case numI: Integral[A] => transform { v => numI.quot(v, rhs) } 116 | case numI: Integral[_] => transform { v => numI.asInstanceOf[Integral[A]].quot(v, rhs) } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/Sink.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | object Sink { 6 | 7 | /** `Sink.View[+A]` consists of the contra-variant write-only operations of 8 | * `Ref.View[A]`. 9 | */ 10 | trait View[-A] { 11 | 12 | /** Returns a `Sink` that accesses the same memory location as this view. 13 | * The returned `Sink` might be the original reference that was used to 14 | * construct this view, or it might be a `Sink` that is equivalent 15 | * (and `==`) to the original. 16 | * @return a `Sink` that accesses the same memory location as this view. 17 | */ 18 | def ref: Sink[A] 19 | 20 | /** Performs an atomic write of the value in `ref`. If an atomic block 21 | * is active (see `Txn.findCurrent`) then the write will be performed 22 | * as part of the transaction, otherwise it will act as if it was 23 | * performed inside a new atomic block. Equivalent to `set(v)`. 24 | */ 25 | def update(v: A): Unit = set(v) 26 | 27 | /** Performs an atomic write; equivalent to `update(v)`. */ 28 | def set(v: A): Unit 29 | 30 | /** Performs an atomic write and returns true, or returns false. The 31 | * STM implementation may choose to return false to reduce (not 32 | * necessarily avoid) blocking. If no other threads are performing any 33 | * transactional or atomic accesses then this method will succeed. 34 | */ 35 | def trySet(v: A): Boolean 36 | } 37 | } 38 | 39 | /** `Sink[+A]` consists of the contra-variant write-only operations of 40 | * `Ref[A]`. 41 | * 42 | * @author Nathan Bronson 43 | */ 44 | trait Sink[-A] extends SinkLike[A, InTxn] { 45 | 46 | /** See `Ref.single`. */ 47 | def single: Sink.View[A] 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/SinkLike.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | 6 | /** Provides all of the operations of a `Sink[A]`, without the ability to get 7 | * a `Sink.View`. 8 | * 9 | * @author Nathan Bronson 10 | */ 11 | trait SinkLike[-A, Context] { 12 | 13 | /** Performs a transactional write. The new value will not be visible by 14 | * any other threads until (and unless) `txn` successfully commits. 15 | * Equivalent to `set(v)`. 16 | * 17 | * Example: {{{ 18 | * val x = Ref(0) 19 | * atomic { implicit t => 20 | * ... 21 | * x() = 10 // perform a write inside a transaction 22 | * ... 23 | * } 24 | * }}} 25 | * @param v a value to store in the `Ref`. 26 | * @throws IllegalStateException if `txn` is not active. */ 27 | def update(v: A)(implicit txn: Context): Unit = set(v) 28 | 29 | /** Performs a transactional write. The new value will not be visible by 30 | * any other threads until (and unless) `txn` successfully commits. 31 | * Equivalent to `update(v)`. 32 | * @param v a value to store in the `Ref`. 33 | * @throws IllegalStateException if `txn` is not active. 34 | */ 35 | def set(v: A)(implicit txn: Context): Unit 36 | 37 | /** Performs a transactional write and returns true, or returns false. The 38 | * STM implementation may choose to return false to reduce (not necessarily 39 | * avoid) blocking. If no other threads are performing any transactional or 40 | * atomic accesses then this method will succeed. 41 | */ 42 | def trySet(v: A)(implicit txn: Context): Boolean 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/Source.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import java.util.concurrent.TimeUnit 6 | 7 | object Source { 8 | 9 | /** `Source.View[+A]` consists of the covariant read-only operations of 10 | * `Ref.View[A]`. 11 | */ 12 | trait View[+A] extends TxnDebuggable { 13 | 14 | /** Returns a `Source` that accesses the same memory location as this view. 15 | * The returned `Source` might be the original reference that was used to 16 | * construct this view, or it might be a `Source` that is equivalent 17 | * (and `==`) to the original. 18 | * @return a `Source` that accesses the same memory location as this view. 19 | */ 20 | def ref: Source[A] 21 | 22 | /** Performs an atomic read of the value in `ref`. If an atomic block is 23 | * active (see `Txn.findCurrent`) then the read will be performed as part 24 | * of the transaction, otherwise it will act as if it was performed inside 25 | * a new atomic block. Equivalent to `get`. 26 | * @return the value of the `Ref` as observed by the current context. 27 | */ 28 | def apply(): A = get 29 | 30 | /** Performs an atomic read; equivalent to `apply()`. 31 | * @return the value of the `Ref` as observed by the current context. 32 | */ 33 | def get: A 34 | 35 | /** Acts like `ref.getWith(f)` if there is an active transaction, otherwise 36 | * just returns `f(get)`. 37 | * @param f an idempotent function. 38 | * @return the result of applying `f` to the value contained in `ref`. 39 | */ 40 | def getWith[Z](f: A => Z): Z 41 | 42 | /** Acts like `ref.relaxedGet(equiv)` if there is an active transaction, 43 | * otherwise just returns `get`. 44 | * @param equiv an equivalence function that returns true if a transaction 45 | * that observed the first argument will still complete correctly, 46 | * where the second argument is the actual value that should have been 47 | * observed. 48 | * @return a value of the `Ref`, not necessary consistent with the rest of 49 | * the reads performed by the active transaction, if any. 50 | */ 51 | def relaxedGet(equiv: (A, A) => Boolean): A 52 | 53 | /** Blocks until `f(get)` is true, in a manner consistent with the current 54 | * context. Requires that the predicate be safe to reevaluate, and that 55 | * `f(x) == f(y)` if `x == y`. 56 | * 57 | * `v.await(f)` is equivalent to {{{ 58 | * atomic { implicit t => 59 | * if (!f(v.get)) retry 60 | * } 61 | * }}} 62 | * 63 | * If you want to wait for a predicate that involves more than one `Ref` 64 | * then use `retry` directly. 65 | * @param f a predicate that is safe to evaluate multiple times. 66 | */ 67 | def await(f: A => Boolean): Unit 68 | 69 | /** Blocks until `f(get)` is true and returns true, or returns false if 70 | * the condition does not become true within within the specified timeout. 71 | * 72 | * `v.tryAwait(timeout)(f)` is equivalent to {{{ 73 | * atomic { implicit t => 74 | * f(v.get) || { retryFor(timeout) ; false } 75 | * } 76 | * }}} 77 | * 78 | * @param f a predicate that is safe to evaluate multiple times. 79 | * @param timeout the maximum amount of time to wait, in units of `unit`. 80 | * @param unit the units in which the timeout is measured, defaulting to 81 | * milliseconds. 82 | * @return true if the predicate was satisfied, false if the wait timed 83 | * out. 84 | */ 85 | def tryAwait(timeout: Long, unit: TimeUnit = TimeUnit.MILLISECONDS)(f: A => Boolean): Boolean 86 | 87 | def dbgStr: String = ref.dbgStr 88 | def dbgValue: Any = ref.dbgValue 89 | } 90 | } 91 | 92 | /** `Source[+A]` consists of the covariant read-only operations of `Ref[A]`. */ 93 | trait Source[+A] extends SourceLike[A, InTxn] with TxnDebuggable { 94 | 95 | /** See `Ref.single`. */ 96 | def single: Source.View[A] 97 | 98 | def dbgStr: String = atomic.unrecorded({ implicit txn => "Ref(" + get + ")" }, { _.toString }) 99 | def dbgValue: Any = atomic.unrecorded({ get(_) }, { x => x }) 100 | } 101 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/SourceLike.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | 6 | /** Provides all of the operations of a `Source[A]`, without the ability to get 7 | * a `Source.View`. 8 | * 9 | * @author Nathan Bronson 10 | */ 11 | trait SourceLike[+A, Context] { 12 | 13 | /** Performs a transactional read and checks that it is consistent with all 14 | * reads already made by `txn`. Equivalent to `get`. 15 | * 16 | * Example: {{{ 17 | * val x = Ref(0) 18 | * atomic { implicit t => 19 | * ... 20 | * val v = x() // perform a read inside a transaction 21 | * ... 22 | * } 23 | * }}} 24 | * @param txn an active transaction. 25 | * @return the value of the `Ref` as observed by `txn`. 26 | * @throws IllegalStateException if `txn` is not active. 27 | */ 28 | def apply()(implicit txn: Context): A = get 29 | 30 | /** Performs a transactional read and checks that it is consistent with all 31 | * reads already made by `txn`. Equivalent to `apply()`, which is more 32 | * concise in many situations. 33 | * @param txn an active transaction. 34 | * @return the value of the `Ref` as observed by `txn`. 35 | * @throws IllegalStateException if `txn` is not active. 36 | */ 37 | def get(implicit txn: Context): A 38 | 39 | /** Returns `f(get)`, possibly reevaluating `f` to avoid rollback if a 40 | * conflicting change is made but the old and new values are equal after 41 | * application of `f`. Requires that `f(x) == f(y)` if `x == y`. 42 | * 43 | * `getWith(f)` is equivalent to `f(relaxedGet({ f(_) == f(_) }))`, although 44 | * perhaps more efficient. 45 | * @param f an idempotent function. 46 | * @return the result of applying `f` to the value contained in this `Ref`. 47 | */ 48 | def getWith[Z](f: A => Z)(implicit txn: Context): Z 49 | 50 | /** Returns the same value as `get`, but allows the caller to determine 51 | * whether `txn` should be rolled back if another thread changes the value 52 | * of this `Ref` before `txn` is committed. If `ref.relaxedGet(equiv)` 53 | * returns `v0` in `txn`, another context changes `ref` to `v1`, and 54 | * `equiv(v0, v1) == true`, then `txn` won't be required to roll back (at 55 | * least not due to this read). If additional changes are made to `ref` 56 | * additional calls to the equivalence function will be made, always with 57 | * `v0` as the first parameter. 58 | * 59 | * `equiv` will always be invoked on the current thread. Extreme care 60 | * should be taken if the equivalence function accesses any `Ref`s. 61 | * 62 | * As an example, to perform a read that will not be validated during commit 63 | * you can use the maximally permissive equivalence function: {{{ 64 | * val unvalidatedValue = ref.relaxedGet({ (_, _) => true }) 65 | * }}} 66 | * To check view serializability rather than conflict serializability for a 67 | * read: {{{ 68 | * val viewSerializableValue = ref.relaxedGet({ _ == _ }) 69 | * }}} 70 | * The `getWith` method provides related functionality. 71 | * @param equiv an equivalence function that returns true if a transaction 72 | * that observed the first argument will still complete correctly, 73 | * where the second argument is the actual value that should have been 74 | * observed. 75 | * @return a value of the `Ref`, not necessary consistent with the rest of 76 | * the reads performed by `txn`. 77 | */ 78 | def relaxedGet(equiv: (A, A) => Boolean)(implicit txn: Context): A 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/TArray.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import scala.collection.{immutable, mutable} 6 | import scala.reflect.ClassTag 7 | 8 | object TArray { 9 | 10 | /** A view that supports accesses to a `TArray` instance outside the static 11 | * scope of a `Txn`. `TArray.View` is to `TArray` as `Ref.View` is to 12 | * `Ref`. 13 | */ 14 | trait View[A] extends mutable.IndexedSeq[A] with TxnDebuggable { 15 | /** The `TArray` from which this view was created. */ 16 | def tarray: TArray[A] 17 | 18 | def length: Int 19 | 20 | /** Performs an atomic read of the `index`th element of `array`. If an 21 | * atomic block is active (see `Txn.findCurrent`) then the read will be 22 | * performed as part of the transaction, otherwise it will act as if it 23 | * was performed inside a new atomic block. 24 | */ 25 | def apply(index: Int): A 26 | 27 | /** Performs an atomic write of the `index`th element of `array`. If an 28 | * atomic block is active (see `Txn.findCurrent`) then the write will be 29 | * performed as part of the transaction, otherwise it will act as if it 30 | * was performed inside a new atomic block. 31 | */ 32 | def update(index: Int, v: A): Unit 33 | 34 | /** Returns a sequence of `Ref.View` that are backed by the elements of 35 | * `array`. All operations on the contained `Ref.View`s are supported. 36 | */ 37 | def refViews: immutable.IndexedSeq[Ref.View[A]] 38 | } 39 | 40 | //////////////// factory methods 41 | 42 | // We don't include apply(xs: A*) because it is surprising when 43 | // TArray[Int](1000) creates a TArray of length 1. 44 | 45 | /** Returns a new `TArray[A]` containing `length` copies of the default value 46 | * for elements of type `A`. 47 | */ 48 | def ofDim[A : ClassTag](length: Int): TArray[A] = impl.STMImpl.instance.newTArray[A](length) 49 | 50 | /** Returns a new `TArray[A]` containing the elements of `data`. */ 51 | def apply[A : ClassTag](data: TraversableOnce[A]): TArray[A] = impl.STMImpl.instance.newTArray[A](data) 52 | } 53 | 54 | /** Bulk transactional storage, roughly equivalent to `Array[Ref[T]]` but 55 | * potentially much more space efficient. Elements can be read and written 56 | * directly, or the `refs` method can be used to obtain transient `Ref` 57 | * instances backed by the elements of the `TArray`. 58 | * 59 | * @author Nathan Bronson 60 | */ 61 | trait TArray[A] extends TxnDebuggable { 62 | 63 | /** Returns the length of this `TArray`, which does not change. */ 64 | def length: Int 65 | 66 | /** Performs a transactional read of the `index`th element of this 67 | * transactional array. Equivalent to `refs(index).get`. 68 | */ 69 | def apply(index: Int)(implicit txn: InTxn): A 70 | 71 | /** Performs a transactional write to the `index`th element of this 72 | * transactional array. Equivalent to `refs(index).set(v)`. 73 | */ 74 | def update(index: Int, v: A)(implicit txn: InTxn): Unit 75 | 76 | /** Returns a `TArray.View` that allows access to the contents of this 77 | * `TArray` without requiring that an `InTxn` be available. See `Ref.View`. 78 | */ 79 | def single: TArray.View[A] 80 | 81 | /** Returns a sequence of `Ref` instances that are backed by elements of this 82 | * `TArray`. All operations on the contained `Ref`s are supported. 83 | * 84 | * As an example, the following code tests whether `a(i)` is greater than 85 | * 10 without requiring the transaction to roll back for all writes to 86 | * `a(i)`: {{{ 87 | * atomic { implicit t => 88 | * if (a.refs(i).getWith( _ > 10 )) { 89 | * ... lots of stuff 90 | * } 91 | * } 92 | * }}} 93 | */ 94 | def refs: immutable.IndexedSeq[Ref[A]] 95 | } 96 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/TMap.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import scala.collection.{generic, immutable, mutable} 6 | import scala.language.implicitConversions 7 | 8 | object TMap { 9 | 10 | object View extends generic.MutableMapFactory[View] { 11 | 12 | implicit def canBuildFrom[A, B]: generic.CanBuildFrom[Coll, (A, B), View[A, B]] = new MapCanBuildFrom[A, B] 13 | 14 | def empty[A, B]: View[A, B] = TMap.empty[A, B].single 15 | 16 | override def newBuilder[A, B]: mutable.Builder[(A, B), View[A, B]] = new mutable.Builder[(A, B), View[A, B]] { 17 | private val underlying = TMap.newBuilder[A, B] 18 | 19 | def clear(): Unit = underlying.clear() 20 | def += (kv: (A, B)): this.type = { underlying += kv ; this } 21 | def result(): View[A, B] = underlying.result().single 22 | } 23 | 24 | override def apply[A, B](kvs: (A, B)*): TMap.View[A, B] = (TMap.newBuilder[A, B] ++= kvs).result().single 25 | } 26 | 27 | /** A `Map` that provides atomic execution of all of its methods. */ 28 | trait View[A, B] extends mutable.Map[A, B] with mutable.MapLike[A, B, View[A, B]] with TxnDebuggable { 29 | /** Returns the `TMap` perspective on this transactional map, which 30 | * provides map functionality only inside atomic blocks. 31 | */ 32 | def tmap: TMap[A, B] 33 | 34 | def clone: TMap.View[A, B] 35 | 36 | /** Takes an atomic snapshot of this transactional map. */ 37 | def snapshot: immutable.Map[A, B] 38 | 39 | override def empty: View[A, B] = TMap.empty[A, B].single 40 | 41 | override protected[this] def newBuilder: mutable.Builder[(A, B), View[A, B]] = View.newBuilder[A, B] 42 | 43 | override def stringPrefix = "TMap" 44 | } 45 | 46 | 47 | /** Constructs and returns a new empty `TMap`. */ 48 | def empty[A, B]: TMap[A, B] = impl.STMImpl.instance.newTMap[A, B] 49 | 50 | /** Returns a builder of `TMap`. */ 51 | def newBuilder[A, B]: mutable.Builder[(A, B), TMap[A, B]] = impl.STMImpl.instance.newTMapBuilder[A, B] 52 | 53 | /** Constructs and returns a new `TMap` that will contain the key/value pairs 54 | * from `kvs`. 55 | */ 56 | def apply[A, B](kvs: (A, B)*): TMap[A, B] = (newBuilder[A, B] ++= kvs).result() 57 | 58 | 59 | /** Allows a `TMap` in a transactional context to be used as a `Map`. */ 60 | implicit def asMap[A, B](m: TMap[A, B])(implicit txn: InTxn): View[A, B] = m.single 61 | } 62 | 63 | 64 | /** A transactional map implementation that requires that all of its map-like 65 | * operations be called from inside an atomic block. Rather than extending 66 | * `Map`, an implicit conversion is provided from `TMap` to `Map` if the 67 | * current scope is part of an atomic block (see `TMap.asMap`). 68 | * 69 | * The keys (with type `A`) must be immutable, or at least not modified while 70 | * they are in the map. The `TMap` implementation assumes that it can safely 71 | * perform key equality and hash checks outside a transaction without 72 | * affecting atomicity. 73 | * 74 | * @author Nathan Bronson 75 | */ 76 | trait TMap[A, B] extends TxnDebuggable { 77 | 78 | /** Returns an instance that provides transactional map functionality without 79 | * requiring that operations be performed inside the static scope of an 80 | * atomic block. 81 | */ 82 | def single: TMap.View[A, B] 83 | 84 | def clone(implicit txn: InTxn): TMap[A, B] = single.clone.tmap 85 | 86 | // The following method work fine via the asMap mechanism, but is important 87 | // enough that we don't want the implicit conversion to make it invisible to 88 | // ScalaDoc or IDE auto-completion 89 | 90 | def snapshot: immutable.Map[A, B] = single.snapshot 91 | 92 | // The following methods work fine via the asMap mechanism, but are heavily 93 | // used. We add transactional versions of them to allow overrides to get 94 | // access to the InTxn instance without a ThreadLocal lookup. 95 | 96 | def isEmpty(implicit txn: InTxn): Boolean 97 | def size(implicit txn: InTxn): Int 98 | def foreach[U](f: ((A, B)) => U)(implicit txn: InTxn): Unit 99 | def contains(key: A)(implicit txn: InTxn): Boolean 100 | def apply(key: A)(implicit txn: InTxn): B 101 | def get(key: A)(implicit txn: InTxn): Option[B] 102 | def update(key: A, value: B)(implicit txn: InTxn): Unit = put(key, value) 103 | def put(key: A, value: B)(implicit txn: InTxn): Option[B] 104 | def remove(key: A)(implicit txn: InTxn): Option[B] 105 | 106 | // The following methods return the wrong receiver when invoked via the asMap 107 | // conversion. They are exactly the methods of mutable.Map whose return type 108 | // is this.type. Note that there are other methods of mutable.Map that we 109 | // allow to use the implicit mechanism, such as getOrElseUpdate(k). 110 | 111 | def += (kv: (A, B))(implicit txn: InTxn): this.type = { put(kv._1, kv._2) ; this } 112 | def += (kv1: (A, B), kv2: (A, B), kvs: (A, B)*)(implicit txn: InTxn): this.type = { this += kv1 += kv2 ++= kvs } 113 | def ++= (kvs: TraversableOnce[(A, B)])(implicit txn: InTxn): this.type = { for (kv <- kvs) this += kv ; this } 114 | def -= (k: A)(implicit txn: InTxn): this.type = { remove(k) ; this } 115 | def -= (k1: A, k2: A, ks: A*)(implicit txn: InTxn): this.type = { this -= k1 -= k2 --= ks } 116 | def --= (ks: TraversableOnce[A])(implicit txn: InTxn): this.type = { for (k <- ks) this -= k ; this } 117 | 118 | def transform(f: (A, B) => B)(implicit txn: InTxn): this.type 119 | def retain(p: (A, B) => Boolean)(implicit txn: InTxn): this.type 120 | } 121 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/TSet.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import scala.collection.{generic, immutable, mutable} 6 | import scala.language.implicitConversions 7 | 8 | object TSet { 9 | 10 | object View extends generic.MutableSetFactory[View] { 11 | 12 | implicit def canBuildFrom[A]: generic.CanBuildFrom[Coll, A, View[A]] = setCanBuildFrom[A] 13 | 14 | override def empty[A]: View[A] = TSet.empty[A].single 15 | 16 | override def newBuilder[A]: mutable.Builder[A, View[A]] = new mutable.Builder[A, View[A]] { 17 | private val underlying = TSet.newBuilder[A] 18 | 19 | def clear(): Unit = underlying.clear() 20 | def += (x: A): this.type = { underlying += x ; this } 21 | def result(): View[A] = underlying.result().single 22 | } 23 | 24 | override def apply[A](xs: A*): TSet.View[A] = (TSet.newBuilder[A] ++= xs).result().single 25 | } 26 | 27 | /** A `Set` that provides atomic execution of all of its methods. */ 28 | trait View[A] extends mutable.Set[A] with mutable.SetLike[A, View[A]] with TxnDebuggable { 29 | 30 | /** Returns the `TSet` perspective on this transactional set, which 31 | * provides set functionality only inside atomic blocks. 32 | */ 33 | def tset: TSet[A] 34 | 35 | def clone: TSet.View[A] 36 | 37 | /** Takes an atomic snapshot of this transactional set. */ 38 | def snapshot: immutable.Set[A] 39 | 40 | override def empty: View[A] = TSet.empty[A].single 41 | override def companion: generic.GenericCompanion[View] = View 42 | override protected[this] def newBuilder: mutable.Builder[A, View[A]] = View.newBuilder[A] 43 | 44 | override def stringPrefix = "TSet" 45 | } 46 | 47 | 48 | /** Constructs and returns a new empty `TSet`. */ 49 | def empty[A]: TSet[A] = impl.STMImpl.instance.newTSet[A] 50 | 51 | /** Returns a builder of `TSet`. */ 52 | def newBuilder[A]: mutable.Builder[A, TSet[A]] = impl.STMImpl.instance.newTSetBuilder[A] 53 | 54 | /** Constructs and returns a new `TSet` that will contain the elements from 55 | * `xs`. 56 | */ 57 | def apply[A](xs: A*): TSet[A] = (newBuilder[A] ++= xs).result() 58 | 59 | 60 | /** Allows a `TSet` in a transactional context to be used as a `Set`. */ 61 | implicit def asSet[A](s: TSet[A])(implicit txn: InTxn): View[A] = s.single 62 | } 63 | 64 | 65 | /** A transactional set implementation that requires that all of its set-like 66 | * operations be called from inside an atomic block. Rather than extending 67 | * `Set`, an implicit conversion is provided from `TSet` to `Set` if the 68 | * current scope is part of an atomic block (see `TSet.asSet`). 69 | * 70 | * The elements (with type `A`) must be immutable, or at least not modified 71 | * while they are in the set. The `TSet` implementation assumes that it can 72 | * safely perform equality and hash checks outside a transaction without 73 | * affecting atomicity. 74 | * 75 | * @author Nathan Bronson 76 | */ 77 | trait TSet[A] extends TxnDebuggable { 78 | 79 | /** Returns an instance that provides transactional set functionality without 80 | * requiring that operations be performed inside the static scope of an 81 | * atomic block. 82 | */ 83 | def single: TSet.View[A] 84 | 85 | def clone(implicit txn: InTxn): TSet[A] = single.clone.tset 86 | 87 | // Fast snapshots are one of TSet's core features, so we don't want the 88 | // implicit conversion to hide it from ScalaDoc and IDE completion 89 | def snapshot: immutable.Set[A] = single.snapshot 90 | 91 | // The following methods work fine via the asSet mechanism, but are heavily 92 | // used. We add transactional versions of them to allow overrides. 93 | 94 | def isEmpty(implicit txn: InTxn): Boolean 95 | def size(implicit txn: InTxn): Int 96 | def foreach[U](f: A => U)(implicit txn: InTxn): Unit 97 | def contains(elem: A)(implicit txn: InTxn): Boolean 98 | def apply(elem: A)(implicit txn: InTxn): Boolean = contains(elem) 99 | def add(elem: A)(implicit txn: InTxn): Boolean 100 | def update(elem: A, included: Boolean)(implicit txn: InTxn): Unit = if (included) add(elem) else remove(elem) 101 | def remove(elem: A)(implicit txn: InTxn): Boolean 102 | 103 | // The following methods return the wrong receiver when invoked via the asSet 104 | // conversion. They are exactly the methods of mutable.Set whose return type 105 | // is this.type. 106 | 107 | def += (x: A)(implicit txn: InTxn): this.type = { add(x) ; this } 108 | def += (x1: A, x2: A, xs: A*)(implicit txn: InTxn): this.type = { this += x1 += x2 ++= xs } 109 | def ++= (xs: TraversableOnce[A])(implicit txn: InTxn): this.type = { for (x <- xs) this += x ; this } 110 | def -= (x: A)(implicit txn: InTxn): this.type = { remove(x) ; this } 111 | def -= (x1: A, x2: A, xs: A*)(implicit txn: InTxn): this.type = { this -= x1 -= x2 --= xs } 112 | def --= (xs: TraversableOnce[A])(implicit txn: InTxn): this.type = { for (x <- xs) this -= x ; this } 113 | 114 | def retain(p: A => Boolean)(implicit txn: InTxn): this.type 115 | } 116 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/TxnDebuggable.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | /** This trait implements methods that can be used to examine the content of 6 | * transactional data structures in a debugger with minimal modification to 7 | * the behavior of the program. Normal transactional reads would add to an 8 | * atomic block's read set, which could reduce the number of valid program 9 | * execution orders. `dbgStr` and `dbgValue` perform transactional reads, 10 | * but then erase them from the enclosing transaction (if any). 11 | * 12 | * You can use these methods from an IDE debugger manually, by watching 13 | * `x.dbgStr` or `x.dbgValue` rather than `x`. 14 | * 15 | * If you use Eclipse, you can make this method the default view by going to 16 | * ''Window->Preferences->Java[+]->Debug[+]->Detail Formatters'' and 17 | * entering the code snippet `dbgStr()` (or `dbgValue()`) for instances of 18 | * `scala.concurrent.stm.TxnDebuggable`. 19 | * 20 | * If you use IntelliJ IDEA, go to 21 | * ''File->Settings...->Debugger->Data Type Renderers'' and create a new 22 | * renderer for `scala.concurrent.stm.TxnDebuggable` that uses 23 | * `dbgStr()` for rendering and `dbgValue()` for node expansion. 24 | * 25 | * @author Nathan Bronson 26 | */ 27 | trait TxnDebuggable { 28 | 29 | /** Returns a string representation of the transactional value in this 30 | * instance for debugging convenience. The `Ref` reads (and writes) 31 | * performed while constructing the result will be discarded before 32 | * returning. This method works fine outside a transaction. 33 | * 34 | * If this method is called from within a transaction that is already 35 | * doomed (status `Txn.Rolledback`), a string describing the reason 36 | * for the outer transaction's rollback will be returned. 37 | */ 38 | def dbgStr: String 39 | 40 | /** Returns some value that is suitable for examination in a debugger, 41 | * or returns a `Txn.RollbackCause` if called from inside a doomed atomic 42 | * block. 43 | */ 44 | def dbgValue: Any 45 | 46 | /** Helper function for generating `dbgStr` of a transactional type that 47 | * can produce an iterable view. 48 | */ 49 | private[stm] def mkStringPrefix(typeName: String, values: Iterable[_], unabbrevLen: Int = 1000): String = { 50 | val buf = new StringBuilder(typeName + "[size=" + values.size + "](") 51 | val iter = values.iterator 52 | while (iter.hasNext && buf.length < unabbrevLen - 1) { 53 | buf.append(iter.next()) 54 | if (iter.hasNext) 55 | buf.append(", ") 56 | } 57 | if (iter.hasNext) 58 | buf.append("...") 59 | buf.append(")") 60 | buf.toString() 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/TxnLocal.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | 6 | object TxnLocal { 7 | /** Returns a new transaction-local that holds values of type `A`. One 8 | * `TxnLocal` instance holds a separate value for each transaction in which 9 | * it has been accessed. The value associated with a particular atomic 10 | * block is created on demand, and discarded at the same time that the 11 | * atomic block's after-completion handlers are invoked. `TxnLocal` has a 12 | * similar relationship to transactions as `ThreadLocal` has to threads. 13 | * 14 | * There are two ways to specify the initial value that will be used if the 15 | * first access inside a transaction is not a `set`. If no `InTxn` context 16 | * is needed to compute the initial value then the by-name parameter `init` 17 | * is the most convenient. Because this is the first parameter, you can 18 | * omit the parameter name. To construct a `TxnLocal` with a default value 19 | * of `aValue`, simply {{{ 20 | * val tl = TxnLocal(aValue) 21 | * }}} 22 | * If computing the initial value requires access to `Ref`s, then it is 23 | * better to use the `initialValue` parameter, which lets you write {{{ 24 | * val tl = TxnLocal(initialValue = { implicit txn => 25 | * // Ref reads or writes, or handler registration 26 | * }) 27 | * }}} 28 | * 29 | * Unlike `Ref`s, `TxnLocal`s can be read or written from inside 30 | * while-preparing or while-committing callbacks, with two conditions: if 31 | * the first access is from one of these callbacks then no `beforeCommit` 32 | * parameter can be present; and if the first access is from one of these 33 | * callbacks and it is not a write then you must use the `init` 34 | * initialization method. 35 | * 36 | * This factory method also accepts parameters that correspond to `Txn`'s 37 | * transaction life-cycle handlers. These handlers will be registered in any 38 | * transaction that reads or writes the returned `TxnLocal`. They are 39 | * roughly 40 | * - `beforeCommit` - the last time that `Ref`s can be read or written; 41 | * - `whilePreparing` - the last time that the transaction can be rolled 42 | * back; 43 | * - `whileCommitting` - actions that should be atomic with respect to the 44 | * transaction (keep them fast to avoid scalability issues); 45 | * - `afterCommit` - called at some time after commit; 46 | * - `afterRollback` - called at some time after rollback of the nesting 47 | * level in which the `TxnLocal` was first accessed; and 48 | * - `afterCompletion` - called either after commit or after rollback. 49 | * 50 | * The value stored in a `TxnLocal` is subject to partial rollback: initial 51 | * value computations and writes from a nested atomic block will be 52 | * discarded if the block is rolled back. 53 | */ 54 | def apply[A](init: => A = null.asInstanceOf[A], 55 | initialValue: InTxn => A = null, 56 | beforeCommit: InTxn => Unit = null, 57 | whilePreparing: InTxnEnd => Unit = null, 58 | whileCommitting: InTxnEnd => Unit = null, 59 | afterCommit: A => Unit = null, 60 | afterRollback: Txn.Status => Unit = null, 61 | afterCompletion: Txn.Status => Unit = null): TxnLocal[A] = { 62 | impl.STMImpl.instance.newTxnLocal( 63 | init, initialValue, beforeCommit, whilePreparing, whileCommitting, afterCommit, afterRollback, afterCompletion) 64 | } 65 | } 66 | 67 | /** `TxnLocal[A]` holds an instance of `A` that is local to an atomic block. 68 | * See the factory method in the companion object for information about the 69 | * life-cycle. 70 | * 71 | * @author Nathan Bronson 72 | */ 73 | trait TxnLocal[A] extends RefLike[A, InTxnEnd] { 74 | 75 | /** Returns true if a value is already associated with this `TxnLocal` in the 76 | * current transactional context, false otherwise. 77 | */ 78 | def isInitialized(implicit txn: InTxnEnd): Boolean 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/TxnUnknown.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | /** An object that represents the absence of a statically-bound current 6 | * transaction. 7 | * @see scala.concurrent.stm.MaybeTxn 8 | * 9 | * @author Nathan Bronson 10 | */ 11 | object TxnUnknown extends MaybeTxn 12 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/CCSTMExecutor.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | import scala.util.control.ControlThrowable 7 | 8 | private[ccstm] object CCSTMExecutor { 9 | val DefaultControlFlowTest: Throwable => Boolean = _.isInstanceOf[ControlThrowable] 10 | 11 | val DefaultPostDecisionFailureHandler: (Txn.Status, Throwable) => Unit = { (status, x) => 12 | new Exception("status=" + status, x).printStackTrace() 13 | } 14 | } 15 | 16 | private[ccstm] case class CCSTMExecutor private ( 17 | retryTimeoutNanos: Option[Long], 18 | controlFlowTest: Throwable => Boolean, 19 | postDecisionFailureHandler: (Txn.Status, Throwable) => Unit 20 | ) extends TxnExecutor { 21 | 22 | def this() = this(None, CCSTMExecutor.DefaultControlFlowTest, CCSTMExecutor.DefaultPostDecisionFailureHandler) 23 | 24 | def apply[Z](block: InTxn => Z)(implicit mt: MaybeTxn): Z = InTxnImpl().atomic(this, block) 25 | 26 | def oneOf[Z](blocks: (InTxn => Z)*)(implicit mt: MaybeTxn): Z = InTxnImpl().atomicOneOf(this, blocks) 27 | 28 | def unrecorded[Z](block: InTxn => Z, outerFailure: Txn.RollbackCause => Z)(implicit mt: MaybeTxn): Z = 29 | InTxnImpl().unrecorded(this, block, outerFailure) 30 | 31 | def pushAlternative[Z](mt: MaybeTxn, block: (InTxn) => Z): Boolean = InTxnImpl().pushAlternative(block) 32 | 33 | def compareAndSet[A, B](a: Ref[A], a0: A, a1: A, b: Ref[B], b0: B, b1: B): Boolean = { 34 | val ah = a.asInstanceOf[Handle.Provider[A]].handle 35 | val bh = b.asInstanceOf[Handle.Provider[B]].handle 36 | InTxnImpl.dynCurrentOrNull match { 37 | case null => NonTxn.transform2[A, B, Boolean](ah, bh, { (av, bv) => if (a0 == av && b0 == bv) (a1, b1, true) else (av, bv, false) }) 38 | case txn => a0 == txn.get(ah) && b0 == txn.get(bh) && { txn.set(ah, a1) ; txn.set(bh, b1) ; true } 39 | } 40 | } 41 | 42 | def compareAndSetIdentity[A <: AnyRef, B <: AnyRef](a: Ref[A], a0: A, a1: A, b: Ref[B], b0: B, b1: B): Boolean = { 43 | val aRead = a0 eq a1 44 | val bRead = b0 eq b1 45 | if (aRead && bRead) 46 | cci(a, a0, b, b0) 47 | else if (aRead) 48 | ccasi(a, a0, b, b0, b1) 49 | else if (bRead) 50 | ccasi(b, b0, a, a0, a1) 51 | else 52 | dcasi(a, a0, a1, b, b0, b1) 53 | } 54 | 55 | private def cci[A <: AnyRef, B <: AnyRef](a: Ref[A], a0: A, b: Ref[B], b0: B): Boolean = { 56 | val ah = a.asInstanceOf[Handle.Provider[A]].handle 57 | val bh = b.asInstanceOf[Handle.Provider[B]].handle 58 | InTxnImpl.dynCurrentOrNull match { 59 | case null => NonTxn.cci(ah, a0, bh, b0) 60 | case txn => (a0 eq txn.get(ah)) && (b0 eq txn.get(bh)) 61 | } 62 | } 63 | 64 | private def ccasi[A <: AnyRef, B <: AnyRef](a: Ref[A], a0: A, b: Ref[B], b0: B, b1: B): Boolean = { 65 | val ah = a.asInstanceOf[Handle.Provider[A]].handle 66 | val bh = b.asInstanceOf[Handle.Provider[B]].handle 67 | InTxnImpl.dynCurrentOrNull match { 68 | case null => NonTxn.ccasi(ah, a0, bh, b0, b1) 69 | case txn => (a0 eq txn.get(ah)) && (b0 eq txn.get(bh)) && { txn.set(bh, b1) ; true } 70 | } 71 | } 72 | 73 | private def dcasi[A <: AnyRef, B <: AnyRef](a: Ref[A], a0: A, a1: A, b: Ref[B], b0: B, b1: B): Boolean = { 74 | val ah = a.asInstanceOf[Handle.Provider[A]].handle 75 | val bh = b.asInstanceOf[Handle.Provider[B]].handle 76 | InTxnImpl.dynCurrentOrNull match { 77 | case null => NonTxn.transform2[A, B, Boolean](ah, bh, { (av, bv) => if ((a0 eq av) && (b0 eq bv)) (a1, b1, true) else (av, bv, false) }) 78 | case txn => (a0 eq txn.get(ah)) && (b0 eq txn.get(bh)) && { txn.set(ah, a1) ; txn.set(bh, b1) ; true } 79 | } 80 | } 81 | 82 | def withRetryTimeoutNanos(timeout: Option[Long]): TxnExecutor = copy(retryTimeoutNanos = timeout) 83 | 84 | def isControlFlow(x: Throwable): Boolean = controlFlowTest(x) 85 | 86 | def withControlFlowRecognizer(pf: PartialFunction[Throwable, Boolean]): TxnExecutor = { 87 | copy(controlFlowTest = { x: Throwable => if (pf.isDefinedAt(x)) pf(x) else controlFlowTest(x) }) 88 | } 89 | 90 | def withPostDecisionFailureHandler(handler: (Txn.Status, Throwable) => Unit): TxnExecutor = { 91 | copy(postDecisionFailureHandler = handler) 92 | } 93 | 94 | override def toString: String = { 95 | "CCSTMExecutor@" + hashCode.toHexString + 96 | "(retryTimeoutNanos=" + retryTimeoutNanos + 97 | ", controlFlowTest=" + 98 | (if (controlFlowTest eq CCSTMExecutor.DefaultControlFlowTest) "default" else controlFlowTest) + 99 | ", postDecisionFailureHandler=" + 100 | (if (postDecisionFailureHandler eq CCSTMExecutor.DefaultPostDecisionFailureHandler) "default" else postDecisionFailureHandler) + 101 | ")" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/Counter.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.ccstm 4 | 5 | import java.util.concurrent.atomic.AtomicLong 6 | import scala.annotation.tailrec 7 | 8 | /** A counter with a linearizable increment operator and adaptive contention 9 | * avoidance. Reading the counter with `apply()` is not linearizable (unless 10 | * the only delta passed to += is 1) and is not optimized. 11 | */ 12 | private[ccstm] class Counter extends { 13 | private final val MaxStripes = 64 14 | 15 | // this doesn't need to be volatile because when we grow it we retain all of 16 | // the old AtomicLong-s 17 | private var _stripes = Array(new AtomicLong) 18 | 19 | private def grow(): Unit = 20 | synchronized { 21 | if (_stripes.length < MaxStripes) { 22 | val repl = new Array[AtomicLong](_stripes.length * 2) 23 | System.arraycopy(_stripes, 0, repl, 0, _stripes.length) 24 | var i = _stripes.length 25 | while (i < repl.length) { 26 | repl(i) = new AtomicLong 27 | i += 1 28 | } 29 | _stripes = repl 30 | } 31 | } 32 | 33 | def += (delta: Int): Unit = 34 | if (delta != 0) { 35 | incr(delta) 36 | } 37 | 38 | @tailrec private def incr(delta: Int): Unit = { 39 | val s = _stripes 40 | val i = CCSTM.hash(Thread.currentThread) & (s.length - 1) 41 | val prev = s(i).get 42 | if (!s(i).compareAndSet(prev, prev + delta)) { 43 | grow() 44 | incr(delta) 45 | } 46 | } 47 | 48 | def apply(): Long = _stripes.foldLeft(0L)( _ + _.get ) 49 | 50 | override def toString: String = apply().toString 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/GV6.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | import java.util.concurrent.atomic.AtomicLong 7 | 8 | 9 | private[ccstm] trait GV6 { 10 | 11 | /** The global timestamp. We use TL2's GV6 scheme to avoid the need to 12 | * increment this during every transactional commit. Non-transactional 13 | * writes are even more conservative, incrementing the global version only 14 | * when it lags the local version by a (configurable) fixed amount. This 15 | * helps reduce contention (especially when there are many non-transactional 16 | * writes), but it means we must always validate transactions that are not 17 | * read-only. To help reduce the spurious revalidations caused by GV6, we 18 | * adaptively adjust the ratio of silent commits. Spurious revalidations 19 | * can also be triggered by silent non-txn run ahead, so we use bit 0 of the 20 | * version to differentiate the type of the most recent write, 0 for non-txn 21 | * and 1 for txn. 22 | */ 23 | val globalVersion = new AtomicLong(1) 24 | 25 | private val silentCommitRatioMin = System.getProperty("ccstm.gv6.min", "1").toInt 26 | private val silentCommitRatioMax = 27 | System.getProperty("ccstm.gv6.max", (Runtime.getRuntime.availableProcessors min 16).toString).toInt 28 | 29 | /** The approximate ratio of the number of commits to the number of 30 | * increments of `globalVersion`, as in TL2's GV6 scheme. If 31 | * greater than one, the actual choice to advance or not is made with a 32 | * random number generator. 33 | */ 34 | private var silentCommitRatio = 35 | (Runtime.getRuntime.availableProcessors / 2) max silentCommitRatioMin min silentCommitRatioMax 36 | 37 | private var silentCommitCutoff = intRangeFraction(silentCommitRatio) 38 | 39 | private val silentCommitRand = skel.SimpleRandom 40 | 41 | /** The maximum value of `nonTxnWriteVersion - globalVersion` that 42 | * will be allowed before a non-transactional store attempts to increase 43 | * `globalVersion`. Any value larger than zero admits the 44 | * possibility that a non-transactional write will leave a version number 45 | * that forces revalidation of a transaction that discovers it (like a 46 | * silently-committed txn under GV6). Larger values can help amortize the 47 | * cost of updating the counter. This is double the "ccstm.nontxn.runahead" 48 | * property because we reserve bit 0 to record the txn state of the last 49 | * writer. 50 | */ 51 | private val nonTxnSilentRunAhead = 2 * System.getProperty("ccstm.nontxn.runahead", "32").toInt 52 | 53 | /** If `x` is a signed integer evenly chosen from a uniform distribution 54 | * between Integer.MIN_VALUE and Integer.MAX_VALUE, then the test 55 | * `(x <= intRangeFraction(r))` will succeed `1.0 / r` of the time. 56 | */ 57 | private def intRangeFraction(r: Int) = ((1 << 31) + ((1L << 32) / r) - 1).asInstanceOf[Int] 58 | 59 | /** Returns a value that is greater than `prevVersion` and greater 60 | * than the value of `globalVersion` on entry. May increase 61 | * `globalVersion`. 62 | */ 63 | def nonTxnWriteVersion(prevVersion: Long): Long = { 64 | val g = globalVersion.get 65 | val result = (math.max(g, prevVersion) + 2) & ~1L 66 | if (result > g + nonTxnSilentRunAhead) { 67 | globalVersion.compareAndSet(g, prevVersion + 1) 68 | } 69 | result 70 | } 71 | 72 | /** Returns a version to use for reading in a new transaction. */ 73 | def freshReadVersion: Long = globalVersion.get 74 | 75 | /** Guarantees that `globalVersion.get` is ≥ 76 | * `minRV`, and returns `globalVersion.get`. This form of 77 | * `freshReadVersion` is used when the read version of a transaction needs 78 | * to be advanced by revalidating. 79 | */ 80 | def freshReadVersion(minRV: Long): Long = { 81 | // A call to this method corresponds to an mid-txn revalidation, which is 82 | // relatively costly. If minRV is odd then the revalidation is due to a 83 | // silent commit, so consider adjusting the silent commit ratio. We only 84 | // do this 1/64 of the time, though. 85 | if ((minRV & 127) == 1) { 86 | reduceRevalidateProbability() 87 | } 88 | 89 | (while (true) { 90 | val g = globalVersion.get 91 | if (g >= minRV) { 92 | return g 93 | } 94 | if (globalVersion.compareAndSet(g, minRV)) { 95 | return minRV 96 | } 97 | }).asInstanceOf[Nothing] 98 | } 99 | 100 | /** Returns a value that is greater than `globalVersion.get` and greater than 101 | * `readVersion`, possibly increasing `globalVersion`. 102 | */ 103 | def freshCommitVersion(readVersion: Long): Long = { 104 | val cutoff = silentCommitCutoff 105 | val install = cutoff == Int.MaxValue || silentCommitRand.nextInt <= cutoff 106 | 107 | val gvSnap = globalVersion.get 108 | val result = (math.max(readVersion, gvSnap) + 1) | 1L 109 | 110 | // if we fail to install with a CAS, 1/128 prob of adjusting 111 | // silentCommitRatio 112 | if (install && 113 | !globalVersion.compareAndSet(gvSnap, result) && 114 | (result & 254) == 0) 115 | reduceContentionProbability() 116 | 117 | result 118 | } 119 | 120 | private def reduceRevalidateProbability(): Unit = { 121 | // increment the commit version on more commits, so reduce the ratio 122 | val r = silentCommitRatio 123 | if (r > silentCommitRatioMin) { 124 | silentCommitRatio = r - 1 125 | silentCommitCutoff = intRangeFraction(r - 1) 126 | //System.err.println("reduced ratio to " + (r - 1)) 127 | } 128 | } 129 | 130 | private def reduceContentionProbability(): Unit = { 131 | // increment the commit version on fewer commits, so reduce the ratio 132 | val r = silentCommitRatio 133 | if (r < silentCommitRatioMax) { 134 | silentCommitRatio = r + 1 135 | silentCommitCutoff = intRangeFraction(r + 1) 136 | //System.err.println("increased ratio to " + (r + 1)) 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/Handle.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | 7 | private[ccstm] object Handle { 8 | 9 | /** A `Handle.Provider` has a single associated handle. */ 10 | trait Provider[T] { 11 | def handle: Handle[T] 12 | 13 | override def hashCode: Int = { 14 | val h = handle 15 | CCSTM.hash(h.base, h.offset) 16 | } 17 | 18 | override def equals(rhs: Any): Boolean = { 19 | (this eq rhs.asInstanceOf[AnyRef]) || (rhs match { 20 | case r: Handle.Provider[_] => 21 | val h1 = handle 22 | val h2 = r.handle 23 | (h1.base eq h2.base) && (h1.offset == h2.offset) 24 | 25 | case r: Ref[_] => r equals this 26 | case v: Ref.View[_] => v equals this 27 | case _ => false 28 | }) 29 | } 30 | } 31 | } 32 | 33 | /** A `Handle` defines the operations that must be available for a particular 34 | * memory location in order for it to be managed by CCSTM. The identity of 35 | * the location is determined by `base` and `offset`, with `base` be used only 36 | * in comparisons using `eq` or `ne` (no methods of `base` will ever be 37 | * invoked). Metadata is identified by `base` and `metaOffset` (the assumption 38 | * made during blocking is that a write to a handle's `meta` may affect the 39 | * `meta` read by any handle with the same `base` and `metaOffset`). 40 | * 41 | * @author Nathan Bronson 42 | */ 43 | private[ccstm] abstract class Handle[T] { 44 | def meta: Long 45 | def meta_=(v: Long): Unit 46 | def metaCAS(before: Long, after: Long): Boolean 47 | def base: AnyRef 48 | def offset: Int 49 | def metaOffset: Int 50 | def data: T 51 | def data_=(v: T): Unit 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/RefOps.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | /** The default implementation of `Ref`'s operations in CCSTM. */ 7 | private[ccstm] trait RefOps[T] extends Ref[T] with Handle.Provider[T] { 8 | 9 | private def impl(implicit txn: InTxn): InTxnImpl = txn.asInstanceOf[InTxnImpl] 10 | 11 | /** Override this to provide the handle that this `RefOps` uses. */ 12 | def handle: Handle[T] 13 | 14 | //////////////// Source stuff 15 | 16 | override def apply()(implicit txn: InTxn): T = impl.get(handle) 17 | def get(implicit txn: InTxn): T = impl.get(handle) 18 | def getWith[Z](f: (T) => Z)(implicit txn: InTxn): Z = impl.getWith(handle, f) 19 | def relaxedGet(equiv: (T, T) => Boolean)(implicit txn: InTxn): T = impl.relaxedGet(handle, equiv) 20 | 21 | //////////////// Sink stuff 22 | 23 | 24 | override def update(v: T)(implicit txn: InTxn): Unit = impl.set(handle, v) 25 | def set(v: T)(implicit txn: InTxn): Unit = impl.set(handle, v) 26 | def trySet(v: T)(implicit txn: InTxn): Boolean = impl.trySet(handle, v) 27 | 28 | //////////////// Ref stuff 29 | 30 | def swap(v: T)(implicit txn: InTxn): T = impl.swap(handle, v) 31 | 32 | def transform(f: T => T)(implicit txn: InTxn): Unit = { 33 | // only sub-types of Ref actually perform deferral, the base implementation 34 | // evaluates f immediately 35 | impl.getAndTransform(handle, f) 36 | } 37 | 38 | override def transformAndGet(f: T => T)(implicit txn: InTxn): T = impl.transformAndGet(handle, f) 39 | 40 | override def getAndTransform(f: T => T)(implicit txn: InTxn): T = impl.getAndTransform(handle, f) 41 | 42 | override def transformAndExtract[V](f: T => (T,V))(implicit txn: InTxn): V = impl.transformAndExtract(handle, f) 43 | 44 | def transformIfDefined(pf: PartialFunction[T,T])(implicit txn: InTxn): Boolean = { 45 | impl.transformIfDefined(handle, pf) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/RetrySet.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.ccstm 4 | 5 | import annotation.tailrec 6 | import java.util.concurrent.TimeUnit 7 | 8 | /** A retry set representation. */ 9 | private[ccstm] class RetrySet(val size: Int, 10 | handles: Array[Handle[_]], 11 | versions: Array[CCSTM.Version]) { 12 | import CCSTM._ 13 | 14 | /** Returns the number of nanoseconds of blocking that was performed. */ 15 | @throws(classOf[InterruptedException]) 16 | def awaitRetry(timeoutNanos: Long): Long = { 17 | if (size == 0 && timeoutNanos == Long.MaxValue) 18 | throw new IllegalStateException("explicit retries cannot succeed because cumulative read set is empty") 19 | 20 | val begin = System.nanoTime 21 | 22 | val d = begin + timeoutNanos 23 | val deadline = if (d < 0) Long.MaxValue else d // handle arithmetic overflow 24 | 25 | attemptAwait(deadline) 26 | 27 | val actualElapsed = System.nanoTime - begin 28 | 29 | if (Stats.top != null) { 30 | Stats.top.retrySet += size 31 | val millis = TimeUnit.NANOSECONDS.toMillis(actualElapsed) 32 | Stats.top.retryWaitElapsed += millis.asInstanceOf[Int] 33 | } 34 | 35 | // to cause the proper retryFor to wake up we need to present an illusion 36 | // that we didn't block for too long 37 | // 38 | // reportedElapsed = min(now, deadline) - begin 39 | // reportedElapsed = min(now - begin, deadline - begin) 40 | math.min(actualElapsed, deadline - begin) 41 | } 42 | 43 | /** Returns true if something changed, false if the deadline was reached. */ 44 | @throws(classOf[InterruptedException]) 45 | private def attemptAwait(nanoDeadline: Long): Boolean = { 46 | // Spin a few times, counting one spin per read set element 47 | var spins = 0 48 | while (size > 0 && spins < SpinCount + YieldCount) { 49 | if (changed) 50 | return true 51 | spins += size 52 | if (spins > SpinCount) { 53 | Thread.`yield`() 54 | if (nanoDeadline != Long.MaxValue && System.nanoTime > nanoDeadline) 55 | return false 56 | } 57 | } 58 | 59 | blockingAttemptAwait(nanoDeadline) 60 | } 61 | 62 | @throws(classOf[InterruptedException]) 63 | @tailrec 64 | private def blockingAttemptAwait(nanoDeadline: Long, 65 | event: WakeupManager.Event = wakeupManager.subscribe, 66 | i: Int = size - 1): Boolean = { 67 | if (i < 0) { 68 | // event has been completed, time to block 69 | if (!event.tryAwaitUntil(nanoDeadline)) 70 | false // timed out 71 | else 72 | changed || blockingAttemptAwait(nanoDeadline) // event fired 73 | } else { 74 | // still building the event 75 | val h = handles(i) 76 | if (!event.addSource(h)) 77 | changed || blockingAttemptAwait(nanoDeadline) // event fired 78 | else if (!addPendingWakeup(h, versions(i))) 79 | true // direct evidence of change 80 | else 81 | blockingAttemptAwait(nanoDeadline, event, i - 1) // keep building 82 | } 83 | } 84 | 85 | @tailrec 86 | private def addPendingWakeup(handle: Handle[_], ver: CCSTM.Version): Boolean = { 87 | val m = handle.meta 88 | if (changing(m) || version(m) != ver) 89 | false // handle has already changed 90 | else if (pendingWakeups(m) || handle.metaCAS(m, withPendingWakeups(m))) 91 | true // already has pending wakeup, or CAS to add it was successful 92 | else 93 | addPendingWakeup(handle, ver) // try again 94 | } 95 | 96 | private def changed: Boolean = { 97 | var i = size - 1 98 | while (i >= 0) { 99 | val m = handles(i).meta 100 | if (changing(m) || version(m) != versions(i)) 101 | return true 102 | i -= 1 103 | } 104 | false 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/RetrySetBuilder.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.ccstm 4 | 5 | import annotation.tailrec 6 | 7 | 8 | /** This is basically a specialized builder for a map from `Handle` to 9 | * `Version`. 10 | * 11 | * @author Nathan Bronson 12 | */ 13 | private[ccstm] final class RetrySetBuilder { 14 | private final val InitialCap = 16 15 | private var _size = 0 16 | private var _handles = new Array[Handle[_]](maxSizeForCap(InitialCap) + 1) 17 | private var _versions = new Array[CCSTM.Version](maxSizeForCap(InitialCap) + 1) 18 | private var _next = new Array[Int](maxSizeForCap(InitialCap) + 1) 19 | private var _dispatch = new Array[Int](InitialCap) 20 | 21 | private def maxSizeForCap(cap: Int) = cap - (cap / 4) 22 | 23 | def size: Int = _size 24 | 25 | def += (handle: Handle[_], version: CCSTM.Version): Unit = { 26 | val slot = CCSTM.hash(handle.base, handle.offset) & (_dispatch.length - 1) 27 | addImpl(slot, _dispatch(slot), handle, version) 28 | } 29 | 30 | @tailrec 31 | private def addImpl(slot: Int, i: Int, handle: Handle[_], version: CCSTM.Version): Unit = { 32 | if (i == 0) 33 | append(slot, handle, version) 34 | else if (!hEq(_handles(i - 1), handle)) 35 | addImpl(slot, _next(i - 1), handle, version) 36 | // else it is a duplicate 37 | } 38 | 39 | private def append(slot: Int, handle: Handle[_], version: CCSTM.Version): Unit = { 40 | val i = _size + 1 41 | _size = i 42 | _handles(i - 1) = handle 43 | _versions(i - 1) = version 44 | _next(i - 1) = _dispatch(slot) 45 | _dispatch(slot) = i 46 | if (_size > maxSizeForCap(_dispatch.length)) 47 | grow() 48 | } 49 | 50 | private def grow(): Unit = { 51 | // store the current contents 52 | val s = _size 53 | val hh = _handles 54 | val vv = _versions 55 | 56 | // reallocate 57 | _size = 0 58 | val c = _dispatch.length * 2 59 | _handles = new Array[Handle[_]](maxSizeForCap(c) + 1) 60 | _versions = new Array[CCSTM.Version](maxSizeForCap(c) + 1) 61 | _next = new Array[Int](maxSizeForCap(c) + 1) 62 | _dispatch = new Array[Int](c) 63 | 64 | // reinsert the current contents 65 | var i = 0 66 | while (i < s) { 67 | val h = hh(i) 68 | append(CCSTM.hash(h.base, h.offset) & (c - 1), h, vv(i)) 69 | i += 1 70 | } 71 | } 72 | 73 | private def hEq(a: Handle[_], b: Handle[_]) = (a eq b) || ((a.base eq b.base) && (a.offset == b.offset)) 74 | 75 | def result(): RetrySet = { 76 | _dispatch = null 77 | _next = null 78 | new RetrySet(_size, _handles, _versions) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/Stats.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.ccstm 4 | 5 | import collection.mutable.ArrayBuffer 6 | 7 | private[ccstm] object Stats { 8 | 9 | val Enabled: Boolean = "yYtT1".indexOf((System.getProperty("ccstm.stats", "") + "0").charAt(0)) >= 0 10 | 11 | class LazyCounterMap[A] { 12 | import scala.collection.JavaConverters._ 13 | 14 | private val _counters = new java.util.concurrent.ConcurrentHashMap[A, Counter] 15 | 16 | def += (k: A): Unit = { 17 | var v = _counters.get(k) 18 | if (v == null) { 19 | _counters.putIfAbsent(k, new Counter) 20 | v = _counters.get(k) 21 | } 22 | v += 1 23 | } 24 | 25 | def toStr(k: A): String = k.toString 26 | 27 | def contents: Seq[(String, Long)] = { 28 | val aa = _counters.entrySet.asScala.toSeq map { e => toStr(e.getKey) -> e.getValue.apply() } 29 | aa sortBy { -_._2 } 30 | } 31 | } 32 | 33 | class Histo(numBuckets: Int) { 34 | private val _sum = new Counter 35 | private val _buckets = Array.tabulate(numBuckets) { _ => new Counter } 36 | 37 | def += (value: Int): Unit = 38 | if (value != 0) { 39 | _sum += value 40 | _buckets(bucketFor(value)) += 1 41 | } 42 | 43 | protected def bucketFor(value: Int): Int = { 44 | if (value < 0 || value >= _buckets.length) 45 | _buckets.length - 1 46 | else 47 | value 48 | } 49 | 50 | def contents: Seq[Long] = { 51 | val snap = _buckets map { _.apply() } 52 | snap.take(1 + snap.lastIndexWhere { _ != 0L }) 53 | } 54 | 55 | override def toString: String = { 56 | val s = _sum() 57 | val c = contents 58 | val count = c.foldLeft(0L)( _ + _ ) 59 | val avg = if (count == 0) 0.0 else s * 1.0 / count 60 | "sum= %-10d count= %-8d avg= %-5.1f [%s]".format(s, count, avg, c.mkString(" ")) 61 | } 62 | } 63 | 64 | class ExponentialHisto extends Histo(32) { 65 | override protected def bucketFor(value: Int): Int = { 66 | var v = value >>> 1 67 | var i = 0 68 | while (v != 0) { 69 | v >>>= 1 70 | i += 1 71 | } 72 | i 73 | } 74 | } 75 | 76 | class Level(isTop: Boolean) { 77 | val commits = new Counter 78 | val alternatives = new Histo(10) 79 | val retrySet = if (isTop) new ExponentialHisto else null 80 | val retryWaitElapsed = if (isTop) new ExponentialHisto else null 81 | val explicitRetries = new Counter 82 | val unrecordedTxns = new Counter 83 | val optimisticRetries = new LazyCounterMap[Symbol] 84 | val failures = new LazyCounterMap[Class[_]] { override def toStr(k: Class[_]): String = k.getSimpleName } 85 | val blockingAcquires = new Counter 86 | val commitReadSet = if (isTop) new ExponentialHisto else null 87 | val commitBargeSet = if (isTop) new ExponentialHisto else null 88 | val commitWriteSet = if (isTop) new ExponentialHisto else null 89 | val rollbackReadSet = new ExponentialHisto 90 | val rollbackBargeSet = new ExponentialHisto 91 | val rollbackWriteSet = new ExponentialHisto 92 | 93 | def contents: Seq[String] = { 94 | val buf = new ArrayBuffer[String] 95 | for (f <- getClass.getDeclaredFields) { 96 | val name = f.getName 97 | val value = getClass.getDeclaredMethod(name).invoke(this) 98 | value match { 99 | case null => 100 | case c: Counter => buf += "%17s= %d".format(name, c()) 101 | case m: LazyCounterMap[_] => 102 | for ((k, v) <- m.contents) 103 | buf += "%17s: %7d %s".format(name, v, k) 104 | 105 | case h: Histo => buf += "%17s: %s".format(name, h) 106 | } 107 | } 108 | buf.result 109 | } 110 | 111 | def mkString(prefix: String): String = { 112 | prefix + ("-" * 64) + "\n" + contents.map( prefix + _ ).mkString("\n") 113 | } 114 | } 115 | 116 | val top : Level = if (Enabled) new Level(true) else null 117 | val nested: Level = if (Enabled) new Level(false) else null 118 | registerShutdownHook() 119 | 120 | private def registerShutdownHook(): Unit = 121 | if (top != null) 122 | Runtime.getRuntime.addShutdownHook(new Thread("shutdown stats printer") { 123 | override def run(): Unit = println(Stats) 124 | }) 125 | 126 | override def toString: String = 127 | top.mkString("CCSTM: top: ") + "\n" + nested.mkString("CCSTM: nested: ") 128 | } -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/TArrayImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | import java.util.concurrent.atomic.AtomicLongArray 7 | 8 | import scala.collection.immutable 9 | import scala.concurrent.stm.skel.AtomicArray 10 | import scala.reflect.ClassTag 11 | 12 | 13 | private[ccstm] class TArrayImpl[A](private val values: AtomicArray[A])(implicit m: ClassTag[A]) extends TArray[A] with TArray.View[A] { 14 | import TArray._ 15 | 16 | def this(length0: Int)(implicit m: ClassTag[A]) = this(AtomicArray[A](length0)) 17 | 18 | def this(data0: TraversableOnce[A])(implicit m: ClassTag[A]) = this(AtomicArray[A](data0)) 19 | 20 | val length: Int = values.length 21 | 22 | //////////////// TArray 23 | 24 | def single: View[A] = this 25 | 26 | def apply(index: Int)(implicit txn: InTxn): A = getRef(index).get 27 | 28 | def update(index: Int, v: A)(implicit txn: InTxn): Unit = getRef(index).set(v) 29 | 30 | /** Returns a sequence that will produce transient `Ref` instances that are 31 | * backed by elements of this `TArray`. This allows use of all of `Ref`'s 32 | * functionality for reading, writing, and transforming elements. 33 | */ 34 | def refs: immutable.IndexedSeq[Ref[A]] = new immutable.IndexedSeq[Ref[A]] { 35 | def length: Int = TArrayImpl.this.length 36 | def apply(index0: Int): Ref[A] = getRef(index0) 37 | } 38 | 39 | //////////////// TArray.View 40 | 41 | def tarray = TArrayImpl.this 42 | 43 | def apply(index: Int): A = getRef(index).single.get 44 | 45 | def update(index: Int, v: A): Unit = getRef(index).single.set(v) 46 | 47 | def refViews: immutable.IndexedSeq[Ref.View[A]] = new immutable.IndexedSeq[Ref.View[A]] { 48 | def length: Int = tarray.length 49 | def apply(index: Int): Ref.View[A] = getRef(index).single 50 | } 51 | 52 | /////////////// TxnDebuggable 53 | 54 | def dbgStr: String = atomic.unrecorded({ _ => mkStringPrefix("TArray", single) }, { _.toString }) 55 | 56 | def dbgValue: Any = atomic.unrecorded({ _ => toArray }, { x => x }) 57 | 58 | /////////////// Internal implementation 59 | 60 | private val metaIndexMask = { 61 | // We use min(length, nextPowerOfTwo(6 + length/16)) metadata elements. 62 | // The mask is always nextPowerOfTwo(6 + length/16) - 1, even if that is 63 | // too large. 64 | val n = 6 + length / 16 65 | var m = 7 66 | while (m < n - 1) 67 | m = (m << 1) + 1 68 | m 69 | } 70 | 71 | private val metaValues = new AtomicLongArray(math.min(metaIndexMask + 1, length)) 72 | 73 | private def getRef(index: Int): Ref[A] = { 74 | if (index < 0 || index >= length) 75 | throw new ArrayIndexOutOfBoundsException(index) 76 | 77 | new Handle[A] with RefOps[A] with ViewOps[A] { 78 | def handle: Handle[A] = this 79 | def single: Ref.View[A] = this 80 | def ref: Ref[A] = this 81 | 82 | def meta: Long = metaValues.get(metaOffset) 83 | def meta_=(v: Long): Unit = metaValues.set(metaOffset, v) 84 | def metaCAS(before: Long, after: Long): Boolean = metaValues.compareAndSet(metaOffset, before, after) 85 | def base = TArrayImpl.this 86 | def offset: Int = index 87 | def metaOffset: Int = index & metaIndexMask 88 | def data: A = values(index) 89 | def data_=(v: A): Unit = values(index) = v 90 | 91 | override def dbgStr: String = super[RefOps].dbgStr 92 | override def dbgValue: Any = super[RefOps].dbgValue 93 | 94 | override def toString: String = { 95 | "TArray@" + Integer.toHexString(System.identityHashCode(TArrayImpl.this)) + "(" + index + ")" 96 | } 97 | } 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/TxnLocalImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | // TxnLocalImpl 7 | 8 | private[ccstm] class TxnLocalImpl[A](init: => A, 9 | initialValue: InTxn => A, 10 | beforeCommit: InTxn => Unit, 11 | whilePreparing: InTxnEnd => Unit, 12 | whileCommitting: InTxnEnd => Unit, 13 | afterCommit: A => Unit, 14 | afterRollback: Txn.Status => Unit, 15 | afterCompletion: Txn.Status => Unit) extends Handle[A] with TxnLocal[A] { 16 | 17 | //////// stateless Handle 18 | 19 | // TODO: Return types should probably not be refined from Unit to Nothing 20 | 21 | def meta: Long = CCSTM.txnLocalMeta 22 | def meta_=(v: Long): Nothing = throw new Error 23 | def metaCAS(before: Long, after: Long): Boolean = throw new Error 24 | def base: AnyRef = this 25 | def offset: Int = 0 26 | def metaOffset: Int = 0 27 | def data: A = throw new Error 28 | def data_=(v: A): Unit = () 29 | 30 | 31 | //////// TxnLocal 32 | 33 | def isInitialized(implicit txn: InTxnEnd): Boolean = { 34 | txn.asInstanceOf[InTxnImpl].txnLocalFind(this) >= 0 35 | } 36 | 37 | //////// SourceLike 38 | 39 | def get(implicit txn: InTxnEnd): A = { 40 | val impl = txn.asInstanceOf[InTxnImpl] 41 | val i = impl.txnLocalFind(this) 42 | if (i >= 0) 43 | impl.txnLocalGet[A](i) 44 | else 45 | initialize(impl) 46 | } 47 | 48 | private def initialize(impl: InTxnImpl): A = { 49 | if (initialValue != null || beforeCommit != null) 50 | impl.requireActive() 51 | 52 | val v = if (initialValue != null) initialValue(impl) else init 53 | impl.txnLocalInsert(this, v) 54 | 55 | registerCallbacks(impl) 56 | 57 | v 58 | } 59 | 60 | private def registerCallbacks(impl: InTxnImpl): Unit = { 61 | // need to do afterRollback and afterCompletion first so that if there is a 62 | // remote txn cancel we've got them in place 63 | if (afterRollback != null) 64 | impl.afterRollback(afterRollback) 65 | if (afterCompletion != null) 66 | impl.afterCompletion(afterCompletion) 67 | 68 | if (beforeCommit != null) 69 | impl.beforeCommit(beforeCommit) 70 | if (whilePreparing != null) 71 | impl.whilePreparing(whilePreparing) 72 | if (whileCommitting != null) 73 | impl.whileCommitting(whileCommitting) 74 | if (afterCommit != null) { 75 | impl.whileCommitting { _ => 76 | val finalValue = impl.txnLocalGet[A](impl.txnLocalFind(this)) 77 | impl.afterCommit { _ => afterCommit(finalValue) } 78 | } 79 | } 80 | } 81 | 82 | def getWith[Z](f: (A) => Z)(implicit txn: InTxnEnd): Z = f(get) 83 | 84 | def relaxedGet(equiv: (A, A) => Boolean)(implicit txn: InTxnEnd): A = get 85 | 86 | //////// SinkLike 87 | 88 | def set(v: A)(implicit txn: InTxnEnd): Unit = { 89 | val impl = txn.asInstanceOf[InTxnImpl] 90 | val i = impl.txnLocalFind(this) 91 | if (i >= 0) 92 | impl.txnLocalUpdate(i, v) 93 | else { 94 | impl.txnLocalInsert(this, v) 95 | registerCallbacks(impl) 96 | } 97 | } 98 | 99 | def trySet(v: A)(implicit txn: InTxnEnd): Boolean = { set(v) ; true } 100 | 101 | //////// RefLike 102 | 103 | def swap(v: A)(implicit txn: InTxnEnd): A = { val z = get ; set(v) ; z } 104 | 105 | def transform(f: (A) => A)(implicit txn: InTxnEnd): Unit = set(f(get)) 106 | 107 | def transformIfDefined(pf: PartialFunction[A, A])(implicit txn: InTxnEnd): Boolean = { 108 | val v0 = get 109 | pf.isDefinedAt(v0) && { set(pf(v0)) ; true } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/TxnSlotManager.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | 7 | import java.util.concurrent.atomic.AtomicReferenceArray 8 | 9 | 10 | private case class SlotLock(txn: AnyRef, refCount: Int) 11 | 12 | /** This class manages a mapping from active transaction to a bounded integral 13 | * range, so that transaction identities to be packed into some of the bits of 14 | * an integral value. 15 | * 16 | * @author Nathan Bronson 17 | */ 18 | private[ccstm] final class TxnSlotManager[T <: AnyRef](range: Int, reservedSlots: Int) { 19 | 20 | assert(range >= 16 & (range & (range - 1)) == 0) 21 | assert(range >= reservedSlots + 16) 22 | 23 | private def nextSlot(tries: Int) = { 24 | ((skel.SimpleRandom.nextInt << 4) | ((-tries >> 1) & 0xf)) & (range - 1) 25 | } 26 | 27 | /** CAS on the entries manages the actual acquisition. Entries are either 28 | * transactions, or SlotLock objects. 29 | */ 30 | private val slots = new AtomicReferenceArray[AnyRef](range) 31 | 32 | @throws(classOf[InterruptedException]) 33 | def assign(txn: T, slotHint: Int): Int = { 34 | // We advance to the next slot number after the hint, wrapping around in a 35 | // 64 byte space. This avoids rollback from late steals, but keeps us in 36 | // a cache line we already own. 37 | var s = ((slotHint & ~0xf) | ((slotHint + 1) & 0xf)) & (range - 1) 38 | 39 | var tries = 0 40 | while (s < reservedSlots || slots.get(s) != null || !slots.compareAndSet(s, null, txn)) { 41 | s = nextSlot(tries) 42 | tries += 1 43 | if (tries > 100) { 44 | if (Thread.interrupted) 45 | throw new InterruptedException 46 | Thread.`yield`() 47 | } 48 | } 49 | s 50 | } 51 | 52 | /** Returns the slot associated with `slot` at some instant. The 53 | * returned value may be obsolete before this method returns. 54 | */ 55 | def lookup(slot:Int): T = unwrap(slots.get(slot)) 56 | 57 | private def unwrap(e: AnyRef): T = { 58 | e match { 59 | case SlotLock(txn, _) => txn.asInstanceOf[T] 60 | case txn => txn.asInstanceOf[T] 61 | } 62 | } 63 | 64 | /** A non-racy version of `lookup`, that must be paired with 65 | * `endLookup`. 66 | */ 67 | def beginLookup(slot: Int): T = { 68 | var e: AnyRef = null 69 | do { 70 | e = slots.get(slot) 71 | } while (e != null && !slots.compareAndSet(slot, e, locked(e))) 72 | unwrap(e) 73 | } 74 | 75 | private def locked(e: AnyRef): AnyRef = { 76 | e match { 77 | case SlotLock(txn, rc) => SlotLock(txn, rc + 1) 78 | case txn => SlotLock(txn, 1) 79 | } 80 | } 81 | 82 | def endLookup(slot: Int, observed: T): Unit = 83 | if (null != observed) release(slot) 84 | 85 | def release(slot: Int): Unit = { 86 | var e: AnyRef = null 87 | do { 88 | e = slots.get(slot) 89 | } while (!slots.compareAndSet(slot, e, unlocked(e))) 90 | } 91 | 92 | private def unlocked(e: AnyRef): AnyRef = { 93 | e match { 94 | case SlotLock(txn, 1) => txn 95 | case SlotLock(txn, rc) => SlotLock(txn, rc - 1) 96 | case _ => null 97 | } 98 | } 99 | 100 | // def assertAllReleased() { 101 | // for (i <- 0 until range) { 102 | // val e = slots.get(i) 103 | // if (null != e) { 104 | // assert(false, i + " -> " + e) 105 | // } 106 | // } 107 | // } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/UnrecordedRead.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.ccstm 4 | 5 | 6 | /** Holds the result of an unrecorded read, which may be used to avoid 7 | * transaction conflicts, or to detect ABA changes when performing 8 | * single-operation transactions. `ReleasableRead`s provide a 9 | * related functionality. 10 | * 11 | * When an unrecorded read is performed in a transaction, the caller is 12 | * responsible for guaranteeing that the transaction's behavior is correct, 13 | * even if the read becomes invalid prior to commit. Unrecorded reads may be 14 | * useful for heuristic decisions that can tolerate inconsistent or stale 15 | * data, for methods that register transaction handlers to perform 16 | * validation at a semantic level, or for optimistically traversing linked 17 | * data structures while tolerating mutations to earlier links. When used in 18 | * combination with transaction resource callbacks, it is important to 19 | * consider the case that the unrecorded read is already invalid before it is 20 | * returned to the requester. 21 | * 22 | * Writes by the same transaction that performed the unrecorded read are 23 | * '''not''' considered to invalidate the read. 24 | * 25 | * When called from a non-transactional context the returned instance can be 26 | * used to determine if a value has remained unchanged for a particular 27 | * interval, which may be useful to detect ABA situations. 28 | * 29 | * @author Nathan Bronson 30 | */ 31 | private[ccstm] trait UnrecordedRead[+T] { 32 | 33 | /** Returns the value observed by this `UnrecordedRead`, regardless of 34 | * whether it is still valid. 35 | */ 36 | def value: T 37 | 38 | /** Returns true if definitely no writes have been performed by a context 39 | * other than the one that performed this unrecorded read. 40 | */ 41 | def stillValid: Boolean 42 | 43 | /** Returns true if the unrecorded read was performed in a transaction, and 44 | * the source of this read is known to be in the transaction's read or write 45 | * set, in which case `stillValid` will definitely be true at the 46 | * transaction's commit (linearization) point if the transaction commits. 47 | * Returns false for non-transactional unrecorded reads. This method is for 48 | * optimization purposes only, a false result does not guarantee that the 49 | * read is not in the transaction's read or write set. If this method 50 | * returns true for an `UnrecordedRead` instance it will always 51 | * return true. 52 | * @return true if the caller may assume that `stillValid` will 53 | * be true for this `UnrecordedRead` if the bound transaction 54 | * is successfully committed. 55 | */ 56 | def recorded: Boolean 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/ccstm/ViewOps.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package ccstm 5 | 6 | import java.util.concurrent.TimeUnit 7 | 8 | /** The default implementation of `Ref.View`'s operations in CCSTM. */ 9 | private[ccstm] trait ViewOps[T] extends Ref.View[T] with Handle.Provider[T] { 10 | 11 | def handle: Handle[T] 12 | 13 | def get: T = InTxnImpl.dynCurrentOrNull match { 14 | case null => NonTxn.get(handle) 15 | case txn => txn.get(handle) 16 | } 17 | def getWith[Z](f: T => Z): Z = InTxnImpl.dynCurrentOrNull match { 18 | case null => f(NonTxn.get(handle)) 19 | case txn => txn.getWith(handle, f) 20 | } 21 | def relaxedGet(equiv: (T, T) => Boolean): T = InTxnImpl.dynCurrentOrNull match { 22 | case null => NonTxn.get(handle) 23 | case txn => txn.relaxedGet(handle, equiv) 24 | } 25 | def await(f: T => Boolean): Unit = InTxnImpl.dynCurrentOrNull match { 26 | case null => NonTxn.await(handle, f) 27 | case txn => if (!f(txn.get(handle))) Txn.retry(txn) 28 | } 29 | def tryAwait(timeout: Long, unit: TimeUnit)(f: T => Boolean): Boolean = InTxnImpl.dynCurrentOrNull match { 30 | case null => NonTxn.tryAwait(handle, f, unit.toNanos(timeout)) 31 | case txn => f(txn.get(handle)) || { Txn.retryFor(timeout, unit)(txn) ; false } 32 | } 33 | def set(v: T): Unit = InTxnImpl.dynCurrentOrNull match { 34 | case null => NonTxn.set(handle, v) 35 | case txn => txn.set(handle, v) 36 | } 37 | def trySet(v: T): Boolean = InTxnImpl.dynCurrentOrNull match { 38 | case null => NonTxn.trySet(handle, v) 39 | case txn => txn.trySet(handle, v) 40 | } 41 | def swap(v: T): T = InTxnImpl.dynCurrentOrNull match { 42 | case null => NonTxn.swap(handle, v) 43 | case txn => txn.swap(handle, v) 44 | } 45 | def compareAndSet(before: T, after: T): Boolean = InTxnImpl.dynCurrentOrNull match { 46 | case null => NonTxn.compareAndSet(handle, before, after) 47 | case txn => txn.compareAndSet(handle, before, after) 48 | } 49 | def compareAndSetIdentity[R <: AnyRef with T](before: R, after: T): Boolean = InTxnImpl.dynCurrentOrNull match { 50 | case null => NonTxn.compareAndSetIdentity(handle, before, after) 51 | case txn => txn.compareAndSetIdentity(handle, before, after) 52 | } 53 | def transform(f: T => T): Unit = InTxnImpl.dynCurrentOrNull match { 54 | case null => NonTxn.transformAndGet(handle, f) 55 | case txn => txn.transformAndGet(handle, f) 56 | } 57 | def getAndTransform(f: T => T): T = InTxnImpl.dynCurrentOrNull match { 58 | case null => NonTxn.getAndTransform(handle, f) 59 | case txn => txn.getAndTransform(handle, f) 60 | } 61 | def transformAndGet(f: T => T): T = InTxnImpl.dynCurrentOrNull match { 62 | case null => NonTxn.transformAndGet(handle, f) 63 | case txn => txn.transformAndGet(handle, f) 64 | } 65 | override def transformAndExtract[V](f: T => (T,V)): V = InTxnImpl.dynCurrentOrNull match { 66 | case null => NonTxn.transformAndExtract(handle, f) 67 | case txn => txn.transformAndExtract(handle, f) 68 | } 69 | 70 | def transformIfDefined(pf: PartialFunction[T,T]): Boolean = InTxnImpl.dynCurrentOrNull match { 71 | case null => NonTxn.transformIfDefined(handle, pf) 72 | case txn => txn.transformIfDefined(handle, pf) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/impl/AlternativeResult.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package impl 5 | 6 | import util.control.ControlThrowable 7 | 8 | /** See `PendingAtomicBlock` */ 9 | private[stm] case class AlternativeResult(value: Any) extends ControlThrowable 10 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/impl/RefFactory.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package impl 5 | 6 | import scala.collection.mutable 7 | import scala.reflect.ClassTag 8 | 9 | /** `RefFactory` is responsible for creating concrete `Ref` instances. */ 10 | trait RefFactory { 11 | def newRef(v0: Boolean): Ref[Boolean] 12 | def newRef(v0: Byte): Ref[Byte] 13 | def newRef(v0: Short): Ref[Short] 14 | def newRef(v0: Char): Ref[Char] 15 | def newRef(v0: Int): Ref[Int] 16 | def newRef(v0: Float): Ref[Float] 17 | def newRef(v0: Long): Ref[Long] 18 | def newRef(v0: Double): Ref[Double] 19 | def newRef(v0: Unit): Ref[Unit] 20 | 21 | /** `T` will not be one of the primitive types (for which a `newRef` 22 | * specialization exists). 23 | */ 24 | def newRef[A : ClassTag](v0: A): Ref[A] 25 | 26 | def newTxnLocal[A](init: => A, 27 | initialValue: InTxn => A, 28 | beforeCommit: InTxn => Unit, 29 | whilePreparing: InTxnEnd => Unit, 30 | whileCommitting: InTxnEnd => Unit, 31 | afterCommit: A => Unit, 32 | afterRollback: Txn.Status => Unit, 33 | afterCompletion: Txn.Status => Unit): TxnLocal[A] 34 | 35 | def newTArray[A : ClassTag](length: Int): TArray[A] 36 | def newTArray[A : ClassTag](xs: TraversableOnce[A]): TArray[A] 37 | 38 | def newTMap[A, B]: TMap[A, B] 39 | def newTMapBuilder[A, B]: mutable.Builder[(A, B), TMap[A, B]] 40 | 41 | def newTSet[A]: TSet[A] 42 | def newTSetBuilder[A]: mutable.Builder[A, TSet[A]] 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/impl/STMImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package impl 5 | 6 | import java.util.concurrent.TimeUnit 7 | 8 | private[impl] object STMImplHolder { 9 | var instance: STMImpl = STMImpl.createInstance() 10 | } 11 | 12 | /** `STMImpl` gathers all of the functionality required to plug an STM 13 | * implementation into `scala.concurrent.stm`. Only one implementation can 14 | * be selected, because `Ref`s and atomic blocks from different STM 15 | * implementations are not compatible. `STMImpl.instance` returns the 16 | * `STMImpl` instance that has been selected for this program execution. 17 | * 18 | * There are two ways to explicitly select the `STMImpl` instance: 19 | * 20 | * 1. set the JVM system property "scala.stm.impl" to the name of a class 21 | * that implements `STMImpl`; or 22 | * 23 | * 2. arrange for `STMImpl.select` or `STMImpl.trySelect` to be called 24 | * before any `Ref`s are constructed and before any atomic blocks are 25 | * executed. 26 | * 27 | * Setting the JVM system property "scala.stm.impl" is equivalent to making a 28 | * call to `STMImpl.select(System.getProperty("scala.stm.impl"))` before any 29 | * other `STMImpl` selections. 30 | * 31 | * If there is no explicitly selected `STMImpl` instance and the classpath 32 | * contains a class `scala.concurrent.stm.impl.DefaultFactory` that extends 33 | * `scala.concurrent.stm.impl.STMImpl.Factory`, then an instance of that 34 | * class will be instantiated and used to generate the `STMImpl` instance. 35 | * ScalaSTM implementations are encouraged to implement `DefaultFactory` so 36 | * that if a user includes the implementation's JAR file, it will be 37 | * automatically selected. 38 | * 39 | * If no explicit selection has been made and there is no definition of 40 | * `scala.concurrent.stm.impl.DefaultFactory` present in the classpath, then 41 | * ScalaSTM will fall back to the reference implementation 42 | * "scala.concurrent.stm.ccstm.CCSTM". 43 | * 44 | * @author Nathan Bronson 45 | */ 46 | object STMImpl { 47 | trait Factory { 48 | def createInstance(): STMImpl 49 | } 50 | 51 | /** Returns the instance of `STMImpl` that should be used to implement all 52 | * ScalaSTM functionality. Calling this method forces the implementation 53 | * to be chosen if it has not already been selected. 54 | */ 55 | def instance: STMImpl = STMImplHolder.instance 56 | 57 | // We duplicate the implementation of select() to avoid the need to 58 | // instantiate an STM that we won't end up using 59 | 60 | /** If no `STMImpl` instance has yet been selected, installs an instance of 61 | * `Class.forName(implClassName)` as the system-wide STM implementation. 62 | * Returns true if `implClassName` was newly or previously selected, or 63 | * returns false if another STM implementation was chosen. 64 | */ 65 | def trySelect(implClassName: String): Boolean = { 66 | explicitChoice = implClassName 67 | instance.getClass.getName == implClassName 68 | } 69 | 70 | /** Installs `Class.forName(implClassName)` as the system-wide STM 71 | * implementation if no `STMImpl` has yet been chosen, or verifies that 72 | * `implClassName` was previously selected, throwing 73 | * `IllegalStateException` if a different STM implementation has already 74 | * been selected 75 | */ 76 | def select(implClassName: String): Unit = { 77 | if (!trySelect(implClassName)) { 78 | throw new IllegalStateException( 79 | "unable to select STMImpl class " + implClassName + ", " + instance + " already installed") 80 | } 81 | } 82 | 83 | /** Installs `impl` as the system-wide STM implementation if no `STMImpl` 84 | * has yet been chosen, or verifies that `impl` is equal to the previously 85 | * selected instance, throwing `IllegalStateException` if an STM 86 | * implementation has already been selected and `impl != instance` 87 | */ 88 | def select(impl: STMImpl): Unit = { 89 | explicitChoice = impl 90 | if (impl != instance) { 91 | throw new IllegalStateException( 92 | "unable to select STMImpl " + impl + ", " + instance + " already installed") 93 | } 94 | } 95 | 96 | 97 | /** May be a String class name, an STMImpl, or null */ 98 | @volatile private var explicitChoice: AnyRef = null 99 | 100 | private[impl] def createInstance(): STMImpl = { 101 | var choice: AnyRef = System.getProperty("scala.stm.impl") 102 | 103 | if (choice == null) 104 | choice = explicitChoice 105 | 106 | if (choice == null) { 107 | choice = try { 108 | val fc = Class.forName("scala.concurrent.stm.impl.DefaultFactory") 109 | fc.newInstance().asInstanceOf[STMImpl.Factory].createInstance() 110 | } catch { 111 | case _: ClassNotFoundException => "scala.concurrent.stm.ccstm.CCSTM" 112 | } 113 | } 114 | 115 | choice match { 116 | case s: String => Class.forName(s).newInstance().asInstanceOf[STMImpl] 117 | case i: STMImpl => i 118 | } 119 | } 120 | } 121 | 122 | /** `STMImpl` gathers all of the functionality required to plug an STM 123 | * implementation into `scala.concurrent.stm`. See the `STMImpl` companion 124 | * object for information on controlling which `STMImpl` is selected at run 125 | * time. 126 | * 127 | * @author Nathan Bronson 128 | */ 129 | trait STMImpl extends RefFactory with TxnContext with TxnExecutor { 130 | 131 | /** Returns a new commit barrier suitable for coordinating commits by this 132 | * STM implementation. 133 | */ 134 | def newCommitBarrier(timeout: Long, unit: TimeUnit): CommitBarrier 135 | } 136 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/impl/TxnContext.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package impl 5 | 6 | /** `TxnContext` captures the implementation-specific functionality of locating 7 | * the `InTxn` dynamically bound to the current `Thread`. Users should use the 8 | * lookup methods provided by `object Txn`. 9 | * 10 | * @author Nathan Bronson 11 | */ 12 | trait TxnContext { 13 | 14 | /** Returns `Some(txn)` if `txn` is the `InTxn` active or in the process of 15 | * committing on the current thread, `None` otherwise. 16 | */ 17 | def findCurrent(implicit mt: MaybeTxn): Option[InTxn] 18 | 19 | /** Returns the current `InTxn` instance if it is active or in the process of 20 | * committing on the current thread, `null` otherwise. Always performs a 21 | * dynamic lookup. 22 | */ 23 | def dynCurrentOrNull: InTxn 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/package.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent 4 | 5 | import java.util.concurrent.TimeUnit 6 | 7 | import scala.language.implicitConversions 8 | 9 | package object stm { 10 | 11 | /** Atomically executes atomic blocks using the default `TxnExecutor`. See 12 | * `TxnExecutor.apply`. 13 | */ 14 | def atomic: scala.concurrent.stm.TxnExecutor = scala.concurrent.stm.TxnExecutor.defaultAtomic 15 | 16 | /** Equivalent to `Txn.retry`. */ 17 | def retry(implicit txn: scala.concurrent.stm.InTxn): Nothing = scala.concurrent.stm.Txn.retry 18 | 19 | /** Equivalent to `Txn.retryFor(timeout, unit)`. */ 20 | def retryFor(timeout: Long, unit: TimeUnit = TimeUnit.MILLISECONDS)(implicit txn: scala.concurrent.stm.InTxn): Unit = 21 | scala.concurrent.stm.Txn.retryFor(timeout, unit) 22 | 23 | /** This is the first half of the machinery for implementing `orAtomic`. */ 24 | implicit def wrapChainedAtomic[A](lhs: => A): PendingAtomicBlock[A] = new PendingAtomicBlock(lhs) 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/AbstractNestingLevel.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | private[stm] trait AbstractNestingLevel extends NestingLevel { 7 | def txn: AbstractInTxn 8 | def parLevel: AbstractNestingLevel 9 | override def root: AbstractNestingLevel 10 | 11 | def parent: Option[NestingLevel] = Option(parLevel) 12 | 13 | private[skel] var _beforeCommitSize = 0 14 | private[skel] var _whileValidatingSize = 0 15 | private[skel] var _whilePreparingSize = 0 16 | private[skel] var _whileCommittingSize = 0 17 | private[skel] var _afterCommitSize = 0 18 | private[skel] var _afterRollbackSize = 0 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/CallbackList.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import annotation.tailrec 7 | 8 | 9 | private[stm] class CallbackList[A] { 10 | 11 | private final val InitialCapacity = 128 12 | private final val MaxEmptyCapacity = 8192 13 | 14 | private var _size = 0 15 | private var _data = new Array[A => Unit](InitialCapacity) 16 | 17 | def isEmpty: Boolean = _size == 0 18 | 19 | def size: Int = _size 20 | 21 | def size_= (newSize: Int): Unit = { 22 | if (newSize != _size) 23 | changeSize(newSize) 24 | } 25 | 26 | private def changeSize(newSize: Int): Unit = { 27 | if (newSize < 0 || newSize > _size) 28 | throw new IllegalArgumentException 29 | 30 | if (newSize == 0 && _data.length > MaxEmptyCapacity) { 31 | // reallocate if the array is too big, so that a single large txn doesn't 32 | // permanently increase the memory footprint of this thread 33 | reset() 34 | } else { 35 | java.util.Arrays.fill(_data.asInstanceOf[Array[AnyRef]], newSize, _size, null) 36 | _size = newSize 37 | } 38 | } 39 | 40 | private def reset(): Unit = { 41 | _data = new Array[A => Unit](InitialCapacity) 42 | _size = 0 43 | } 44 | 45 | def += (handler: A => Unit): Unit = { 46 | if (_size == _data.length) 47 | grow() 48 | _data(_size) = handler 49 | _size += 1 50 | } 51 | 52 | private def grow(): Unit = { 53 | val a = new Array[A => Unit](_data.length * 2) 54 | System.arraycopy(_data, 0, a, 0, _data.length) 55 | _data = a 56 | } 57 | 58 | def apply(i: Int): (A => Unit) = _data(i) 59 | 60 | def fire(level: NestingLevel, arg: A): Unit = { 61 | if (_size > 0) 62 | fire(level, arg, 0) 63 | } 64 | 65 | @tailrec private def fire(level: NestingLevel, arg: A, i: Int): Unit = { 66 | if (i < _size && shouldFire(level)) { 67 | try { 68 | _data(i)(arg) 69 | } catch { 70 | case x: Throwable => 71 | val s = level.requestRollback(Txn.UncaughtExceptionCause(x)) 72 | assert(s.isInstanceOf[Txn.RolledBack]) 73 | } 74 | fire(level, arg, i + 1) 75 | } 76 | } 77 | 78 | private def shouldFire(level: NestingLevel): Boolean = !level.status.isInstanceOf[Txn.RolledBack] 79 | 80 | /** Sets the size of this callback list to `newSize`, and returns a the 81 | * discarded handlers. 82 | */ 83 | def truncate(newSize: Int): Array[A => Unit] = { 84 | if (_size == newSize) { 85 | // nothing to do 86 | null 87 | } else { 88 | // copy 89 | val z = new Array[A => Unit](_size - newSize) 90 | System.arraycopy(_data, newSize, z, 0, z.length) 91 | size = newSize 92 | z 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/HashTrieTMap.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import scala.collection.mutable 7 | 8 | private[stm] object HashTrieTMap { 9 | 10 | def empty[A, B]: TMap[A, B] = new HashTrieTMap(Ref(TxnHashTrie.emptyMapNode[A, B]).single) 11 | 12 | def newBuilder[A, B]: mutable.Builder[(A, B), TMap[A, B]] = new mutable.Builder[(A, B), TMap[A, B]] { 13 | var root: TxnHashTrie.BuildingNode[A, B] = TxnHashTrie.emptyMapBuildingNode[A, B] 14 | 15 | def clear(): Unit = { root = TxnHashTrie.emptyMapBuildingNode[A, B] } 16 | 17 | def += (kv: (A, B)): this.type = { root = TxnHashTrie.buildingPut(root, kv._1, kv._2) ; this } 18 | 19 | def result(): TMap[A, B] = { 20 | val r = root 21 | root = null 22 | new HashTrieTMap(Ref(r.endBuild).single) 23 | } 24 | } 25 | } 26 | 27 | private[skel] class HashTrieTMap[A, B] private (root0: Ref.View[TxnHashTrie.Node[A, B]] 28 | ) extends TxnHashTrie[A, B](root0) with TMapViaClone[A, B] { 29 | 30 | //// construction 31 | 32 | override def empty: TMap.View[A, B] = new HashTrieTMap(Ref(TxnHashTrie.emptyMapNode[A, B]).single) 33 | override def clone: HashTrieTMap[A, B] = new HashTrieTMap(cloneRoot) 34 | 35 | //// TMap.View aggregates 36 | 37 | override def isEmpty: Boolean = singleIsEmpty 38 | override def size: Int = singleSize 39 | override def iterator: Iterator[(A, B)] = mapIterator 40 | override def keysIterator: Iterator[A] = mapKeyIterator 41 | override def valuesIterator: Iterator[B] = mapValueIterator 42 | override def foreach[U](f: ((A, B)) => U): Unit = singleMapForeach(f) 43 | 44 | override def clear(): Unit = { root() = TxnHashTrie.emptyMapNode[A, B] } 45 | 46 | //// TMap.View per-element 47 | 48 | override def contains(key: A): Boolean = singleContains(key) 49 | override def apply(key: A): B = singleGetOrThrow(key) 50 | def get(key: A): Option[B] = singleGet(key) 51 | 52 | override def put(key: A, value: B): Option[B] = singlePut(key, value) 53 | override def update(key: A, value: B): Unit = singlePut(key, value) 54 | override def += (kv: (A, B)): this.type = { singlePut(kv._1, kv._2) ; this } 55 | 56 | override def remove(key: A): Option[B] = singleRemove(key) 57 | override def -= (key: A): this.type = { singleRemove(key) ; this } 58 | 59 | //// optimized TMap versions 60 | 61 | def isEmpty(implicit txn: InTxn): Boolean = txnIsEmpty 62 | def size(implicit txn: InTxn): Int = singleSize 63 | def foreach[U](f: ((A, B)) => U)(implicit txn: InTxn): Unit = txnMapForeach(f) 64 | 65 | def contains(key: A)(implicit txn: InTxn): Boolean = txnContains(key) 66 | def apply(key: A)(implicit txn: InTxn): B = txnGetOrThrow(key) 67 | def get(key: A)(implicit txn: InTxn): Option[B] = txnGet(key) 68 | def put(key: A, value: B)(implicit txn: InTxn): Option[B] = txnPut(key, value) 69 | def remove(key: A)(implicit txn: InTxn): Option[B] = txnRemove(key) 70 | 71 | def transform(f: (A, B) => B)(implicit txn: InTxn): this.type = { single transform f ; this } 72 | def retain(p: (A, B) => Boolean)(implicit txn: InTxn): this.type = { single retain p ; this } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/HashTrieTSet.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import scala.collection.mutable 7 | import scala.concurrent.stm.skel.TxnHashTrie.SetBuildingNode 8 | 9 | private[stm] object HashTrieTSet { 10 | 11 | def empty[A]: TSet[A] = new HashTrieTSet(Ref(TxnHashTrie.emptySetNode[A]).single) 12 | 13 | def newBuilder[A]: mutable.Builder[A, TSet[A]] = new mutable.Builder[A, TSet[A]] { 14 | var root: SetBuildingNode[A] = TxnHashTrie.emptySetBuildingNode[A] 15 | 16 | def clear(): Unit = { root = TxnHashTrie.emptySetBuildingNode[A] } 17 | 18 | def += (elem: A): this.type = { root = TxnHashTrie.buildingAdd(root, elem) ; this } 19 | 20 | def result(): TSet[A] = { 21 | val r = root 22 | root = null 23 | new HashTrieTSet(Ref(r.endBuild).single) 24 | } 25 | } 26 | } 27 | 28 | private[skel] class HashTrieTSet[A] private (root0: Ref.View[TxnHashTrie.SetNode[A]] 29 | ) extends TxnHashTrie[A, AnyRef](root0) with TSetViaClone[A] { 30 | 31 | //// construction 32 | 33 | override def empty: TSet.View[A] = new HashTrieTSet(Ref(TxnHashTrie.emptySetNode[A]).single) 34 | override def clone: HashTrieTSet[A] = new HashTrieTSet(cloneRoot) 35 | 36 | //// TSet.View aggregates 37 | 38 | override def isEmpty: Boolean = singleIsEmpty 39 | override def size: Int = singleSize 40 | override def iterator: Iterator[A] = setIterator 41 | override def foreach[U](f: A => U): Unit = { singleSetForeach(f) } 42 | override def clear(): Unit = { root() = TxnHashTrie.emptySetNode[A] } 43 | 44 | //// TSet.View per-element 45 | 46 | def contains(elem: A): Boolean = singleContains(elem) 47 | 48 | override def add(elem: A): Boolean = singlePut(elem, null).isEmpty 49 | def += (elem: A): this.type = { singlePut(elem, null) ; this } 50 | 51 | override def remove(elem: A): Boolean = singleRemove(elem).isDefined 52 | def -= (elem: A): this.type = { singleRemove(elem) ; this } 53 | 54 | //// optimized TSet versions 55 | 56 | def isEmpty(implicit txn: InTxn): Boolean = txnIsEmpty 57 | def size(implicit txn: InTxn): Int = singleSize 58 | def foreach[U](f: A => U)(implicit txn: InTxn): Unit = txnSetForeach(f) 59 | 60 | def contains(elem: A)(implicit txn: InTxn): Boolean = txnContains(elem) 61 | def add(elem: A)(implicit txn: InTxn): Boolean = txnPut(elem, null ).isEmpty 62 | def remove(elem: A)(implicit txn: InTxn): Boolean = txnRemove(elem).isDefined 63 | 64 | def retain(p: (A) => Boolean)(implicit txn: InTxn): this.type = { single retain p ; this } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/RollbackError.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.skel 4 | 5 | import util.control.ControlThrowable 6 | 7 | 8 | /** A reusable exception instance, thrown by CCSTM when a transaction is doomed 9 | * and should not continue executing. User code should either rethrow this 10 | * exception or not catch it. 11 | * 12 | * @author Nathan Bronson 13 | */ 14 | private[stm] object RollbackError extends Error with ControlThrowable { 15 | override def fillInStackTrace(): Throwable = this 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/SimpleRandom.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.skel 4 | 5 | 6 | /** A random number generator that focuses on speed and lack of inter-thread 7 | * interference, rather than on the quality of the numbers returned. The 8 | * `object SimpleRandom` is striped internally to reduce 9 | * contention when accessed from multiple threads. The `class 10 | * SimpleRandom` should only be used by a single thread. 11 | *

12 | * The constants in this 64-bit linear congruential random number generator 13 | * are from http://nuclear.llnl.gov/CNP/rng/rngman/node4.html. 14 | * 15 | * @author Nathan Bronson 16 | */ 17 | object SimpleRandom { 18 | // 64 byte cache lines are typical, so there are 8 slots per cache line. 19 | // This means that the probability that any two threads have false sharing is 20 | // p = 8 / #slots. If there are n processors, each of which is running 1 21 | // thread, then the probability that no other threads have false sharing with 22 | // the current thread is (1-p)^(n-1). If p is small, that is about 23 | // 1 - (n-1)p, which is pretty close to 1 - np. If we want the probability 24 | // of false conflict for a thread to be less than k, then we need np < k, or 25 | // p < k/n, or 8/Slots < k/n, or #slots > 8n/k. If we let k = 1/8, then we 26 | // get #slots=64*n. 27 | private val mask = { 28 | val min = 64 * Runtime.getRuntime.availableProcessors 29 | var slots = 1 30 | while (slots < min) slots *= 2 31 | slots - 1 32 | } 33 | 34 | private val states = Array.tabulate(mask + 1)({ _ * 0x123456789abcdefL }) 35 | 36 | /** Returns a random value chosen from a uniform distribution of all valid 37 | * `Int`s. 38 | */ 39 | def nextInt(): Int = { 40 | val id = (Thread.currentThread.getId.asInstanceOf[Int] * 13) & mask 41 | 42 | val next = step(states(id)) 43 | states(id) = next 44 | 45 | extract(next) 46 | } 47 | 48 | /** Returns a random value chosen from a uniform distribution of the 49 | * non-negative integers less than `n`, or throws `IllegalArgumentException` 50 | * if `n` is negative or zero. 51 | */ 52 | def nextInt(n: Int): Int = { 53 | if (n <= 0) 54 | throw new IllegalArgumentException 55 | 56 | var x = -1 57 | while (x == -1) x = tryClamp(nextInt(), n) 58 | x 59 | } 60 | 61 | private def step(x: Long) = x * 2862933555777941757L + 3037000493L 62 | 63 | private def extract(x: Long) = (x >> 30).asInstanceOf[Int] 64 | 65 | /** r is the random, returns -1 on failure. */ 66 | private def tryClamp(r: Int, n: Int): Int = { 67 | // get a positive number 68 | val x = r & Int.MaxValue 69 | 70 | if ((n & -n) == n) { 71 | // for powers of two, we use high bits instead of low bits 72 | ((x.toLong * n) >> 31).toInt 73 | } else { 74 | val z = x % n 75 | if (x - z + (n - 1) < 0) { 76 | // x is bigger than floor(MAX_INT/n)*n, so we are not getting an even 77 | // distribution. Try again. 78 | -1 79 | } else { 80 | z 81 | } 82 | } 83 | } 84 | } 85 | 86 | /** An clonable unsynchronized random number generator that uses the same 87 | * algorithm as the concurrent `object SimpleRandom`. The caller must ensure 88 | * that each `SimpleRandom` instance is used from only one thread at a time. 89 | * 90 | * @author Nathan Bronson 91 | */ 92 | class SimpleRandom private (private var _state: Long, dummy: Boolean) { 93 | import SimpleRandom._ 94 | 95 | def this(seed: Int) = this(SimpleRandom.step(SimpleRandom.step(seed)), false) 96 | def this() = this(System.identityHashCode(Thread.currentThread)) 97 | 98 | override def clone = new SimpleRandom(_state, false) 99 | 100 | /** Returns a random value chosen from a uniform distribution of all valid 101 | * `Int`s. 102 | */ 103 | def nextInt(): Int = { 104 | _state = step(_state) 105 | extract(_state) 106 | } 107 | 108 | /** Returns a random value chosen from a uniform distribution of the 109 | * non-negative integers less than `n`, or throws `IllegalArgumentException` 110 | * if `n` is negative or zero. 111 | */ 112 | def nextInt(n: Int): Int = { 113 | if (n <= 0) 114 | throw new IllegalArgumentException 115 | 116 | var x = -1 117 | while (x == -1) x = tryClamp(nextInt(), n) 118 | x 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/StubInTxn.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | class StubInTxn extends InTxn { 7 | import concurrent.stm.Txn._ 8 | 9 | // TODO: Return types should probably not be refined from Unit to Nothing 10 | 11 | def executor: TxnExecutor = throw new AbstractMethodError 12 | def status: Status = throw new AbstractMethodError 13 | def rootLevel: NestingLevel = throw new AbstractMethodError 14 | def currentLevel: NestingLevel = throw new AbstractMethodError 15 | def rollback(cause: RollbackCause): Nothing = throw new AbstractMethodError 16 | def retry(): Nothing = throw new AbstractMethodError 17 | def retryFor(timeoutNanos: Long): Unit = { throw new AbstractMethodError } 18 | def beforeCommit(handler: InTxn => Unit): Nothing = throw new AbstractMethodError 19 | def whilePreparing(handler: InTxnEnd => Unit): Nothing = throw new AbstractMethodError 20 | def whileCommitting(handler: InTxnEnd => Unit): Nothing = throw new AbstractMethodError 21 | def afterCommit(handler: Status => Unit): Nothing = throw new AbstractMethodError 22 | def afterRollback(handler: Status => Unit): Nothing = throw new AbstractMethodError 23 | def afterCompletion(handler: Status => Unit): Nothing = throw new AbstractMethodError 24 | def setExternalDecider(decider: ExternalDecider): Nothing = throw new AbstractMethodError 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/StubSTMImpl.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import java.util.concurrent.TimeUnit 7 | 8 | import scala.collection.mutable 9 | import scala.reflect.ClassTag 10 | 11 | class StubSTMImpl extends impl.STMImpl { 12 | 13 | //////// RefFactory 14 | 15 | def newRef(v0: Boolean) : Ref[Boolean] = throw new AbstractMethodError 16 | def newRef(v0: Byte) : Ref[Byte] = throw new AbstractMethodError 17 | def newRef(v0: Short) : Ref[Short] = throw new AbstractMethodError 18 | def newRef(v0: Char) : Ref[Char] = throw new AbstractMethodError 19 | def newRef(v0: Int) : Ref[Int] = throw new AbstractMethodError 20 | def newRef(v0: Float) : Ref[Float] = throw new AbstractMethodError 21 | def newRef(v0: Long) : Ref[Long] = throw new AbstractMethodError 22 | def newRef(v0: Double) : Ref[Double] = throw new AbstractMethodError 23 | def newRef(v0: Unit) : Ref[Unit] = throw new AbstractMethodError 24 | def newRef[A : ClassTag](v0: A): Ref[A] = throw new AbstractMethodError 25 | 26 | def newTxnLocal[A](init: => A, 27 | initialValue: InTxn => A, 28 | beforeCommit: InTxn => Unit, 29 | whilePreparing: InTxnEnd => Unit, 30 | whileCommitting: InTxnEnd => Unit, 31 | afterCommit: A => Unit, 32 | afterRollback: Txn.Status => Unit, 33 | afterCompletion: Txn.Status => Unit): TxnLocal[A] = throw new AbstractMethodError 34 | 35 | def newTArray[A : ClassTag](length: Int): TArray[A] = throw new AbstractMethodError 36 | def newTArray[A : ClassTag](xs: TraversableOnce[A]): TArray[A] = throw new AbstractMethodError 37 | 38 | def newTMap[A, B]: TMap[A, B] = throw new AbstractMethodError 39 | def newTMapBuilder[A, B]: mutable.Builder[(A, B), TMap[A, B]] = throw new AbstractMethodError 40 | 41 | def newTSet[A]: TSet[A] = throw new AbstractMethodError 42 | def newTSetBuilder[A]: mutable.Builder[A, TSet[A]] = throw new AbstractMethodError 43 | 44 | //////// TxnContext 45 | 46 | def findCurrent(implicit mt: MaybeTxn): Option[InTxn] = throw new AbstractMethodError 47 | def dynCurrentOrNull: InTxn = throw new AbstractMethodError 48 | 49 | //////// TxnExecutor 50 | 51 | def apply[Z](block: InTxn => Z)(implicit mt: MaybeTxn): Z = throw new AbstractMethodError 52 | def oneOf[Z](blocks: (InTxn => Z)*)(implicit mt: MaybeTxn): Z = throw new AbstractMethodError 53 | def unrecorded[Z](block: InTxn => Z, outerFailure: Txn.RollbackCause => Z)(implicit mt: MaybeTxn): Z = throw new AbstractMethodError 54 | def pushAlternative[Z](mt: MaybeTxn, block: InTxn => Z): Boolean = throw new AbstractMethodError 55 | def compareAndSet[A, B](a: Ref[A], a0: A, a1: A, b: Ref[B], b0: B, b1: B): Boolean = throw new AbstractMethodError 56 | def compareAndSetIdentity[A <: AnyRef, B <: AnyRef](a: Ref[A], a0: A, a1: A, b: Ref[B], b0: B, b1: B): Boolean = throw new AbstractMethodError 57 | def retryTimeoutNanos: Option[Long] = throw new AbstractMethodError 58 | def withRetryTimeoutNanos(timeout: Option[Long]): TxnExecutor = throw new AbstractMethodError 59 | def isControlFlow(x: Throwable): Boolean = throw new AbstractMethodError 60 | def withControlFlowRecognizer(pf: PartialFunction[Throwable, Boolean]): TxnExecutor = throw new AbstractMethodError 61 | def postDecisionFailureHandler: (Txn.Status, Throwable) => Unit = throw new AbstractMethodError 62 | def withPostDecisionFailureHandler(handler: (Txn.Status, Throwable) => Unit): TxnExecutor = throw new AbstractMethodError 63 | 64 | //////// STMImpl 65 | 66 | def newCommitBarrier(timeout: Long, unit: TimeUnit): CommitBarrier = throw new AbstractMethodError 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/TMapViaClone.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import scala.collection.{immutable, mutable} 7 | 8 | private[stm] object TMapViaClone { 9 | class FrozenMutableMap[A, B](self: mutable.Map[A, B]) extends immutable.Map[A, B] { 10 | override def isEmpty: Boolean = self.isEmpty 11 | override def size: Int = self.size 12 | def get(key: A): Option[B] = self.get(key) 13 | def iterator: Iterator[(A, B)] = self.iterator 14 | override def foreach[U](f: ((A, B)) => U): Unit = { self foreach f } 15 | def + [B1 >: B](kv: (A, B1)): immutable.Map[A, B1] = 16 | new FrozenMutableMap(self.clone().asInstanceOf[mutable.Map[A, B1]] += kv) 17 | def - (k: A): immutable.Map[A, B] = new FrozenMutableMap(self.clone() -= k) 18 | } 19 | } 20 | 21 | /** Provides an implementation for the bulk of the functionality of `TMap` and 22 | * `TMap.View` by making extensive use of `clone()`. Assumes that the 23 | * underlying implementation of `clone()` is O(1). 24 | * 25 | * @author Nathan Bronson 26 | */ 27 | private[stm] trait TMapViaClone[A, B] extends TMap.View[A, B] with TMap[A, B] { 28 | import TMapViaClone._ 29 | 30 | // Implementations may be able to do better. 31 | override def snapshot: immutable.Map[A, B] = new FrozenMutableMap(clone()) 32 | 33 | def tmap: TMap[A, B] = this 34 | def single: TMap.View[A, B] = this 35 | 36 | /** Something like `"TMap[size=1]((1 -> 10))"`, stopping after 1K chars */ 37 | def dbgStr: String = atomic.unrecorded({ _ => 38 | mkStringPrefix("TMap", single.view.map { kv => kv._1 + " -> " + kv._2 }) 39 | }, { _.toString }) 40 | 41 | /** Returns an array of key/value pairs, since that is likely to be the 42 | * easiest to examine in a debugger. Also, this avoids problems with 43 | * relying on copy-on-write after discarding `Ref` writes. 44 | */ 45 | def dbgValue: Any = atomic.unrecorded({ _ => toArray }, { x => x }) 46 | 47 | //////////// builder functionality (from mutable.MapLike via TMap.View) 48 | 49 | override protected[this] def newBuilder: TMap.View[A, B] = empty 50 | 51 | override def result(): TMap.View[A, B] = this 52 | 53 | 54 | //////////// construction of new TMaps 55 | 56 | // A cheap clone means that mutable.MapLike's implementations of +, ++, 57 | // -, and -- are all pretty reasonable. 58 | 59 | override def clone: TMap.View[A, B] 60 | 61 | //////////// atomic compound ops 62 | 63 | override def getOrElseUpdate(key: A, op: => B): B = { 64 | single.get(key) getOrElse { 65 | atomic { implicit txn => 66 | tmap.get(key) getOrElse { val v = op ; tmap.put(key, v) ; v } 67 | } 68 | } 69 | } 70 | 71 | override def transform(f: (A, B) => B): this.type = { 72 | atomic { implicit txn => 73 | for (kv <- tmap) 74 | tmap.update(kv._1, f(kv._1, kv._2)) 75 | } 76 | this 77 | } 78 | 79 | override def retain(p: (A, B) => Boolean): this.type = { 80 | atomic { implicit txn => 81 | for (kv <- tmap) 82 | if (!p(kv._1, kv._2)) 83 | tmap -= kv._1 84 | } 85 | this 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/scala/scala/concurrent/stm/skel/TSetViaClone.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import scala.collection.{immutable, mutable} 7 | 8 | private[stm] object TSetViaClone { 9 | class FrozenMutableSet[A](self: mutable.Set[A]) extends immutable.Set[A] { 10 | override def isEmpty: Boolean = self.isEmpty 11 | override def size: Int = self.size 12 | def contains(key: A): Boolean = self.contains(key) 13 | def iterator: Iterator[(A)] = self.iterator 14 | override def foreach[U](f: A => U): Unit = { self foreach f } 15 | def + (x: A): immutable.Set[A] = new FrozenMutableSet(self.clone() += x) 16 | def - (x: A): immutable.Set[A] = new FrozenMutableSet(self.clone() -= x) 17 | } 18 | } 19 | 20 | /** Provides an implementation for the bulk of the functionality of `TSet` and 21 | * `TSet.View` by making extensive use of `clone()`. Assumes that the 22 | * underlying implementation of `clone()` is O(1). 23 | * 24 | * @author Nathan Bronson 25 | */ 26 | private[stm] trait TSetViaClone[A] extends TSet.View[A] with TSet[A] { 27 | import TSetViaClone._ 28 | 29 | // Implementations may be able to do better. 30 | override def snapshot: immutable.Set[A] = new FrozenMutableSet(clone()) 31 | 32 | def tset: TSet[A] = this 33 | def single: TSet.View[A] = this 34 | 35 | /** Something like `"TSet[size=3](3, 1, 10)"`, stopping after 1K chars */ 36 | def dbgStr: String = atomic.unrecorded({ _ => mkStringPrefix("TSet", single) }, { _.toString }) 37 | 38 | /** Returns an array of elements, since that is likely to be the 39 | * easiest to examine in a debugger. Also, this avoids problems with 40 | * relying on copy-on-write after discarding `Ref` writes. 41 | */ 42 | def dbgValue: Any = atomic.unrecorded({ _ => toArray[Any] }, { x => x }) 43 | 44 | //////////// builder functionality (from mutable.SetLike via TSet.View) 45 | 46 | override protected[this] def newBuilder: TSet.View[A] = empty 47 | 48 | override def result(): TSet.View[A] = this 49 | 50 | 51 | //////////// construction of new TSets 52 | 53 | // A cheap clone() means that mutable.SetLike's implementations of +, ++, 54 | // -, and -- are all pretty reasonable. 55 | 56 | override def clone: TSet.View[A] 57 | 58 | //////////// atomic compound ops 59 | 60 | override def retain(p: A => Boolean): Unit = { 61 | atomic { implicit txn => 62 | for (x <- tset) 63 | if (!p(x)) 64 | tset -= x 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/scala/concurrent/stm/JavaAPITests.java: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm; 4 | 5 | import static org.junit.Assert.*; 6 | import org.junit.Test; 7 | 8 | import static scala.concurrent.stm.japi.STM.*; 9 | 10 | import java.util.concurrent.Callable; 11 | 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.List; 15 | 16 | public class JavaAPITests { 17 | @Test 18 | public void createIntegerRef() { 19 | Ref.View ref = newRef(0); 20 | int unboxed = ref.get(); 21 | assertEquals(0, unboxed); 22 | } 23 | 24 | @Test 25 | public void atomicWithRunnable() { 26 | final Ref.View ref = newRef(0); 27 | atomic(new Runnable() { 28 | public void run() { 29 | ref.set(10); 30 | } 31 | }); 32 | int value = ref.get(); 33 | assertEquals(10, value); 34 | } 35 | 36 | @Test 37 | public void atomicWithCallable() { 38 | final Ref.View ref = newRef(0); 39 | int oldValue = atomic(new Callable() { 40 | public Integer call() { 41 | return ref.swap(10); 42 | } 43 | }); 44 | assertEquals(0, oldValue); 45 | int newValue = ref.get(); 46 | assertEquals(10, newValue); 47 | } 48 | 49 | @Test(expected = TestException.class) 50 | public void failingTransaction() { 51 | final Ref.View ref = newRef(0); 52 | try { 53 | atomic(new Runnable() { 54 | public void run() { 55 | ref.set(10); 56 | throw new TestException(); 57 | } 58 | }); 59 | } catch (TestException e) { 60 | int value = ref.get(); 61 | assertEquals(0, value); 62 | throw e; 63 | } 64 | } 65 | 66 | @Test 67 | public void transformInteger() { 68 | Ref.View ref = newRef(0); 69 | transform(ref, new Transformer() { 70 | public Integer apply(Integer i) { 71 | return i + 10; 72 | } 73 | }); 74 | int value = ref.get(); 75 | assertEquals(10, value); 76 | } 77 | 78 | @Test 79 | public void getAndTransformInteger() { 80 | Ref.View ref = newRef(0); 81 | int value = getAndTransform(ref, new Transformer() { 82 | public Integer apply(Integer i) { 83 | return i + 10; 84 | } 85 | }); 86 | assertEquals(0, value); 87 | } 88 | 89 | @Test 90 | public void transformAndGetInteger() { 91 | Ref.View ref = newRef(0); 92 | int value = transformAndGet(ref, new Transformer() { 93 | public Integer apply(Integer i) { 94 | return i + 10; 95 | } 96 | }); 97 | assertEquals(10, value); 98 | } 99 | 100 | @Test 101 | public void incrementInteger() { 102 | Ref.View ref = newRef(0); 103 | increment(ref, 10); 104 | int value = ref.get(); 105 | assertEquals(10, value); 106 | } 107 | 108 | @Test 109 | public void incrementLong() { 110 | Ref.View ref = newRef(0L); 111 | increment(ref, 10L); 112 | long value = ref.get(); 113 | assertEquals(10L, value); 114 | } 115 | 116 | @Test 117 | public void createAndUseTMap() { 118 | Map map = newMap(); 119 | map.put(1, "one"); 120 | map.put(2, "two"); 121 | assertEquals("one", map.get(1)); 122 | assertEquals("two", map.get(2)); 123 | assertTrue(map.containsKey(2)); 124 | map.remove(2); 125 | assertFalse(map.containsKey(2)); 126 | } 127 | 128 | @Test(expected = TestException.class) 129 | public void failingTMapTransaction() { 130 | final Map map = newMap(); 131 | try { 132 | atomic(new Runnable() { 133 | public void run() { 134 | map.put(1, "one"); 135 | map.put(2, "two"); 136 | assertTrue(map.containsKey(1)); 137 | assertTrue(map.containsKey(2)); 138 | throw new TestException(); 139 | } 140 | }); 141 | } catch (TestException e) { 142 | assertFalse(map.containsKey(1)); 143 | assertFalse(map.containsKey(2)); 144 | throw e; 145 | } 146 | } 147 | 148 | @Test 149 | public void createAndUseTSet() { 150 | Set set = newSet(); 151 | set.add("one"); 152 | set.add("two"); 153 | assertTrue(set.contains("one")); 154 | assertTrue(set.contains("two")); 155 | assertEquals(2, set.size()); 156 | set.add("one"); 157 | assertEquals(2, set.size()); 158 | set.remove("two"); 159 | assertFalse(set.contains("two")); 160 | assertEquals(1, set.size()); 161 | } 162 | 163 | @Test 164 | public void createAndUseTArray() { 165 | List list = newArrayAsList(3); 166 | assertEquals(null, list.get(0)); 167 | assertEquals(null, list.get(1)); 168 | assertEquals(null, list.get(2)); 169 | list.set(0, "zero"); 170 | list.set(1, "one"); 171 | list.set(2, "two"); 172 | assertEquals("zero", list.get(0)); 173 | assertEquals("one", list.get(1)); 174 | assertEquals("two", list.get(2)); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/scala/concurrent/stm/TestException.java: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm; 4 | 5 | public class TestException extends RuntimeException { 6 | public TestException() { 7 | super("Expected failure"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/HistogramSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import java.util.concurrent.CyclicBarrier 6 | 7 | import org.scalatest.{FunSuite, Tag} 8 | 9 | 10 | class HistogramSuite extends FunSuite { 11 | 12 | for ((opsPerTest, name, slow) <- List((10000, "10K", false), 13 | (1000000, "1M", true))) { 14 | for (buckets <- List(1, 30, 10000)) { 15 | for (threads <- List(1, 2, 4, 8, 16, 32, 64, 128, 256, 512) if threads <= 2 * Runtime.getRuntime.availableProcessors) { 16 | for (useTArray <- List(false, true)) { 17 | val str = "" + buckets + " buckets, " + threads + " threads, " + 18 | (if (useTArray) "TArray[Int]" else "Array[Ref[Int]]") 19 | addTest("single-op-txn, " + str + ", " + name, Slow) { 20 | histogram(buckets, threads, opsPerTest / threads, useTArray, 100, 1) 21 | } 22 | 23 | for ((accesses, slow2) <- List((1, true), (3, false), (100, true))) { 24 | addTest("txn, " + str + ", " + accesses + " incr per txn, " + name, Slow) { 25 | histogram(buckets, threads, opsPerTest / threads, useTArray, 0, accesses) 26 | } 27 | val g = if (slow || slow2) List[Tag](Slow) else List.empty[Tag] 28 | addTest("mix, " + str + ", " + accesses + " incr per txn " + name, g:_*) { 29 | histogram(buckets, threads, opsPerTest / threads, useTArray, 50, accesses) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | private def addTest(name: String, tags: Tag*)(block: => Unit): Unit = 38 | test(name, tags:_*)(block) 39 | 40 | def histogram(bucketCount: Int, 41 | workerCount: Int, 42 | samplesPerWorker: Int, 43 | useTArray: Boolean, 44 | singlePct: Int, 45 | samplesPerTxn: Int): Unit = { 46 | 47 | val buckets: IndexedSeq[Ref[Int]] = if (useTArray) { 48 | TArray.ofDim[Int](bucketCount).refs 49 | } else { 50 | Array.tabulate(bucketCount)({ _ => Ref(0) }) 51 | } 52 | val threads = new Array[Thread](workerCount) 53 | val barrier = new CyclicBarrier(workerCount, new Runnable { 54 | var start = 0L 55 | def run(): Unit = { 56 | val now = System.nanoTime 57 | if (start == 0) { 58 | start = now 59 | } else { 60 | val elapsed = now - start 61 | println("hist(" + bucketCount + "," + workerCount + "," + samplesPerWorker + "," + 62 | useTArray + "," + singlePct + 63 | "," + samplesPerTxn + ") total_elapsed=" + elapsed + " nanos, throughput=" + 64 | (samplesPerWorker * workerCount * 1000000000L) / elapsed + " ops/sec, per_thread_latency=" + 65 | elapsed / samplesPerWorker + " nanos/op, avg_arrival=" + 66 | elapsed / (samplesPerWorker * workerCount) + " nanos/op") 67 | } 68 | } 69 | }) 70 | 71 | for (worker <- 0 until workerCount) { 72 | val work = new Runnable { 73 | def run(): Unit = { 74 | barrier.await 75 | var i = 0 76 | while (i < samplesPerWorker) { 77 | if (math.abs(hash(i, worker) % 100) < singlePct) { 78 | if ((i % 2) == 0) { 79 | buckets(math.abs(hash(worker, i) % bucketCount)).single.transform(_ + 1) 80 | } else { 81 | val nt = buckets(math.abs(hash(worker, i) % bucketCount)).single 82 | var x = nt() 83 | while (!nt.compareAndSet(x, x + 1)) x = nt() 84 | } 85 | i += 1 86 | } else { 87 | atomic { implicit t => 88 | var j = 0 89 | while (j < samplesPerTxn && i + j < samplesPerWorker) { 90 | val tv = buckets(math.abs(hash(worker, i + j) % bucketCount)) 91 | //tv.getAndTransform(_ + 1) 92 | tv() = tv() + 1 93 | //tv() = tv.bind.readForWrite + 1 94 | j += 1 95 | } 96 | } 97 | i += samplesPerTxn 98 | } 99 | } 100 | barrier.await 101 | } 102 | } 103 | if (worker < workerCount - 1) { 104 | threads(worker) = new Thread(work, "worker " + worker) 105 | threads(worker).start() 106 | } else { 107 | work.run() 108 | } 109 | } 110 | 111 | for (worker <- 0 until workerCount - 1) threads(worker).join() 112 | 113 | val sum = buckets.map(_.single.get).sum 114 | assert(samplesPerWorker * workerCount === sum) 115 | } 116 | 117 | private def hash(i: Int, j: Int) = { 118 | var h = i * 37 + j * 101 119 | h ^= (h >>> 20) ^ (h >>> 12) 120 | h ^ (h >>> 7) ^ (h >>> 4) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/InterruptSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2016, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import org.scalatest.{Tag, FunSuite} 6 | import skel.SimpleRandom 7 | import java.util.concurrent.atomic.AtomicInteger 8 | 9 | /** Verifies that blocking STM operations can be interrupted. */ 10 | class InterruptSuite extends FunSuite { 11 | 12 | 13 | test("txn retry arriving interrupt") { 14 | delayedInterrupt(100) 15 | val x = Ref(0) 16 | intercept[InterruptedException] { 17 | atomic { implicit txn => 18 | if (x() == 0) retry 19 | } 20 | } 21 | } 22 | 23 | test("txn retry pending interrupt") { 24 | Thread.currentThread.interrupt() 25 | val x = Ref(0) 26 | intercept[InterruptedException] { 27 | atomic { implicit txn => 28 | if (x() == 0) retry 29 | } 30 | } 31 | } 32 | 33 | test("single await arriving interrupt") { 34 | delayedInterrupt(100) 35 | val x = Ref(0) 36 | intercept[InterruptedException] { 37 | x.single.await( _ != 0 ) 38 | } 39 | } 40 | 41 | test("single await pending interrupt") { 42 | Thread.currentThread.interrupt() 43 | val x = Ref(0) 44 | intercept[InterruptedException] { 45 | x.single.await( _ != 0 ) 46 | } 47 | } 48 | 49 | test("random interrupts during contention") { 50 | val refs = Array.tabulate(100)( _ => Ref(0) ) 51 | val txnInterrupts = new AtomicInteger 52 | val nonTxnInterrupts = new AtomicInteger 53 | var failure = null : Throwable 54 | lazy val threads: Array[Thread] = Array.tabulate[Thread](10)( _ => new Thread { 55 | override def run(): Unit = { 56 | try { 57 | for (_ <- 0 until 10000) { 58 | try { 59 | atomic { implicit txn => 60 | for (r <- refs) r() = r() + 1 61 | } 62 | } catch { 63 | case _: InterruptedException => txnInterrupts.incrementAndGet 64 | } 65 | for (r <- refs) { 66 | try { 67 | r.single += 1 68 | } catch { 69 | case _: InterruptedException => nonTxnInterrupts.incrementAndGet 70 | } 71 | } 72 | threads(SimpleRandom.nextInt(threads.length)).interrupt() 73 | } 74 | } catch { 75 | case x: Throwable => failure = x 76 | } 77 | } 78 | }) 79 | for (t <- threads) t.start() 80 | for (t <- threads) t.join() 81 | if (failure != null) 82 | throw failure 83 | println(txnInterrupts.get + " txn rollbacks, " + nonTxnInterrupts.get + " non-txn interrupts") 84 | } 85 | 86 | //////// machinery for InterruptSuite 87 | 88 | private val pendingInterrupts = new ThreadLocal[List[Thread]] { 89 | override def initialValue: List[Thread] = Nil 90 | } 91 | 92 | override protected def test(testName: String, testTags: Tag*)(f: => Any)(implicit pos: org.scalactic.source.Position): Unit = { 93 | super.test(testName, testTags: _*) { 94 | // we have to use another thread, because sbt overrides .interrupt() on 95 | // its worker threads 96 | var failure = null : Throwable 97 | val t = new Thread { 98 | override def run(): Unit = { 99 | try { 100 | f 101 | } catch { 102 | case x: Throwable => failure = x 103 | } finally { 104 | while (pendingInterrupts.get.nonEmpty) { 105 | try { 106 | pendingInterrupts.get.head.join() 107 | pendingInterrupts.set(pendingInterrupts.get.tail) 108 | } catch { 109 | case _: Throwable => 110 | } 111 | } 112 | Thread.interrupted 113 | } 114 | } 115 | } 116 | t.start() 117 | t.join() 118 | if (failure != null) 119 | throw failure 120 | } 121 | } 122 | 123 | private def delayedInterrupt(delay: Long): Unit = { delayedInterrupt(Thread.currentThread, delay) } 124 | 125 | private def delayedInterrupt(target: Thread, delay: Long): Unit = { 126 | val t = new Thread { 127 | override def run(): Unit = { 128 | Thread.sleep(delay) 129 | target.interrupt() 130 | } 131 | } 132 | pendingInterrupts.set(t :: pendingInterrupts.get) 133 | t.start() 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/JavaAPISuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import org.scalatest.junit.JUnitWrapperSuite 6 | 7 | class JavaAPISuite extends JUnitWrapperSuite("scala.concurrent.stm.JavaAPITests", Thread.currentThread.getContextClassLoader) 8 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/MaybeTxnSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import org.scalatest.FunSuite 6 | 7 | class MaybeTxnSuite extends FunSuite { 8 | test("implicit InTxn match") { 9 | implicit val txn: InTxn = new skel.StubInTxn 10 | 11 | assert(implicitly[MaybeTxn] eq txn) 12 | } 13 | 14 | test("implicit TxnUnknown match") { 15 | assert(implicitly[MaybeTxn] eq TxnUnknown) 16 | } 17 | 18 | test("TxnUnknown is found") { 19 | assert(context eq TxnUnknown) 20 | } 21 | 22 | test("InTxn is found") { 23 | atomic { t0 => 24 | implicit val t: InTxn = t0 25 | assert(context eq t) 26 | } 27 | atomic { implicit t => 28 | assert(context eq t) 29 | } 30 | } 31 | 32 | private def context(implicit mt: MaybeTxn) = mt 33 | 34 | test("Static nesting lookup") { 35 | val x = Ref(10) 36 | atomic { implicit t => 37 | assert(x() === 10) 38 | x() = 11 39 | atomic { implicit t => 40 | assert(x() === 11) 41 | x() = 12 42 | atomic { implicit t => 43 | assert(x() === 12) 44 | x() = 13 45 | } 46 | assert(x() === 13) 47 | } 48 | assert(x() === 13) 49 | } 50 | assert(x.single() === 13) 51 | } 52 | 53 | test("Dynamic nesting lookup") { 54 | val x = Ref(10) 55 | val xs = x.single 56 | def loop(expected: Int): Unit = { 57 | atomic { implicit t => 58 | assert(x() === expected) 59 | assert(xs() === expected) 60 | x() = expected + 1 61 | if (expected < 100) 62 | loop(expected + 1) 63 | assert(x() === 101) 64 | } 65 | } 66 | loop(10) 67 | assert(xs() === 101) 68 | assert(x.single() === 101) 69 | } 70 | 71 | test("Static vs dynamic lookup") { 72 | implicit var t0: InTxn = null 73 | val n0 = atomic { t => 74 | t0 = t 75 | assert(Txn.findCurrent === Some(t)) 76 | assert(impl.STMImpl.instance.findCurrent === Some(t)) 77 | NestingLevel.root 78 | } 79 | assert(n0.status === Txn.Committed) 80 | assert(Txn.findCurrent === None) 81 | assert(impl.STMImpl.instance.findCurrent === None) 82 | atomic { t => 83 | assert(NestingLevel.current(t) != n0) 84 | assert(NestingLevel.root(t).status === Txn.Active) 85 | assert(Txn.status === Txn.Active) 86 | assert(Txn.findCurrent === Some(t)) 87 | assert(impl.STMImpl.instance.findCurrent === Some(t)) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/RelaxedValidationSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import java.util.concurrent.CountDownLatch 6 | 7 | import org.scalatest.FunSuite 8 | 9 | 10 | /** Tests of the relaxed validation methods `getWith` and `relaxedGet` in 11 | * multi-threaded contexts. Single-threaded tests are found in 12 | * `IsolatedRefSuite` and more multi-threaded tests are embedded in 13 | * `FlipperSuite`. 14 | */ 15 | class RelaxedValidationSuite extends FunSuite { 16 | 17 | test("self-write vs getWith") { 18 | val x = Ref(0) 19 | atomic { implicit txn => 20 | assert(x.getWith { _ & 1 } === 0) 21 | x() = 1 22 | } 23 | assert(x.single() === 1) 24 | } 25 | 26 | test("self-write vs getWith with interference") { 27 | val x = Ref(0) 28 | val b1 = new CountDownLatch(1) 29 | val b2 = new CountDownLatch(1) 30 | 31 | new Thread { 32 | override def run(): Unit = { 33 | b1.await() 34 | x.single() = 2 35 | b2.countDown() 36 | } 37 | }.start() 38 | 39 | atomic { implicit txn => 40 | assert(x.getWith { _ & 1 } === 0) 41 | b1.countDown() 42 | b2.await() 43 | assert(x.swap(1) === 2) 44 | } 45 | assert(x.single() === 1) 46 | } 47 | 48 | test("getWith multiple revalidations") { 49 | val x = Ref("abc") 50 | 51 | // sleep is okay for this test because all interleavings should pass 52 | 53 | new Thread { 54 | override def run(): Unit = { 55 | for (_ <- 0 until 10) { 56 | Thread.sleep(10) 57 | x.single.transform { 58 | _ + "X" 59 | } 60 | } 61 | x.single() = "" 62 | } 63 | }.start() 64 | 65 | assert(atomic { implicit txn => 66 | for (_ <- 0 until 10) { 67 | x.getWith { _.take(3) } 68 | Thread.sleep(15) 69 | } 70 | x.getWith { _.take(3) } 71 | } === "") 72 | } 73 | 74 | test("self-write vs failing transformIfDefined") { 75 | val x = Ref(0) 76 | atomic { implicit txn => 77 | assert(!x.transformIfDefined { 78 | case 1 => 2 79 | }) 80 | x() = 1 81 | } 82 | assert(x.single() === 1) 83 | } 84 | 85 | test("self-write vs failing transformIfDefined with interference") { 86 | val x = Ref(0) 87 | val b1 = new CountDownLatch(1) 88 | val b2 = new CountDownLatch(1) 89 | 90 | new Thread { 91 | override def run(): Unit = { 92 | b1.await() 93 | x.single() = 2 94 | b2.countDown() 95 | } 96 | }.start() 97 | 98 | atomic { implicit txn => 99 | assert(!x.transformIfDefined { 100 | case v if (v & 1) != 0 => v 101 | }) 102 | b1.countDown() 103 | b2.await() 104 | assert(x.swap(1) === 2) 105 | } 106 | assert(x.single() === 1) 107 | } 108 | 109 | test("self-write vs relaxedGet") { 110 | val x = Ref(0) 111 | atomic { implicit txn => 112 | assert(x.relaxedGet( _ == _ ) === 0) 113 | x() = 1 114 | } 115 | assert(x.single() === 1) 116 | } 117 | 118 | test("self-write vs relaxedGet with interference") { 119 | val x = Ref(0) 120 | val b1 = new CountDownLatch(1) 121 | val b2 = new CountDownLatch(1) 122 | 123 | new Thread { 124 | override def run(): Unit = { 125 | b1.await() 126 | x.single() = 2 127 | b2.countDown() 128 | } 129 | }.start() 130 | 131 | atomic { implicit txn => 132 | assert(x.relaxedGet({ (seen, correct) => (seen & 1) == (correct & 1) }) === 0) 133 | b1.countDown() 134 | b2.await() 135 | assert(x.swap(1) === 2) 136 | } 137 | assert(x.single() === 1) 138 | } 139 | 140 | test("relaxedGet multiple accepting revalidations") { 141 | val x = Ref("abc") 142 | 143 | // sleep is okay for this test because all interleavings should pass 144 | 145 | new Thread { 146 | override def run(): Unit = { 147 | for (_ <- 0 until 10) { 148 | Thread.sleep(10) 149 | x.single.transform { 150 | _ + "X" 151 | } 152 | } 153 | x.single() = "" 154 | } 155 | }.start() 156 | 157 | val (first, last) = atomic { implicit txn => 158 | val first = x.relaxedGet( (_, _) => true ) 159 | for (_ <- 0 until 10) { 160 | x.relaxedGet( (_, _) => true ) 161 | Thread.sleep(15) 162 | } 163 | (first, x.relaxedGet( (_, _) => true )) 164 | } 165 | assert(first === "abc") 166 | assert(last === "") 167 | } 168 | 169 | test("relaxedGet multiple ending with equality check") { 170 | val x = Ref("abc") 171 | 172 | // sleep is okay for this test because all interleavings should pass 173 | 174 | new Thread { 175 | override def run(): Unit = { 176 | for (_ <- 0 until 10) { 177 | Thread.sleep(10) 178 | x.single.transform { 179 | _ + "X" 180 | } 181 | } 182 | x.single() = "" 183 | } 184 | }.start() 185 | 186 | val (first, last) = atomic { implicit txn => 187 | val first = x.relaxedGet( _.isEmpty == _.isEmpty ) 188 | for (_ <- 0 until 10) { 189 | x.relaxedGet( _.isEmpty == _.isEmpty ) 190 | Thread.sleep(15) 191 | } 192 | (first, x.relaxedGet( _ == _ )) 193 | } 194 | assert(first === "") 195 | assert(last === "") 196 | } 197 | } -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/Slow.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import org.scalatest.Tag 6 | 7 | object Slow extends Tag("slow") 8 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/TokenRingSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import java.util.concurrent.CyclicBarrier 6 | 7 | import org.scalatest.FunSuite 8 | 9 | 10 | /** This test uses the transactional retry mechanism to pass a token around a 11 | * ring of threads. When there are two threads this is a ping-pong test. A 12 | * separate `Ref` is used for each handoff. 13 | * 14 | * @author Nathan Bronson 15 | */ 16 | class TokenRingSuite extends FunSuite { 17 | test("small non-txn threesome") { tokenRing(3, 10000, false, false) } 18 | test("small txn threesome") { tokenRing(3, 1000, true, false) } 19 | test("small txn threesome reading via write") { tokenRing(3, 1000, true, true) } 20 | 21 | test("non-txn ping-pong", Slow) { tokenRing(2, 1000000, false, false) } 22 | test("non-txn threesome", Slow) { tokenRing(3, 1000000, false, false) } 23 | test("non-txn large ring", Slow) { tokenRing(32, 10000, false, false) } 24 | test("txn ping-pong", Slow) { tokenRing(2, 100000, true, false) } 25 | test("txn threesome", Slow) { tokenRing(3, 100000, true, false) } 26 | test("txn large ring", Slow) { tokenRing(32, 10000, true, false) } 27 | test("txn ping-pong reading via write", Slow) { tokenRing(2, 100000, true, true) } 28 | test("txn threesome reading via write", Slow) { tokenRing(3, 100000, true, true) } 29 | test("txn large ring reading via write", Slow) { tokenRing(32, 10000, true, true) } 30 | 31 | def tokenRing(ringSize: Int, handoffsPerThread: Int, useTxns: Boolean, useSwap: Boolean): Unit = { 32 | val ready = Array.tabulate(ringSize)(i => Ref(i == 0)) 33 | val threads = new Array[Thread](ringSize - 1) 34 | val barrier = new CyclicBarrier(ringSize, new Runnable { 35 | var start = 0L 36 | def run(): Unit = { 37 | val now = System.currentTimeMillis 38 | if (start == 0) { 39 | start = now 40 | } else { 41 | val elapsed = now - start 42 | val handoffs = handoffsPerThread * ringSize 43 | println("tokenRing(" + ringSize + "," + handoffsPerThread + "," + useTxns + 44 | ") total_elapsed=" + elapsed + " msec, throughput=" + 45 | (handoffs * 1000L) / elapsed + " handoffs/sec, latency=" + 46 | (elapsed * 1000000L) / handoffs + " nanos/handoff") 47 | } 48 | } 49 | }) 50 | 51 | for (index <- 0 until ringSize) { 52 | val work = new Runnable { 53 | def run(): Unit = { 54 | val next = (index + 1) % ringSize 55 | barrier.await 56 | for (_ <- 0 until handoffsPerThread) { 57 | if (!useTxns) { 58 | ready(index).single await { _ == true } 59 | ready(index).single() = false 60 | ready(next).single() = true 61 | } else { 62 | atomic { implicit t => 63 | if (!useSwap) { 64 | if (ready(index).get == false) retry 65 | ready(index)() = false 66 | } else { 67 | if (ready(index).swap(false) == false) retry 68 | } 69 | ready(next)() = true 70 | } 71 | } 72 | } 73 | barrier.await 74 | } 75 | } 76 | if (index < ringSize - 1) { 77 | threads(index) = new Thread(work, "worker " + index) 78 | threads(index).start() 79 | } else { 80 | work.run() 81 | } 82 | } 83 | 84 | for (t <- threads) t.join() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/UnrecordedTxnSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2012, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import org.scalatest.FunSuite 6 | import java.util.concurrent.CountDownLatch 7 | 8 | 9 | class UnrecordedTxnSuite extends FunSuite { 10 | 11 | test("fixed unrecorded txn") { 12 | val z = atomic.unrecorded { implicit txn => "foo" } 13 | assert(z === "foo") 14 | } 15 | 16 | test("nested fixed unrecorded txn") { 17 | val x = Ref(0) 18 | val z = atomic { implicit txn => 19 | x() = 1 20 | atomic.unrecorded { implicit txn => "foo" } 21 | } 22 | assert(z === "foo") 23 | } 24 | 25 | test("writing unrecorded txn") { 26 | val x = Ref(0) 27 | val z = atomic.unrecorded { implicit txn => 28 | x() = 1 29 | "foo" 30 | } 31 | assert(z === "foo") 32 | assert(x.single() === 0) 33 | } 34 | 35 | test("nested unrecorded txn") { 36 | val x = Ref(0) 37 | val z = atomic.unrecorded { implicit txn => 38 | x += 1 39 | atomic.unrecorded { implicit txn => 40 | x += 1 41 | atomic.unrecorded { implicit txn => 42 | x += 1 43 | atomic.unrecorded { implicit txn => 44 | x += 1 45 | atomic.unrecorded { implicit txn => 46 | x += 1 47 | atomic.unrecorded { implicit txn => 48 | x += 1 49 | atomic.unrecorded { implicit txn => 50 | x() 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | assert(z === 6) 59 | assert(x.single() === 0) 60 | } 61 | 62 | test("nested new write unrecorded txn") { 63 | val x = Ref(0) 64 | val z = atomic { implicit txn => 65 | atomic.unrecorded { implicit txn => 66 | x() = 1 67 | "foo" 68 | } 69 | } 70 | assert(x.single() === 0) 71 | assert(z === "foo") 72 | } 73 | 74 | test("nested update unrecorded txn") { 75 | val x = Ref(0) 76 | val z = atomic { implicit txn => 77 | x() = 1 78 | atomic.unrecorded { implicit txn => 79 | x() = 2 80 | "foo" 81 | } 82 | } 83 | assert(x.single() === 1) 84 | assert(z === "foo") 85 | } 86 | 87 | test("nested preceding unrecorded txn") { 88 | val x = Ref(0) 89 | val z = atomic { implicit txn => 90 | val z = atomic.unrecorded { implicit txn => 91 | x() = 2 92 | "foo" 93 | } 94 | x() = 1 95 | z 96 | } 97 | assert(x.single() === 1) 98 | assert(z === "foo") 99 | } 100 | 101 | test("read set emptied") { 102 | val b = new CountDownLatch(1) 103 | val e = new CountDownLatch(1) 104 | 105 | val x = Ref(0) 106 | 107 | new Thread { 108 | override def run(): Unit = { 109 | b.await() 110 | x.single() = 1 111 | e.countDown() 112 | } 113 | }.start() 114 | 115 | var tries = 0 116 | val (z1, z2) = atomic { implicit txn => 117 | tries += 1 118 | val z1 = atomic.unrecorded { implicit txn => x() } 119 | b.countDown() 120 | e.await() 121 | (z1, x()) 122 | } 123 | 124 | assert(z1 === 0) 125 | assert(z2 === 1) 126 | assert(tries === 1) 127 | } 128 | 129 | class TestException extends Exception 130 | 131 | test("outerFailure handler") { 132 | 133 | val x = Ref(0) 134 | 135 | var z: Any = null 136 | intercept[TestException] { 137 | atomic { implicit txn => 138 | val level = NestingLevel.root 139 | val done = new CountDownLatch(1) 140 | new Thread { 141 | override def run(): Unit = { 142 | level.requestRollback(Txn.UncaughtExceptionCause(new TestException)) 143 | done.countDown() 144 | } 145 | }.start() 146 | done.await() 147 | 148 | z = atomic.unrecorded({ implicit txn => x() }, { cause => cause }) 149 | } 150 | } 151 | 152 | assert(z.isInstanceOf[Txn.UncaughtExceptionCause]) 153 | } 154 | } -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/WriteSkewSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2016, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | 5 | import org.scalatest.FunSuite 6 | 7 | 8 | class WriteSkewSuite extends FunSuite { 9 | val IncrCount = 1000000 10 | 11 | test("write skew test 1K") { runTest(1000) } 12 | test("write skew test 1M", Slow) { runTest(1000000) } 13 | 14 | def runTest(incrCount: Int): Unit = { 15 | // Two threads, each of which increments its own Ref if the other Ref is 16 | // even. Neither thread should ever observe that both Refs are odd. 17 | // MVCC STMs will require the addition of something like Clojure's "ensure" 18 | // or SQL's "select for update" to avoid the write skew. 19 | val refs = Array(Ref(0), Ref(0)) 20 | val threads = new Array[Thread](2) 21 | 22 | @volatile var failure: Throwable = null 23 | for (id <- 0 to 1) { 24 | threads(id) = new Thread("write skew #" + id) { 25 | val self = refs(id) 26 | val other = refs(1 - id) 27 | 28 | override def run(): Unit = { 29 | try { 30 | for (_ <- 0 until incrCount) { 31 | if (null != failure) 32 | return 33 | atomic { implicit t => 34 | if ((other() % 2) != 0) { 35 | if ((self() % 2) != 0) 36 | fail("refs=" + refs.map(_.get)) 37 | retry 38 | } 39 | self() = self() + 1 40 | } 41 | } 42 | } catch { 43 | case x: Throwable => 44 | if (null == failure) 45 | failure = x 46 | } 47 | } 48 | } 49 | } 50 | 51 | val begin = System.currentTimeMillis 52 | for (t <- threads) t.start() 53 | for (t <- threads) t.join() 54 | 55 | if (null != failure) 56 | throw failure 57 | val elapsed = System.currentTimeMillis - begin 58 | println("writeSkew(" + (2 * incrCount) + "): " + elapsed + " millis total") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/examples/BasicSyntax.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package examples 5 | 6 | object BasicSyntax { 7 | case class Point(x: Int, y: Int) 8 | val origin = Point(0, 0) 9 | 10 | val top = Ref(origin) 11 | val bottom = Ref(origin) 12 | val left = Ref(origin) 13 | val right = Ref(origin) 14 | 15 | def updateExtremes(p: Point): Unit = { 16 | atomic { implicit txn => 17 | if (p.x < left().x) 18 | left() = p 19 | if (p.x > right().x) 20 | right() = p 21 | if (p.y < top().y) 22 | top() = p 23 | if (p.y > bottom().y) 24 | bottom() = p 25 | } 26 | } 27 | 28 | def alternatives(): Unit = { 29 | val z = atomic { implicit txn => 30 | if (!(left().x < -100)) 31 | retry 32 | "left" 33 | } orAtomic { implicit txn => 34 | if (!(right().x > +100)) 35 | retry 36 | "right" 37 | } orAtomic { implicit txn => 38 | if (!(top().y < -100)) 39 | retry 40 | "top" 41 | } orAtomic { implicit txn => 42 | if (!(bottom().y > +100)) 43 | retry 44 | "bottom" 45 | } 46 | println("first out was " + z) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/examples/ConcurrentIntList.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.examples 4 | 5 | import scala.concurrent.stm._ 6 | 7 | class ConcurrentIntList { 8 | private class Node(val elem: Int, prev0: Node, next0: Node) { 9 | val isHeader: Boolean = prev0 == null 10 | val prev = Ref(if (isHeader) this else prev0) 11 | val next = Ref(if (isHeader) this else next0) 12 | } 13 | 14 | private val header = new Node(-1, null, null) 15 | 16 | def addLast(elem: Int): Unit = { 17 | atomic { implicit txn => 18 | val p = header.prev() 19 | val newNode = new Node(elem, p, header) 20 | p.next() = newNode 21 | header.prev() = newNode 22 | } 23 | } 24 | 25 | def addLast(e1: Int, e2: Int, elems: Int*): Unit = { 26 | atomic { implicit txn => 27 | addLast(e1) 28 | addLast(e2) 29 | elems.foreach(addLast) 30 | } 31 | } 32 | 33 | //def isEmpty = atomic { implicit t => header.next() == header } 34 | def isEmpty: Boolean = header.next.single() == header 35 | 36 | def removeFirst(): Int = atomic { implicit txn => 37 | val n = header.next() 38 | if (n == header) 39 | retry 40 | val nn = n.next() 41 | header.next() = nn 42 | nn.prev() = header 43 | n.elem 44 | } 45 | 46 | def maybeRemoveFirst(): Option[Int] = { 47 | atomic { implicit txn => 48 | Some(removeFirst()) 49 | } orAtomic { implicit txn => 50 | None 51 | } 52 | } 53 | 54 | override def toString: String = 55 | atomic { implicit txn => 56 | val buf = new StringBuilder("ConcurrentIntList(") 57 | var n = header.next() 58 | while (n != header) { 59 | buf ++= n.elem.toString 60 | n = n.next() 61 | if (n != header) buf ++= "," 62 | } 63 | buf.++=(")").toString 64 | } 65 | } 66 | 67 | object ConcurrentIntList { 68 | def select(stacks: ConcurrentIntList*): (ConcurrentIntList, Int) = { 69 | atomic { implicit txn => 70 | for (s <- stacks) { 71 | s.maybeRemoveFirst() match { 72 | case Some(e) => return (s, e) 73 | case None => 74 | } 75 | } 76 | retry 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/examples/DiningPhilosophers.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package examples 5 | 6 | /** See http://en.wikipedia.org/wiki/Dining_philosophers_problem 7 | * The STM solution is particularly straightforward because we can 8 | * simultaneously pick up two forks. 9 | */ 10 | object DiningPhilosophers { 11 | 12 | class Fork { 13 | val inUse = Ref(false) 14 | } 15 | 16 | class PhilosopherThread(meals: Int, left: Fork, right: Fork) extends Thread { 17 | override def run(): Unit = { 18 | for (_ <- 0 until meals) { 19 | // THINK 20 | pickUpBothForks() 21 | // EAT 22 | putDown(left) 23 | putDown(right) 24 | } 25 | } 26 | 27 | def pickUpBothForks(): Unit = { 28 | atomic { implicit txn => 29 | if (left.inUse() || right.inUse()) 30 | retry 31 | left.inUse() = true 32 | right.inUse() = true 33 | } 34 | } 35 | 36 | def putDown(f: Fork): Unit = { 37 | f.inUse.single() = false 38 | } 39 | } 40 | 41 | def time(tableSize: Int, meals: Int): Long = { 42 | val forks = Array.tabulate(tableSize) { _ => new Fork } 43 | val threads = Array.tabulate(tableSize) { i => new PhilosopherThread(meals, forks(i), forks((i + 1) % tableSize)) } 44 | val start = System.currentTimeMillis 45 | for (t <- threads) t.start() 46 | for (t <- threads) t.join() 47 | System.currentTimeMillis - start 48 | } 49 | 50 | def main(args: Array[String]): Unit = { 51 | val meals = 100000 52 | for (_ <- 0 until 3) { 53 | val elapsed = time(5, meals) 54 | printf("%3.1f usec/meal\n", (elapsed * 1000.0) / meals) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/examples/IndexedMap.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package examples 5 | 6 | import util.Random 7 | 8 | class IndexedMap[A, B] { 9 | 10 | private class Index[C](view: (A, B) => Iterable[C]) extends (C => Map[A, B]) { 11 | val mapping: TMap[C, Map[A, B]] = TMap.empty 12 | 13 | def apply(derived: C): Map[A, B] = mapping.single.getOrElse(derived, Map.empty[A, B]) 14 | 15 | def += (kv: (A, B))(implicit txn: InTxn): Unit = { 16 | for (c <- view(kv._1, kv._2)) 17 | mapping(c) = apply(c) + kv 18 | } 19 | 20 | def -= (kv: (A, B))(implicit txn: InTxn): Unit = { 21 | for (c <- view(kv._1, kv._2)) { 22 | val after = mapping(c) - kv._1 23 | if (after.isEmpty) 24 | mapping -= c 25 | else 26 | mapping(c) = after 27 | } 28 | } 29 | } 30 | 31 | private val contents = TMap.empty[A, B] 32 | private val indices = Ref(List.empty[Index[_]]) 33 | 34 | def addIndex[C](view: (A, B) => Iterable[C]): (C => Map[A, B]) = atomic { implicit txn => 35 | val index = new Index(view) 36 | indices() = index :: indices() 37 | contents foreach { index += _ } 38 | index 39 | } 40 | 41 | def get(key: A): Option[B] = contents.single.get(key) 42 | 43 | def put(key: A, value: B): Option[B] = atomic { implicit txn => 44 | val prev = contents.put(key, value) 45 | for (p <- prev; i <- indices()) 46 | i -= (key -> p) 47 | for (i <- indices()) 48 | i += (key -> value) 49 | prev 50 | } 51 | 52 | def remove(key: A): Option[B] = atomic { implicit txn => 53 | val prev = contents.remove(key) 54 | for (p <- prev; i <- indices()) 55 | i -= (key -> p) 56 | prev 57 | } 58 | } 59 | 60 | object IndexedMap { 61 | 62 | case class User(id: Int, name: String, likes: Set[String]) 63 | 64 | val users = new IndexedMap[Int, User] 65 | val byName: String => Map[Int, User] = users.addIndex { (_, u) => Some(u.name) } 66 | val byLike: String => Map[Int, User] = users.addIndex { (_, u) => u.likes } 67 | 68 | //////// data 69 | 70 | val topBabyNames = Array( 71 | "Ethan", "Isabella", "Jacob", "Olivia", "Noah", "Sophia", "Logan", "Emma", "Liam", "Ava", "Aiden", "Abigail", 72 | "Mason", "Chloe", "Jackson", "Madison", "Jack", "Ella", "Jayden", "Addison", "Ryan", "Emily", "Matthew", "Lily", 73 | "Lucas", "Mia", "Michael", "Avery", "Alexander", "Grace", "Nathan", "Hannah") 74 | 75 | val languages = Array("scala", "java", "C++", "haskell", "clojure", "python", "ruby", "pascal", "perl") 76 | val sports = Array("climbing", "cycling", "hiking", "football", "baseball", "underwater hockey") 77 | 78 | val numIDs = 1000 79 | 80 | //////// operations 81 | 82 | def pick[A](a: Array[A])(implicit rand: Random) = a(rand.nextInt(a.length)) 83 | 84 | def newName(id: Int)(implicit rand: Random): Unit = { 85 | atomic { implicit txn => 86 | val before = users.get(id).getOrElse(User(id, "John Doe", Set.empty)) 87 | val after = before copy (name = pick(topBabyNames)) 88 | users.put(id, after) 89 | } 90 | } 91 | 92 | def newLikes(id: Int)(implicit rand: Random): Unit = { 93 | atomic { implicit txn => 94 | val before = users.get(id).getOrElse(User(id, "John Doe", Set.empty)) 95 | val after = before copy (likes = Set(pick(languages), pick(sports))) 96 | users.put(id, after) 97 | } 98 | } 99 | 100 | def randomOp()(implicit rand: Random): Unit = { 101 | val pct = rand.nextInt(100) 102 | if (pct < 10) { 103 | // query by ID 104 | users.get(rand.nextInt(numIDs)) 105 | } else if (pct < 50) { 106 | // query by name 107 | byName(pick(topBabyNames)) 108 | } else if (pct < 70) { 109 | // query by sport 110 | byLike(pick(sports)) 111 | } else if (pct < 90) { 112 | // query by language 113 | byLike(pick(languages)) 114 | } else if (pct < 95) { 115 | newName(rand.nextInt(numIDs)) 116 | } else { 117 | newLikes(rand.nextInt(numIDs)) 118 | } 119 | } 120 | 121 | def populate(): Unit = { 122 | implicit val rand: Random = new Random 123 | for (id <- 0 until numIDs) { 124 | newName(id) 125 | newLikes(id) 126 | } 127 | } 128 | 129 | def run(numThreads: Int, opsPerThread: Int): Long = { 130 | val threads = Array.tabulate(numThreads) { _ => 131 | new Thread() { 132 | override def run(): Unit = { 133 | implicit val rand: Random = new Random 134 | for (_ <- 0 until opsPerThread) randomOp() 135 | } 136 | } 137 | } 138 | val begin = System.currentTimeMillis 139 | for (t <- threads) t.start() 140 | for (t <- threads) t.join() 141 | System.currentTimeMillis - begin 142 | } 143 | 144 | def main(args: Array[String]): Unit = { 145 | populate() 146 | 147 | for (_ <- 0 until 3; tc <- List(1, 2, 4, 8)) { 148 | val elapsed = run(tc, 1000000 / tc) 149 | printf("%d thread: %4.2f usec/op total throughput (90%% read)\n", tc, elapsed * 0.001) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/examples/RealityShowPhilosophers.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package examples 5 | 6 | import annotation.tailrec 7 | 8 | /** This extends a solution to the dining philosopher's problem to include an 9 | * outside perspective that occasionally examines everything that is 10 | * happening. 11 | */ 12 | object RealityShowPhilosophers { 13 | 14 | class Fork { 15 | val owner = Ref(None : Option[String]) 16 | } 17 | 18 | class PhilosopherThread(val name: String, val meals: Int, left: Fork, right: Fork) extends Thread { 19 | val mealsEaten = Ref(0) 20 | 21 | override def run(): Unit = { 22 | for (_ <- 0 until meals) { 23 | // thinking 24 | atomic { implicit txn => 25 | if (!(left.owner().isEmpty && right.owner().isEmpty)) 26 | retry 27 | left.owner() = Some(name) 28 | right.owner() = Some(name) 29 | } 30 | // eating 31 | atomic { implicit txn => 32 | mealsEaten += 1 33 | left.owner() = None 34 | right.owner() = None 35 | } 36 | } 37 | } 38 | 39 | def done: Boolean = mealsEaten.single() == meals 40 | 41 | override def toString: String = 42 | "%s is %5.2f%% done".format(name, mealsEaten.single() * 100.0 / meals) 43 | } 44 | 45 | class CameraThread(intervalMilli: Int, forks: Seq[Fork], philosophers: Seq[PhilosopherThread]) extends Thread { 46 | 47 | @tailrec final override def run(): Unit = { 48 | Thread.sleep(intervalMilli) 49 | val (str, done) = image 50 | println(str) 51 | if (!done) 52 | run() 53 | } 54 | 55 | /** We want to print exactly one image of the final state, so we check 56 | * completion at the same time as building the image. 57 | */ 58 | def image: (String, Boolean) = atomic { implicit txn => 59 | val buf = new StringBuilder 60 | for (i <- forks.indices) 61 | buf ++= "fork %d is owned by %s\n".format(i, forks(i).owner.single()) 62 | var done = true 63 | for (p <- philosophers) { 64 | buf ++= p.toString += '\n' 65 | done &&= p.done 66 | } 67 | (buf.toString, done) 68 | } 69 | } 70 | 71 | def time(names: Seq[String], meals: Int): Long = { 72 | val forks = Array.tabulate(names.size) { _ => new Fork } 73 | val pthreads = Array.tabulate(names.size) { i => new PhilosopherThread(names(i), meals, forks(i), forks((i + 1) % forks.length)) } 74 | val camera = new CameraThread(1000 / 60, forks, pthreads) 75 | val start = System.currentTimeMillis 76 | camera.start() 77 | for (t <- pthreads) t.start() 78 | for (t <- pthreads) t.join() 79 | val elapsed = System.currentTimeMillis - start 80 | camera.join() 81 | elapsed 82 | } 83 | 84 | def main(args: Array[String]): Unit = { 85 | val meals = 100000 86 | for (_ <- 0 until 3) { 87 | val elapsed = time(List("Aristotle", "Hippocrates", "Plato", "Pythagoras", "Socrates"), meals) 88 | printf("%3.1f usec/meal\n", (elapsed * 1000.0) / meals) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/examples/SyntaxCheatSheet.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm.examples 4 | 5 | object SyntaxCheatSheet { 6 | import scala.concurrent.stm._ 7 | 8 | val x: Ref[Int] = Ref(0) // allocate a Ref[Int] 9 | val y: Ref[String] = Ref.make[String]() // type-specific default 10 | val z: Ref.View[Int] = x.single // Ref.View[Int] 11 | 12 | atomic { implicit txn => 13 | val i = x() // read 14 | y() = "x was " + i // write 15 | val eq = atomic { implicit txn => // nested atomic 16 | x() == z() // both Ref and Ref.View can be used inside atomic 17 | } 18 | assert(eq) 19 | y.set(y.get + ", long-form access") 20 | } 21 | 22 | // only Ref.View can be used outside atomic 23 | println("y was '" + y.single() + "'") 24 | println("z was " + z()) 25 | 26 | atomic { implicit txn => 27 | y() = y() + ", first alternative" 28 | if (x getWith { _ > 0 }) // read via a function 29 | retry // try alternatives or block 30 | } orAtomic { implicit txn => 31 | y() = y() + ", second alternative" 32 | } 33 | 34 | val prev: Int = z.swap(10) // atomic swap 35 | val success: Boolean = z.compareAndSet(10, 11) // atomic compare-and-set 36 | z.transform { _ max 20 } // atomic transformation 37 | val pre : String = y.single.getAndTransform { _.toUpperCase } 38 | val post: String = y.single.transformAndGet { _.filterNot { _ == ' ' } } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/impl/RefFactorySuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2010, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package impl 5 | 6 | import org.scalatest.FunSuite 7 | 8 | import scala.reflect.ClassTag 9 | 10 | class RefFactorySuite extends FunSuite { 11 | 12 | private case class Fact(expected: String) extends skel.StubSTMImpl with RefFactory { 13 | 14 | private def called(w: String) = { 15 | assert(w === expected) 16 | null 17 | } 18 | 19 | override def newRef(v0: Boolean): Ref[Boolean] = called("Boolean") 20 | override def newRef(v0: Byte): Ref[Byte] = called("Byte") 21 | override def newRef(v0: Short): Ref[Short] = called("Short") 22 | override def newRef(v0: Char): Ref[Char] = called("Char") 23 | override def newRef(v0: Int): Ref[Int] = called("Int") 24 | override def newRef(v0: Float): Ref[Float] = called("Float") 25 | override def newRef(v0: Long): Ref[Long] = called("Long") 26 | override def newRef(v0: Double): Ref[Double] = called("Double") 27 | override def newRef(v0: Unit): Ref[Unit] = called("Unit") 28 | override def newRef[T](v0: T)(implicit m: ClassTag[T]): Ref[T] = called("Any") 29 | } 30 | 31 | object TestRef extends RefCompanion { 32 | var factory: RefFactory = null 33 | } 34 | 35 | test("signature specialization") { 36 | TestRef.factory = Fact("Boolean") 37 | TestRef(false) 38 | 39 | TestRef.factory = Fact("Byte") 40 | TestRef(0 : Byte) 41 | 42 | TestRef.factory = Fact("Short") 43 | TestRef(0 : Short) 44 | 45 | TestRef.factory = Fact("Char") 46 | TestRef(0 : Char) 47 | 48 | TestRef.factory = Fact("Int") 49 | TestRef(0 : Int) 50 | 51 | TestRef.factory = Fact("Float") 52 | TestRef(0 : Float) 53 | 54 | TestRef.factory = Fact("Long") 55 | TestRef(0 : Long) 56 | 57 | TestRef.factory = Fact("Double") 58 | TestRef(0 : Double) 59 | 60 | TestRef.factory = Fact("Unit") 61 | TestRef(()) 62 | 63 | TestRef.factory = Fact("Any") 64 | TestRef("abc") 65 | TestRef(null) 66 | TestRef(0.asInstanceOf[AnyRef]) 67 | val x: Any = 0 68 | TestRef(x) 69 | } 70 | 71 | test("missing manifest TestRef.apply") { 72 | TestRef.factory = Fact("Any") 73 | 74 | def go[T](x: T) = TestRef(x) 75 | 76 | go(123) 77 | go(1.23) 78 | go(null) 79 | go("abc") 80 | } 81 | 82 | test("dynamic specialization") { 83 | def go[T : ClassTag](v0: T, which: String): Unit = { 84 | TestRef.factory = Fact(which) 85 | TestRef(v0) 86 | } 87 | 88 | go(false, "Boolean") 89 | go(0 : Byte, "Byte") 90 | go(0 : Short, "Short") 91 | go(0 : Char, "Char") 92 | go(0 : Int, "Int") 93 | go(0 : Float, "Float") 94 | go(0 : Long, "Long") 95 | go(0 : Double, "Double") 96 | go((), "Unit") 97 | go("abc", "Any") 98 | go(null, "Any") 99 | go(0.asInstanceOf[AnyRef], "Any") 100 | val x: Any = 0 101 | go(x, "Any") 102 | } 103 | 104 | test("default value specialization") { 105 | def go[T : ClassTag](default: T, which: String): Unit = { 106 | TestRef.factory = Fact(which) 107 | TestRef.make[T]() 108 | //assert(x.single() == default) 109 | } 110 | 111 | go(false, "Boolean") 112 | go(0 : Byte, "Byte") 113 | go(0 : Short, "Short") 114 | go(0 : Char, "Char") 115 | go(0 : Int, "Int") 116 | go(0 : Float, "Float") 117 | go(0 : Long, "Long") 118 | go(0 : Double, "Double") 119 | go((), "Unit") 120 | go[String](null, "Any") 121 | go[AnyRef](null, "Any") 122 | go[Null](null, "Any") 123 | go[Any](null, "Any") 124 | } 125 | 126 | test("missing manifest TestRef.make") { 127 | TestRef.factory = Fact("Any") 128 | 129 | def go[T]() = TestRef.make[T]() 130 | 131 | go[Boolean]() 132 | go[Byte]() 133 | go[Short]() 134 | go[Char]() 135 | go[Int]() 136 | go[Float]() 137 | go[Long]() 138 | go[Double]() 139 | go[Unit]() 140 | go[String]() 141 | go[AnyRef]() 142 | go[Null]() 143 | go[Any]() 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/skel/AtomicArraySuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2014, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import org.scalatest.FunSuite 7 | 8 | import scala.reflect.ClassTag 9 | 10 | class AtomicArraySuite extends FunSuite { 11 | 12 | test("Unit") { 13 | runIsolatedTest(List((), (), ())) 14 | } 15 | 16 | test("Boolean") { 17 | runIsolatedTest(List(false, true, false, true)) 18 | } 19 | 20 | test("Byte") { 21 | runIsolatedTest(Array(0 : Byte, 1 : Byte, 2 : Byte)) 22 | } 23 | 24 | test("Short") { 25 | runIsolatedTest(List(0 : Short, 1 : Short, 2 : Short)) 26 | } 27 | 28 | test("Char") { 29 | runIsolatedTest("abcdefg".toSeq) 30 | } 31 | 32 | test("Int") { 33 | runIsolatedTest(100 until 200) 34 | } 35 | 36 | test("Float") { 37 | runIsolatedTest((20 to 30) map { _ * 0.1f }) 38 | } 39 | 40 | test("Long") { 41 | runIsolatedTest((100 until 200) map { _ - 100L * Int.MaxValue }) 42 | } 43 | 44 | test("Double") { 45 | runIsolatedTest((10 until 20) map { math.exp(_) }) 46 | } 47 | 48 | test("AnyRef") { 49 | runIsolatedTest((10 until 20) map { i => "x" + i : AnyRef }) 50 | } 51 | 52 | test("Any") { 53 | runIsolatedTest[Any]((10 until 20) map { i => i : Any }) 54 | } 55 | 56 | def runIsolatedTest[A](values: Seq[A])(implicit am: ClassTag[A]): Unit = { 57 | val singleton = AtomicArray[A](1) 58 | if (am != implicitly[ClassTag[Unit]]) 59 | assert(singleton(0) === am.newArray(1)(0)) 60 | 61 | val aa = AtomicArray(values) 62 | for (i <- 0 until aa.length) 63 | assert(values(i) === aa(i)) 64 | for (i <- 0 until aa.length) 65 | aa(i) = values(aa.length - 1 - i) 66 | for (i <- 0 until aa.length) 67 | assert(aa(i) === values(aa.length - 1 - i)) 68 | for (i <- 0 until aa.length) { 69 | if (aa(i) == values(0)) { 70 | assert(aa.compareAndSet(i, values(0), values(i))) 71 | assert(aa(i) === values(i)) 72 | } else { 73 | assert(!aa.compareAndSet(i, values(0), values(i))) 74 | assert(aa(i) === values(aa.length - 1 - i)) 75 | } 76 | } 77 | for (i <- 0 until aa.length) 78 | assert(aa(i) === aa.getAndTransform(i)( v => v )) 79 | for (i <- 0 until aa.length) { 80 | val prev = aa(i) 81 | assert(aa.swap(i, values(i)) === prev) 82 | } 83 | 84 | intercept[IndexOutOfBoundsException] { 85 | aa(-1) 86 | } 87 | intercept[IndexOutOfBoundsException] { 88 | aa(-1) = aa(0) 89 | } 90 | intercept[IndexOutOfBoundsException] { 91 | aa(aa.length) 92 | } 93 | intercept[IndexOutOfBoundsException] { 94 | aa(aa.length) = aa(0) 95 | } 96 | intercept[IndexOutOfBoundsException] { 97 | aa.compareAndSet(-1, aa(0), aa(0)) 98 | } 99 | intercept[IndexOutOfBoundsException] { 100 | aa.compareAndSet(aa.length, aa(0), aa(0)) 101 | } 102 | intercept[IndexOutOfBoundsException] { 103 | aa(Int.MinValue) 104 | } 105 | intercept[IndexOutOfBoundsException] { 106 | aa(Int.MaxValue) 107 | } 108 | 109 | val copy = aa.clone 110 | for (i <- 0 until aa.length) 111 | assert(copy(i) === aa(i)) 112 | 113 | val str0 = aa map { _.toString } 114 | val str: AtomicArray[String] = str0 115 | for (i <- 0 until aa.length) 116 | assert(aa(i).toString === str(i)) 117 | 118 | val seq0 = aa.toList 119 | for (i <- 0 until aa.length) 120 | assert(aa(i) == seq0(i)) 121 | 122 | val seq1 = aa.iterator.toList 123 | for (i <- 0 until aa.length) 124 | assert(aa(i) == seq1(i)) 125 | 126 | val bb = aa ++ seq0 127 | assert(bb.length === aa.length * 2) 128 | for (i <- 0 until aa.length) { 129 | assert(aa(i) === bb(i)) 130 | assert(aa(i) === bb(i + aa.length)) 131 | } 132 | 133 | assert(aa.toString.startsWith("AtomicArray")) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/scala/scala/concurrent/stm/skel/SimpleRandomSuite.scala: -------------------------------------------------------------------------------- 1 | /* scala-stm - (c) 2009-2011, Stanford University, PPL */ 2 | 3 | package scala.concurrent.stm 4 | package skel 5 | 6 | import org.scalatest.FunSuite 7 | 8 | class SimpleRandomSuite extends FunSuite { 9 | 10 | test("nextInt") { 11 | val f = new SimpleRandom 12 | var s = 0 13 | for (_ <- 0 until 100000) { 14 | s |= SimpleRandom.nextInt 15 | s |= f.nextInt 16 | } 17 | assert(s != 0) 18 | } 19 | 20 | test("nextInt(n) in range") { 21 | val f = new SimpleRandom 22 | val rand = new scala.util.Random 23 | for (_ <- 0 until 100000) { 24 | val n = rand.nextInt(Int.MaxValue - 1) + 1 25 | val gr = SimpleRandom.nextInt(n) 26 | assert(gr >= 0 && gr < n) 27 | val lr = f.nextInt(n) 28 | assert(lr >= 0 && lr < n) 29 | } 30 | } 31 | 32 | test("clone") { 33 | val f1 = new SimpleRandom 34 | for (_ <- 0 until 1000) 35 | f1.nextInt() 36 | val f2 = f1.clone 37 | for (_ <- 0 until 1000) 38 | assert(f1.nextInt(9999) === f2.nextInt(9999)) 39 | } 40 | 41 | test("seeded") { 42 | val f1 = new SimpleRandom(100) 43 | val f2 = new SimpleRandom(100) 44 | for (_ <- 0 until 1000) 45 | assert(f1.nextInt === f2.nextInt) 46 | } 47 | 48 | test("global SimpleRandom distribution") { 49 | val buckets = new Array[Int](100) 50 | for (_ <- 0 until 100000) 51 | buckets(SimpleRandom.nextInt(buckets.length)) += 1 52 | for (b <- buckets) 53 | assert(b > 0) 54 | } 55 | 56 | test("local SimpleRandom distribution") { 57 | val f = new SimpleRandom 58 | val buckets = new Array[Int](100) 59 | for (_ <- 0 until 100000) 60 | buckets(f.nextInt(buckets.length)) += 1 61 | for (b <- buckets) 62 | assert(b > 0) 63 | } 64 | } --------------------------------------------------------------------------------