From ed8575e09b6f4802235e25b31a6498986d770098 Mon Sep 17 00:00:00 2001 From: Sergei Winitzki Date: Wed, 21 Dec 2016 10:45:19 -0800 Subject: [PATCH 01/12] add deprecation warnings to build.sbt --- build.sbt | 2 +- lib/src/main/scala/code/winitzki/jc/JoinRun.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 3d82d05a..7db5130d 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,7 @@ val commonSettings = Defaults.coreDefaultSettings ++ Seq( resolvers += "Typesafe releases" at "http://repo.typesafe.com/typesafe/releases", scalacOptions ++= Seq( // https://tpolecat.github.io/2014/04/11/scalac-flags.html -// "-deprecation", + "-deprecation", "-unchecked", "-encoding", "UTF-8", // yes, this is 2 args "-feature", diff --git a/lib/src/main/scala/code/winitzki/jc/JoinRun.scala b/lib/src/main/scala/code/winitzki/jc/JoinRun.scala index fda16703..129c57d0 100644 --- a/lib/src/main/scala/code/winitzki/jc/JoinRun.scala +++ b/lib/src/main/scala/code/winitzki/jc/JoinRun.scala @@ -257,7 +257,7 @@ object JoinRun { // Wait until the reaction site to which `molecule` is bound becomes quiescent, then emit `callback`. // TODO: implement - def wait_until_quiet[T](molecule: M[T], callback: M[Unit]): Unit = molecule.site.setQuiescenceCallback(callback) + def waitUntilQuiet[T](molecule: M[T], callback: M[Unit]): Unit = molecule.site.setQuiescenceCallback(callback) /** * Convenience syntax: users can write a(x)+b(y) to emit several molecules at once. From 95d0f87910360453574945125ffeae9ebe6eb8e6 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 16:21:12 -0800 Subject: [PATCH 02/12] refactor to eliminate JoinRun and JoinRunUtils objects in favor of package objects --- .../code/winitzki/benchmark/Benchmarks1.scala | 3 +- .../code/winitzki/benchmark/Benchmarks4.scala | 3 +- .../code/winitzki/benchmark/Benchmarks7.scala | 1 - .../code/winitzki/benchmark/Benchmarks9.scala | 1 - .../code/winitzki/benchmark/MainApp.scala | 11 +- .../winitzki/benchmark/MergesortSpec.scala | 3 +- .../winitzki/benchmark/MultithreadSpec.scala | 3 +- .../test/DiningPhilosophersSpec.scala | 3 +- .../code/winitzki/test/FairnessSpec.scala | 3 +- .../code/winitzki/test/MapReduceSpec.scala | 3 +- .../code/winitzki/test/MoreBlockingSpec.scala | 3 +- .../code/winitzki/test/ParallelOrSpec.scala | 3 +- .../code/winitzki/test/ShutdownSpec.scala | 3 +- .../winitzki/test/SingletonMoleculeSpec.scala | 3 +- .../winitzki/test/StaticAnalysisSpec.scala | 3 +- .../jc/{Library.scala => Chymyst.scala} | 4 +- .../main/scala/code/winitzki/jc/JoinRun.scala | 600 ------------------ .../scala/code/winitzki/jc/JoinRunUtils.scala | 43 -- .../scala/code/winitzki/jc/Molecules.scala | 486 ++++++++++++++ .../main/scala/code/winitzki/jc/Pool.scala | 4 +- .../scala/code/winitzki/jc/ReactionSite.scala | 16 +- .../scala/code/winitzki/jc/SmartPool.scala | 4 +- .../scala/code/winitzki/jc/SmartThread.scala | 3 - .../code/winitzki/jc/StaticAnalysis.scala | 3 - .../main/scala/code/winitzki/jc/package.scala | 149 +++++ ...Spec.scala => BlockingMoleculesSpec.scala} | 5 +- .../{LibrarySpec.scala => ChymystSpec.scala} | 5 +- ...{JoinRunSpec.scala => MoleculesSpec.scala} | 7 +- ...inRunUtilsSpec.scala => PackageSpec.scala} | 3 +- .../scala/code/winitzki/jc/PoolSpec.scala | 9 +- ...unUtilsSha1Props.scala => Sha1Props.scala} | 3 +- .../main/scala/code/winitzki/jc/Macros.scala | 32 +- .../scala/code/winitzki/jc/MacrosSpec.scala | 3 +- 33 files changed, 697 insertions(+), 731 deletions(-) rename lib/src/main/scala/code/winitzki/jc/{Library.scala => Chymyst.scala} (97%) delete mode 100644 lib/src/main/scala/code/winitzki/jc/JoinRun.scala delete mode 100644 lib/src/main/scala/code/winitzki/jc/JoinRunUtils.scala create mode 100644 lib/src/main/scala/code/winitzki/jc/Molecules.scala create mode 100644 lib/src/main/scala/code/winitzki/jc/package.scala rename lib/src/test/scala/code/winitzki/jc/{JoinRunBlockingSpec.scala => BlockingMoleculesSpec.scala} (98%) rename lib/src/test/scala/code/winitzki/jc/{LibrarySpec.scala => ChymystSpec.scala} (96%) rename lib/src/test/scala/code/winitzki/jc/{JoinRunSpec.scala => MoleculesSpec.scala} (97%) rename lib/src/test/scala/code/winitzki/jc/{JoinRunUtilsSpec.scala => PackageSpec.scala} (91%) rename lib/src/test/scala/code/winitzki/jc/{JoinRunUtilsSha1Props.scala => Sha1Props.scala} (71%) diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala index 6275e625..58713fe9 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala @@ -3,9 +3,8 @@ package code.winitzki.benchmark import java.time.LocalDateTime import code.winitzki.benchmark.Common._ -import code.winitzki.jc.Pool +import code.winitzki.jc._ import code.winitzki.jc.Macros._ -import code.winitzki.jc.JoinRun._ object Benchmarks1 { diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala index 1d4561c5..8d50ed21 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala @@ -3,9 +3,8 @@ package code.winitzki.benchmark import java.time.LocalDateTime import code.winitzki.benchmark.Common._ -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ -import code.winitzki.jc.Pool object Benchmarks4 { val differentReactions = 100 diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala index 97302bec..180cd1a2 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala @@ -3,7 +3,6 @@ package code.winitzki.benchmark import java.time.LocalDateTime import code.winitzki.benchmark.Common._ import code.winitzki.jc._ -import code.winitzki.jc.JoinRun._ import code.winitzki.jc.Macros._ object Benchmarks7 { diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala index 05fd3648..80f098cb 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala @@ -3,7 +3,6 @@ package code.winitzki.benchmark import java.time.LocalDateTime import code.winitzki.benchmark.Common._ import code.winitzki.jc._ -import code.winitzki.jc.JoinRun._ import code.winitzki.jc.Macros._ import scala.concurrent.duration._ diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/MainApp.scala b/benchmark/src/main/scala/code/winitzki/benchmark/MainApp.scala index 185b9c0a..abf29122 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/MainApp.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/MainApp.scala @@ -1,11 +1,10 @@ package code.winitzki.benchmark -import code.winitzki.benchmark.Benchmarks1._ -import code.winitzki.benchmark.Benchmarks4._ -import code.winitzki.benchmark.Benchmarks7._ -import code.winitzki.benchmark.Benchmarks9._ -import code.winitzki.jc.{FixedPool, Pool} -import code.winitzki.jc.JoinRun.{defaultSitePool, defaultReactionPool} +import Benchmarks1._ +import Benchmarks4._ +import Benchmarks7._ +import Benchmarks9._ +import code.winitzki.jc._ object MainAppConfig { diff --git a/benchmark/src/test/scala/code/winitzki/benchmark/MergesortSpec.scala b/benchmark/src/test/scala/code/winitzki/benchmark/MergesortSpec.scala index f90d906c..cb073a48 100644 --- a/benchmark/src/test/scala/code/winitzki/benchmark/MergesortSpec.scala +++ b/benchmark/src/test/scala/code/winitzki/benchmark/MergesortSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.benchmark -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.{FlatSpec, Matchers} diff --git a/benchmark/src/test/scala/code/winitzki/benchmark/MultithreadSpec.scala b/benchmark/src/test/scala/code/winitzki/benchmark/MultithreadSpec.scala index 441ac0ec..0e9fa6ab 100644 --- a/benchmark/src/test/scala/code/winitzki/benchmark/MultithreadSpec.scala +++ b/benchmark/src/test/scala/code/winitzki/benchmark/MultithreadSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.benchmark -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.{FlatSpec, Matchers} import Common._ diff --git a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala index 01ecd86b..c996bef8 100644 --- a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.test -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.time.{Millis, Span} diff --git a/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala b/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala index 08212ce6..de716e72 100644 --- a/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.test -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.time.{Millis, Span} diff --git a/joinrun/src/test/scala/code/winitzki/test/MapReduceSpec.scala b/joinrun/src/test/scala/code/winitzki/test/MapReduceSpec.scala index 526373c3..537675a0 100644 --- a/joinrun/src/test/scala/code/winitzki/test/MapReduceSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/MapReduceSpec.scala @@ -3,8 +3,7 @@ package code.winitzki.test import java.time.LocalDateTime import java.time.temporal.ChronoUnit -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.{FlatSpec, Matchers} diff --git a/joinrun/src/test/scala/code/winitzki/test/MoreBlockingSpec.scala b/joinrun/src/test/scala/code/winitzki/test/MoreBlockingSpec.scala index 68a4b395..f583ab46 100644 --- a/joinrun/src/test/scala/code/winitzki/test/MoreBlockingSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/MoreBlockingSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.test -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.{FlatSpec, Matchers} import org.scalatest.concurrent.TimeLimitedTests diff --git a/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala b/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala index 8dc22ae1..e71489ec 100644 --- a/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala @@ -1,8 +1,7 @@ package code.winitzki.test -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ -import code.winitzki.jc.{FixedPool, Pool} import org.scalatest.{FlatSpec, Matchers} import scala.annotation.tailrec diff --git a/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala b/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala index 171dff2e..21fd8cec 100644 --- a/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.test -import code.winitzki.jc.FixedPool -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import org.scalatest.{FlatSpec, Matchers} diff --git a/joinrun/src/test/scala/code/winitzki/test/SingletonMoleculeSpec.scala b/joinrun/src/test/scala/code/winitzki/test/SingletonMoleculeSpec.scala index 36b2c1f2..ae3a282e 100644 --- a/joinrun/src/test/scala/code/winitzki/test/SingletonMoleculeSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/SingletonMoleculeSpec.scala @@ -1,8 +1,7 @@ package code.winitzki.test -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ -import code.winitzki.jc.{FixedPool, SmartPool} import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.time.{Millis, Span} import org.scalatest.{FlatSpec, Matchers} diff --git a/joinrun/src/test/scala/code/winitzki/test/StaticAnalysisSpec.scala b/joinrun/src/test/scala/code/winitzki/test/StaticAnalysisSpec.scala index f50558fa..85fb872b 100644 --- a/joinrun/src/test/scala/code/winitzki/test/StaticAnalysisSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/StaticAnalysisSpec.scala @@ -1,8 +1,7 @@ package code.winitzki.test -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ -import code.winitzki.jc.WarningsAndErrors import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.time.{Millis, Span} import org.scalatest.{FlatSpec, Matchers} diff --git a/lib/src/main/scala/code/winitzki/jc/Library.scala b/lib/src/main/scala/code/winitzki/jc/Chymyst.scala similarity index 97% rename from lib/src/main/scala/code/winitzki/jc/Library.scala rename to lib/src/main/scala/code/winitzki/jc/Chymyst.scala index baab066a..7a499d76 100644 --- a/lib/src/main/scala/code/winitzki/jc/Library.scala +++ b/lib/src/main/scala/code/winitzki/jc/Chymyst.scala @@ -1,12 +1,10 @@ package code.winitzki.jc -import code.winitzki.jc.JoinRun._ - import scala.concurrent.{ExecutionContext, Future, Promise} import scala.reflect.ClassTag import scala.util.{Try, Success, Failure} -object Library { +object Chymyst { /** Create a non-blocking molecule that, when emitted, will resolve the future. * Example usage: val (m, fut) = moleculeFuture[String](pool) * diff --git a/lib/src/main/scala/code/winitzki/jc/JoinRun.scala b/lib/src/main/scala/code/winitzki/jc/JoinRun.scala deleted file mode 100644 index 129c57d0..00000000 --- a/lib/src/main/scala/code/winitzki/jc/JoinRun.scala +++ /dev/null @@ -1,600 +0,0 @@ -package code.winitzki.jc - -/* -Join Calculus (JC) is a micro-framework for declarative concurrency. - -JC is basically “Actors” made type-safe, stateless, and more high-level. - -The code is inspired by previous implementations by He Jiansen (https://github.com/Jiansen/ScalaJoin, 2011) -and Philipp Haller (http://lampwww.epfl.ch/~phaller/joins/index.html, 2008). - * */ - -import java.util.UUID -import java.util.concurrent.{ConcurrentLinkedQueue, Semaphore, TimeUnit} - -import JoinRunUtils._ - -import scala.collection.mutable -import scala.concurrent.duration.Duration -import scala.collection.JavaConverters._ - -object JoinRun { - - sealed trait InputPatternType - - case object Wildcard extends InputPatternType - - case object SimpleVar extends InputPatternType - - final case class SimpleConst(v: Any) extends InputPatternType - - final case class OtherInputPattern(matcher: PartialFunction[Any, Unit]) extends InputPatternType - - case object UnknownInputPattern extends InputPatternType - - sealed trait OutputPatternType - - final case class ConstOutputValue(v: Any) extends OutputPatternType - - case object OtherOutputPattern extends OutputPatternType - - sealed trait GuardPresenceType { - def knownFalse: Boolean = this match { - case GuardAbsent => true - case _ => false - } - } - - case object GuardPresent extends GuardPresenceType - - case object GuardAbsent extends GuardPresenceType - - case object GuardPresenceUnknown extends GuardPresenceType - - /** Compile-time information about an input molecule pattern in a reaction. - * This class is immutable. - * - * @param molecule The molecule emitter value that represents the input molecule. - * @param flag Type of the input pattern: wildcard, constant match, etc. - * @param sha1 Hash sum of the source code (AST tree) of the input pattern. - */ - final case class InputMoleculeInfo(molecule: Molecule, flag: InputPatternType, sha1: String) { - /** Determine whether this input molecule pattern is weaker than another pattern. - * Pattern a(xxx) is weaker than b(yyy) if a==b and if anything matched by yyy will also be matched by xxx. - * - * @param info The input molecule info for another input molecule. - * @return Some(true) if we can surely determine that this matcher is weaker than another; - * Some(false) if we can surely determine that this matcher is not weaker than another; - * None if we cannot determine anything because information is insufficient. - */ - private[jc] def matcherIsWeakerThan(info: InputMoleculeInfo): Option[Boolean] = { - if (molecule =!= info.molecule) Some(false) - else flag match { - case Wildcard | SimpleVar => Some(true) - case OtherInputPattern(matcher1) => info.flag match { - case SimpleConst(c) => Some(matcher1.isDefinedAt(c)) - case OtherInputPattern(_) => if (sha1 === info.sha1) Some(true) else None // We can reliably determine identical matchers. - case _ => Some(false) // Here we can reliably determine that this matcher is not weaker. - } - case SimpleConst(c) => Some(info.flag match { - case SimpleConst(`c`) => true - case _ => false - }) - case _ => Some(false) - } - } - - private[jc] def matcherIsWeakerThanOutput(info: OutputMoleculeInfo): Option[Boolean] = { - if (molecule =!= info.molecule) Some(false) - else flag match { - case Wildcard | SimpleVar => Some(true) - case OtherInputPattern(matcher1) => info.flag match { - case ConstOutputValue(c) => Some(matcher1.isDefinedAt(c)) - case _ => None // Here we can't reliably determine whether this matcher is weaker. - } - case SimpleConst(c) => info.flag match { - case ConstOutputValue(`c`) => Some(true) - case ConstOutputValue(_) => Some(false) // definitely not the same constant - case _ => None // Otherwise, it could be this constant but we can't determine. - } - case _ => Some(false) - } - } - - // Here "similar" means either it's definitely weaker or it could be weaker (but it is definitely not stronger). - private[jc] def matcherIsSimilarToOutput(info: OutputMoleculeInfo): Option[Boolean] = { - if (molecule =!= info.molecule) Some(false) - else flag match { - case Wildcard | SimpleVar => Some(true) - case OtherInputPattern(matcher1) => info.flag match { - case ConstOutputValue(c) => Some(matcher1.isDefinedAt(c)) - case _ => Some(true) // Here we can't reliably determine whether this matcher is weaker, but it's similar (i.e. could be weaker). - } - case SimpleConst(c) => Some(info.flag match { - case ConstOutputValue(`c`) => true - case ConstOutputValue(_) => false // definitely not the same constant - case _ => true // Otherwise, it could be this constant. - }) - case UnknownInputPattern => Some(true) // pattern unknown - could be weaker. - } - } - - override def toString: String = { - val printedPattern = flag match { - case Wildcard => "_" - case SimpleVar => "." - case SimpleConst(c) => c.toString - case OtherInputPattern(_) => s"<${sha1.substring(0, 4)}...>" - case UnknownInputPattern => s"?" - } - - s"$molecule($printedPattern)" - } - - } - - /** Compile-time information about an output molecule pattern in a reaction. - * This class is immutable. - * - * @param molecule The molecule emitter value that represents the output molecule. - * @param flag Type of the output pattern: either a constant value or other value. - */ - final case class OutputMoleculeInfo(molecule: Molecule, flag: OutputPatternType) { - override def toString: String = { - val printedPattern = flag match { - case ConstOutputValue(()) => "" - case ConstOutputValue(c) => c.toString - case OtherOutputPattern => "?" - } - - s"$molecule($printedPattern)" - } - } - - // This class is immutable. - final case class ReactionInfo(inputs: List[InputMoleculeInfo], outputs: Option[List[OutputMoleculeInfo]], hasGuard: GuardPresenceType, sha1: String) { - - // The input pattern sequence is pre-sorted for further use. - private[jc] val inputsSorted: List[InputMoleculeInfo] = inputs.sortBy { case InputMoleculeInfo(mol, flag, sha) => - // wildcard and simplevars are sorted together; more specific matchers must precede less specific matchers - val patternPrecedence = flag match { - case Wildcard | SimpleVar => 3 - case OtherInputPattern(_) => 2 - case SimpleConst(_) => 1 - case _ => 0 - } - (mol.toString, patternPrecedence, sha) - } - - override val toString: String = s"${inputsSorted.map(_.toString).mkString(" + ")}${hasGuard match { - case GuardAbsent => "" - case GuardPresent => " if(...)" - case GuardPresenceUnknown => " ?" - }} => ${outputs match { - case Some(outputMoleculeInfos) => outputMoleculeInfos.map(_.toString).mkString(" + ") - case None => "?" - }}" - } - - /** A special value for {{{ReactionInfo}}} to signal that we are not running a reaction. - * - */ - private[jc] val emptyReactionInfo = ReactionInfo(Nil, None, GuardPresenceUnknown, "") - - // for M[T] molecules, the value inside AbsMolValue[T] is of type T; for B[T,R] molecules, the value is of type - // ReplyValue[T,R]. For now, we don't use shapeless to enforce this typing relation. - private[jc] type MoleculeBag = MutableBag[Molecule, AbsMolValue[_]] - private[jc] type MutableLinearMoleculeBag = mutable.Map[Molecule, AbsMolValue[_]] - private[jc] type LinearMoleculeBag = Map[Molecule, AbsMolValue[_]] - - private[jc] def moleculeBagToString(mb: MoleculeBag): String = - mb.getMap.toSeq - .map{ case (m, vs) => (m.toString, vs) } - .sortBy(_._1) - .flatMap { - case (m, vs) => vs.map { - case (mv, 1) => s"$m($mv)" - case (mv, i) => s"$m($mv) * $i" - } - }.mkString(", ") - - private[jc] def moleculeBagToString(mb: LinearMoleculeBag): String = - mb.map { - case (m, jmv) => s"$m($jmv)" - }.mkString(", ") - - private[jc] sealed class ExceptionInJoinRun(message: String) extends Exception(message) - private[JoinRun] final class ExceptionNoReactionSite(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionMoleculeAlreadyBound(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionNoSitePool(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionEmittingSingleton(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionNoReactionPool(message: String) extends ExceptionInJoinRun(message) - private final class ExceptionNoWrapper(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionWrongInputs(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionEmptyReply(message: String) extends ExceptionInJoinRun(message) - private[jc] final class ExceptionNoSingleton(message: String) extends ExceptionInJoinRun(message) - - /** Represents a reaction body. This class is immutable. - * - * @param body Partial function of type {{{ UnapplyArg => Unit }}} - * @param threadPool Thread pool on which this reaction will be scheduled. (By default, the common pool is used.) - * @param retry Whether the reaction should be run again when an exception occurs in its body. Default is false. - */ - final case class Reaction(info: ReactionInfo, body: ReactionBody, threadPool: Option[Pool] = None, retry: Boolean) { - - /** Convenience method to specify thread pools per reaction. - * - * Example: go { case a(x) => ... } onThreads threadPool24 - * - * @param newThreadPool A custom thread pool on which this reaction will be scheduled. - * @return New reaction value with the thread pool set. - */ - def onThreads(newThreadPool: Pool): Reaction = Reaction(info, body, Some(newThreadPool), retry) - - /** Convenience method to specify the "retry" option for a reaction. - * - * @return New reaction value with the "retry" flag set. - */ - def withRetry: Reaction = Reaction(info, body, threadPool, retry = true) - - /** Convenience method to specify the "no retry" option for a reaction. - * (This option is the default.) - * - * @return New reaction value with the "retry" flag unset. - */ - def noRetry: Reaction = Reaction(info, body, threadPool, retry = false) - - // Optimization: this is used often. - val inputMolecules: Seq[Molecule] = info.inputs.map(_.molecule).sortBy(_.toString) - - /** Convenience method for debugging. - * - * @return String representation of input molecules of the reaction. - */ - override val toString: String = s"${inputMolecules.map(_.toString).mkString(" + ")} => ...${if (retry) - "/R" else ""}" - } - - // Wait until the reaction site to which `molecule` is bound becomes quiescent, then emit `callback`. - // TODO: implement - def waitUntilQuiet[T](molecule: M[T], callback: M[Unit]): Unit = molecule.site.setQuiescenceCallback(callback) - - /** - * Convenience syntax: users can write a(x)+b(y) to emit several molecules at once. - * (However, the molecules are emitted one by one in the present implementation.) - * - * @param x the first emitted molecule - * @return a class with a + operator - */ - implicit final class EmitMultiple(x: Unit) { - def +(n: Unit): Unit = () - } - - /** - * Convenience syntax: users can write a(x)+b(y) in reaction patterns. - * Pattern-matching can be extended to molecule values as well, for example - * {{{ { case a(MyCaseClass(x,y)) + b(Some(z)) => ... } }}} - * - * @return an unapply operation - */ - object + { - def unapply(attr:Any): Option[(Any, Any)] = Some((attr,attr)) - } - - /** Create a reaction value out of a simple reaction body. Used only for testing. - * The reaction body must be "simple" in the sense that it allows very limited pattern-matching with molecule values: - * - all patterns must be simple variables or wildcards, or {{{null}}} or zero constant values, except the last molecule in the reaction. - * - the last molecule in the reaction can have a nontrivial pattern matcher. - * - * The only reason this method exists is to enable testing JoinRun without depending on the macro package. - * Since this method does not provide a full compile-time analysis of reactions, it should be used only for internal testing and debugging of JoinRun itself. - * At the moment, this method is used in benchmarks and unit tests of JoinRun that run without depending on the macro package. - * - * @param body Body of the reaction. Should not contain any pattern-matching on molecule values, except possibly for the last molecule in the list of input molecules. - * @return Reaction value. The [[ReactionInfo]] structure will be filled out in a minimal fashion (only has information about input molecules, and all patterns are "unknown"). - */ - private[jc] def _go(body: ReactionBody): Reaction = { - val moleculesInThisReaction = UnapplyCheckSimple(mutable.MutableList.empty) - body.isDefinedAt(moleculesInThisReaction) - // detect nonlinear patterns - val duplicateMolecules = moleculesInThisReaction.inputMolecules diff moleculesInThisReaction.inputMolecules.distinct - if (duplicateMolecules.nonEmpty) throw new ExceptionInJoinRun(s"Nonlinear pattern: ${duplicateMolecules.mkString(", ")} used twice") - val inputMoleculesUsed = moleculesInThisReaction.inputMolecules.toList - val inputMoleculeInfo = inputMoleculesUsed.map(m => InputMoleculeInfo(m, UnknownInputPattern, UUID.randomUUID().toString)) - val simpleInfo = ReactionInfo(inputMoleculeInfo, None, GuardPresenceUnknown, UUID.randomUUID().toString) - Reaction(simpleInfo, body, retry = false) - } - - // Abstract container for molecule values. - private[jc] sealed trait AbsMolValue[T] { - def getValue: T - - override def toString: String = getValue match { case () => ""; case v@_ => v.asInstanceOf[T].toString } - } - - /** Container for the value of a non-blocking molecule. - * - * @param v The value of type T carried by the molecule. - * @tparam T The type of the value. - */ - private[jc] final case class MolValue[T](v: T) extends AbsMolValue[T] { - override def getValue: T = v - } - - /** Container for the value of a blocking molecule. - * - * @param v The value of type T carried by the molecule. - * @param replyValue The wrapper for the reply value, which will ultimately return a value of type R. - * @tparam T The type of the value carried by the molecule. - * @tparam R The type of the reply value. - */ - private[jc] final case class BlockingMolValue[T,R](v: T, replyValue: ReplyValue[T,R]) extends AbsMolValue[T] with PersistentHashCode { - override def getValue: T = v - } - - /** Abstract molecule emitter class. - * This class is not parameterized b type and is used in collections of molecules that do not require knowledge of molecule types. - * - */ - sealed trait Molecule extends PersistentHashCode { - - val name: String - - override def toString: String = (if (name.isEmpty) "" else name) + (if (isBlocking) "/B" else "") - - /** Check whether the molecule is already bound to a reaction site. - * Note that molecules can be emitted only if they are bound. - * - * @return True if already bound, false otherwise. - */ - def isBound: Boolean = reactionSiteOpt.nonEmpty - - @volatile private[jc] var reactionSiteOpt: Option[ReactionSite] = None - - private[jc] def site: ReactionSite = - reactionSiteOpt.getOrElse(throw new ExceptionNoReactionSite(s"Molecule ${this} is not bound to any reaction site")) - - /** The set of reactions that can consume this molecule. - * - * @return {{{None}}} if the molecule emitter is not yet bound to any reaction site. - */ - private[jc] def consumingReactions: Option[Set[Reaction]] = reactionSiteOpt.map(_ => consumingReactionsSet) - - private lazy val consumingReactionsSet: Set[Reaction] = reactionSiteOpt.get.reactionInfos.keys.filter(_.inputMolecules contains this).toSet - - /** The set of all reactions that *potentially* emit this molecule as output. - * Some of these reactions may evaluate a runtime condition to decide whether to emit the molecule; so emission is not guaranteed. - * - * Note that these reactions may be defined in any reaction sites, not necessarily at the site to which this molecule is bound. - * The set of these reactions may change at run time if new reaction sites are written that output this molecule. - * - * @return Empty set if the molecule is not yet bound to any reaction site. - */ - private[jc] def emittingReactions: Set[Reaction] = emittingReactionsSet.toSet - - private[jc] val emittingReactionsSet: mutable.Set[Reaction] = mutable.Set() - - def setLogLevel(logLevel: Int): Unit = - site.logLevel = logLevel - - def logSoup: String = site.printBag - - def isBlocking: Boolean - - def isSingleton: Boolean = false - } - - /** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. - * - * @param name Name of the molecule, used for debugging only. - * @tparam T Type of the value carried by the molecule. - */ - final class M[T](val name: String) extends (T => Unit) with Molecule { - /** Emit a non-blocking molecule. - * - * @param v Value to be put onto the emitted molecule. - */ - def apply(v: T): Unit = site.emit[T](this, MolValue(v)) - - def unapply(arg: UnapplyArg): Option[T] = arg match { - - case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go - inputMoleculesProbe += this - Some(null.asInstanceOf[T]) // hack for testing only. This value will not be used. - - // When we are gathering information about the input molecules, `unapply` will always return Some(...), - // so that any pattern-matching on arguments will continue with null (since, at this point, we have no values). - // Any pattern-matching will work unless null fails. - // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. - // We also gather the information about the molecule values actually used by the reaction, in case the reaction can start. - case UnapplyRunCheck(moleculeValues, usedInputs) => - for { - v <- moleculeValues.getOne(this) - } yield { - usedInputs += (this -> v) - v.asInstanceOf[AbsMolValue[T]].getValue - } - - // This is used when running the chosen reaction. - case UnapplyRun(moleculeValues) => moleculeValues.get(this) - .map(_.asInstanceOf[MolValue[T]].getValue) - } - - /** Volatile reader for a molecule. - * The molecule must be declared as a singleton. - * - * @return The value carried by the singleton when it was last emitted. Will throw exception if the singleton has not yet been emitted. - */ - def volatileValue: T = site.getVolatileValue(this) - - def hasVolatileValue: Boolean = site.hasVolatileValue(this) - - @volatile private[jc] var isSingletonBoolean = false - - override def isSingleton: Boolean = isSingletonBoolean - - override def isBlocking = false - } - - /** Reply-value wrapper for blocking molecules. This is a mutable class. - * - * @param molecule The blocking molecule whose reply value this wrapper represents. - * result Reply value as {{{Option[R]}}}. Initially this is None, and it may be assigned at most once by the - * "reply" action if the reply is "valid" (i.e. not timed out). - * semaphore Mutable semaphore reference. This is initialized only once when creating an instance of this - * class. The semaphore will be acquired when emitting the molecule and released by the "reply" - * action. The semaphore will be destroyed and never initialized again once a reply is received. - * errorMessage Optional error message, to notify the caller or to raise an exception when the user made a - * mistake in chemistry. - * replyTimeout Will be set to "true" if the molecule was emitted with a timeout and the timeout was reached. - * replyRepeated Will be set to "true" if the molecule received a reply more than once. - * @tparam T Type of the value carried by the molecule. - * @tparam R Type of the value replied to the caller via the "reply" action. - */ - private[jc] final class ReplyValue[T,R] (molecule: B[T,R]) extends (R => Boolean) { - - @volatile var result: Option[R] = None - - @volatile private var semaphore: Semaphore = { - val s = new Semaphore(0, false); s.drainPermits(); s - } - - @volatile var errorMessage: Option[String] = None - - @volatile var replyTimeout: Boolean = false - - @volatile var replyRepeated: Boolean = false - - private[jc] def releaseSemaphore(): Unit = synchronized { - if (Option(semaphore).isDefined) semaphore.release() - } - - private[jc] def deleteSemaphore(): Unit = synchronized { - semaphore = null - } - - private[jc] def acquireSemaphore(timeoutNanos: Option[Long]): Boolean = - if (Option(semaphore).isDefined) - timeoutNanos match { - case Some(nanos) => semaphore.tryAcquire(nanos, TimeUnit.NANOSECONDS) - case None => semaphore.acquire(); true - } - else false - - /** Perform a reply action for a blocking molecule. - * For each blocking molecule consumed by a reaction, exactly one reply action should be performed within the reaction body. - * - * @param x Value to reply with. - * @return True if the reply was successful. False if the blocking molecule timed out, or if a reply action was already performed. - */ - def apply(x: R): Boolean = synchronized { - // The reply value will be assigned only if there was no timeout and no previous reply action. - if (!replyTimeout && !replyRepeated && result.isEmpty) { - result = Some(x) - } else if (!replyTimeout && result.nonEmpty) { - replyRepeated = true - } - - releaseSemaphore() - - !replyTimeout && !replyRepeated - } - } - - /** Blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. - * - * @param name Name of the molecule, used for debugging only. - * @tparam T Type of the value carried by the molecule. - * @tparam R Type of the value replied to the caller via the "reply" action. - */ - final class B[T, R](val name: String) extends (T => R) with Molecule { - - /** Emit a blocking molecule and receive a value when the reply action is performed. - * - * @param v Value to be put onto the emitted molecule. - * @return The "reply" value. - */ - def apply(v: T): R = - site.emitAndReply[T,R](this, v, new ReplyValue[T,R](molecule = this)) - - /** Emit a blocking molecule and receive a value when the reply action is performed, unless a timeout is reached. - * - * @param timeout Timeout in any time interval. - * @param v Value to be put onto the emitted molecule. - * @return Non-empty option if the reply was received; None on timeout. - */ - def timeout(timeout: Duration)(v: T): Option[R] = - site.emitAndReplyWithTimeout[T,R](timeout.toNanos, this, v, new ReplyValue[T,R](molecule = this)) - - def unapply(arg: UnapplyArg): Option[(T, ReplyValue[T,R])] = arg match { - - case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go - inputMoleculesProbe += this - Some((null, null).asInstanceOf[(T, ReplyValue[T,R])]) // hack for testing purposes only: - // The null value will not be used in any production code since _go is private. - - // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. - // We also gather the information about the molecule values actually used by the reaction, in case the reaction can start. - case UnapplyRunCheck(moleculeValues, usedInputs) => - for { - v <- moleculeValues.getOne(this) - } yield { - usedInputs += (this -> v) - (v.getValue, null).asInstanceOf[(T, ReplyValue[T,R])] // hack for verifying isDefinedAt: - // The null value will not be used, since the reply value is always matched unconditionally. - } - - // This is used when running the chosen reaction. - case UnapplyRun(moleculeValues) => moleculeValues.get(this).map { - case BlockingMolValue(v, srv) => (v, srv).asInstanceOf[(T, ReplyValue[T,R])] - case m@_ => - throw new ExceptionNoWrapper(s"Internal error: molecule $this with no value wrapper around value $m") - } - } - - override def isBlocking = true - } - - val defaultSitePool = new FixedPool(2) - val defaultReactionPool = new FixedPool(4) - - private[jc] sealed trait UnapplyArg // The disjoint union type for arguments passed to the unapply methods. - private final case class UnapplyCheckSimple(inputMolecules: mutable.MutableList[Molecule]) extends UnapplyArg // used only for `_go` and in tests - private[jc] final case class UnapplyRunCheck(moleculeValues: MoleculeBag, usedInputs: MutableLinearMoleculeBag) extends UnapplyArg // used for checking that reaction values pass the pattern-matching, before running the reaction - private[jc] final case class UnapplyRun(moleculeValues: LinearMoleculeBag) extends UnapplyArg // used for running the reaction - - /** Type alias for reaction body. - * - */ - private[jc] type ReactionBody = PartialFunction[UnapplyArg, Any] - - def site(reactions: Reaction*): WarningsAndErrors = site(defaultReactionPool, defaultSitePool)(reactions: _*) - def site(reactionPool: Pool)(reactions: Reaction*): WarningsAndErrors = site(reactionPool, reactionPool)(reactions: _*) - - /** Create a reaction site with one or more reactions. - * All input and output molecules in reactions used in this site should have been - * already defined, and input molecules should not be already bound to another site. - * - * @param reactions One or more reactions of type [[JoinRun#Reaction]] - * @param reactionPool Thread pool for running new reactions. - * @param sitePool Thread pool for use when making decisions to schedule reactions. - * @return List of warning messages. - */ - def site(reactionPool: Pool, sitePool: Pool)(reactions: Reaction*): WarningsAndErrors = { - - // Create a reaction site object holding the given local chemistry. - // The constructor of ReactionSite will perform static analysis of all given reactions. - new ReactionSite(reactions, reactionPool, sitePool).diagnostics - - } - - private val errorLog: ConcurrentLinkedQueue[String] = new ConcurrentLinkedQueue[String]() - - private[jc] def reportError(message: String): Unit = { - errorLog.add(message) - () - } - - def errors: Iterable[String] = errorLog.iterator().asScala.toIterable - -} diff --git a/lib/src/main/scala/code/winitzki/jc/JoinRunUtils.scala b/lib/src/main/scala/code/winitzki/jc/JoinRunUtils.scala deleted file mode 100644 index 36d070c5..00000000 --- a/lib/src/main/scala/code/winitzki/jc/JoinRunUtils.scala +++ /dev/null @@ -1,43 +0,0 @@ -package code.winitzki.jc - -private[jc] object JoinRunUtils { - - private lazy val sha1Digest = java.security.MessageDigest.getInstance("SHA-1") - - def getSha1(c: Any): String = sha1Digest.digest(c.toString.getBytes("UTF-8")).map("%02X".format(_)).mkString - -// def flatten[T](optionSet: Option[Set[T]]): Set[T] = optionSet.getOrElse(Set()) -// def flatten[T](optionSeq: Option[Seq[T]]): Seq[T] = optionSeq.getOrElse(Seq()) - - trait PersistentHashCode { - // Make hash code persistent across mutations with this simple trick. - private lazy val hashCodeValue: Int = super.hashCode() - override def hashCode(): Int = hashCodeValue - } - - /** Add a random shuffle method to sequences. - * - * @param a Sequence to be shuffled. - * @tparam T Type of sequence elements. - */ - implicit final class ShufflableSeq[T](a: Seq[T]) { - /** Shuffle sequence elements randomly. - * - * @return A new sequence with randomly permuted elements. - */ - def shuffle: Seq[T] = scala.util.Random.shuffle(a) - } - - def nonemptyOpt[S](s: Seq[S]): Option[Seq[S]] = if (s.isEmpty) None else Some(s) - - @SuppressWarnings(Array("org.wartremover.warts.Equals")) - implicit final class AnyOpsEquals[A](self: A) { - def ===(other: A): Boolean = self == other - } - - @SuppressWarnings(Array("org.wartremover.warts.Equals")) - implicit final class AnyOpsNotEquals[A](self: A) { - def =!=(other: A): Boolean = self != other - } - -} diff --git a/lib/src/main/scala/code/winitzki/jc/Molecules.scala b/lib/src/main/scala/code/winitzki/jc/Molecules.scala new file mode 100644 index 00000000..dc2918fd --- /dev/null +++ b/lib/src/main/scala/code/winitzki/jc/Molecules.scala @@ -0,0 +1,486 @@ +package code.winitzki.jc + +/* +Join Calculus (JC) is a micro-framework for declarative concurrency. + +JC is basically “Actors” made type-safe, stateless, and more high-level. + +The code is inspired by previous implementations by He Jiansen (https://github.com/Jiansen/ScalaJoin, 2011) +and Philipp Haller (http://lampwww.epfl.ch/~phaller/joins/index.html, 2008). + * */ + +import java.util.concurrent.{Semaphore, TimeUnit} + +import scala.collection.mutable +import scala.concurrent.duration.Duration + +sealed trait InputPatternType + +case object Wildcard extends InputPatternType + +case object SimpleVar extends InputPatternType + +final case class SimpleConst(v: Any) extends InputPatternType + +final case class OtherInputPattern(matcher: PartialFunction[Any, Unit]) extends InputPatternType + +case object UnknownInputPattern extends InputPatternType + +sealed trait OutputPatternType + +final case class ConstOutputValue(v: Any) extends OutputPatternType + +case object OtherOutputPattern extends OutputPatternType + +sealed trait GuardPresenceType { + def knownFalse: Boolean = this match { + case GuardAbsent => true + case _ => false + } +} + +case object GuardPresent extends GuardPresenceType + +case object GuardAbsent extends GuardPresenceType + +case object GuardPresenceUnknown extends GuardPresenceType + +/** Compile-time information about an input molecule pattern in a reaction. + * This class is immutable. + * + * @param molecule The molecule emitter value that represents the input molecule. + * @param flag Type of the input pattern: wildcard, constant match, etc. + * @param sha1 Hash sum of the source code (AST tree) of the input pattern. + */ +final case class InputMoleculeInfo(molecule: Molecule, flag: InputPatternType, sha1: String) { + /** Determine whether this input molecule pattern is weaker than another pattern. + * Pattern a(xxx) is weaker than b(yyy) if a==b and if anything matched by yyy will also be matched by xxx. + * + * @param info The input molecule info for another input molecule. + * @return Some(true) if we can surely determine that this matcher is weaker than another; + * Some(false) if we can surely determine that this matcher is not weaker than another; + * None if we cannot determine anything because information is insufficient. + */ + private[jc] def matcherIsWeakerThan(info: InputMoleculeInfo): Option[Boolean] = { + if (molecule =!= info.molecule) Some(false) + else flag match { + case Wildcard | SimpleVar => Some(true) + case OtherInputPattern(matcher1) => info.flag match { + case SimpleConst(c) => Some(matcher1.isDefinedAt(c)) + case OtherInputPattern(_) => if (sha1 === info.sha1) Some(true) else None // We can reliably determine identical matchers. + case _ => Some(false) // Here we can reliably determine that this matcher is not weaker. + } + case SimpleConst(c) => Some(info.flag match { + case SimpleConst(`c`) => true + case _ => false + }) + case _ => Some(false) + } + } + + private[jc] def matcherIsWeakerThanOutput(info: OutputMoleculeInfo): Option[Boolean] = { + if (molecule =!= info.molecule) Some(false) + else flag match { + case Wildcard | SimpleVar => Some(true) + case OtherInputPattern(matcher1) => info.flag match { + case ConstOutputValue(c) => Some(matcher1.isDefinedAt(c)) + case _ => None // Here we can't reliably determine whether this matcher is weaker. + } + case SimpleConst(c) => info.flag match { + case ConstOutputValue(`c`) => Some(true) + case ConstOutputValue(_) => Some(false) // definitely not the same constant + case _ => None // Otherwise, it could be this constant but we can't determine. + } + case _ => Some(false) + } + } + + // Here "similar" means either it's definitely weaker or it could be weaker (but it is definitely not stronger). + private[jc] def matcherIsSimilarToOutput(info: OutputMoleculeInfo): Option[Boolean] = { + if (molecule =!= info.molecule) Some(false) + else flag match { + case Wildcard | SimpleVar => Some(true) + case OtherInputPattern(matcher1) => info.flag match { + case ConstOutputValue(c) => Some(matcher1.isDefinedAt(c)) + case _ => Some(true) // Here we can't reliably determine whether this matcher is weaker, but it's similar (i.e. could be weaker). + } + case SimpleConst(c) => Some(info.flag match { + case ConstOutputValue(`c`) => true + case ConstOutputValue(_) => false // definitely not the same constant + case _ => true // Otherwise, it could be this constant. + }) + case UnknownInputPattern => Some(true) // pattern unknown - could be weaker. + } + } + + override def toString: String = { + val printedPattern = flag match { + case Wildcard => "_" + case SimpleVar => "." + case SimpleConst(c) => c.toString + case OtherInputPattern(_) => s"<${sha1.substring(0, 4)}...>" + case UnknownInputPattern => s"?" + } + + s"$molecule($printedPattern)" + } + +} + +/** Compile-time information about an output molecule pattern in a reaction. + * This class is immutable. + * + * @param molecule The molecule emitter value that represents the output molecule. + * @param flag Type of the output pattern: either a constant value or other value. + */ +final case class OutputMoleculeInfo(molecule: Molecule, flag: OutputPatternType) { + override def toString: String = { + val printedPattern = flag match { + case ConstOutputValue(()) => "" + case ConstOutputValue(c) => c.toString + case OtherOutputPattern => "?" + } + + s"$molecule($printedPattern)" + } +} + +// This class is immutable. +final case class ReactionInfo(inputs: List[InputMoleculeInfo], outputs: Option[List[OutputMoleculeInfo]], hasGuard: GuardPresenceType, sha1: String) { + + // The input pattern sequence is pre-sorted for further use. + private[jc] val inputsSorted: List[InputMoleculeInfo] = inputs.sortBy { case InputMoleculeInfo(mol, flag, sha) => + // wildcard and simplevars are sorted together; more specific matchers must precede less specific matchers + val patternPrecedence = flag match { + case Wildcard | SimpleVar => 3 + case OtherInputPattern(_) => 2 + case SimpleConst(_) => 1 + case _ => 0 + } + (mol.toString, patternPrecedence, sha) + } + + override val toString: String = s"${inputsSorted.map(_.toString).mkString(" + ")}${hasGuard match { + case GuardAbsent => "" + case GuardPresent => " if(...)" + case GuardPresenceUnknown => " ?" + }} => ${outputs match { + case Some(outputMoleculeInfos) => outputMoleculeInfos.map(_.toString).mkString(" + ") + case None => "?" + }}" +} + +/** + * Convenience syntax: users can write a(x)+b(y) in reaction patterns. + * Pattern-matching can be extended to molecule values as well, for example + * {{{ { case a(MyCaseClass(x,y)) + b(Some(z)) => ... } }}} + * + * @return an unapply operation + */ +object + { + def unapply(attr:Any): Option[(Any, Any)] = Some((attr,attr)) +} + +/** Represents a reaction body. This class is immutable. + * + * @param body Partial function of type {{{ UnapplyArg => Unit }}} + * @param threadPool Thread pool on which this reaction will be scheduled. (By default, the common pool is used.) + * @param retry Whether the reaction should be run again when an exception occurs in its body. Default is false. + */ +final case class Reaction(info: ReactionInfo, body: ReactionBody, threadPool: Option[Pool] = None, retry: Boolean) { + + /** Convenience method to specify thread pools per reaction. + * + * Example: go { case a(x) => ... } onThreads threadPool24 + * + * @param newThreadPool A custom thread pool on which this reaction will be scheduled. + * @return New reaction value with the thread pool set. + */ + def onThreads(newThreadPool: Pool): Reaction = Reaction(info, body, Some(newThreadPool), retry) + + /** Convenience method to specify the "retry" option for a reaction. + * + * @return New reaction value with the "retry" flag set. + */ + def withRetry: Reaction = Reaction(info, body, threadPool, retry = true) + + /** Convenience method to specify the "no retry" option for a reaction. + * (This option is the default.) + * + * @return New reaction value with the "retry" flag unset. + */ + def noRetry: Reaction = Reaction(info, body, threadPool, retry = false) + + // Optimization: this is used often. + val inputMolecules: Seq[Molecule] = info.inputs.map(_.molecule).sortBy(_.toString) + + /** Convenience method for debugging. + * + * @return String representation of input molecules of the reaction. + */ + override val toString: String = s"${inputMolecules.map(_.toString).mkString(" + ")} => ...${if (retry) + "/R" else ""}" +} + +// Abstract container for molecule values. +private[jc] sealed trait AbsMolValue[T] { + def getValue: T + + override def toString: String = getValue match { case () => ""; case v@_ => v.asInstanceOf[T].toString } +} + +/** Container for the value of a non-blocking molecule. + * + * @param v The value of type T carried by the molecule. + * @tparam T The type of the value. + */ +private[jc] final case class MolValue[T](v: T) extends AbsMolValue[T] { + override def getValue: T = v +} + +/** Container for the value of a blocking molecule. + * + * @param v The value of type T carried by the molecule. + * @param replyValue The wrapper for the reply value, which will ultimately return a value of type R. + * @tparam T The type of the value carried by the molecule. + * @tparam R The type of the reply value. + */ +private[jc] final case class BlockingMolValue[T,R](v: T, replyValue: ReplyValue[T,R]) extends AbsMolValue[T] with PersistentHashCode { + override def getValue: T = v +} + +/** Abstract molecule emitter class. + * This class is not parameterized b type and is used in collections of molecules that do not require knowledge of molecule types. + * + */ +sealed trait Molecule extends PersistentHashCode { + + val name: String + + override def toString: String = (if (name.isEmpty) "" else name) + (if (isBlocking) "/B" else "") + + /** Check whether the molecule is already bound to a reaction site. + * Note that molecules can be emitted only if they are bound. + * + * @return True if already bound, false otherwise. + */ + def isBound: Boolean = reactionSiteOpt.nonEmpty + + @volatile private[jc] var reactionSiteOpt: Option[ReactionSite] = None + + private[jc] def site: ReactionSite = + reactionSiteOpt.getOrElse(throw new ExceptionNoReactionSite(s"Molecule ${this} is not bound to any reaction site")) + + /** The set of reactions that can consume this molecule. + * + * @return {{{None}}} if the molecule emitter is not yet bound to any reaction site. + */ + private[jc] def consumingReactions: Option[Set[Reaction]] = reactionSiteOpt.map(_ => consumingReactionsSet) + + private lazy val consumingReactionsSet: Set[Reaction] = reactionSiteOpt.get.reactionInfos.keys.filter(_.inputMolecules contains this).toSet + + /** The set of all reactions that *potentially* emit this molecule as output. + * Some of these reactions may evaluate a runtime condition to decide whether to emit the molecule; so emission is not guaranteed. + * + * Note that these reactions may be defined in any reaction sites, not necessarily at the site to which this molecule is bound. + * The set of these reactions may change at run time if new reaction sites are written that output this molecule. + * + * @return Empty set if the molecule is not yet bound to any reaction site. + */ + private[jc] def emittingReactions: Set[Reaction] = emittingReactionsSet.toSet + + private[jc] val emittingReactionsSet: mutable.Set[Reaction] = mutable.Set() + + def setLogLevel(logLevel: Int): Unit = + site.logLevel = logLevel + + def logSoup: String = site.printBag + + def isBlocking: Boolean + + def isSingleton: Boolean = false +} + +/** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. + * + * @param name Name of the molecule, used for debugging only. + * @tparam T Type of the value carried by the molecule. + */ +final class M[T](val name: String) extends (T => Unit) with Molecule { + /** Emit a non-blocking molecule. + * + * @param v Value to be put onto the emitted molecule. + */ + def apply(v: T): Unit = site.emit[T](this, MolValue(v)) + + def unapply(arg: UnapplyArg): Option[T] = arg match { + + case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go + inputMoleculesProbe += this + Some(null.asInstanceOf[T]) // hack for testing only. This value will not be used. + + // When we are gathering information about the input molecules, `unapply` will always return Some(...), + // so that any pattern-matching on arguments will continue with null (since, at this point, we have no values). + // Any pattern-matching will work unless null fails. + // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. + // We also gather the information about the molecule values actually used by the reaction, in case the reaction can start. + case UnapplyRunCheck(moleculeValues, usedInputs) => + for { + v <- moleculeValues.getOne(this) + } yield { + usedInputs += (this -> v) + v.asInstanceOf[AbsMolValue[T]].getValue + } + + // This is used when running the chosen reaction. + case UnapplyRun(moleculeValues) => moleculeValues.get(this) + .map(_.asInstanceOf[MolValue[T]].getValue) + } + + /** Volatile reader for a molecule. + * The molecule must be declared as a singleton. + * + * @return The value carried by the singleton when it was last emitted. Will throw exception if the singleton has not yet been emitted. + */ + def volatileValue: T = site.getVolatileValue(this) + + def hasVolatileValue: Boolean = site.hasVolatileValue(this) + + @volatile private[jc] var isSingletonBoolean = false + + override def isSingleton: Boolean = isSingletonBoolean + + override def isBlocking = false +} + +/** Reply-value wrapper for blocking molecules. This is a mutable class. + * + * @param molecule The blocking molecule whose reply value this wrapper represents. + * result Reply value as {{{Option[R]}}}. Initially this is None, and it may be assigned at most once by the + * "reply" action if the reply is "valid" (i.e. not timed out). + * semaphore Mutable semaphore reference. This is initialized only once when creating an instance of this + * class. The semaphore will be acquired when emitting the molecule and released by the "reply" + * action. The semaphore will be destroyed and never initialized again once a reply is received. + * errorMessage Optional error message, to notify the caller or to raise an exception when the user made a + * mistake in chemistry. + * replyTimeout Will be set to "true" if the molecule was emitted with a timeout and the timeout was reached. + * replyRepeated Will be set to "true" if the molecule received a reply more than once. + * @tparam T Type of the value carried by the molecule. + * @tparam R Type of the value replied to the caller via the "reply" action. + */ +private[jc] final class ReplyValue[T,R] (molecule: B[T,R]) extends (R => Boolean) { + + @volatile var result: Option[R] = None + + @volatile private var semaphore: Semaphore = { + val s = new Semaphore(0, false); s.drainPermits(); s + } + + @volatile var errorMessage: Option[String] = None + + @volatile var replyTimeout: Boolean = false + + @volatile var replyRepeated: Boolean = false + + private[jc] def releaseSemaphore(): Unit = synchronized { + if (Option(semaphore).isDefined) semaphore.release() + } + + private[jc] def deleteSemaphore(): Unit = synchronized { + semaphore = null + } + + private[jc] def acquireSemaphore(timeoutNanos: Option[Long]): Boolean = + if (Option(semaphore).isDefined) + timeoutNanos match { + case Some(nanos) => semaphore.tryAcquire(nanos, TimeUnit.NANOSECONDS) + case None => semaphore.acquire(); true + } + else false + + /** Perform a reply action for a blocking molecule. + * For each blocking molecule consumed by a reaction, exactly one reply action should be performed within the reaction body. + * + * @param x Value to reply with. + * @return True if the reply was successful. False if the blocking molecule timed out, or if a reply action was already performed. + */ + def apply(x: R): Boolean = synchronized { + // The reply value will be assigned only if there was no timeout and no previous reply action. + if (!replyTimeout && !replyRepeated && result.isEmpty) { + result = Some(x) + } else if (!replyTimeout && result.nonEmpty) { + replyRepeated = true + } + + releaseSemaphore() + + !replyTimeout && !replyRepeated + } +} + +/** Blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. + * + * @param name Name of the molecule, used for debugging only. + * @tparam T Type of the value carried by the molecule. + * @tparam R Type of the value replied to the caller via the "reply" action. + */ +final class B[T, R](val name: String) extends (T => R) with Molecule { + + /** Emit a blocking molecule and receive a value when the reply action is performed. + * + * @param v Value to be put onto the emitted molecule. + * @return The "reply" value. + */ + def apply(v: T): R = + site.emitAndReply[T,R](this, v, new ReplyValue[T,R](molecule = this)) + + /** Emit a blocking molecule and receive a value when the reply action is performed, unless a timeout is reached. + * + * @param timeout Timeout in any time interval. + * @param v Value to be put onto the emitted molecule. + * @return Non-empty option if the reply was received; None on timeout. + */ + def timeout(timeout: Duration)(v: T): Option[R] = + site.emitAndReplyWithTimeout[T,R](timeout.toNanos, this, v, new ReplyValue[T,R](molecule = this)) + + def unapply(arg: UnapplyArg): Option[(T, ReplyValue[T,R])] = arg match { + + case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go + inputMoleculesProbe += this + Some((null, null).asInstanceOf[(T, ReplyValue[T,R])]) // hack for testing purposes only: + // The null value will not be used in any production code since _go is private. + + // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. + // We also gather the information about the molecule values actually used by the reaction, in case the reaction can start. + case UnapplyRunCheck(moleculeValues, usedInputs) => + for { + v <- moleculeValues.getOne(this) + } yield { + usedInputs += (this -> v) + (v.getValue, null).asInstanceOf[(T, ReplyValue[T,R])] // hack for verifying isDefinedAt: + // The null value will not be used, since the reply value is always matched unconditionally. + } + + // This is used when running the chosen reaction. + case UnapplyRun(moleculeValues) => moleculeValues.get(this).map { + case BlockingMolValue(v, srv) => (v, srv).asInstanceOf[(T, ReplyValue[T,R])] + case m@_ => + throw new ExceptionNoWrapper(s"Internal error: molecule $this with no value wrapper around value $m") + } + } + + override def isBlocking = true +} + +private[jc] sealed trait UnapplyArg // The disjoint union type for arguments passed to the unapply methods. +private[jc] final case class UnapplyCheckSimple(inputMolecules: mutable.MutableList[Molecule]) extends UnapplyArg // used only for `_go` and in tests +private[jc] final case class UnapplyRunCheck(moleculeValues: MoleculeBag, usedInputs: MutableLinearMoleculeBag) extends UnapplyArg // used for checking that reaction values pass the pattern-matching, before running the reaction +private[jc] final case class UnapplyRun(moleculeValues: LinearMoleculeBag) extends UnapplyArg // used for running the reaction + +/** Mix this trait into your class to make the has code persistent after the first time it's computed. + * + */ +trait PersistentHashCode { + private lazy val hashCodeValue: Int = super.hashCode() + override def hashCode(): Int = hashCodeValue +} diff --git a/lib/src/main/scala/code/winitzki/jc/Pool.scala b/lib/src/main/scala/code/winitzki/jc/Pool.scala index f5ae802f..8d195e42 100644 --- a/lib/src/main/scala/code/winitzki/jc/Pool.scala +++ b/lib/src/main/scala/code/winitzki/jc/Pool.scala @@ -3,8 +3,6 @@ package code.winitzki.jc import java.util.concurrent._ -import code.winitzki.jc.JoinRun.ReactionInfo - import scala.concurrent.{ExecutionContext, Future} import scala.language.reflectiveCalls @@ -29,7 +27,7 @@ trait Pool { } private[jc] class PoolExecutor(threads: Int = 8, execFactory: Int => ExecutorService) extends Pool { - protected val execService = execFactory(threads) + protected val execService: ExecutorService = execFactory(threads) val sleepTime = 200L diff --git a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala index fdda688f..b90a2993 100644 --- a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala +++ b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala @@ -1,7 +1,5 @@ package code.winitzki.jc -import code.winitzki.jc.JoinRun._ -import code.winitzki.jc.JoinRunUtils._ import java.util.concurrent.{ConcurrentHashMap, ConcurrentMap} @@ -16,7 +14,7 @@ import collection.mutable * @param reactionPool The thread pool on which reactions will be scheduled. * @param sitePool The thread pool on which the reaction site will decide reactions and manage the molecule bag. */ -private final class ReactionSite(reactions: Seq[Reaction], reactionPool: Pool, sitePool: Pool) { +private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Pool, sitePool: Pool) { private val (nonSingletonReactions, singletonReactions) = reactions.partition(_.inputMolecules.nonEmpty) @@ -423,3 +421,15 @@ final case class WarningsAndErrors(warnings: Seq[String], errors: Seq[String], j def ++(other: WarningsAndErrors): WarningsAndErrors = WarningsAndErrors(warnings ++ other.warnings, errors ++ other.errors, joinDef) } + + +private[jc] sealed class ExceptionInJoinRun(message: String) extends Exception(message) +private[jc] final class ExceptionNoReactionSite(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionMoleculeAlreadyBound(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionNoSitePool(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionEmittingSingleton(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionNoReactionPool(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionNoWrapper(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionWrongInputs(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionEmptyReply(message: String) extends ExceptionInJoinRun(message) +private[jc] final class ExceptionNoSingleton(message: String) extends ExceptionInJoinRun(message) diff --git a/lib/src/main/scala/code/winitzki/jc/SmartPool.scala b/lib/src/main/scala/code/winitzki/jc/SmartPool.scala index d47ae049..466c2bb6 100644 --- a/lib/src/main/scala/code/winitzki/jc/SmartPool.scala +++ b/lib/src/main/scala/code/winitzki/jc/SmartPool.scala @@ -2,8 +2,6 @@ package code.winitzki.jc import java.util.concurrent._ -import code.winitzki.jc.JoinRun.ReactionInfo - /** This is similar to scala.concurrent.blocking and is used to annotate expressions that should lead to a possible increase of thread count. * Multiple nested calls to {{{BlockingIdle}}} are equivalent to one call. */ @@ -45,7 +43,7 @@ class SmartPool(parallelism: Int) extends Pool { val maxQueueCapacity: Int = parallelism*1000 + 100 - private val queue = new LinkedBlockingQueue[Runnable](maxQueueCapacity) + private val queue = new ArrayBlockingQueue[Runnable](maxQueueCapacity) val initialThreads: Int = parallelism val secondsToRecycleThread = 1L diff --git a/lib/src/main/scala/code/winitzki/jc/SmartThread.scala b/lib/src/main/scala/code/winitzki/jc/SmartThread.scala index 82d07611..3ed2d240 100644 --- a/lib/src/main/scala/code/winitzki/jc/SmartThread.scala +++ b/lib/src/main/scala/code/winitzki/jc/SmartThread.scala @@ -2,9 +2,6 @@ package code.winitzki.jc import java.util.concurrent.ThreadFactory -import code.winitzki.jc.JoinRun.ReactionInfo - - class SmartThread(runnable: Runnable, pool: SmartPool) extends ThreadWithInfo(runnable) { private var inBlockingCall: Boolean = false diff --git a/lib/src/main/scala/code/winitzki/jc/StaticAnalysis.scala b/lib/src/main/scala/code/winitzki/jc/StaticAnalysis.scala index 645168c4..fa79de9f 100644 --- a/lib/src/main/scala/code/winitzki/jc/StaticAnalysis.scala +++ b/lib/src/main/scala/code/winitzki/jc/StaticAnalysis.scala @@ -1,8 +1,5 @@ package code.winitzki.jc -import code.winitzki.jc.JoinRun._ -import JoinRunUtils._ - import scala.annotation.tailrec diff --git a/lib/src/main/scala/code/winitzki/jc/package.scala b/lib/src/main/scala/code/winitzki/jc/package.scala new file mode 100644 index 00000000..b9d7b452 --- /dev/null +++ b/lib/src/main/scala/code/winitzki/jc/package.scala @@ -0,0 +1,149 @@ +package code.winitzki + +import java.util.UUID +import java.util.concurrent.ConcurrentLinkedQueue + +import scala.collection.JavaConverters._ +import scala.collection.mutable + +package object jc { + + /** A special value for {{{ReactionInfo}}} to signal that we are not running a reaction. + * + */ + private[jc] val emptyReactionInfo = ReactionInfo(Nil, None, GuardPresenceUnknown, "") + + private lazy val sha1Digest = java.security.MessageDigest.getInstance("SHA-1") + + def getSha1(c: Any): String = sha1Digest.digest(c.toString.getBytes("UTF-8")).map("%02X".format(_)).mkString + + // def flatten[T](optionSet: Option[Set[T]]): Set[T] = optionSet.getOrElse(Set()) + // def flatten[T](optionSeq: Option[Seq[T]]): Seq[T] = optionSeq.getOrElse(Seq()) + + + def nonemptyOpt[S](s: Seq[S]): Option[Seq[S]] = if (s.isEmpty) None else Some(s) + + + /** Add a random shuffle method to sequences. + * + * @param a Sequence to be shuffled. + * @tparam T Type of sequence elements. + */ + implicit final class ShufflableSeq[T](a: Seq[T]) { + /** Shuffle sequence elements randomly. + * + * @return A new sequence with randomly permuted elements. + */ + def shuffle: Seq[T] = scala.util.Random.shuffle(a) + } + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + implicit final class AnyOpsEquals[A](self: A) { + def ===(other: A): Boolean = self == other + } + + @SuppressWarnings(Array("org.wartremover.warts.Equals")) + implicit final class AnyOpsNotEquals[A](self: A) { + def =!=(other: A): Boolean = self != other + } + + + val defaultSitePool = new FixedPool(2) + val defaultReactionPool = new FixedPool(4) + + + // Wait until the reaction site to which `molecule` is bound becomes quiescent, then emit `callback`. + // TODO: implement + def waitUntilQuiet[T](molecule: M[T], callback: M[Unit]): Unit = molecule.site.setQuiescenceCallback(callback) + + /** Create a reaction value out of a simple reaction body. Used only for testing. + * The reaction body must be "simple" in the sense that it allows very limited pattern-matching with molecule values: + * - all patterns must be simple variables or wildcards, or {{{null}}} or zero constant values, except the last molecule in the reaction. + * - the last molecule in the reaction can have a nontrivial pattern matcher. + * + * The only reason this method exists is to enable testing JoinRun without depending on the macro package. + * Since this method does not provide a full compile-time analysis of reactions, it should be used only for internal testing and debugging of JoinRun itself. + * At the moment, this method is used in benchmarks and unit tests of JoinRun that run without depending on the macro package. + * + * @param body Body of the reaction. Should not contain any pattern-matching on molecule values, except possibly for the last molecule in the list of input molecules. + * @return Reaction value. The [[ReactionInfo]] structure will be filled out in a minimal fashion (only has information about input molecules, and all patterns are "unknown"). + */ + private[jc] def _go(body: ReactionBody): Reaction = { + val moleculesInThisReaction = UnapplyCheckSimple(mutable.MutableList.empty) + body.isDefinedAt(moleculesInThisReaction) + // detect nonlinear patterns + val duplicateMolecules = moleculesInThisReaction.inputMolecules diff moleculesInThisReaction.inputMolecules.distinct + if (duplicateMolecules.nonEmpty) throw new ExceptionInJoinRun(s"Nonlinear pattern: ${duplicateMolecules.mkString(", ")} used twice") + val inputMoleculesUsed = moleculesInThisReaction.inputMolecules.toList + val inputMoleculeInfo = inputMoleculesUsed.map(m => InputMoleculeInfo(m, UnknownInputPattern, UUID.randomUUID().toString)) + val simpleInfo = ReactionInfo(inputMoleculeInfo, None, GuardPresenceUnknown, UUID.randomUUID().toString) + Reaction(simpleInfo, body, retry = false) + } + + /** + * Convenience syntax: users can write a(x)+b(y) to emit several molecules at once. + * (However, the molecules are emitted one by one in the present implementation.) + * + * @param x the first emitted molecule + * @return a class with a + operator + */ + implicit final class EmitMultiple(x: Unit) { + def +(n: Unit): Unit = () + } + + /** Type alias for reaction body. + * + */ + private[jc] type ReactionBody = PartialFunction[UnapplyArg, Any] + + // for M[T] molecules, the value inside AbsMolValue[T] is of type T; for B[T,R] molecules, the value is of type + // ReplyValue[T,R]. For now, we don't use shapeless to enforce this typing relation. + private[jc] type MoleculeBag = MutableBag[Molecule, AbsMolValue[_]] + private[jc] type MutableLinearMoleculeBag = mutable.Map[Molecule, AbsMolValue[_]] + private[jc] type LinearMoleculeBag = Map[Molecule, AbsMolValue[_]] + + private[jc] def moleculeBagToString(mb: MoleculeBag): String = + mb.getMap.toSeq + .map{ case (m, vs) => (m.toString, vs) } + .sortBy(_._1) + .flatMap { + case (m, vs) => vs.map { + case (mv, 1) => s"$m($mv)" + case (mv, i) => s"$m($mv) * $i" + } + }.mkString(", ") + + private[jc] def moleculeBagToString(mb: LinearMoleculeBag): String = + mb.map { + case (m, jmv) => s"$m($jmv)" + }.mkString(", ") + + def site(reactions: Reaction*): WarningsAndErrors = site(defaultReactionPool, defaultSitePool)(reactions: _*) + def site(reactionPool: Pool)(reactions: Reaction*): WarningsAndErrors = site(reactionPool, reactionPool)(reactions: _*) + + /** Create a reaction site with one or more reactions. + * All input and output molecules in reactions used in this site should have been + * already defined, and input molecules should not be already bound to another site. + * + * @param reactions One or more reactions of type [[Reaction]] + * @param reactionPool Thread pool for running new reactions. + * @param sitePool Thread pool for use when making decisions to schedule reactions. + * @return List of warning messages. + */ + def site(reactionPool: Pool, sitePool: Pool)(reactions: Reaction*): WarningsAndErrors = { + + // Create a reaction site object holding the given local chemistry. + // The constructor of ReactionSite will perform static analysis of all given reactions. + new ReactionSite(reactions, reactionPool, sitePool).diagnostics + + } + + private val errorLog: ConcurrentLinkedQueue[String] = new ConcurrentLinkedQueue[String]() + + private[jc] def reportError(message: String): Unit = { + errorLog.add(message) + () + } + + def globalErrorLog: Iterable[String] = errorLog.iterator().asScala.toIterable + +} diff --git a/lib/src/test/scala/code/winitzki/jc/JoinRunBlockingSpec.scala b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala similarity index 98% rename from lib/src/test/scala/code/winitzki/jc/JoinRunBlockingSpec.scala rename to lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala index 70b19d63..ae724e97 100644 --- a/lib/src/test/scala/code/winitzki/jc/JoinRunBlockingSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.jc -import JoinRun._ -import Library.withPool +import Chymyst.withPool import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.time.{Millis, Span} import org.scalatest.{BeforeAndAfterEach, FlatSpec, Matchers} @@ -12,7 +11,7 @@ import scala.concurrent.duration.DurationInt /** More unit tests for blocking molecule functionality. * */ -class JoinRunBlockingSpec extends FlatSpec with Matchers with TimeLimitedTests with BeforeAndAfterEach { +class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with BeforeAndAfterEach { var tp0: Pool = _ diff --git a/lib/src/test/scala/code/winitzki/jc/LibrarySpec.scala b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala similarity index 96% rename from lib/src/test/scala/code/winitzki/jc/LibrarySpec.scala rename to lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala index 682676c9..a667072e 100644 --- a/lib/src/test/scala/code/winitzki/jc/LibrarySpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.jc -import JoinRun._ -import Library._ +import Chymyst._ import org.scalactic.source.Position import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.{FlatSpec, Matchers} @@ -11,7 +10,7 @@ import org.scalatest.time.{Millis, Span} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future -class LibrarySpec extends FlatSpec with Matchers with TimeLimitedTests { +class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { val timeLimit = Span(2000, Millis) diff --git a/lib/src/test/scala/code/winitzki/jc/JoinRunSpec.scala b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala similarity index 97% rename from lib/src/test/scala/code/winitzki/jc/JoinRunSpec.scala rename to lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala index ac4e46a2..ca3bf707 100644 --- a/lib/src/test/scala/code/winitzki/jc/JoinRunSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala @@ -1,6 +1,5 @@ package code.winitzki.jc -import JoinRun._ import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.concurrent.Waiters.{PatienceConfig, Waiter} import org.scalatest.time.{Millis, Span} @@ -9,7 +8,7 @@ import org.scalatest.{BeforeAndAfterEach, FlatSpec, Matchers} import scala.concurrent.duration._ import scala.language.postfixOps -class JoinRunSpec extends FlatSpec with Matchers with TimeLimitedTests with BeforeAndAfterEach { +class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with BeforeAndAfterEach { var tp0: Pool = _ @@ -374,12 +373,12 @@ class JoinRunSpec extends FlatSpec with Matchers with TimeLimitedTests with Befo c(n) (1 to n).foreach { _ => if (d.timeout(1500 millis)().isEmpty) { - println(JoinRun.errors.toList) // this should not happen, but will be helpful for debugging + println(globalErrorLog.toList) // this should not happen, but will be helpful for debugging } } val result = g.timeout(1500 millis)() - JoinRun.errors.exists(_.contains("Message: crash! (it's OK, ignore this)")) + globalErrorLog.exists(_.contains("Message: crash! (it's OK, ignore this)")) tp.shutdownNow() result shouldEqual Some(()) } diff --git a/lib/src/test/scala/code/winitzki/jc/JoinRunUtilsSpec.scala b/lib/src/test/scala/code/winitzki/jc/PackageSpec.scala similarity index 91% rename from lib/src/test/scala/code/winitzki/jc/JoinRunUtilsSpec.scala rename to lib/src/test/scala/code/winitzki/jc/PackageSpec.scala index 56041520..c1b080df 100644 --- a/lib/src/test/scala/code/winitzki/jc/JoinRunUtilsSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/PackageSpec.scala @@ -1,11 +1,10 @@ package code.winitzki.jc -import JoinRunUtils._ import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.{FlatSpec, Matchers} import org.scalatest.time.{Millis, Span} -class JoinRunUtilsSpec extends FlatSpec with Matchers with TimeLimitedTests { +class PackageSpec extends FlatSpec with Matchers with TimeLimitedTests { val timeLimit = Span(500, Millis) diff --git a/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala b/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala index 76feefe7..d5dddbd0 100644 --- a/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala @@ -1,6 +1,5 @@ package code.winitzki.jc -import code.winitzki.jc.JoinRun._ import org.scalatest.concurrent.TimeLimitedTests import org.scalatest.concurrent.Waiters.{PatienceConfig, Waiter} import org.scalatest.time.{Millis, Span} @@ -32,19 +31,19 @@ class PoolSpec extends FlatSpec with Matchers with TimeLimitedTests { } it should "run tasks on a thread with info, in fixed pool" in { - Library.withPool(new FixedPool(2))(checkPool).get shouldEqual () + Chymyst.withPool(new FixedPool(2))(checkPool).get shouldEqual () } it should "run tasks on a thread with info, in cached pool" in { - Library.withPool(new CachedPool(2))(checkPool).get shouldEqual () + Chymyst.withPool(new CachedPool(2))(checkPool).get shouldEqual () } it should "run tasks on a thread with info, in smart pool" in { - Library.withPool(new SmartPool(2))(checkPool).get shouldEqual () + Chymyst.withPool(new SmartPool(2))(checkPool).get shouldEqual () } it should "run reactions on a thread with reaction info" in { - Library.withPool(new FixedPool(2)){ tp => + Chymyst.withPool(new FixedPool(2)){ tp => val waiter = new Waiter val a = new M[Unit]("a") diff --git a/lib/src/test/scala/code/winitzki/jc/JoinRunUtilsSha1Props.scala b/lib/src/test/scala/code/winitzki/jc/Sha1Props.scala similarity index 71% rename from lib/src/test/scala/code/winitzki/jc/JoinRunUtilsSha1Props.scala rename to lib/src/test/scala/code/winitzki/jc/Sha1Props.scala index 303c1d39..57686d09 100644 --- a/lib/src/test/scala/code/winitzki/jc/JoinRunUtilsSha1Props.scala +++ b/lib/src/test/scala/code/winitzki/jc/Sha1Props.scala @@ -1,10 +1,9 @@ package code.winitzki.jc -import code.winitzki.jc.JoinRunUtils.getSha1 import org.scalacheck.Properties import org.scalacheck.Prop.forAll -object JoinRunUtilsSha1Specification extends Properties("JoinRunUtilsSha1") { +object Sha1Props extends Properties("JoinRunUtilsSha1") { property("invariantForLongOrString") = forAll { (a: Long) => getSha1(a) == getSha1(a.toString) } diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index 806f21af..2570957c 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -4,8 +4,6 @@ import scala.collection.mutable import scala.language.experimental.macros import scala.reflect.macros._ import scala.reflect.NameTransformer.LOCAL_SUFFIX_STRING -import JoinRun._ -import JoinRunUtils._ import scala.annotation.tailrec @@ -107,12 +105,12 @@ object Macros { /** * Users will define reactions using this function. * Examples: {{{ go { a(_) => ... } }}} - * {{{ go { a (_) => ...} onThreads threadPool }}} + * {{{ go { a (_) => ...}.withRetry onThreads threadPool }}} * * The macro also obtains statically checkable information about input and output molecules in the reaction. * * @param reactionBody The body of the reaction. This must be a partial function with pattern-matching on molecules. - * @return A reaction value, to be used later in [[JoinRun#join]]. + * @return A reaction value, to be used later in [[site]]. */ def go(reactionBody: ReactionBody): Reaction = macro buildReactionImpl @@ -221,8 +219,8 @@ object Macros { override def traverse(tree: Tree): Unit = { tree match { // avoid traversing nested reactions: check whether this subtree is a Reaction() value - case q"code.winitzki.jc.JoinRun.Reaction.apply($_,$_,$_,$_)" => () - case q"JoinRun.Reaction.apply($_,$_,$_,$_)" => () + case q"code.winitzki.jc.Reaction.apply($_,$_,$_,$_)" => () + case q"Reaction.apply($_,$_,$_,$_)" => () // matcher with a single argument: a(x) case UnApply(Apply(Select(t@Ident(TermName(_)), TermName("unapply")), List(Ident(TermName("")))), List(binder)) if t.tpe <:< typeOf[Molecule] => @@ -279,22 +277,22 @@ object Macros { // this boilerplate is necessary for being able to use PatternType values in macro quasiquotes implicit val liftablePatternFlag: c.universe.Liftable[InputPatternFlag] = Liftable[InputPatternFlag] { - case WildcardF => q"_root_.code.winitzki.jc.JoinRun.Wildcard" - case SimpleConstF(x) => q"_root_.code.winitzki.jc.JoinRun.SimpleConst(${x.asInstanceOf[c.Tree]})" - case SimpleVarF => q"_root_.code.winitzki.jc.JoinRun.SimpleVar" - case OtherPatternF(matcherTree) => q"_root_.code.winitzki.jc.JoinRun.OtherInputPattern(${matcherTree.asInstanceOf[c.Tree]})" - case _ => q"_root_.code.winitzki.jc.JoinRun.UnknownInputPattern" + case WildcardF => q"_root_.code.winitzki.jc.Wildcard" + case SimpleConstF(x) => q"_root_.code.winitzki.jc.SimpleConst(${x.asInstanceOf[c.Tree]})" + case SimpleVarF => q"_root_.code.winitzki.jc.SimpleVar" + case OtherPatternF(matcherTree) => q"_root_.code.winitzki.jc.OtherInputPattern(${matcherTree.asInstanceOf[c.Tree]})" + case _ => q"_root_.code.winitzki.jc.UnknownInputPattern" } implicit val liftableOutputPatternFlag: c.universe.Liftable[OutputPatternFlag] = Liftable[OutputPatternFlag] { - case ConstOutputPattern(x) => q"_root_.code.winitzki.jc.JoinRun.ConstOutputValue(${x.asInstanceOf[c.Tree]})" - case _ => q"_root_.code.winitzki.jc.JoinRun.OtherOutputPattern" + case ConstOutputPattern(x) => q"_root_.code.winitzki.jc.ConstOutputValue(${x.asInstanceOf[c.Tree]})" + case _ => q"_root_.code.winitzki.jc.OtherOutputPattern" } implicit val liftableGuardFlag: c.universe.Liftable[GuardPresenceType] = Liftable[GuardPresenceType] { - case GuardPresent => q"_root_.code.winitzki.jc.JoinRun.GuardPresent" - case GuardAbsent => q"_root_.code.winitzki.jc.JoinRun.GuardAbsent" - case GuardPresenceUnknown => q"_root_.code.winitzki.jc.JoinRun.GuardPresenceUnknown" + case GuardPresent => q"_root_.code.winitzki.jc.GuardPresent" + case GuardAbsent => q"_root_.code.winitzki.jc.GuardAbsent" + case GuardPresenceUnknown => q"_root_.code.winitzki.jc.GuardPresenceUnknown" } def maybeError[T](what: String, patternWhat: String, molecules: Seq[T], connector: String = "not contain a pattern that", method: (c.Position, String) => Unit = c.error) = { @@ -339,7 +337,7 @@ object Macros { if (patternIn.isEmpty && !isSingletonReaction(pattern, guard, body)) c.error(c.enclosingPosition, "Reaction should not have an empty list of input molecules") - val inputMolecules = patternIn.map { case (s, p, _, sha1) => q"InputMoleculeInfo(${s.asTerm}, $p, $sha1)" } + val inputMolecules = patternIn.map { case (s, p, _, patternSha1) => q"InputMoleculeInfo(${s.asTerm}, $p, $patternSha1)" } // Note: the output molecules could be sometimes not emitted according to a runtime condition. // We do not try to examine the reaction body to determine which output molecules are always emitted. diff --git a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala index fb4569e8..86e0a767 100644 --- a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala +++ b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala @@ -1,7 +1,6 @@ package code.winitzki.jc -import JoinRun._ -import Macros.{getName, rawTree, m,b, go} +import Macros.{getName, rawTree, m, b, go} import org.scalatest.{BeforeAndAfterEach, FlatSpec, Matchers} import scala.concurrent.duration.DurationInt From 79d0f47d959fcc1569ff202aedddabcf488ce28c Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 18:32:24 -0800 Subject: [PATCH 03/12] refactor to use new subclasses E and F --- .../code/winitzki/test/FairnessSpec.scala | 2 +- .../scala/code/winitzki/jc/Molecules.scala | 181 +++++++++++------- .../scala/code/winitzki/jc/ReactionSite.scala | 20 +- .../winitzki/jc/BlockingMoleculesSpec.scala | 118 ++++++------ .../scala/code/winitzki/jc/ChymystSpec.scala | 16 +- .../code/winitzki/jc/MoleculesSpec.scala | 90 ++++----- .../main/scala/code/winitzki/jc/Macros.scala | 50 +++-- .../scala/code/winitzki/jc/MacrosSpec.scala | 57 +++--- 8 files changed, 293 insertions(+), 241 deletions(-) diff --git a/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala b/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala index de716e72..cac7e51e 100644 --- a/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala @@ -141,7 +141,7 @@ class FairnessSpec extends FlatSpec with Matchers with TimeLimitedTests { val tp = new FixedPool(8) - def makeRS(d1: M[Unit], d2: M[Unit]): (M[Unit],M[Unit],M[Unit]) = { + def makeRS(d1: E, d2: E): (E,E,E) = { val a = m[Unit] val b = m[Unit] val c = m[Unit] diff --git a/lib/src/main/scala/code/winitzki/jc/Molecules.scala b/lib/src/main/scala/code/winitzki/jc/Molecules.scala index dc2918fd..6f6c8286 100644 --- a/lib/src/main/scala/code/winitzki/jc/Molecules.scala +++ b/lib/src/main/scala/code/winitzki/jc/Molecules.scala @@ -253,7 +253,7 @@ private[jc] final case class BlockingMolValue[T,R](v: T, replyValue: ReplyValue[ * This class is not parameterized b type and is used in collections of molecules that do not require knowledge of molecule types. * */ -sealed trait Molecule extends PersistentHashCode { +trait Molecule extends PersistentHashCode { val name: String @@ -296,22 +296,18 @@ sealed trait Molecule extends PersistentHashCode { def logSoup: String = site.printBag - def isBlocking: Boolean + val isBlocking: Boolean - def isSingleton: Boolean = false + def isSingleton: Boolean } -/** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. - * - * @param name Name of the molecule, used for debugging only. - * @tparam T Type of the value carried by the molecule. - */ -final class M[T](val name: String) extends (T => Unit) with Molecule { - /** Emit a non-blocking molecule. - * - * @param v Value to be put onto the emitted molecule. - */ - def apply(v: T): Unit = site.emit[T](this, MolValue(v)) +private[jc] trait NonblockingMolecule[T] extends Molecule { + + val isBlocking = false + + @volatile private[jc] var isSingletonBoolean = false + + override def isSingleton: Boolean = isSingletonBoolean def unapply(arg: UnapplyArg): Option[T] = arg match { @@ -337,6 +333,79 @@ final class M[T](val name: String) extends (T => Unit) with Molecule { .map(_.asInstanceOf[MolValue[T]].getValue) } +} +private[jc] trait BlockingMolecule[T, R] extends Molecule { + + val isBlocking = true + + val isSingleton = false + +// protected def makeReplyValue(molecule: BlockingMolecule[T, R]): ReplyValue[T, R] = { +// +// val replyGeneric: ReplyValue[T, R] = new ReplyValue[T,R](molecule) +// val replySpecific: ReplyValue[T, Unit] = new EmptyReplyValue[T](molecule) +// } + + /** Emit a blocking molecule and receive a value when the reply action is performed, unless a timeout is reached. + * + * @param duration Timeout in any time interval. + * @param v Value to be put onto the emitted molecule. + * @return Non-empty option if the reply was received; None on timeout. + */ + def timeout(duration: Duration)(v: T): Option[R] = + site.emitAndReplyWithTimeout[T,R](duration.toNanos, this, v, new ReplyValue[T,R](molecule = this)) + + def unapply(arg: UnapplyArg): Option[(T, ReplyValue[T,R])] = arg match { + + case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go + inputMoleculesProbe += this + Some((null, null).asInstanceOf[(T, ReplyValue[T,R])]) // hack for testing purposes only: + // The null value will not be used in any production code since _go is private. + + // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. + // We also gather the information about the molecule values actually used by the reaction, in case the reaction can start. + case UnapplyRunCheck(moleculeValues, usedInputs) => + for { + v <- moleculeValues.getOne(this) + } yield { + usedInputs += (this -> v) + (v.getValue, null).asInstanceOf[(T, ReplyValue[T, R])] // hack for verifying isDefinedAt: + // The null value will not be used, since the reply value is always matched unconditionally. + } + + // This is used when running the chosen reaction. + case UnapplyRun(moleculeValues) => moleculeValues.get(this).map { + case BlockingMolValue(v, srv) => (v, srv).asInstanceOf[(T, ReplyValue[T, R])] + case m@_ => + throw new ExceptionNoWrapper(s"Internal error: molecule $this with no value wrapper around value $m") + } + } + +} + +final class E(name: String) extends M[Unit](name) { + def apply(): Unit = site.emit[Unit](this, MolValue(())) +} + +final class F[R](name: String) extends B[Unit, R](name) { + def apply(): R = site.emitAndReply[Unit, R](this, (), new ReplyValue[Unit, R](molecule = this)) + + def timeout(duration: Duration)(): Option[R] = + site.emitAndReplyWithTimeout[Unit, R](duration.toNanos, this, (), new ReplyValue[Unit, R](molecule = this)) +} + +/** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. + * + * @param name Name of the molecule, used for debugging only. + * @tparam T Type of the value carried by the molecule. + */ +class M[T](val name: String) extends (T => Unit) with NonblockingMolecule[T] { + /** Emit a non-blocking molecule. + * + * @param v Value to be put onto the emitted molecule. + */ + def apply(v: T): Unit = site.emit[T](this, MolValue(v)) + /** Volatile reader for a molecule. * The molecule must be declared as a singleton. * @@ -346,16 +415,10 @@ final class M[T](val name: String) extends (T => Unit) with Molecule { def hasVolatileValue: Boolean = site.hasVolatileValue(this) - @volatile private[jc] var isSingletonBoolean = false - - override def isSingleton: Boolean = isSingletonBoolean - - override def isBlocking = false } -/** Reply-value wrapper for blocking molecules. This is a mutable class. +/** This trait contains implementations of most methods for the [[ReplyValue]] and [[EmptyReplyValue]] classes. * - * @param molecule The blocking molecule whose reply value this wrapper represents. * result Reply value as {{{Option[R]}}}. Initially this is None, and it may be assigned at most once by the * "reply" action if the reply is "valid" (i.e. not timed out). * semaphore Mutable semaphore reference. This is initialized only once when creating an instance of this @@ -365,10 +428,12 @@ final class M[T](val name: String) extends (T => Unit) with Molecule { * mistake in chemistry. * replyTimeout Will be set to "true" if the molecule was emitted with a timeout and the timeout was reached. * replyRepeated Will be set to "true" if the molecule received a reply more than once. - * @tparam T Type of the value carried by the molecule. - * @tparam R Type of the value replied to the caller via the "reply" action. + + * + * @tparam T Type of the value that the molecule carries. + * @tparam R Type of the reply value. */ -private[jc] final class ReplyValue[T,R] (molecule: B[T,R]) extends (R => Boolean) { +private[jc] trait AbsReplyValue[T, R] { @volatile var result: Option[R] = None @@ -398,13 +463,7 @@ private[jc] final class ReplyValue[T,R] (molecule: B[T,R]) extends (R => Boolean } else false - /** Perform a reply action for a blocking molecule. - * For each blocking molecule consumed by a reaction, exactly one reply action should be performed within the reaction body. - * - * @param x Value to reply with. - * @return True if the reply was successful. False if the blocking molecule timed out, or if a reply action was already performed. - */ - def apply(x: R): Boolean = synchronized { + protected def applyInternal(x: R): Boolean = synchronized { // The reply value will be assigned only if there was no timeout and no previous reply action. if (!replyTimeout && !replyRepeated && result.isEmpty) { result = Some(x) @@ -418,13 +477,34 @@ private[jc] final class ReplyValue[T,R] (molecule: B[T,R]) extends (R => Boolean } } +private[jc] class EmptyReplyValue[T](molecule: BlockingMolecule[T, Unit]) extends (() => Boolean) with AbsReplyValue[T, Unit] { + override def apply(): Boolean = applyInternal(()) +} + +/** Reply-value wrapper for blocking molecules. This is a mutable class. + * + * @param molecule The blocking molecule whose reply value this wrapper represents. + * @tparam T Type of the value carried by the molecule. + * @tparam R Type of the value replied to the caller via the "reply" action. + */ +private[jc] class ReplyValue[T, R](molecule: BlockingMolecule[T, R]) extends (R => Boolean) with AbsReplyValue[T, R] { + + /** Perform a reply action for a blocking molecule. + * For each blocking molecule consumed by a reaction, exactly one reply action should be performed within the reaction body. + * + * @param x Value to reply with. + * @return True if the reply was successful. False if the blocking molecule timed out, or if a reply action was already performed. + */ + def apply(x: R): Boolean = applyInternal(x) +} + /** Blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. * * @param name Name of the molecule, used for debugging only. * @tparam T Type of the value carried by the molecule. * @tparam R Type of the value replied to the caller via the "reply" action. */ -final class B[T, R](val name: String) extends (T => R) with Molecule { +class B[T, R](val name: String) extends (T => R) with BlockingMolecule[T, R] { /** Emit a blocking molecule and receive a value when the reply action is performed. * @@ -433,43 +513,6 @@ final class B[T, R](val name: String) extends (T => R) with Molecule { */ def apply(v: T): R = site.emitAndReply[T,R](this, v, new ReplyValue[T,R](molecule = this)) - - /** Emit a blocking molecule and receive a value when the reply action is performed, unless a timeout is reached. - * - * @param timeout Timeout in any time interval. - * @param v Value to be put onto the emitted molecule. - * @return Non-empty option if the reply was received; None on timeout. - */ - def timeout(timeout: Duration)(v: T): Option[R] = - site.emitAndReplyWithTimeout[T,R](timeout.toNanos, this, v, new ReplyValue[T,R](molecule = this)) - - def unapply(arg: UnapplyArg): Option[(T, ReplyValue[T,R])] = arg match { - - case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go - inputMoleculesProbe += this - Some((null, null).asInstanceOf[(T, ReplyValue[T,R])]) // hack for testing purposes only: - // The null value will not be used in any production code since _go is private. - - // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. - // We also gather the information about the molecule values actually used by the reaction, in case the reaction can start. - case UnapplyRunCheck(moleculeValues, usedInputs) => - for { - v <- moleculeValues.getOne(this) - } yield { - usedInputs += (this -> v) - (v.getValue, null).asInstanceOf[(T, ReplyValue[T,R])] // hack for verifying isDefinedAt: - // The null value will not be used, since the reply value is always matched unconditionally. - } - - // This is used when running the chosen reaction. - case UnapplyRun(moleculeValues) => moleculeValues.get(this).map { - case BlockingMolValue(v, srv) => (v, srv).asInstanceOf[(T, ReplyValue[T,R])] - case m@_ => - throw new ExceptionNoWrapper(s"Internal error: molecule $this with no value wrapper around value $m") - } - } - - override def isBlocking = true } private[jc] sealed trait UnapplyArg // The disjoint union type for arguments passed to the unapply methods. diff --git a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala index b90a2993..050a9de8 100644 --- a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala +++ b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala @@ -280,46 +280,46 @@ private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Poo } // Remove a blocking molecule if it is present. - private def removeBlockingMolecule[T,R](m: B[T,R], blockingMolValue: BlockingMolValue[T,R], hadTimeout: Boolean): Unit = { + private def removeBlockingMolecule[T,R](bm: BlockingMolecule[T,R], blockingMolValue: BlockingMolValue[T,R], hadTimeout: Boolean): Unit = { moleculesPresent.synchronized { - moleculesPresent.removeFromBag(m, blockingMolValue) - if (logLevel > 0) println(s"Debug: $this removed $m($blockingMolValue) on thread pool $sitePool, now have molecules ${moleculeBagToString(moleculesPresent)}") + moleculesPresent.removeFromBag(bm, blockingMolValue) + if (logLevel > 0) println(s"Debug: $this removed $bm($blockingMolValue) on thread pool $sitePool, now have molecules ${moleculeBagToString(moleculesPresent)}") } blockingMolValue.synchronized { blockingMolValue.replyValue.replyTimeout = hadTimeout } } - private def emitAndReplyInternal[T,R](timeoutOpt: Option[Long], m: B[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): Boolean = { + private def emitAndReplyInternal[T,R](timeoutOpt: Option[Long], bm: BlockingMolecule[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): Boolean = { val blockingMolValue = BlockingMolValue(v, replyValueWrapper) - emit(m, blockingMolValue) + emit(bm, blockingMolValue) val success = BlockingIdle { replyValueWrapper.acquireSemaphore(timeoutNanos = timeoutOpt) } replyValueWrapper.deleteSemaphore() // We might have timed out, in which case we need to forcibly remove the blocking molecule from the soup. - removeBlockingMolecule(m, blockingMolValue, !success) + removeBlockingMolecule(bm, blockingMolValue, !success) success } // Adding a blocking molecule may trigger at most one reaction and must return a value of type R. // We must make this a blocking call, so we acquire a semaphore (with or without timeout). - private[jc] def emitAndReply[T,R](m: B[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): R = { - emitAndReplyInternal(timeoutOpt = None, m, v, replyValueWrapper) + private[jc] def emitAndReply[T,R](bm: BlockingMolecule[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): R = { + emitAndReplyInternal(timeoutOpt = None, bm, v, replyValueWrapper) // check if we had any errors, and that we have a result value replyValueWrapper.errorMessage match { case Some(message) => throw new Exception(message) case None => replyValueWrapper.result.getOrElse( - throw new ExceptionEmptyReply(s"Internal error: In $this: $m received an empty reply without an error message" + throw new ExceptionEmptyReply(s"Internal error: In $this: $bm received an empty reply without an error message" ) ) } } // This is a separate method because it has a different return type than emitAndReply. - private[jc] def emitAndReplyWithTimeout[T,R](timeout: Long, m: B[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): + private[jc] def emitAndReplyWithTimeout[T,R](timeout: Long, m: BlockingMolecule[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): Option[R] = { val haveReply = emitAndReplyInternal(timeoutOpt = Some(timeout), m, v, replyValueWrapper) // check if we had any errors, and that we have a result value diff --git a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala index ae724e97..be312536 100644 --- a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala @@ -33,8 +33,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "block for a blocking molecule" in { - val a = new M[Unit]("a") - val f = new B[Unit,Int]("f") + val a = new E("a") + val f = new F[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => r(3) }) a() a() @@ -47,7 +47,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "not timeout when a blocking molecule is responding" in { (1 to 1000).map { _ => - val f = new B[Unit,Int]("f") + val f = new F[Int]("f") site(tp0)( _go { case f(_, r) => r(0) }) f.timeout(300 millis)().getOrElse(1) @@ -56,8 +56,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "timeout when a blocking molecule is not responding at all" in { - val a = new M[Unit]("a") - val f = new B[Unit,Int]("f") + val a = new E("a") + val f = new F[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => r(3) }) a() f() shouldEqual 3 // now the a() molecule is gone @@ -66,8 +66,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "not timeout when a blocking molecule is responding quickly enough" in { - val a = new M[Unit]("a") - val f = new B[Unit,Int]("f") + val a = new E("a") + val f = new F[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => Thread.sleep(100); r(3) }) a() f.timeout(500 millis)() shouldEqual Some(3) @@ -75,8 +75,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "timeout when a blocking molecule is not responding quickly enough" in { - val a = new M[Unit]("a") - val f = new B[Unit,Int]("f") + val a = new E("a") + val f = new F[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => Thread.sleep(500); r(3) }) a() f.timeout(200 millis)() shouldEqual None @@ -86,9 +86,9 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "use the first reply when a reaction attempts to reply twice" in { val c = new M[Int]("c") - val d = new M[Unit]("d") - val f = new B[Unit,Unit]("f") - val g = new B[Unit,Int]("g") + val d = new E("d") + val f = new F[Unit]("f") + val g = new F[Int]("g") site(tp0)( _go { case c(n) + g(_,r) => c(n); r(n); r(n+1); d() }, _go { case d(_) + f(_,r) => r() } @@ -100,11 +100,11 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "use the first replies when a reaction attempts to reply twice to more than one molecule" in { val c = new M[Int]("c") - val d = new M[Unit]("d") + val d = new E("d") val d2 = new M[Int]("d2") - val e = new B[Unit,Int]("e") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") + val e = new F[Int]("e") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") site(tp0)( _go { case d(_) => d2(g2()) }, _go { case d2(x) + e(_, r) => r(x) }, @@ -117,8 +117,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests } it should "throw exception when a reaction does not reply to one blocking molecule" in { - val c = new M[Unit]("c") - val g = new B[Unit,Int]("g") + val c = new E("c") + val g = new F[Int]("g") site(tp0)( _go { case c(_) + g(_,r) => c() } ) @@ -132,10 +132,10 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests } it should "throw exception when a reaction does not reply to two blocking molecules)" in { - val c = new M[Unit]("c") - val d = new M[Unit]("d") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") + val c = new E("c") + val d = new E("d") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") val tp = new FixedPool(4) site(tp)( _go { case d(_) => g2() } onThreads tp, @@ -153,10 +153,10 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests } it should "get a reply when a reaction does not reply to one blocking molecule but does reply to another" in { - val c = new M[Unit]("c") - val d = new M[Unit]("d") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") + val c = new E("c") + val d = new E("d") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") val tp = new FixedPool(4) site(tp)( _go { case d(_) => g() } onThreads tp, @@ -176,13 +176,13 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests behavior of "deadlocked threads with blocking molecules" it should "not produce deadlock when two blocking molecules are emitted from different threads" in { - val c = new M[Unit]("c") - val d = new M[Unit]("d") + val c = new E("c") + val d = new E("d") val e = new M[Int]("e") - val f = new M[Unit]("f") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") - val h = new B[Unit,Int]("h") + val f = new E("f") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") + val h = new F[Int]("h") val tp = new FixedPool(4) site(tp)( _go { case c(_) => e(g2()) }, // e(0) should be emitted now @@ -200,13 +200,13 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests } it should "produce deadlock when two blocking molecules are emitted from the same thread" in { - val c = new M[Unit]("c") - val d = new M[Unit]("d") + val c = new E("c") + val d = new E("d") val e = new M[Int]("e") - val f = new M[Unit]("f") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") - val h = new B[Unit,Int]("h") + val f = new E("f") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") + val h = new F[Int]("h") val tp = new FixedPool(4) site(tp)( _go { case c(_) => val x = g(); g2(); e(x) }, // e(0) should never be emitted because this thread is deadlocked @@ -231,10 +231,10 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests } it should "not block the 2-thread threadpool when one thread is blocked" in { - val c = new M[Unit]("c") - val d = new M[Unit]("d") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") + val c = new E("c") + val d = new E("d") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") val tp = new FixedPool(4) site(tp)( _go { case d(_) => g() }, // this will be used to emit g() and blocked @@ -248,10 +248,10 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests } it should "block the 1-thread threadpool when one thread is blocked" in { - val c = new M[Unit]("c") - val d = new M[Unit]("d") - val g = new B[Unit,Int]("g") - val g2 = new B[Unit,Int]("g2") + val c = new E("c") + val d = new E("d") + val g = new F[Int]("g") + val g2 = new F[Int]("g2") val tp = new FixedPool(1) val tp1 = new FixedPool(1) site(tp,tp1)( @@ -266,11 +266,11 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests tp1.shutdownNow() } - def makeBlockingCheck(sleeping: => Unit, tp1: Pool): (B[Unit,Unit], B[Unit,Int]) = { - val c = new M[Unit]("c") - val d = new M[Unit]("d") - val g = new B[Unit,Unit]("g") - val g2 = new B[Unit,Int]("g2") + def makeBlockingCheck(sleeping: => Unit, tp1: Pool): (F[Unit], F[Int]) = { + val c = new E("c") + val d = new E("d") + val g = new F[Unit]("g") + val g2 = new F[Int]("g2") site(tp0)( _go { case c(_) + g(_, r) => r() } ) // we will use this to monitor the d() reaction @@ -334,14 +334,14 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests behavior of "thread starvation with different threadpools" - def blockThreadsDueToBlockingMolecule(tp1: Pool): B[Unit, Unit] = { - val c = new M[Unit]("c") - val cStarted = new M[Unit]("cStarted") - val never = new M[Unit]("never") - val f = new B[Unit,Int]("forever") - val f2 = new B[Unit,Int]("forever2") - val g = new B[Unit,Unit]("g") - val started = new B[Unit,Unit]("started") + def blockThreadsDueToBlockingMolecule(tp1: Pool): F[Unit] = { + val c = new E("c") + val cStarted = new E("cStarted") + val never = new E("never") + val f = new F[Int]("forever") + val f2 = new F[Int]("forever2") + val g = new F[Unit]("g") + val started = new F[Unit]("started") site(tp1,tp0)( _go { case g(_, r) => r() }, // and so this reaction will be blocked forever diff --git a/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala index a667072e..1dd70e8f 100644 --- a/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala @@ -24,8 +24,8 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { it should "emit a molecule from a future computed out of a given future" in { - val c = new M[Unit]("c") - val f = new B[Unit, Unit]("f") + val c = new E("c") + val f = new F[Unit]("f") val tp = new FixedPool(2) site(tp)( @@ -58,11 +58,11 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { it should "not emit a molecule from a future prematurely" in { val waiter = new Waiter - val c = new M[Unit]("c") - val d = new M[Unit]("d") - val e = new M[Unit]("e") - val f = new B[Unit, String]("f") - val f2 = new B[Unit, String]("f2") + val c = new E("c") + val d = new E("d") + val e = new E("e") + val f = new F[String]("f") + val f2 = new F[String]("f2") val tp = new FixedPool(4) site(tp)( @@ -93,7 +93,7 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { val waiter = new Waiter val tp = new FixedPool(4) - val b = new M[Unit]("b") + val b = new E("b") // "fut" will succeed when "c" is emitted val (c, fut) = moleculeFuture[String](tp) diff --git a/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala index ca3bf707..c4a736ac 100644 --- a/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala @@ -31,9 +31,9 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be behavior of "reaction site" it should "track whether molecule emitters are bound" in { - val a = new M[Unit]("a123") - val b = new M[Unit]("b") - val c = new M[Unit]("") + val a = new E("a123") + val b = new E("b") + val c = new E("") a.toString shouldEqual "a123" b.toString shouldEqual "b" @@ -59,9 +59,9 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be } it should "define a reaction with correct inputs" in { - val a = new M[Unit]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val a = new E("a") + val b = new E("b") + val c = new E("c") site(tp0)(_go { case a(_) + b(_) + c(_) => }) a.logSoup shouldEqual "Site{a + b + c => ...}\nNo molecules" @@ -69,10 +69,10 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be } it should "correctly list molecules present in soup" in { - val a = new M[Unit]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") - val f = new B[Unit, Unit]("f") + val a = new E("a") + val b = new E("b") + val c = new E("c") + val f = new F[Unit]("f") site(tp0)( _go { case a(_) + b(_) + c(_) + f(_, r) => r() } @@ -91,8 +91,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "define a reaction with correct inputs with non-default pattern-matching at end of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(_go { case b(_) + c(_) + a(Some(x)) => }) @@ -101,8 +101,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "define a reaction with correct inputs with zero default pattern-matching at start of reaction" in { val a = new M[Int]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(_go { case a(0) + b(_) + c(_) => }) @@ -111,8 +111,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "define a reaction with correct inputs with constant non-default pattern-matching at end of reaction" in { val a = new M[Int]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(_go { case b(_) + c(_) + a(1) => }) @@ -123,7 +123,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val waiter = new Waiter - val a = new M[Unit]("a") + val a = new E("a") site(tp0)( _go { case a(_) => waiter.dismiss() }) a() @@ -134,7 +134,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val waiter = new Waiter - val a = new M[Unit]("a") + val a = new E("a") site(tp0)( _go { case a(_) => waiter.dismiss() }) a() @@ -145,8 +145,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val waiter = new Waiter - val a = new M[Unit]("a") - val b = new M[Unit]("b") + val a = new E("a") + val b = new E("b") site(tp0)( _go { case a(_) => b() }, _go { case b(_) => waiter.dismiss() }) a() @@ -168,7 +168,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern is nonlinear" in { val thrown = intercept[Exception] { - val a = new M[Unit]("a") + val a = new E("a") site( _go { case a(_) + a(_) => () }) } thrown.getMessage shouldEqual "Nonlinear pattern: a used twice" @@ -177,7 +177,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern is nonlinear, with blocking molecule" in { val thrown = intercept[Exception] { - val a = new B[Unit,Unit]("a") + val a = new F[Unit]("a") site( _go { case a(_,r) + a(_,s) => () }) } thrown.getMessage shouldEqual "Nonlinear pattern: a/B used twice" @@ -185,7 +185,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern attempts to redefine a blocking molecule" in { val thrown = intercept[Exception] { - val a = new B[Unit,Unit]("a") + val a = new F[Unit]("a") site( _go { case a(_,_) => () }) site( _go { case a(_,_) => () }) } @@ -194,8 +194,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern attempts to redefine a non-blocking molecule" in { val thrown = intercept[Exception] { - val a = new M[Unit]("x") - val b = new M[Unit]("y") + val a = new E("x") + val b = new E("y") site( _go { case a(_) + b(_) => () }) site( _go { case a(_) => () }) } @@ -204,7 +204,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to emit a blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new B[Unit,Unit]("x") + val a = new F[Unit]("x") a() } thrown.getMessage shouldEqual "Molecule x/B is not bound to any reaction site" @@ -212,7 +212,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to emit a non-blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new M[Unit]("x") + val a = new E("x") a() } thrown.getMessage shouldEqual "Molecule x is not bound to any reaction site" @@ -220,7 +220,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to log soup of a blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new B[Unit,Unit]("x") + val a = new F[Unit]("x") a.logSoup } thrown.getMessage shouldEqual "Molecule x/B is not bound to any reaction site" @@ -228,7 +228,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to log soup a non-blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new M[Unit]("x") + val a = new E("x") a.logSoup } thrown.getMessage shouldEqual "Molecule x is not bound to any reaction site" @@ -238,7 +238,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val a = new M[Int]("a") val b = new M[Int]("b") - val f = new B[Unit,Int]("f") + val f = new F[Int]("f") site(tp0)( _go { case a(x) + b(0) => a(x+1) }, _go { case a(z) + f(_, r) => r(z) }) a(1) @@ -248,8 +248,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "implement the non-blocking single-access counter" in { val c = new M[Int]("c") - val d = new M[Unit]("decrement") - val g = new B[Unit,Int]("getValue") + val d = new E("decrement") + val g = new F[Int]("getValue") site(tp0)( _go { case c(n) + d(_) => c(n-1) }, _go { case c(0) + g(_,r) => r(0) } @@ -260,10 +260,10 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "use only one thread for concurrent computations" in { val c = new M[Int]("counter") - val d = new M[Unit]("decrement") - val f = new M[Unit]("finished") + val d = new E("decrement") + val f = new E("finished") val a = new M[Int]("all_finished") - val g = new B[Unit,Int]("getValue") + val g = new F[Int]("getValue") val tp = new FixedPool(1) @@ -286,11 +286,11 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be // this test sometimes fails it should "use two threads for concurrent computations" in { - val c = new M[Unit]("counter") - val d = new M[Unit]("decrement") - val f = new M[Unit]("finished") + val c = new E("counter") + val d = new E("decrement") + val f = new E("finished") val a = new M[Int]("all_finished") - val g = new B[Unit,Int]("getValue") + val g = new F[Int]("getValue") val tp = new FixedPool(2) @@ -312,8 +312,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val probabilityOfCrash = 0.5 val c = new M[Int]("counter") - val d = new M[Unit]("decrement") - val g = new B[Unit, Unit]("getValue") + val d = new E("decrement") + val g = new F[Unit]("getValue") val tp = new FixedPool(2) site(tp0)( @@ -336,8 +336,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val probabilityOfCrash = 0.5 val c = new M[Int]("counter") - val d = new M[Unit]("decrement") - val g = new B[Unit, Unit]("getValue") + val d = new E("decrement") + val g = new F[Unit]("getValue") val tp = new FixedPool(2) site(tp0)( @@ -360,8 +360,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val probabilityOfCrash = 0.5 val c = new M[Int]("counter") - val d = new B[Unit, Unit]("decrement") - val g = new B[Unit, Unit]("getValue") + val d = new F[Unit]("decrement") + val g = new F[Unit]("getValue") val tp = new FixedPool(2) site(tp0)( diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index 2570957c..e452fd87 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -4,12 +4,11 @@ import scala.collection.mutable import scala.language.experimental.macros import scala.reflect.macros._ import scala.reflect.NameTransformer.LOCAL_SUFFIX_STRING - import scala.annotation.tailrec object Macros { - type theContext = blackbox.Context + type theContext = whitebox.Context private[jc] def rawTree(x: Any): String = macro rawTreeImpl @@ -33,27 +32,32 @@ object Macros { c.Expr[String](q"$s") } - def m[T]: M[T] = macro mImpl[T] + def m[T]: NonblockingMolecule[T] = macro mImpl[T] - def mImpl[T: c.WeakTypeTag](c: theContext): c.Expr[M[T]] = { + def mImpl[T: c.WeakTypeTag](c: theContext): c.universe.Tree = { import c.universe._ - val s = getEnclosingName(c) - - val t = c.weakTypeOf[T] + val moleculeName = getEnclosingName(c) - c.Expr[M[T]](q"new M[$t]($s)") + val moleculeValueType = c.weakTypeOf[T] + if (moleculeValueType =:= typeOf[Unit]) + q"new E($moleculeName)" + else + q"new M[$moleculeValueType]($moleculeName)" } - def b[T, R]: B[T, R] = macro bImpl[T, R] + def b[T, R]: BlockingMolecule[T, R] = macro bImpl[T, R] - def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: blackbox.Context): c.Expr[B[T, R]] = { + def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: theContext): c.Expr[BlockingMolecule[T, R]] = { import c.universe._ - val s = c.internal.enclosingOwner.name.decodedName.toString.stripSuffix(LOCAL_SUFFIX_STRING).stripSuffix("$lzy") + val moleculeName = getEnclosingName(c) - val t = c.weakTypeOf[T] - val r = c.weakTypeOf[R] + val moleculeValueType = c.weakTypeOf[T] + val replyValueType = c.weakTypeOf[R] - c.Expr[B[T, R]](q"new B[$t,$r]($s)") + if (moleculeValueType =:= typeOf[Unit]) + c.Expr[BlockingMolecule[T,R]](q"new F[$replyValueType]($moleculeName)") + else + c.Expr[B[T, R]](q"new B[$moleculeValueType,$replyValueType]($moleculeName)") } /** Describes the pattern matcher for input molecules. @@ -90,17 +94,19 @@ object Macros { /** Describes the pattern matcher for output molecules. * Possible values: - * ConstOutputPattern: a(1) - * OtherOutputPattern: a(x) or anything else + * ConstOutputPatternF(x): a(x) + * EmptyOutputPatternF: a() + * OtherOutputPatternF: a(x+y) or anything else */ private sealed trait OutputPatternFlag { def notSimple: Boolean = this match { - case ConstOutputPattern(_) => false + case ConstOutputPatternF(_) | EmptyOutputPatternF => false case _ => true } } private case object OtherOutputPatternF extends OutputPatternFlag - private final case class ConstOutputPattern(v: Any) extends OutputPatternFlag + private case object EmptyOutputPatternF extends OutputPatternFlag + private final case class ConstOutputPatternF(v: Any) extends OutputPatternFlag /** * Users will define reactions using this function. @@ -212,7 +218,8 @@ object Macros { } private def getOutputFlag(binderTerms: List[Tree]): OutputPatternFlag = binderTerms match { - case List(t@Literal(_)) => ConstOutputPattern(t) + case List(t@Literal(_)) => ConstOutputPatternF(t) + case Nil => EmptyOutputPatternF case _ => OtherOutputPatternF } @@ -265,7 +272,7 @@ object Macros { outputMolecules.append((t.symbol, flag1)) } } - if (t.tpe <:< weakTypeOf[ReplyValue[_,_]]) { + if (t.tpe <:< weakTypeOf[AbsReplyValue[_,_]]) { replyActions.append((t.symbol, flag1)) } @@ -285,7 +292,8 @@ object Macros { } implicit val liftableOutputPatternFlag: c.universe.Liftable[OutputPatternFlag] = Liftable[OutputPatternFlag] { - case ConstOutputPattern(x) => q"_root_.code.winitzki.jc.ConstOutputValue(${x.asInstanceOf[c.Tree]})" + case ConstOutputPatternF(x) => q"_root_.code.winitzki.jc.ConstOutputValue(${x.asInstanceOf[c.Tree]})" + case EmptyOutputPatternF => q"_root_.code.winitzki.jc.ConstOutputValue(())" case _ => q"_root_.code.winitzki.jc.OtherOutputPattern" } diff --git a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala index 86e0a767..5f9311a2 100644 --- a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala +++ b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala @@ -109,26 +109,6 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { val constantZeroSha1 = "8227489534FBEA1F404CAAEC9F4CCAEEB9EF2DC1" val constantOneSha1 = "356A192B7913B04C54574D18C28D46E6395428AB" - it should "inspect a _go reaction body" in { - val a = m[Int] - val qq = m[Unit] - val s = b[Unit, Int] - val bb = m[(Int, Option[Int])] - - val result = _go { case a(x) + qq(_) => qq() } - - (result.info.inputs match { - case List( - InputMoleculeInfo(`a`, UnknownInputPattern, _), - InputMoleculeInfo(`qq`, UnknownInputPattern, _) - ) => true - case _ => false - }) shouldEqual true - result.info.outputs shouldEqual None - result.info.hasGuard == GuardPresenceUnknown - result.info.sha1 != "3C5E53F3B9EA1CC2AB58F463114B178EB569390F" shouldEqual true - } - it should "inspect a two-molecule reaction body with None" in { val a = m[Int] val qq = m[Unit] @@ -147,9 +127,11 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { result.info.outputs shouldEqual Some(List(OutputMoleculeInfo(bb, OtherOutputPattern))) result.info.hasGuard == GuardAbsent - result.info.sha1 shouldEqual "E3484A31FA12EA19A7D84422C452E1F8099F8117" + result.info.sha1 shouldEqual "99CC108DF49886DD7839027DEDF2657083520D4F" } + val axqq_qqSha1 = "8DED02120DA6F4D5E8D0931097E3BA3BAC5F7082" + it should "inspect a two-molecule reaction body" in { val a = m[Int] val qq = m[Unit] @@ -164,7 +146,27 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { ) result.info.outputs shouldEqual Some(List(OutputMoleculeInfo(qq, ConstOutputValue(())))) result.info.hasGuard == GuardAbsent - result.info.sha1 shouldEqual "3C5E53F3B9EA1CC2AB58F463114B178EB569390F" + result.info.sha1 shouldEqual axqq_qqSha1 + } + + it should "inspect a _go reaction body" in { + val a = m[Int] + val qq = m[Unit] + val s = b[Unit, Int] + val bb = m[(Int, Option[Int])] + + val result = _go { case a(x) + qq(_) => qq() } + + (result.info.inputs match { + case List( + InputMoleculeInfo(`a`, UnknownInputPattern, _), + InputMoleculeInfo(`qq`, UnknownInputPattern, _) + ) => true + case _ => false + }) shouldEqual true + result.info.outputs shouldEqual None + result.info.hasGuard == GuardPresenceUnknown + result.info.sha1 shouldNot be eq axqq_qqSha1 } it should "inspect a reaction body with another molecule and extra code" in { @@ -365,10 +367,9 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { r.info.outputs shouldEqual Some(List(OutputMoleculeInfo(a, OtherOutputPattern))) r.info.hasGuard shouldEqual GuardAbsent - // Note: Scala 2.11 and Scala 2.12 have different syntax trees for Some(1) - val shaScala211 = "3A03F935B238FBC427CCEC83D25D69820AB5CDBE" - val shaScala212 = "C4A42A1C5082B4C5023CC3B0497BB7BCA6642C17" - Set(shaScala211, shaScala212) should contain oneElementOf List(r.info.sha1) + // Note: Scala 2.11 and Scala 2.12 have different syntax trees for Some(1)? + val shaScala211 = "9F8D8B42C1DB096EEFC80052E603562ECAD0FA29" + Set(shaScala211) should contain oneElementOf List(r.info.sha1) } it should "fail to compile reactions with detectable compile-time errors" in { @@ -573,11 +574,11 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "correctly recognize nested emissions of non-blocking molecules" in { val a = m[Int] - val c = m[Unit] + val c = m[Int] val d = m[Boolean] site( - go { case a(x) + d(_) => c(a(1)) } + go { case a(x) + d(_) => c({ a(1); 2} ) } ) a.isBound shouldEqual true From 2829967429234e532da135c5f26509ae2dc6867d Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 18:43:32 -0800 Subject: [PATCH 04/12] refactor tests to use new subclasses E and F --- .../test/DiningPhilosophersSpec.scala | 16 ++++---- .../code/winitzki/test/FairnessSpec.scala | 6 +-- .../code/winitzki/test/ParallelOrSpec.scala | 4 +- .../main/scala/code/winitzki/jc/Macros.scala | 8 ++-- .../scala/code/winitzki/jc/MacrosSpec.scala | 38 +++++++++---------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala index c996bef8..bf1836b9 100644 --- a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala @@ -33,14 +33,14 @@ class DiningPhilosophersSpec extends FlatSpec with Matchers with TimeLimitedTest val t3 = new M[Int]("Marx is thinking") val t4 = new M[Int]("Russell is thinking") val t5 = new M[Int]("Spinoza is thinking") - val f12 = new M[Unit]("f12") - val f23 = new M[Unit]("f23") - val f34 = new M[Unit]("f34") - val f45 = new M[Unit]("f45") - val f51 = new M[Unit]("f51") - - val done = new M[Unit]("done") - val check = new B[Unit, Unit]("check") + val f12 = new E("f12") + val f23 = new E("f23") + val f34 = new E("f34") + val f45 = new E("f45") + val f51 = new E("f51") + + val done = new E("done") + val check = new F[Unit]("check") site(tp, tp) ( go { case t1(n) => rw(h1); h1(n - 1) }, diff --git a/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala b/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala index cac7e51e..853c26d1 100644 --- a/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/FairnessSpec.scala @@ -23,9 +23,9 @@ class FairnessSpec extends FlatSpec with Matchers with TimeLimitedTests { val reactions = 4 val N = 1000 - val c = new M[(Int, Array[Int])]("c") - val done = new M[Array[Int]]("done") - val getC = new B[Unit, Array[Int]]("getC") + val c = m[(Int, Array[Int])] + val done = m[Array[Int]] + val getC = b[Unit, Array[Int]] val a0 = m[Unit] val a1 = m[Unit] val a2 = m[Unit] diff --git a/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala b/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala index e71489ec..1639c0e5 100644 --- a/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala @@ -23,7 +23,7 @@ class ParallelOrSpec extends FlatSpec with Matchers { * @param tp Thread pool on which to run this. * @return New blocking molecule emitter that will return the desired result or block. */ - def parallelOr(f: B[Unit, Boolean], g: B[Unit, Boolean], tp: Pool): B[Unit, Boolean] = { + def parallelOr(f: F[Boolean], g: F[Boolean], tp: Pool): F[Boolean] = { val c = m[Unit] val d = m[Unit] val done = m[Boolean] @@ -98,7 +98,7 @@ class ParallelOrSpec extends FlatSpec with Matchers { * @tparam T Type of the return value. * @return New blocking molecule emitter that will return the desired result. */ - def firstResult[T](b1: B[Unit, T], b2: B[Unit, T], tp: Pool): B[Unit, T] = { + def firstResult[T](b1: F[T], b2: F[T], tp: Pool): F[T] = { val get = b[Unit, T] val res = b[Unit, T] val res1 = m[Unit] diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index e452fd87..1ead20cb 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -32,7 +32,7 @@ object Macros { c.Expr[String](q"$s") } - def m[T]: NonblockingMolecule[T] = macro mImpl[T] + def m[T]: M[T] = macro mImpl[T] def mImpl[T: c.WeakTypeTag](c: theContext): c.universe.Tree = { import c.universe._ @@ -45,9 +45,9 @@ object Macros { q"new M[$moleculeValueType]($moleculeName)" } - def b[T, R]: BlockingMolecule[T, R] = macro bImpl[T, R] + def b[T, R]: B[T,R] = macro bImpl[T, R] - def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: theContext): c.Expr[BlockingMolecule[T, R]] = { + def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: theContext): c.Expr[B[T, R]] = { import c.universe._ val moleculeName = getEnclosingName(c) @@ -55,7 +55,7 @@ object Macros { val replyValueType = c.weakTypeOf[R] if (moleculeValueType =:= typeOf[Unit]) - c.Expr[BlockingMolecule[T,R]](q"new F[$replyValueType]($moleculeName)") + c.Expr[B[T,R]](q"new F[$replyValueType]($moleculeName)") else c.Expr[B[T, R]](q"new B[$moleculeValueType,$replyValueType]($moleculeName)") } diff --git a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala index 5f9311a2..241bdaab 100644 --- a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala +++ b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala @@ -62,7 +62,7 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { val reaction = go { case a(x) => val q = new M[Int]("q") - val s = new M[Unit]("s") + val s = new E("s") val reaction1 = go { case q(_) + s(()) => } q(0) } @@ -254,8 +254,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with non-default pattern-matching in the middle of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(_go { case b(_) + a(Some(x)) + c(_) => }) @@ -265,8 +265,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with default pattern-matching in the middle of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(go { case b(_) + a(None) + c(_) => }) @@ -275,8 +275,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with non-simple default pattern-matching in the middle of reaction" in { val a = new M[Seq[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(go { case b(_) + a(List()) + c(_) => }) @@ -285,8 +285,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "fail to define a simple reaction with correct inputs with empty option pattern-matching at start of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(_go { case a(None) + b(_) + c(_) => }) @@ -295,8 +295,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with empty option pattern-matching at start of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(go { case a(None) + b(_) + c(_) => }) @@ -305,8 +305,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with non-default pattern-matching at start of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(go { case a(Some(x)) + b(_) + c(_) => }) @@ -315,7 +315,7 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "run reactions correctly with non-default pattern-matching at start of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") + val b = new E("b") site(tp0)(go { case a(Some(x)) + b(_) => }) @@ -329,8 +329,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with constant non-default pattern-matching at start of reaction" in { val a = new M[Int]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(go { case a(1) + b(_) + c(_) => }) @@ -339,8 +339,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { it should "define a reaction with correct inputs with constant default option pattern-matching at start of reaction" in { val a = new M[Option[Int]]("a") - val b = new M[Unit]("b") - val c = new M[Unit]("c") + val b = new E("b") + val c = new E("c") site(tp0)(go { case a(None) + b(_) + c(_) => }) @@ -351,7 +351,7 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { val a = new M[Option[Int]]("a") val b = new M[String]("b") val c = new M[(Int, Int)]("c") - val d = new M[Unit]("d") + val d = new E("d") val r = go { case a(Some(1)) + b("xyz") + d(()) + c((2, 3)) => a(Some(2)) } From d12a34e324bf7812eca7c603e874b9cb832e6248 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 18:51:02 -0800 Subject: [PATCH 05/12] refactor ReplyValue to avoid deprecation warnings --- lib/src/main/scala/code/winitzki/jc/Molecules.scala | 12 ++++++++++-- .../main/scala/code/winitzki/jc/ReactionSite.scala | 6 +++--- .../test/scala/code/winitzki/jc/ChymystSpec.scala | 2 +- macros/src/main/scala/code/winitzki/jc/Macros.scala | 1 + 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/src/main/scala/code/winitzki/jc/Molecules.scala b/lib/src/main/scala/code/winitzki/jc/Molecules.scala index 6f6c8286..da53ba7d 100644 --- a/lib/src/main/scala/code/winitzki/jc/Molecules.scala +++ b/lib/src/main/scala/code/winitzki/jc/Molecules.scala @@ -245,7 +245,7 @@ private[jc] final case class MolValue[T](v: T) extends AbsMolValue[T] { * @tparam T The type of the value carried by the molecule. * @tparam R The type of the reply value. */ -private[jc] final case class BlockingMolValue[T,R](v: T, replyValue: ReplyValue[T,R]) extends AbsMolValue[T] with PersistentHashCode { +private[jc] final case class BlockingMolValue[T,R](v: T, replyValue: AbsReplyValue[T,R]) extends AbsMolValue[T] with PersistentHashCode { override def getValue: T = v } @@ -387,13 +387,21 @@ final class E(name: String) extends M[Unit](name) { def apply(): Unit = site.emit[Unit](this, MolValue(())) } -final class F[R](name: String) extends B[Unit, R](name) { +class F[R](name: String) extends B[Unit, R](name) { def apply(): R = site.emitAndReply[Unit, R](this, (), new ReplyValue[Unit, R](molecule = this)) def timeout(duration: Duration)(): Option[R] = site.emitAndReplyWithTimeout[Unit, R](duration.toNanos, this, (), new ReplyValue[Unit, R](molecule = this)) } +class FE(name: String) extends F[Unit](name) { + override def apply(): Unit = site.emitAndReply[Unit, Unit](this, (), new EmptyReplyValue[Unit](this)) + + override def timeout(duration: Duration)(): Option[Unit] = + site.emitAndReplyWithTimeout[Unit, Unit](duration.toNanos, this, (), new ReplyValue[Unit, Unit](molecule = this)) + +} + /** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. * * @param name Name of the molecule, used for debugging only. diff --git a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala index 050a9de8..fb0b2571 100644 --- a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala +++ b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala @@ -290,7 +290,7 @@ private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Poo } } - private def emitAndReplyInternal[T,R](timeoutOpt: Option[Long], bm: BlockingMolecule[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): Boolean = { + private def emitAndReplyInternal[T,R](timeoutOpt: Option[Long], bm: BlockingMolecule[T,R], v: T, replyValueWrapper: AbsReplyValue[T,R]): Boolean = { val blockingMolValue = BlockingMolValue(v, replyValueWrapper) emit(bm, blockingMolValue) val success = @@ -306,7 +306,7 @@ private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Poo // Adding a blocking molecule may trigger at most one reaction and must return a value of type R. // We must make this a blocking call, so we acquire a semaphore (with or without timeout). - private[jc] def emitAndReply[T,R](bm: BlockingMolecule[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): R = { + private[jc] def emitAndReply[T,R](bm: BlockingMolecule[T,R], v: T, replyValueWrapper: AbsReplyValue[T,R]): R = { emitAndReplyInternal(timeoutOpt = None, bm, v, replyValueWrapper) // check if we had any errors, and that we have a result value replyValueWrapper.errorMessage match { @@ -319,7 +319,7 @@ private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Poo } // This is a separate method because it has a different return type than emitAndReply. - private[jc] def emitAndReplyWithTimeout[T,R](timeout: Long, m: BlockingMolecule[T,R], v: T, replyValueWrapper: ReplyValue[T,R]): + private[jc] def emitAndReplyWithTimeout[T,R](timeout: Long, m: BlockingMolecule[T,R], v: T, replyValueWrapper: AbsReplyValue[T,R]): Option[R] = { val haveReply = emitAndReplyInternal(timeoutOpt = Some(timeout), m, v, replyValueWrapper) // check if we had any errors, and that we have a result value diff --git a/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala index 1dd70e8f..4871d045 100644 --- a/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala @@ -16,7 +16,7 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { val warmupTimeMs = 50L - val patienceConfig = PatienceConfig(timeout = Span(500, Millis)) + val patienceConfig = PatienceConfig(timeout = Span(1000, Millis)) def waitSome(): Unit = Thread.sleep(warmupTimeMs) diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index 1ead20cb..8286bc8e 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -47,6 +47,7 @@ object Macros { def b[T, R]: B[T,R] = macro bImpl[T, R] + // Does providing an explicit return type here as c.Expr[...] helps anything? Looks like it doesn't, so far. def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: theContext): c.Expr[B[T, R]] = { import c.universe._ val moleculeName = getEnclosingName(c) From 55a0671f6455d2aee46e4af96c85693ef99658b0 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 19:06:11 -0800 Subject: [PATCH 06/12] WIP adding all 4 subtypes of B --- .../test/DiningPhilosophersSpec.scala | 4 +- .../scala/code/winitzki/jc/Molecules.scala | 45 +++++++++++++++++-- .../winitzki/jc/BlockingMoleculesSpec.scala | 12 ++--- .../code/winitzki/jc/MoleculesSpec.scala | 18 ++++---- .../main/scala/code/winitzki/jc/Macros.scala | 15 +++++-- 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala index bf1836b9..841109e8 100644 --- a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala @@ -19,7 +19,7 @@ class DiningPhilosophersSpec extends FlatSpec with Matchers with TimeLimitedTest diningPhilosophers(50) } - def diningPhilosophers(cycles: Int) = { + private def diningPhilosophers(cycles: Int) = { val tp = new FixedPool(8) @@ -40,7 +40,7 @@ class DiningPhilosophersSpec extends FlatSpec with Matchers with TimeLimitedTest val f51 = new E("f51") val done = new E("done") - val check = new F[Unit]("check") + val check = new FE("check") site(tp, tp) ( go { case t1(n) => rw(h1); h1(n - 1) }, diff --git a/lib/src/main/scala/code/winitzki/jc/Molecules.scala b/lib/src/main/scala/code/winitzki/jc/Molecules.scala index da53ba7d..fdedd44c 100644 --- a/lib/src/main/scala/code/winitzki/jc/Molecules.scala +++ b/lib/src/main/scala/code/winitzki/jc/Molecules.scala @@ -383,10 +383,23 @@ private[jc] trait BlockingMolecule[T, R] extends Molecule { } +/** Specialized class for non-blocking molecule emitters with empty value. + * These molecules can be emitted with syntax a() without a deprecation warning. + * Macro call m[Unit] returns this type. + * + * @param name Name of the molecule, used for debugging only. + */ final class E(name: String) extends M[Unit](name) { def apply(): Unit = site.emit[Unit](this, MolValue(())) } +/** Specialized class for blocking molecule emitters with empty value (but non-empty reply). + * These molecules can be emitted with syntax f() without a deprecation warning. + * Macro call b[Unit, T] returns this type when T is not Unit. + * + * @param name Name of the molecule, used for debugging only. + * @tparam R Type of the value replied to the caller via the "reply" action. + */ class F[R](name: String) extends B[Unit, R](name) { def apply(): R = site.emitAndReply[Unit, R](this, (), new ReplyValue[Unit, R](molecule = this)) @@ -394,12 +407,36 @@ class F[R](name: String) extends B[Unit, R](name) { site.emitAndReplyWithTimeout[Unit, R](duration.toNanos, this, (), new ReplyValue[Unit, R](molecule = this)) } -class FE(name: String) extends F[Unit](name) { - override def apply(): Unit = site.emitAndReply[Unit, Unit](this, (), new EmptyReplyValue[Unit](this)) +/** Specialized class for blocking molecule emitters with non-empty value and empty reply. + * The reply action for these molecules can be performed with syntax r() without a deprecation warning. + * Example: go { case ef(x, r) => r() } + * + * Macro call b[T, Unit] returns this type when T is not Unit. + * + * @param name Name of the molecule, used for debugging only. + * @tparam T Type of the value carried by the molecule. + */ +final class EF[T](name: String) extends B[T, Unit](name) { + override def apply(v: T): Unit = site.emitAndReply[T, Unit](this, v, new EmptyReplyValue[T](molecule = this)) + + override def timeout(duration: Duration)(v: T): Option[Unit] = + site.emitAndReplyWithTimeout[T, Unit](duration.toNanos, this, v, new EmptyReplyValue[T](molecule = this)) +} + +/**Specialized class for blocking molecule emitters with empty value and empty reply. + * These molecules can be emitted with syntax fe() without a deprecation warning. + * The reply action for these molecules can be performed with syntax r() without a deprecation warning. + * Example: go { case ef(x, r) => r() } + * + * Macro call b[Unit, Unit] returns this type. + * + * @param name Name of the molecule, used for debugging only. + */ +final class FE(name: String) extends F[Unit](name) { + override def apply(): Unit = site.emitAndReply[Unit, Unit](this, (), new EmptyReplyValue[Unit](molecule = this)) override def timeout(duration: Duration)(): Option[Unit] = - site.emitAndReplyWithTimeout[Unit, Unit](duration.toNanos, this, (), new ReplyValue[Unit, Unit](molecule = this)) - + site.emitAndReplyWithTimeout[Unit, Unit](duration.toNanos, this, (), new EmptyReplyValue[Unit](molecule = this)) } /** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. diff --git a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala index be312536..14a6d44f 100644 --- a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala @@ -87,7 +87,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "use the first reply when a reaction attempts to reply twice" in { val c = new M[Int]("c") val d = new E("d") - val f = new F[Unit]("f") + val f = new FE("f") val g = new F[Int]("g") site(tp0)( _go { case c(n) + g(_,r) => c(n); r(n); r(n+1); d() }, @@ -266,10 +266,10 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests tp1.shutdownNow() } - def makeBlockingCheck(sleeping: => Unit, tp1: Pool): (F[Unit], F[Int]) = { + def makeBlockingCheck(sleeping: => Unit, tp1: Pool): (FE, F[Int]) = { val c = new E("c") val d = new E("d") - val g = new F[Unit]("g") + val g = new FE("g") val g2 = new F[Int]("g2") site(tp0)( _go { case c(_) + g(_, r) => r() } ) // we will use this to monitor the d() reaction @@ -334,14 +334,14 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests behavior of "thread starvation with different threadpools" - def blockThreadsDueToBlockingMolecule(tp1: Pool): F[Unit] = { + def blockThreadsDueToBlockingMolecule(tp1: Pool): FE = { val c = new E("c") val cStarted = new E("cStarted") val never = new E("never") val f = new F[Int]("forever") val f2 = new F[Int]("forever2") - val g = new F[Unit]("g") - val started = new F[Unit]("started") + val g = new FE("g") + val started = new FE("started") site(tp1,tp0)( _go { case g(_, r) => r() }, // and so this reaction will be blocked forever diff --git a/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala index c4a736ac..967fe450 100644 --- a/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala @@ -72,7 +72,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val a = new E("a") val b = new E("b") val c = new E("c") - val f = new F[Unit]("f") + val f = new FE("f") site(tp0)( _go { case a(_) + b(_) + c(_) + f(_, r) => r() } @@ -177,7 +177,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern is nonlinear, with blocking molecule" in { val thrown = intercept[Exception] { - val a = new F[Unit]("a") + val a = new FE("a") site( _go { case a(_,r) + a(_,s) => () }) } thrown.getMessage shouldEqual "Nonlinear pattern: a/B used twice" @@ -185,7 +185,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern attempts to redefine a blocking molecule" in { val thrown = intercept[Exception] { - val a = new F[Unit]("a") + val a = new FE("a") site( _go { case a(_,_) => () }) site( _go { case a(_,_) => () }) } @@ -204,7 +204,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to emit a blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new F[Unit]("x") + val a = new FE("x") a() } thrown.getMessage shouldEqual "Molecule x/B is not bound to any reaction site" @@ -220,7 +220,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to log soup of a blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new F[Unit]("x") + val a = new FE("x") a.logSoup } thrown.getMessage shouldEqual "Molecule x/B is not bound to any reaction site" @@ -313,7 +313,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val c = new M[Int]("counter") val d = new E("decrement") - val g = new F[Unit]("getValue") + val g = new FE("getValue") val tp = new FixedPool(2) site(tp0)( @@ -337,7 +337,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val c = new M[Int]("counter") val d = new E("decrement") - val g = new F[Unit]("getValue") + val g = new FE("getValue") val tp = new FixedPool(2) site(tp0)( @@ -360,8 +360,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val probabilityOfCrash = 0.5 val c = new M[Int]("counter") - val d = new F[Unit]("decrement") - val g = new F[Unit]("getValue") + val d = new FE("decrement") + val g = new FE("getValue") val tp = new FixedPool(2) site(tp0)( diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index 8286bc8e..b2410679 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -55,10 +55,17 @@ object Macros { val moleculeValueType = c.weakTypeOf[T] val replyValueType = c.weakTypeOf[R] - if (moleculeValueType =:= typeOf[Unit]) - c.Expr[B[T,R]](q"new F[$replyValueType]($moleculeName)") - else - c.Expr[B[T, R]](q"new B[$moleculeValueType,$replyValueType]($moleculeName)") + if (replyValueType =:= typeOf[Unit]) { + if (moleculeValueType =:= typeOf[Unit]) + c.Expr[B[T, R]](q"new FE[$replyValueType]($moleculeName)") + else + c.Expr[B[T, R]](q"new EF[$moleculeValueType]($moleculeName)") + } else { + if (moleculeValueType =:= typeOf[Unit]) + c.Expr[B[T, R]](q"new F[$replyValueType]($moleculeName)") + else + c.Expr[B[T, R]](q"new B[$moleculeValueType,$replyValueType]($moleculeName)") + } } /** Describes the pattern matcher for input molecules. From 3935c7dd8a8471b933a7275c74ef56d80a552983 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 20:35:53 -0800 Subject: [PATCH 07/12] eliminate all deprecation warnings --- .../code/winitzki/benchmark/Benchmarks1.scala | 4 +- .../code/winitzki/benchmark/Benchmarks4.scala | 4 +- .../code/winitzki/benchmark/Benchmarks7.scala | 8 +-- .../code/winitzki/benchmark/Benchmarks9.scala | 12 ++-- .../code/winitzki/benchmark/JiansenJoin.scala | 2 +- .../benchmark/JiansenFairnessSpec.scala | 17 +++-- .../test/DiningPhilosophersSpec.scala | 8 +-- .../code/winitzki/test/ParallelOrSpec.scala | 4 +- .../code/winitzki/test/ShutdownSpec.scala | 4 +- .../scala/code/winitzki/jc/Molecules.scala | 62 ++++++++++++----- .../scala/code/winitzki/jc/ReactionSite.scala | 4 +- .../main/scala/code/winitzki/jc/package.scala | 2 +- .../winitzki/jc/BlockingMoleculesSpec.scala | 66 +++++++++---------- .../scala/code/winitzki/jc/ChymystSpec.scala | 8 +-- .../code/winitzki/jc/MoleculesSpec.scala | 26 ++++---- .../scala/code/winitzki/jc/PoolSpec.scala | 10 +-- .../main/scala/code/winitzki/jc/Macros.scala | 11 ++-- .../scala/code/winitzki/jc/MacrosSpec.scala | 4 +- 18 files changed, 145 insertions(+), 111 deletions(-) diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala index 58713fe9..cef7730c 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks1.scala @@ -95,7 +95,7 @@ object Benchmarks1 { } j2.c(count) - (1 to count).foreach{ _ => j2.d() } + (1 to count).foreach{ _ => j2.d(()) } j2.f(initialTime) } @@ -104,7 +104,7 @@ object Benchmarks1 { val initialTime = LocalDateTime.now val (d,_,f,_) = make_counter2a(count) - (1 to count).foreach{ _ => d() } + (1 to count).foreach{ _ => d(()) } f(initialTime) } diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala index 8d50ed21..685be7c8 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks4.scala @@ -56,7 +56,7 @@ object Benchmarks4 { case a2(n) => a3(n) case a3(n) => a4(n) case a4(n) => a5(n) - case a5(m) => if (m==0) f() else a0(m-1) + case a5(m) => if (m==0) f(()) else a0(m-1) } a0(count) @@ -280,7 +280,7 @@ object Benchmarks4 { // case a97(n) => a98(n) // case a98(n) => a99(n) - case a99(m) => if (m==0) f() else a0(m-1) + case a99(m) => if (m==0) f(()) else a0(m-1) } a0(count) diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala index 180cd1a2..afedc6b0 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks7.scala @@ -53,11 +53,11 @@ object Benchmarks7 { val initialTime = LocalDateTime.now j8.all_done(count) val d = make_counters8a(j8.done, numberOfCounters, count) - (1 to (count*numberOfCounters)).foreach{ _ => d() } + (1 to (count*numberOfCounters)).foreach{ _ => d(()) } j8.f(initialTime) } - def make_counters(done: M[Unit], counters: Int, init: Int, tp: Pool) = { + private def make_counters(done: E, counters: Int, init: Int, tp: Pool) = { val c = m[Int] val d = m[Unit] @@ -70,13 +70,13 @@ object Benchmarks7 { d } - def make_counters8a(done: AsyName[Unit], counters: Int, init: Int): AsyName[Unit] = { + private def make_counters8a(done: AsyName[Unit], counters: Int, init: Int): AsyName[Unit] = { object j8a extends Join { object c extends AsyName[Int] object d extends AsyName[Unit] join { - case c(0) => done() + case c(0) => done(()) case c(n) and d(_) if n > 0 => c(n-1) } diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala index 80f098cb..7e8b6520 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/Benchmarks9.scala @@ -10,12 +10,12 @@ object Benchmarks9 { val numberOfCounters = 5 - def make_counter_1(done: M[Unit], counters: Int, init: Int, tp: Pool): B[Unit,Unit] = { + def make_counter_1(done: M[Unit], counters: Int, init: Int, tp: Pool): EE = { val c = m[Int] val d = b[Unit, Unit] site(tp)( - go { case c(0) => done() }, + go { case c(0) => done(()) }, go { case c(n) + d(_, r) if n > 0 => c(n - 1); r() } ) (1 to counters).foreach(_ => c(init)) @@ -46,7 +46,7 @@ object Benchmarks9 { } - def make_ping_pong_stack(done: M[Unit], tp: Pool): B[Int,Int] = { + def make_ping_pong_stack(done: E, tp: Pool): B[Int,Int] = { val c = m[Unit] val d = b[Int, Int] val e = b[Int, Int] @@ -140,8 +140,8 @@ object Benchmarks9 { object d extends SynName[Unit, Unit] join { - case c(0) => done() - case c(n) and d(_) if n > 0 => c(n-1); d.reply() + case c(0) => done(()) + case c(n) and d(_) if n > 0 => c(n-1); d.reply(()) } } import b9c1._ @@ -170,7 +170,7 @@ object Benchmarks9 { all_done(numberOfCounters) val d = make_counter_1_Jiansen(done, numberOfCounters, count) - (1 to (count*numberOfCounters)).foreach{ _ => d() } + (1 to (count*numberOfCounters)).foreach{ _ => d(()) } var result = f(initialTime) result diff --git a/benchmark/src/main/scala/code/winitzki/benchmark/JiansenJoin.scala b/benchmark/src/main/scala/code/winitzki/benchmark/JiansenJoin.scala index 5b331d49..a95254a0 100644 --- a/benchmark/src/main/scala/code/winitzki/benchmark/JiansenJoin.scala +++ b/benchmark/src/main/scala/code/winitzki/benchmark/JiansenJoin.scala @@ -132,7 +132,7 @@ class AsyName[Arg](implicit owner: Join, argT:ClassTag[Arg]) extends NameBase{ * @param argT the descriptor the argument type * @param resT the descriptor the return type */ -class SynName[Arg, R](implicit owner: Join, argT:ClassManifest[Arg], resT:ClassManifest[R]) extends NameBase{ +class SynName[Arg, R](implicit owner: Join, argT:ClassTag[Arg], resT:ClassTag[R]) extends NameBase{ // does not support subtyping override def argTypeEqual(t:Any) :Boolean = t match { diff --git a/benchmark/src/test/scala/code/winitzki/benchmark/JiansenFairnessSpec.scala b/benchmark/src/test/scala/code/winitzki/benchmark/JiansenFairnessSpec.scala index 64353d0a..62527de2 100644 --- a/benchmark/src/test/scala/code/winitzki/benchmark/JiansenFairnessSpec.scala +++ b/benchmark/src/test/scala/code/winitzki/benchmark/JiansenFairnessSpec.scala @@ -31,17 +31,20 @@ class JiansenFairnessSpec extends FlatSpec with Matchers with TimeLimitedTests { join { case getC(_) and done(arr) => getC.reply(arr) - case a0(_) and c((n, arr)) => if (n > 0) { arr(0) += 1; c((n-1,arr)); a0() } else done(arr) - case a1(_) and c((n, arr)) => if (n > 0) { arr(1) += 1; c((n-1,arr)); a1() } else done(arr) - case a2(_) and c((n, arr)) => if (n > 0) { arr(2) += 1; c((n-1,arr)); a2() } else done(arr) - case a3(_) and c((n, arr)) => if (n > 0) { arr(3) += 1; c((n-1,arr)); a3() } else done(arr) + case a0(_) and c((n, arr)) => if (n > 0) { arr(0) += 1; c((n-1,arr)); a0(()) } else done(arr) + case a1(_) and c((n, arr)) => if (n > 0) { arr(1) += 1; c((n-1,arr)); a1(()) } else done(arr) + case a2(_) and c((n, arr)) => if (n > 0) { arr(2) += 1; c((n-1,arr)); a2(()) } else done(arr) + case a3(_) and c((n, arr)) => if (n > 0) { arr(3) += 1; c((n-1,arr)); a3(()) } else done(arr) } } - j3.a0(); j3.a1(); j3.a2(); j3.a3() + j3.a0(()) + j3.a1(()) + j3.a2(()) + j3.a3(()) j3.c((N, Array.fill[Int](reactions)(0))) - val result = j3.getC() + val result = j3.getC(()) // println(result.mkString(", ")) @@ -81,7 +84,7 @@ class JiansenFairnessSpec extends FlatSpec with Matchers with TimeLimitedTests { Thread.sleep(200) j4.c(cycles) - val result = j4.getC() + val result = j4.getC(()) // println(result.mkString(", ")) diff --git a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala index 841109e8..11d7a614 100644 --- a/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/DiningPhilosophersSpec.scala @@ -40,7 +40,7 @@ class DiningPhilosophersSpec extends FlatSpec with Matchers with TimeLimitedTest val f51 = new E("f51") val done = new E("done") - val check = new FE("check") + val check = new EE("check") site(tp, tp) ( go { case t1(n) => rw(h1); h1(n - 1) }, @@ -61,13 +61,13 @@ class DiningPhilosophersSpec extends FlatSpec with Matchers with TimeLimitedTest t1(cycles) + t2(cycles) + t3(cycles) + t4(cycles) + t5(cycles) f12() + f23() + f34() + f45() + f51() - check() shouldEqual () + check() shouldEqual (()) // stop the simulation: this should be in unit tests, not here // not yet implemented /* - val stop = ja[Unit] - val wait_for_stop = js[Unit,Unit] + val stop = m[Unit] + val wait_for_stop = b[Unit,Unit] site( go { case stop(_) + wait_for_stop(_,r) => r() } ) wait_until_quiet(t1, stop) wait_for_stop() diff --git a/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala b/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala index 1639c0e5..d0ea790d 100644 --- a/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/ParallelOrSpec.scala @@ -23,7 +23,7 @@ class ParallelOrSpec extends FlatSpec with Matchers { * @param tp Thread pool on which to run this. * @return New blocking molecule emitter that will return the desired result or block. */ - def parallelOr(f: F[Boolean], g: F[Boolean], tp: Pool): F[Boolean] = { + def parallelOr(f: EB[Boolean], g: EB[Boolean], tp: Pool): EB[Boolean] = { val c = m[Unit] val d = m[Unit] val done = m[Boolean] @@ -98,7 +98,7 @@ class ParallelOrSpec extends FlatSpec with Matchers { * @tparam T Type of the return value. * @return New blocking molecule emitter that will return the desired result. */ - def firstResult[T](b1: F[T], b2: F[T], tp: Pool): F[T] = { + def firstResult[T](b1: EB[T], b2: EB[T], tp: Pool): EB[T] = { val get = b[Unit, T] val res = b[Unit, T] val res1 = m[Unit] diff --git a/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala b/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala index 21fd8cec..21bb73fb 100644 --- a/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala +++ b/joinrun/src/test/scala/code/winitzki/test/ShutdownSpec.scala @@ -37,7 +37,7 @@ class ShutdownSpec extends FlatSpec with Matchers { x() - pool2.shutdownNow() shouldEqual () + pool2.shutdownNow() shouldEqual (()) } it should "fail to schedule reactions after shutdown of default thread pools" in { @@ -46,7 +46,7 @@ class ShutdownSpec extends FlatSpec with Matchers { defaultReactionPool.shutdownNow() val x = m[Unit] - site(go{ case x(()) => }) + site(go{ case x(_) => }) val thrown = intercept[Exception] { x() diff --git a/lib/src/main/scala/code/winitzki/jc/Molecules.scala b/lib/src/main/scala/code/winitzki/jc/Molecules.scala index fdedd44c..4deed857 100644 --- a/lib/src/main/scala/code/winitzki/jc/Molecules.scala +++ b/lib/src/main/scala/code/winitzki/jc/Molecules.scala @@ -336,16 +336,14 @@ private[jc] trait NonblockingMolecule[T] extends Molecule { } private[jc] trait BlockingMolecule[T, R] extends Molecule { + /** This type will be ReplyValue[T,R] or EmptyReplyValue[R] depending on the class that inherits from BlockingMolecule. + */ + type Reply + val isBlocking = true val isSingleton = false -// protected def makeReplyValue(molecule: BlockingMolecule[T, R]): ReplyValue[T, R] = { -// -// val replyGeneric: ReplyValue[T, R] = new ReplyValue[T,R](molecule) -// val replySpecific: ReplyValue[T, Unit] = new EmptyReplyValue[T](molecule) -// } - /** Emit a blocking molecule and receive a value when the reply action is performed, unless a timeout is reached. * * @param duration Timeout in any time interval. @@ -355,11 +353,19 @@ private[jc] trait BlockingMolecule[T, R] extends Molecule { def timeout(duration: Duration)(v: T): Option[R] = site.emitAndReplyWithTimeout[T,R](duration.toNanos, this, v, new ReplyValue[T,R](molecule = this)) - def unapply(arg: UnapplyArg): Option[(T, ReplyValue[T,R])] = arg match { + /** Perform the unapply matching and return a generic ReplyValue. + * The specific implementation of unapply will possibly downcast this to EmptyReplyValue. + * + * @param arg One of the UnapplyArg case classes supplied by the reaction site at different phases of making decisions about what reactions to run. + * @return None if there was no match; Some(...) if the reaction inputs matched. + * Also note that a side effect is performed on the mutable data inside UnapplyArg. + * In this way, {{{isDefinedAt}}} is used to gather data about the input molecule values. + */ + def unapplyInternal(arg: UnapplyArg): Option[(T, Reply)] = arg match { case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go inputMoleculesProbe += this - Some((null, null).asInstanceOf[(T, ReplyValue[T,R])]) // hack for testing purposes only: + Some((null, null).asInstanceOf[(T, Reply)]) // hack for testing purposes only: // The null value will not be used in any production code since _go is private. // This is used just before running the actual reactions, to determine which ones pass all the pattern-matching tests. @@ -369,13 +375,13 @@ private[jc] trait BlockingMolecule[T, R] extends Molecule { v <- moleculeValues.getOne(this) } yield { usedInputs += (this -> v) - (v.getValue, null).asInstanceOf[(T, ReplyValue[T, R])] // hack for verifying isDefinedAt: + (v.getValue, null).asInstanceOf[(T, Reply)] // hack for verifying isDefinedAt: // The null value will not be used, since the reply value is always matched unconditionally. } // This is used when running the chosen reaction. case UnapplyRun(moleculeValues) => moleculeValues.get(this).map { - case BlockingMolValue(v, srv) => (v, srv).asInstanceOf[(T, ReplyValue[T, R])] + case BlockingMolValue(v, srv) => (v, srv).asInstanceOf[(T, Reply)] case m@_ => throw new ExceptionNoWrapper(s"Internal error: molecule $this with no value wrapper around value $m") } @@ -400,7 +406,7 @@ final class E(name: String) extends M[Unit](name) { * @param name Name of the molecule, used for debugging only. * @tparam R Type of the value replied to the caller via the "reply" action. */ -class F[R](name: String) extends B[Unit, R](name) { +final class EB[R](name: String) extends B[Unit, R](name) { def apply(): R = site.emitAndReply[Unit, R](this, (), new ReplyValue[Unit, R](molecule = this)) def timeout(duration: Duration)(): Option[R] = @@ -416,11 +422,16 @@ class F[R](name: String) extends B[Unit, R](name) { * @param name Name of the molecule, used for debugging only. * @tparam T Type of the value carried by the molecule. */ -final class EF[T](name: String) extends B[T, Unit](name) { +final class BE[T](name: String) extends B[T, Unit](name) { + + override type Reply = EmptyReplyValue[T] + override def apply(v: T): Unit = site.emitAndReply[T, Unit](this, v, new EmptyReplyValue[T](molecule = this)) override def timeout(duration: Duration)(v: T): Option[Unit] = site.emitAndReplyWithTimeout[T, Unit](duration.toNanos, this, v, new EmptyReplyValue[T](molecule = this)) + + override def unapply(arg: UnapplyArg): Option[(T, EmptyReplyValue[T])] = unapplyInternal(arg) } /**Specialized class for blocking molecule emitters with empty value and empty reply. @@ -432,11 +443,16 @@ final class EF[T](name: String) extends B[T, Unit](name) { * * @param name Name of the molecule, used for debugging only. */ -final class FE(name: String) extends F[Unit](name) { - override def apply(): Unit = site.emitAndReply[Unit, Unit](this, (), new EmptyReplyValue[Unit](molecule = this)) +final class EE(name: String) extends B[Unit, Unit](name) { + + type Reply = EmptyReplyValue[Unit] - override def timeout(duration: Duration)(): Option[Unit] = + def apply(): Unit = site.emitAndReply[Unit, Unit](this, (), new EmptyReplyValue[Unit](molecule = this)) + + def timeout(duration: Duration)(): Option[Unit] = site.emitAndReplyWithTimeout[Unit, Unit](duration.toNanos, this, (), new EmptyReplyValue[Unit](molecule = this)) + + override def unapply(arg: UnapplyArg): Option[(Unit, EmptyReplyValue[Unit])] = unapplyInternal(arg) } /** Non-blocking molecule class. Instance is mutable until the molecule is bound to a reaction site and until all reactions involving this molecule are declared. @@ -522,7 +538,17 @@ private[jc] trait AbsReplyValue[T, R] { } } -private[jc] class EmptyReplyValue[T](molecule: BlockingMolecule[T, Unit]) extends (() => Boolean) with AbsReplyValue[T, Unit] { +/** Specialized reply-value wrapper for blocking molecules with Unit reply values. This is a mutable class. + * + * @param molecule The blocking molecule whose reply value this wrapper represents. + * @tparam T Type of the value carried by the molecule. + */ +private[jc] class EmptyReplyValue[T](molecule: BlockingMolecule[T, Unit]) extends ReplyValue[T, Unit](molecule) with (()=>Boolean) { + /** Perform a reply action for blocking molecules with Unit reply values. The reply action can use the syntax `r()` without deprecation warnings. + * For each blocking molecule consumed by a reaction, exactly one reply action should be performed within the reaction body. + * + * @return True if the reply was successful. False if the blocking molecule timed out, or if a reply action was already performed. + */ override def apply(): Boolean = applyInternal(()) } @@ -551,6 +577,8 @@ private[jc] class ReplyValue[T, R](molecule: BlockingMolecule[T, R]) extends (R */ class B[T, R](val name: String) extends (T => R) with BlockingMolecule[T, R] { + type Reply <: ReplyValue[T, R] + /** Emit a blocking molecule and receive a value when the reply action is performed. * * @param v Value to be put onto the emitted molecule. @@ -558,6 +586,8 @@ class B[T, R](val name: String) extends (T => R) with BlockingMolecule[T, R] { */ def apply(v: T): R = site.emitAndReply[T,R](this, v, new ReplyValue[T,R](molecule = this)) + + def unapply(arg: UnapplyArg): Option[(T, Reply)] = unapplyInternal(arg) } private[jc] sealed trait UnapplyArg // The disjoint union type for arguments passed to the unapply methods. diff --git a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala index fb0b2571..0a6a2322 100644 --- a/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala +++ b/lib/src/main/scala/code/winitzki/jc/ReactionSite.scala @@ -44,7 +44,7 @@ private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Poo private[jc] val reactionInfos: Map[Reaction, List[InputMoleculeInfo]] = nonSingletonReactions.map { r => (r, r.info.inputs) }.toMap // TODO: implement - private val quiescenceCallbacks: mutable.Set[M[Unit]] = mutable.Set.empty + private val quiescenceCallbacks: mutable.Set[E] = mutable.Set.empty private lazy val knownReactions: Seq[Reaction] = reactionInfos.keys.toSeq @@ -64,7 +64,7 @@ private[jc] final class ReactionSite(reactions: Seq[Reaction], reactionPool: Poo s"${this.toString}\n$moleculesPrettyPrinted" } - private[jc] def setQuiescenceCallback(callback: M[Unit]): Unit = { + private[jc] def setQuiescenceCallback(callback: E): Unit = { quiescenceCallbacks.add(callback) () } diff --git a/lib/src/main/scala/code/winitzki/jc/package.scala b/lib/src/main/scala/code/winitzki/jc/package.scala index b9d7b452..00e86b37 100644 --- a/lib/src/main/scala/code/winitzki/jc/package.scala +++ b/lib/src/main/scala/code/winitzki/jc/package.scala @@ -53,7 +53,7 @@ package object jc { // Wait until the reaction site to which `molecule` is bound becomes quiescent, then emit `callback`. // TODO: implement - def waitUntilQuiet[T](molecule: M[T], callback: M[Unit]): Unit = molecule.site.setQuiescenceCallback(callback) + def waitUntilQuiet[T](molecule: M[T], callback: E): Unit = molecule.site.setQuiescenceCallback(callback) /** Create a reaction value out of a simple reaction body. Used only for testing. * The reaction body must be "simple" in the sense that it allows very limited pattern-matching with molecule values: diff --git a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala index 14a6d44f..dcb76b5c 100644 --- a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala @@ -34,7 +34,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "block for a blocking molecule" in { val a = new E("a") - val f = new F[Int]("f") + val f = new EB[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => r(3) }) a() a() @@ -47,7 +47,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "not timeout when a blocking molecule is responding" in { (1 to 1000).map { _ => - val f = new F[Int]("f") + val f = new EB[Int]("f") site(tp0)( _go { case f(_, r) => r(0) }) f.timeout(300 millis)().getOrElse(1) @@ -57,7 +57,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "timeout when a blocking molecule is not responding at all" in { val a = new E("a") - val f = new F[Int]("f") + val f = new EB[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => r(3) }) a() f() shouldEqual 3 // now the a() molecule is gone @@ -67,7 +67,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "not timeout when a blocking molecule is responding quickly enough" in { val a = new E("a") - val f = new F[Int]("f") + val f = new EB[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => Thread.sleep(100); r(3) }) a() f.timeout(500 millis)() shouldEqual Some(3) @@ -76,7 +76,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "timeout when a blocking molecule is not responding quickly enough" in { val a = new E("a") - val f = new F[Int]("f") + val f = new EB[Int]("f") site(tp0)( _go { case a(_) + f(_, r) => Thread.sleep(500); r(3) }) a() f.timeout(200 millis)() shouldEqual None @@ -87,8 +87,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "use the first reply when a reaction attempts to reply twice" in { val c = new M[Int]("c") val d = new E("d") - val f = new FE("f") - val g = new F[Int]("g") + val f = new EE("f") + val g = new EB[Int]("g") site(tp0)( _go { case c(n) + g(_,r) => c(n); r(n); r(n+1); d() }, _go { case d(_) + f(_,r) => r() } @@ -102,9 +102,9 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests val c = new M[Int]("c") val d = new E("d") val d2 = new M[Int]("d2") - val e = new F[Int]("e") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") + val e = new EB[Int]("e") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") site(tp0)( _go { case d(_) => d2(g2()) }, _go { case d2(x) + e(_, r) => r(x) }, @@ -118,7 +118,7 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "throw exception when a reaction does not reply to one blocking molecule" in { val c = new E("c") - val g = new F[Int]("g") + val g = new EB[Int]("g") site(tp0)( _go { case c(_) + g(_,r) => c() } ) @@ -134,8 +134,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "throw exception when a reaction does not reply to two blocking molecules)" in { val c = new E("c") val d = new E("d") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") val tp = new FixedPool(4) site(tp)( _go { case d(_) => g2() } onThreads tp, @@ -155,8 +155,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "get a reply when a reaction does not reply to one blocking molecule but does reply to another" in { val c = new E("c") val d = new E("d") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") val tp = new FixedPool(4) site(tp)( _go { case d(_) => g() } onThreads tp, @@ -180,9 +180,9 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests val d = new E("d") val e = new M[Int]("e") val f = new E("f") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") - val h = new F[Int]("h") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") + val h = new EB[Int]("h") val tp = new FixedPool(4) site(tp)( _go { case c(_) => e(g2()) }, // e(0) should be emitted now @@ -204,9 +204,9 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests val d = new E("d") val e = new M[Int]("e") val f = new E("f") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") - val h = new F[Int]("h") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") + val h = new EB[Int]("h") val tp = new FixedPool(4) site(tp)( _go { case c(_) => val x = g(); g2(); e(x) }, // e(0) should never be emitted because this thread is deadlocked @@ -233,8 +233,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "not block the 2-thread threadpool when one thread is blocked" in { val c = new E("c") val d = new E("d") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") val tp = new FixedPool(4) site(tp)( _go { case d(_) => g() }, // this will be used to emit g() and blocked @@ -250,8 +250,8 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests it should "block the 1-thread threadpool when one thread is blocked" in { val c = new E("c") val d = new E("d") - val g = new F[Int]("g") - val g2 = new F[Int]("g2") + val g = new EB[Int]("g") + val g2 = new EB[Int]("g2") val tp = new FixedPool(1) val tp1 = new FixedPool(1) site(tp,tp1)( @@ -266,11 +266,11 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests tp1.shutdownNow() } - def makeBlockingCheck(sleeping: => Unit, tp1: Pool): (FE, F[Int]) = { + def makeBlockingCheck(sleeping: => Unit, tp1: Pool): (EE, EB[Int]) = { val c = new E("c") val d = new E("d") - val g = new FE("g") - val g2 = new F[Int]("g2") + val g = new EE("g") + val g2 = new EB[Int]("g2") site(tp0)( _go { case c(_) + g(_, r) => r() } ) // we will use this to monitor the d() reaction @@ -334,14 +334,14 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests behavior of "thread starvation with different threadpools" - def blockThreadsDueToBlockingMolecule(tp1: Pool): FE = { + def blockThreadsDueToBlockingMolecule(tp1: Pool): EE = { val c = new E("c") val cStarted = new E("cStarted") val never = new E("never") - val f = new F[Int]("forever") - val f2 = new F[Int]("forever2") - val g = new FE("g") - val started = new FE("started") + val f = new EB[Int]("forever") + val f2 = new EB[Int]("forever2") + val g = new EE("g") + val started = new EE("started") site(tp1,tp0)( _go { case g(_, r) => r() }, // and so this reaction will be blocked forever diff --git a/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala index 4871d045..12a240c7 100644 --- a/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/ChymystSpec.scala @@ -25,7 +25,7 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { it should "emit a molecule from a future computed out of a given future" in { val c = new E("c") - val f = new F[Unit]("f") + val f = new EE("f") val tp = new FixedPool(2) site(tp)( @@ -34,7 +34,7 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { Future { Thread.sleep(50) } & c // insert a molecule from the end of the future - f() shouldEqual () + f() shouldEqual (()) tp.shutdownNow() } @@ -61,8 +61,8 @@ class ChymystSpec extends FlatSpec with Matchers with TimeLimitedTests { val c = new E("c") val d = new E("d") val e = new E("e") - val f = new F[String]("f") - val f2 = new F[String]("f2") + val f = new EB[String]("f") + val f2 = new EB[String]("f2") val tp = new FixedPool(4) site(tp)( diff --git a/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala index 967fe450..a61da83e 100644 --- a/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/MoleculesSpec.scala @@ -72,7 +72,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val a = new E("a") val b = new E("b") val c = new E("c") - val f = new FE("f") + val f = new EE("f") site(tp0)( _go { case a(_) + b(_) + c(_) + f(_, r) => r() } @@ -177,7 +177,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern is nonlinear, with blocking molecule" in { val thrown = intercept[Exception] { - val a = new FE("a") + val a = new EE("a") site( _go { case a(_,r) + a(_,s) => () }) } thrown.getMessage shouldEqual "Nonlinear pattern: a/B used twice" @@ -185,7 +185,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when join pattern attempts to redefine a blocking molecule" in { val thrown = intercept[Exception] { - val a = new FE("a") + val a = new EE("a") site( _go { case a(_,_) => () }) site( _go { case a(_,_) => () }) } @@ -204,7 +204,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to emit a blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new FE("x") + val a = new EE("x") a() } thrown.getMessage shouldEqual "Molecule x/B is not bound to any reaction site" @@ -220,7 +220,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "throw exception when trying to log soup of a blocking molecule that has no join" in { val thrown = intercept[Exception] { - val a = new FE("x") + val a = new EE("x") a.logSoup } thrown.getMessage shouldEqual "Molecule x/B is not bound to any reaction site" @@ -238,7 +238,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val a = new M[Int]("a") val b = new M[Int]("b") - val f = new F[Int]("f") + val f = new EB[Int]("f") site(tp0)( _go { case a(x) + b(0) => a(x+1) }, _go { case a(z) + f(_, r) => r(z) }) a(1) @@ -249,7 +249,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be it should "implement the non-blocking single-access counter" in { val c = new M[Int]("c") val d = new E("decrement") - val g = new F[Int]("getValue") + val g = new EB[Int]("getValue") site(tp0)( _go { case c(n) + d(_) => c(n-1) }, _go { case c(0) + g(_,r) => r(0) } @@ -263,7 +263,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val d = new E("decrement") val f = new E("finished") val a = new M[Int]("all_finished") - val g = new F[Int]("getValue") + val g = new EB[Int]("getValue") val tp = new FixedPool(1) @@ -290,7 +290,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val d = new E("decrement") val f = new E("finished") val a = new M[Int]("all_finished") - val g = new F[Int]("getValue") + val g = new EB[Int]("getValue") val tp = new FixedPool(2) @@ -313,7 +313,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val c = new M[Int]("counter") val d = new E("decrement") - val g = new FE("getValue") + val g = new EE("getValue") val tp = new FixedPool(2) site(tp0)( @@ -337,7 +337,7 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val c = new M[Int]("counter") val d = new E("decrement") - val g = new FE("getValue") + val g = new EE("getValue") val tp = new FixedPool(2) site(tp0)( @@ -360,8 +360,8 @@ class MoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests with Be val probabilityOfCrash = 0.5 val c = new M[Int]("counter") - val d = new FE("decrement") - val g = new FE("getValue") + val d = new EE("decrement") + val g = new EE("getValue") val tp = new FixedPool(2) site(tp0)( diff --git a/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala b/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala index d5dddbd0..f18505df 100644 --- a/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/PoolSpec.scala @@ -31,21 +31,21 @@ class PoolSpec extends FlatSpec with Matchers with TimeLimitedTests { } it should "run tasks on a thread with info, in fixed pool" in { - Chymyst.withPool(new FixedPool(2))(checkPool).get shouldEqual () + Chymyst.withPool(new FixedPool(2))(checkPool).get shouldEqual (()) } it should "run tasks on a thread with info, in cached pool" in { - Chymyst.withPool(new CachedPool(2))(checkPool).get shouldEqual () + Chymyst.withPool(new CachedPool(2))(checkPool).get shouldEqual (()) } it should "run tasks on a thread with info, in smart pool" in { - Chymyst.withPool(new SmartPool(2))(checkPool).get shouldEqual () + Chymyst.withPool(new SmartPool(2))(checkPool).get shouldEqual (()) } it should "run reactions on a thread with reaction info" in { Chymyst.withPool(new FixedPool(2)){ tp => val waiter = new Waiter - val a = new M[Unit]("a") + val a = new E("a") site(tp)( _go { case a(_) => @@ -66,7 +66,7 @@ class PoolSpec extends FlatSpec with Matchers with TimeLimitedTests { a() waiter.await()(patienceConfig, implicitly[Position]) - }.get shouldEqual() + }.get shouldEqual (()) } behavior of "fixed thread pool" diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index b2410679..67dca298 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -48,7 +48,7 @@ object Macros { def b[T, R]: B[T,R] = macro bImpl[T, R] // Does providing an explicit return type here as c.Expr[...] helps anything? Looks like it doesn't, so far. - def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: theContext): c.Expr[B[T, R]] = { + def bImpl[T: c.WeakTypeTag, R: c.WeakTypeTag](c: theContext): c.universe.Tree = { import c.universe._ val moleculeName = getEnclosingName(c) @@ -57,14 +57,15 @@ object Macros { if (replyValueType =:= typeOf[Unit]) { if (moleculeValueType =:= typeOf[Unit]) - c.Expr[B[T, R]](q"new FE[$replyValueType]($moleculeName)") + q"new EE($moleculeName)" else - c.Expr[B[T, R]](q"new EF[$moleculeValueType]($moleculeName)") + q"new BE[$moleculeValueType]($moleculeName)" } else { + // reply type is not Unit if (moleculeValueType =:= typeOf[Unit]) - c.Expr[B[T, R]](q"new F[$replyValueType]($moleculeName)") + q"new EB[$replyValueType]($moleculeName)" else - c.Expr[B[T, R]](q"new B[$moleculeValueType,$replyValueType]($moleculeName)") + q"new B[$moleculeValueType,$replyValueType]($moleculeName)" } } diff --git a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala index 241bdaab..e8b85a08 100644 --- a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala +++ b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala @@ -119,8 +119,8 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { (result.info.inputs match { case List( - InputMoleculeInfo(a, SimpleVar, simpleVarXSha1), - InputMoleculeInfo(bb, OtherInputPattern(_), "4B93FCEF4617B49161D3D2F83E34012391D5A883") + InputMoleculeInfo(`a`, SimpleVar, `simpleVarXSha1`), + InputMoleculeInfo(`bb`, OtherInputPattern(_), "4B93FCEF4617B49161D3D2F83E34012391D5A883") ) => true case _ => false }) shouldEqual true From 0ff365422dc1f4e2366e28b937098223e17ed0a8 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 20:50:39 -0800 Subject: [PATCH 08/12] minor refactor of M#unapply, giving up on more syntax sugar --- lib/src/main/scala/code/winitzki/jc/Molecules.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/main/scala/code/winitzki/jc/Molecules.scala b/lib/src/main/scala/code/winitzki/jc/Molecules.scala index 4deed857..c46a4a4b 100644 --- a/lib/src/main/scala/code/winitzki/jc/Molecules.scala +++ b/lib/src/main/scala/code/winitzki/jc/Molecules.scala @@ -309,9 +309,9 @@ private[jc] trait NonblockingMolecule[T] extends Molecule { override def isSingleton: Boolean = isSingletonBoolean - def unapply(arg: UnapplyArg): Option[T] = arg match { + def unapplyInternal(arg: UnapplyArg): Option[T] = arg match { - case UnapplyCheckSimple(inputMoleculesProbe) => // used only by _go + case UnapplyCheckSimple(inputMoleculesProbe) => // This is used only by `_go`. inputMoleculesProbe += this Some(null.asInstanceOf[T]) // hack for testing only. This value will not be used. @@ -476,6 +476,7 @@ class M[T](val name: String) extends (T => Unit) with NonblockingMolecule[T] { def hasVolatileValue: Boolean = site.hasVolatileValue(this) + def unapply(arg: UnapplyArg): Option[T] = unapplyInternal(arg) } /** This trait contains implementations of most methods for the [[ReplyValue]] and [[EmptyReplyValue]] classes. From 9a9c014fdee5ec700b1fa9f10a384082b32a250c Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 21:01:57 -0800 Subject: [PATCH 09/12] adding a test for the new emitter types EB and EE --- .../code/winitzki/jc/BlockingMoleculesSpec.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala index dcb76b5c..25804205 100644 --- a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala @@ -82,6 +82,20 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests f.timeout(200 millis)() shouldEqual None } + behavior of "syntax of blocking molecules with unit values / replies" + + it should "allow non-unit values and unit replies" in { + val f = new BE[Int]("f") + site(tp0)( _go { case f(x, r) if x == 123 => r() }) + f(123) shouldEqual (()) + } + + it should "allow unit values and unit replies" in { + val f = new EE("f") + site(tp0)( _go { case f(x, r) if x == (()) => r() }) + f() shouldEqual (()) + } + behavior of "reaction sites with invalid replies" it should "use the first reply when a reaction attempts to reply twice" in { From 4b872f346349c9604b63f2241e0a7a55832dc478 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 21:08:39 -0800 Subject: [PATCH 10/12] document the getName macro --- .../main/scala/code/winitzki/jc/Macros.scala | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index 67dca298..6038a871 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -18,20 +18,29 @@ object Macros { q"$result" } + /** This macro is not actually used by Chymyst. + * It serves only for testing the mechanism by which we detect the name of the enclosing value. + * For example, `val myVal = { 1; 2; 3; getName }` returns the string "myVal". + * + * @return The name of the enclosing value as string. + */ private[jc] def getName: String = macro getNameImpl - private def getEnclosingName(c: theContext): String = - c.internal.enclosingOwner.name.decodedName.toString - .stripSuffix(LOCAL_SUFFIX_STRING).stripSuffix("$lzy") - def getNameImpl(c: theContext): c.Expr[String] = { import c.universe._ - val s = getEnclosingName(c) - c.Expr[String](q"$s") } + /** This is how the enclosing name is detected. + * + * @param c The macro context. + * @return String that represents the name of the enclosing value. + */ + private def getEnclosingName(c: theContext): String = + c.internal.enclosingOwner.name.decodedName.toString + .stripSuffix(LOCAL_SUFFIX_STRING).stripSuffix("$lzy") + def m[T]: M[T] = macro mImpl[T] def mImpl[T: c.WeakTypeTag](c: theContext): c.universe.Tree = { From 2a705be16074c0035b8fe80f41407f9beb823047 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 21:22:01 -0800 Subject: [PATCH 11/12] update documentation with simplified imports --- README.md | 4 ++-- docs/chymyst01.md | 4 ++-- docs/chymyst02.md | 2 +- docs/chymyst03.md | 2 +- docs/chymyst04.md | 2 +- macros/src/main/scala/code/winitzki/jc/Macros.scala | 2 -- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 59b8816e..01b788fe 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ There is an integer counter value, to which we have non-blocking access via `inc We can also fetch the current counter value via the `get` molecule, which is blocking. The counter is initialized to the number we specify. ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ // Define the logic of the “non-blocking counter”. @@ -238,7 +238,7 @@ The library offers some debugging facilities: Here are the typical results: ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ val counter = b[Int] // the name of this molecule is "counter" diff --git a/docs/chymyst01.md b/docs/chymyst01.md index e2908c88..cc3ee364 100644 --- a/docs/chymyst01.md +++ b/docs/chymyst01.md @@ -116,7 +116,7 @@ So far, we have been using a kind of chemistry-resembling pseudocode to illustra This pseudocode was designed to prepare us for the actual syntax of `JoinRun`, which is only a little more verbose: ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ // declare the molecule types @@ -221,7 +221,7 @@ This automatically prevents race conditions with the counter: There is no possib The code shown above will not print any output, so it is perhaps instructive to put some print statements into the reaction bodies. ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ // declare the molecule emitters and the value types diff --git a/docs/chymyst02.md b/docs/chymyst02.md index 902db26b..6f29e906 100644 --- a/docs/chymyst02.md +++ b/docs/chymyst02.md @@ -115,7 +115,7 @@ go { case counter(0) + fetch(_, reply) => reply() } Here is the complete code: ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ import java.time.LocalDateTime.now diff --git a/docs/chymyst03.md b/docs/chymyst03.md index 572b205c..552db497 100644 --- a/docs/chymyst03.md +++ b/docs/chymyst03.md @@ -25,7 +25,7 @@ val fetch = new B[Unit, Int]("fetch") This code is completely equivalent to the shorter code written using macros: ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ val counter = m[Int] diff --git a/docs/chymyst04.md b/docs/chymyst04.md index ea87ba29..30a8c46b 100644 --- a/docs/chymyst04.md +++ b/docs/chymyst04.md @@ -124,7 +124,7 @@ Here is the complete code for this example (see also `MapReduceSpec.scala` in th We will apply the function `f(x) = x*x` to elements of an integer array, and then compute the sum of the resulting array of squares. ```scala -import code.winitzki.jc.JoinRun._ +import code.winitzki.jc._ import code.winitzki.jc.Macros._ object C extends App { diff --git a/macros/src/main/scala/code/winitzki/jc/Macros.scala b/macros/src/main/scala/code/winitzki/jc/Macros.scala index 6038a871..e47382a6 100644 --- a/macros/src/main/scala/code/winitzki/jc/Macros.scala +++ b/macros/src/main/scala/code/winitzki/jc/Macros.scala @@ -141,8 +141,6 @@ object Macros { def buildReactionImpl(c: theContext)(reactionBody: c.Expr[ReactionBody]): c.universe.Tree = { import c.universe._ -// val reactionBodyReset = c.untypecheck(reactionBody.tree) - /** Obtain the owner of the current macro call site. * * @return The owner symbol of the current macro call site. From 96cb545e5df30a91be255d301b2610ccadd0d284 Mon Sep 17 00:00:00 2001 From: winitzki Date: Wed, 21 Dec 2016 21:31:27 -0800 Subject: [PATCH 12/12] add tests for macros with new classes --- .../winitzki/jc/BlockingMoleculesSpec.scala | 24 +++++++++++++++++++ .../scala/code/winitzki/jc/MacrosSpec.scala | 20 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala index 25804205..2fa21310 100644 --- a/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala +++ b/lib/src/test/scala/code/winitzki/jc/BlockingMoleculesSpec.scala @@ -84,18 +84,42 @@ class BlockingMoleculesSpec extends FlatSpec with Matchers with TimeLimitedTests behavior of "syntax of blocking molecules with unit values / replies" + it should "allow non-unit values and non-unit replies" in { + val f = new B[Int,Int]("f") + site(tp0)( _go { case f(x, r) if x == 123 => r(456) }) + f(123) shouldEqual 456 + } + + it should "allow non-unit values and non-unit replies with timeout" in { + val f = new B[Int,Int]("f") + site(tp0)( _go { case f(x, r) if x != 123 => r(456) }) + f.timeout(200 millis)(123) shouldEqual None + } + it should "allow non-unit values and unit replies" in { val f = new BE[Int]("f") site(tp0)( _go { case f(x, r) if x == 123 => r() }) f(123) shouldEqual (()) } + it should "allow non-unit values and unit replies with timeout" in { + val f = new BE[Int]("f") + site(tp0)( _go { case f(x, r) if x != 123 => r() }) + f.timeout(200 millis)(123) shouldEqual None + } + it should "allow unit values and unit replies" in { val f = new EE("f") site(tp0)( _go { case f(x, r) if x == (()) => r() }) f() shouldEqual (()) } + it should "allow unit values and unit replies with timeout" in { + val f = new EE("f") + site(tp0)( _go { case f(x, r) if x != (()) => r() }) + f.timeout(200 millis)() shouldEqual None + } + behavior of "reaction sites with invalid replies" it should "use the first reply when a reaction attempts to reply twice" in { diff --git a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala index e8b85a08..4897e59c 100644 --- a/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala +++ b/macros/src/test/scala/code/winitzki/jc/MacrosSpec.scala @@ -34,6 +34,26 @@ class MacrosSpec extends FlatSpec with Matchers with BeforeAndAfterEach { s.toString shouldEqual "s/B" } + it should "create an emitter of class E for m[Unit]" in { + val a = m[Unit] + a.isInstanceOf[E] shouldEqual true + } + + it should "create an emitter of class BE[Int] for b[Int, Unit]" in { + val a = b[Int, Unit] + a.isInstanceOf[BE[Int]] shouldEqual true + } + + it should "create an emitter of class EB[Int] for b[Unit, Int]" in { + val a = b[Unit, Int] + a.isInstanceOf[EB[Int]] shouldEqual true + } + + it should "create an emitter of class EE for b[Unit, Unit]" in { + val a = b[Unit, Unit] + a.isInstanceOf[EE] shouldEqual true + } + behavior of "macros for inspecting a reaction body"