diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala
index 15316f727d..5fbf2dceaa 100644
--- a/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala
@@ -18,9 +18,8 @@ class DeployerSpec extends WordSpec with MustMatchers {
Deploy(
"service-ping",
LeastCPU,
- "akka.serialization.Format$Default$",
Clustered(
- Node("node1"),
+ Vector(Node("node1")),
Replicate(3),
Replication(
TransactionLog,
diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala
index c0cf6a554c..02955798c5 100644
--- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala
@@ -49,7 +49,7 @@ class SupervisorHierarchySpec extends JUnitSuite {
manager.startLink(workerTwo)
manager.startLink(workerThree)
- workerOne ! Exit(workerOne, new FireWorkerException("Fire the worker!"))
+ workerOne ! Death(workerOne, new FireWorkerException("Fire the worker!"))
// manager + all workers should be restarted by only killing a worker
// manager doesn't trap exits, so boss will restart manager
@@ -70,8 +70,8 @@ class SupervisorHierarchySpec extends JUnitSuite {
}).start()
boss.startLink(crasher)
- crasher ! Exit(crasher, new FireWorkerException("Fire the worker!"))
- crasher ! Exit(crasher, new FireWorkerException("Fire the worker!"))
+ crasher ! Death(crasher, new FireWorkerException("Fire the worker!"))
+ crasher ! Death(crasher, new FireWorkerException("Fire the worker!"))
assert(countDown.await(2, TimeUnit.SECONDS))
}
diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala
index 61d0da3555..dd7c6a5133 100644
--- a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala
@@ -259,18 +259,16 @@ abstract class ActorModelSpec extends JUnitSuite {
val counter = new CountDownLatch(200)
a.start()
- def start = spawn { for (i ← 1 to 20) { a ! WaitAck(1, counter) } }
- for (i ← 1 to 10) { start }
+ for (i ← 1 to 10) { spawn { for (i ← 1 to 20) { a ! WaitAck(1, counter) } } }
assertCountDown(counter, Testing.testTime(3000), "Should process 200 messages")
assertRefDefaultZero(a)(registers = 1, msgsReceived = 200, msgsProcessed = 200)
a.stop()
}
- def spawn(f: ⇒ Unit) = {
- val thread = new Thread { override def run { f } }
+ def spawn(f: ⇒ Unit) {
+ val thread = new Thread { override def run { try { f } catch { case e ⇒ e.printStackTrace } } }
thread.start()
- thread
}
@Test
@@ -369,3 +367,5 @@ class BalancingDispatcherModelTest extends ActorModelSpec {
def newInterceptedDispatcher =
new BalancingDispatcher("foo") with MessageDispatcherInterceptor
}
+
+
diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala
index ef951a90d0..f761268a73 100644
--- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala
@@ -309,6 +309,22 @@ class FutureSpec extends WordSpec with MustMatchers with Checkers with BeforeAnd
Futures.fold(0, timeout)(futures)(_ + _).await.exception.get.getMessage must be("shouldFoldResultsWithException: expected")
}
+/* @Test
+ def shouldFoldMutableZeroes {
+ import scala.collection.mutable.ArrayBuffer
+ def test(testNumber: Int) {
+ val fs = (0 to 1000) map (i ⇒ Future(i, 10000))
+ val result = Futures.fold(ArrayBuffer.empty[AnyRef], 10000)(fs) {
+ case (l, i) if i % 2 == 0 ⇒ l += i.asInstanceOf[AnyRef]
+ case (l, _) ⇒ l
+ }.get.asInstanceOf[ArrayBuffer[Int]].sum
+
+ assert(result === 250500)
+ }
+
+ (1 to 100) foreach test //Make sure it tries to provoke the problem
+ }*/
+
"return zero value if folding empty list" in {
Futures.fold(0)(List[Future[Int]]())(_ + _).get must be(0)
}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/AkkaPerformanceTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/AkkaPerformanceTest.scala
new file mode 100755
index 0000000000..5d5bf98943
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/AkkaPerformanceTest.scala
@@ -0,0 +1,85 @@
+package akka.performance.trading.common
+
+import org.junit._
+import Assert._
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import akka.performance.trading.domain._
+import akka.performance.trading.common._
+import akka.actor.ActorRef
+import akka.actor.Actor
+import akka.actor.Actor.actorOf
+import akka.dispatch.Dispatchers
+import akka.actor.PoisonPill
+import akka.event.EventHandler
+
+abstract class AkkaPerformanceTest extends BenchmarkScenarios {
+
+ type TS = AkkaTradingSystem
+
+ val clientDispatcher = Dispatchers.newDispatcher("client-dispatcher")
+ .withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
+ .setCorePoolSize(maxClients)
+ .setMaxPoolSize(maxClients)
+ .build
+
+ override def createTradingSystem: TS = new AkkaTradingSystem
+
+ /**
+ * Implemented in subclass
+ */
+ def placeOrder(orderReceiver: ActorRef, order: Order): Rsp
+
+ override def runScenario(scenario: String, orders: List[Order], repeat: Int, numberOfClients: Int, delayMs: Int) = {
+ val totalNumberOfRequests = orders.size * repeat
+ val repeatsPerClient = repeat / numberOfClients
+ val oddRepeats = repeat - (repeatsPerClient * numberOfClients)
+ val latch = new CountDownLatch(numberOfClients)
+ val receivers = tradingSystem.orderReceivers.toIndexedSeq
+ val clients = (for (i ← 0 until numberOfClients) yield {
+ val receiver = receivers(i % receivers.size)
+ actorOf(new Client(receiver, orders, latch, repeatsPerClient + (if (i < oddRepeats) 1 else 0), delayMs))
+ }).toList
+
+ clients.foreach(_.start)
+ val start = System.nanoTime
+ clients.foreach(_ ! "run")
+ val ok = latch.await((5000 + (2 + delayMs) * totalNumberOfRequests) * timeDilation, TimeUnit.MILLISECONDS)
+ val durationNs = (System.nanoTime - start)
+
+ assertTrue(ok)
+ assertEquals((orders.size / 2) * repeat, TotalTradeCounter.counter.get)
+ logMeasurement(scenario, numberOfClients, durationNs)
+ clients.foreach(_ ! PoisonPill)
+ }
+
+ class Client(orderReceiver: ActorRef, orders: List[Order], latch: CountDownLatch, repeat: Int, delayMs: Int) extends Actor {
+
+ self.dispatcher = clientDispatcher
+
+ def this(orderReceiver: ActorRef, orders: List[Order], latch: CountDownLatch, repeat: Int) {
+ this(orderReceiver, orders, latch, repeat, 0)
+ }
+
+ def receive = {
+ case "run" ⇒
+ (1 to repeat).foreach(i ⇒
+ {
+ for (o ← orders) {
+ val t0 = System.nanoTime
+ val rsp = placeOrder(orderReceiver, o)
+ val duration = System.nanoTime - t0
+ stat.addValue(duration)
+ if (!rsp.status) {
+ EventHandler.error(this, "Invalid rsp")
+ }
+ delay(delayMs)
+ }
+ })
+ latch.countDown()
+
+ }
+ }
+
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala
new file mode 100644
index 0000000000..877c8a3460
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala
@@ -0,0 +1,130 @@
+package akka.performance.trading.common
+
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import java.text.SimpleDateFormat
+import java.util.Date
+
+import scala.collection.mutable.{ Map ⇒ MutableMap }
+
+import akka.event.EventHandler
+
+trait BenchResultRepository {
+ def add(stats: Stats)
+
+ def get(name: String): Seq[Stats]
+
+ def get(name: String, load: Int): Option[Stats]
+
+ def getWithHistorical(name: String, load: Int): Seq[Stats]
+
+}
+
+object BenchResultRepository {
+ private val repository = new FileBenchResultRepository
+ def apply(): BenchResultRepository = repository
+}
+
+class FileBenchResultRepository extends BenchResultRepository {
+ private val statsByName = MutableMap[String, Seq[Stats]]()
+ private val baselineStats = MutableMap[Key, Stats]()
+ private val historicalStats = MutableMap[Key, Seq[Stats]]()
+ private val dir = System.getProperty("benchmark.resultDir", "target/benchmark")
+ private def dirExists: Boolean = new File(dir).exists
+ protected val maxHistorical = 7
+
+ case class Key(name: String, load: Int)
+
+ def add(stats: Stats) {
+ val values = statsByName.getOrElseUpdate(stats.name, IndexedSeq.empty)
+ statsByName(stats.name) = values :+ stats
+ save(stats)
+ }
+
+ def get(name: String): Seq[Stats] = {
+ statsByName.getOrElse(name, IndexedSeq.empty)
+ }
+
+ def get(name: String, load: Int): Option[Stats] = {
+ get(name).find(_.load == load)
+ }
+
+ def getWithHistorical(name: String, load: Int): Seq[Stats] = {
+ val key = Key(name, load)
+ val historical = historicalStats.getOrElse(key, IndexedSeq.empty)
+ val baseline = baselineStats.get(key)
+ val current = get(name, load)
+
+ (IndexedSeq.empty ++ historical ++ baseline ++ current).takeRight(maxHistorical)
+ }
+
+ private def loadFiles() {
+ if (dirExists) {
+ val files =
+ for {
+ f ← new File(dir).listFiles
+ if f.isFile
+ if f.getName.endsWith(".ser")
+ } yield f
+
+ val (baselineFiles, historicalFiles) = files.partition(_.getName.startsWith("baseline-"))
+ val baselines = load(baselineFiles)
+ for (stats ← baselines) {
+ baselineStats(Key(stats.name, stats.load)) = stats
+ }
+ val historical = load(historicalFiles)
+ for (h ← historical) {
+ val values = historicalStats.getOrElseUpdate(Key(h.name, h.load), IndexedSeq.empty)
+ historicalStats(Key(h.name, h.load)) = values :+ h
+ }
+ }
+ }
+
+ private def save(stats: Stats) {
+ if (!dirExists) return
+ val timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(stats.timestamp))
+ val name = stats.name + "--" + timestamp + "--" + stats.load + ".ser"
+ val f = new File(dir, name)
+ var out: ObjectOutputStream = null
+ try {
+ out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)))
+ out.writeObject(stats)
+ } catch {
+ case e: Exception ⇒
+ EventHandler.error(this, "Failed to save [%s] to [%s]".format(stats, f.getAbsolutePath))
+ }
+ finally {
+ if (out ne null) try { out.close() } catch { case ignore: Exception ⇒ }
+ }
+ }
+
+ private def load(files: Iterable[File]): Seq[Stats] = {
+ val result =
+ for (f ← files) yield {
+ var in: ObjectInputStream = null
+ try {
+ in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)))
+ val stats = in.readObject.asInstanceOf[Stats]
+ Some(stats)
+ } catch {
+ case e: Exception ⇒
+ EventHandler.error(this, "Failed to load from [%s]".format(f.getAbsolutePath))
+ None
+ }
+ finally {
+ if (in ne null) try { in.close() } catch { case ignore: Exception ⇒ }
+ }
+ }
+
+ result.flatten.toSeq.sortBy(_.timestamp)
+ }
+
+ loadFiles()
+
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala
new file mode 100755
index 0000000000..6cbd6ee4ca
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala
@@ -0,0 +1,60 @@
+package akka.performance.trading.common
+
+import org.junit._
+import akka.performance.trading.domain._
+
+trait BenchmarkScenarios extends PerformanceTest {
+
+ @Test
+ def complexScenario1 = complexScenario(1)
+ @Test
+ def complexScenario2 = complexScenario(2)
+ @Test
+ def complexScenario4 = complexScenario(4)
+ @Test
+ def complexScenario6 = complexScenario(6)
+ @Test
+ def complexScenario8 = complexScenario(8)
+ @Test
+ def complexScenario10 = complexScenario(10)
+ @Test
+ def complexScenario20 = complexScenario(20)
+ @Test
+ def complexScenario30 = complexScenario(30)
+ @Test
+ def complexScenario40 = complexScenario(40)
+ @Test
+ def complexScenario60 = complexScenario(60)
+ @Test
+ def complexScenario80 = complexScenario(80)
+ @Test
+ def complexScenario100 = complexScenario(100)
+ @Test
+ def complexScenario200 = complexScenario(200)
+ @Test
+ def complexScenario300 = complexScenario(300)
+ @Test
+ def complexScenario400 = complexScenario(400)
+
+ def complexScenario(numberOfClients: Int) {
+ Assume.assumeTrue(numberOfClients >= minClients)
+ Assume.assumeTrue(numberOfClients <= maxClients)
+
+ val repeat = 500 * repeatFactor
+
+ val prefixes = "A" :: "B" :: "C" :: Nil
+ val askOrders = for {
+ s ← prefixes
+ i ← 1 to 5
+ } yield new Ask(s + i, 100 - i, 1000)
+ val bidOrders = for {
+ s ← prefixes
+ i ← 1 to 5
+ } yield new Bid(s + i, 100 - i, 1000)
+ val orders = askOrders ::: bidOrders
+
+ runScenario("benchmark", orders, repeat, numberOfClients, 0)
+ }
+
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/GoogleChartBuilder.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/GoogleChartBuilder.scala
new file mode 100644
index 0000000000..f4fd02e924
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/GoogleChartBuilder.scala
@@ -0,0 +1,122 @@
+package akka.performance.trading.common
+
+import java.io.UnsupportedEncodingException
+import java.net.URLEncoder
+
+import scala.collection.immutable.TreeMap
+
+/**
+ * Generates URLs to Google Chart API http://code.google.com/apis/chart/
+ */
+object GoogleChartBuilder {
+ val BaseUrl = "http://chart.apis.google.com/chart?"
+ val ChartWidth = 750
+ val ChartHeight = 400
+
+ /**
+ * Builds a bar chart for all percentiles in the statistics.
+ */
+ def percentilChartUrl(statistics: Seq[Stats], title: String, legend: Stats ⇒ String): String = {
+ if (statistics.isEmpty) return ""
+
+ val current = statistics.last
+
+ val sb = new StringBuilder()
+ sb.append(BaseUrl)
+ // bar chart
+ sb.append("cht=bvg")
+ sb.append("&")
+ // size
+ sb.append("chs=").append(ChartWidth).append("x").append(ChartHeight)
+ sb.append("&")
+ // title
+ sb.append("chtt=").append(urlEncode(title))
+ sb.append("&")
+ // axis locations
+ sb.append("chxt=y,x,y")
+ sb.append("&")
+ // labels
+ percentileLabels(current.percentiles, sb)
+ sb.append("|2:|min|mean|median")
+ sb.append("&")
+ // label positions
+ sb.append("chxp=2,").append(current.min).append(",").append(current.mean).append(",")
+ .append(current.median)
+ sb.append("&")
+ // label color and font
+ sb.append("chxs=2,D65D82,11.5,0,lt,D65D82")
+ sb.append("&")
+ // lines for min, mean, median
+ sb.append("chxtc=2,-1000")
+ sb.append("&")
+ // legend
+ appendLegend(statistics, sb, legend)
+ sb.append("&")
+ // bar spacing
+ sb.append("chbh=a,4,20")
+ sb.append("&")
+ // bar colors
+ barColors(statistics.size, sb)
+ sb.append("&")
+
+ // data series
+ val maxValue = statistics.map(_.percentiles.last._2).max
+ sb.append("chd=t:")
+ dataSeries(statistics.map(_.percentiles), sb)
+
+ // y range
+ sb.append("&")
+ sb.append("chxr=0,0,").append(maxValue).append("|2,0,").append(maxValue)
+ sb.append("&")
+ sb.append("chds=0,").append(maxValue)
+ sb.append("&")
+
+ // grid lines
+ appendGridSpacing(maxValue, sb)
+
+ return sb.toString()
+ }
+
+ private def percentileLabels(percentiles: TreeMap[Int, Long], sb: StringBuilder) {
+ sb.append("chxl=1:|")
+ val s = percentiles.keys.toList.map(_ + "%").mkString("|")
+ sb.append(s)
+ }
+
+ private def appendLegend(statistics: Seq[Stats], sb: StringBuilder, legend: Stats ⇒ String) {
+ val legends = statistics.map(legend(_))
+ sb.append("chdl=")
+ val s = legends.map(urlEncode(_)).mkString("|")
+ sb.append(s)
+ }
+
+ private def barColors(numberOfSeries: Int, sb: StringBuilder) {
+ sb.append("chco=")
+ val template = ",A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,A2C180,90AA94,3D7930"
+ val s = template.substring(template.length - (numberOfSeries * 7) + 1)
+ sb.append(s)
+ }
+
+ private def dataSeries(allPercentiles: Seq[TreeMap[Int, Long]], sb: StringBuilder) {
+ val series =
+ for {
+ percentiles ← allPercentiles
+ } yield {
+ percentiles.values.mkString(",")
+ }
+ sb.append(series.mkString("|"))
+ }
+
+ private def appendGridSpacing(maxValue: Long, sb: StringBuilder) {
+ sb.append("chg=0,10")
+ }
+
+ private def urlEncode(str: String): String = {
+ try {
+ URLEncoder.encode(str, "ISO-8859-1")
+ } catch {
+ case e: UnsupportedEncodingException ⇒ str
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/MatchingEngine.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/MatchingEngine.scala
new file mode 100755
index 0000000000..7b531444f0
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/MatchingEngine.scala
@@ -0,0 +1,67 @@
+package akka.performance.trading.common
+
+import akka.performance.trading.domain._
+import akka.actor._
+import akka.dispatch.Future
+import akka.dispatch.FutureTimeoutException
+import akka.dispatch.MessageDispatcher
+import akka.event.EventHandler
+
+trait MatchingEngine {
+ val meId: String
+ val orderbooks: List[Orderbook]
+ val supportedOrderbookSymbols = orderbooks map (_.symbol)
+ protected val orderbooksMap: Map[String, Orderbook] =
+ Map() ++ (orderbooks map (o ⇒ (o.symbol, o)))
+
+}
+
+class AkkaMatchingEngine(val meId: String, val orderbooks: List[Orderbook], disp: Option[MessageDispatcher])
+ extends Actor with MatchingEngine {
+
+ for (d ← disp) {
+ self.dispatcher = d
+ }
+
+ var standby: Option[ActorRef] = None
+
+ def receive = {
+ case standbyRef: ActorRef ⇒
+ standby = Some(standbyRef)
+ case order: Order ⇒
+ handleOrder(order)
+ case unknown ⇒
+ EventHandler.warning(this, "Received unknown message: " + unknown)
+ }
+
+ def handleOrder(order: Order) {
+ orderbooksMap.get(order.orderbookSymbol) match {
+ case Some(orderbook) ⇒
+ val pendingStandbyReply: Option[Future[_]] =
+ for (s ← standby) yield { s ? order }
+
+ orderbook.addOrder(order)
+ orderbook.matchOrders()
+ // wait for standby reply
+ pendingStandbyReply.foreach(waitForStandby(_))
+ done(true)
+ case None ⇒
+ EventHandler.warning(this, "Orderbook not handled by this MatchingEngine: " + order.orderbookSymbol)
+ done(false)
+ }
+ }
+
+ def done(status: Boolean) {
+ self.channel ! new Rsp(status)
+ }
+
+ def waitForStandby(pendingStandbyFuture: Future[_]) {
+ try {
+ pendingStandbyFuture.await
+ } catch {
+ case e: FutureTimeoutException ⇒
+ EventHandler.error(this, "Standby timeout: " + e)
+ }
+ }
+
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala
new file mode 100755
index 0000000000..e9162299d1
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala
@@ -0,0 +1,62 @@
+package akka.performance.trading.common
+
+import akka.performance.trading.domain._
+import akka.actor._
+import akka.dispatch.MessageDispatcher
+import akka.event.EventHandler
+
+trait OrderReceiver {
+ type ME
+ val matchingEngines: List[ME]
+ var matchingEnginePartitionsIsStale = true
+ var matchingEngineForOrderbook: Map[String, ME] = Map()
+
+ def refreshMatchingEnginePartitions() {
+ val m = Map() ++
+ (for {
+ me ← matchingEngines
+ orderbookSymbol ← supportedOrderbooks(me)
+ } yield (orderbookSymbol, me))
+
+ matchingEngineForOrderbook = m
+ matchingEnginePartitionsIsStale = false
+ }
+
+ def supportedOrderbooks(me: ME): List[String]
+
+}
+
+class AkkaOrderReceiver(matchingEngineRouting: Map[ActorRef, List[String]], disp: Option[MessageDispatcher])
+ extends Actor with OrderReceiver {
+ type ME = ActorRef
+
+ for (d ← disp) {
+ self.dispatcher = d
+ }
+
+ override val matchingEngines: List[ActorRef] = matchingEngineRouting.keys.toList
+
+ override def preStart() {
+ refreshMatchingEnginePartitions()
+ }
+
+ def receive = {
+ case order: Order ⇒ placeOrder(order)
+ case unknown ⇒ EventHandler.warning(this, "Received unknown message: " + unknown)
+ }
+
+ override def supportedOrderbooks(me: ActorRef): List[String] = {
+ matchingEngineRouting(me)
+ }
+
+ def placeOrder(order: Order) = {
+ val matchingEngine = matchingEngineForOrderbook.get(order.orderbookSymbol)
+ matchingEngine match {
+ case Some(m) ⇒
+ m.forward(order)
+ case None ⇒
+ EventHandler.warning(this, "Unknown orderbook: " + order.orderbookSymbol)
+ self.channel ! new Rsp(false)
+ }
+ }
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala
new file mode 100755
index 0000000000..106a5db3b9
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala
@@ -0,0 +1,224 @@
+package akka.performance.trading.common
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Random
+
+import scala.collection.immutable.TreeMap
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics
+import org.apache.commons.math.stat.descriptive.SynchronizedDescriptiveStatistics
+import org.junit.After
+import org.junit.Before
+import org.scalatest.junit.JUnitSuite
+
+import akka.event.EventHandler
+import akka.performance.trading.domain.Ask
+import akka.performance.trading.domain.Bid
+import akka.performance.trading.domain.Order
+import akka.performance.trading.domain.TotalTradeCounter
+
+trait PerformanceTest extends JUnitSuite {
+
+ // jvm parameters
+ // -server -Xms512m -Xmx1024m -XX:+UseConcMarkSweepGC
+
+ var isWarm = false
+
+ def isBenchmark() = System.getProperty("benchmark") == "true"
+
+ def minClients() = System.getProperty("benchmark.minClients", "1").toInt;
+
+ def maxClients() = System.getProperty("benchmark.maxClients", "40").toInt;
+
+ def repeatFactor() = {
+ val defaultRepeatFactor = if (isBenchmark) "150" else "2"
+ System.getProperty("benchmark.repeatFactor", defaultRepeatFactor).toInt
+ }
+
+ def warmupRepeatFactor() = {
+ val defaultRepeatFactor = if (isBenchmark) "200" else "1"
+ System.getProperty("benchmark.warmupRepeatFactor", defaultRepeatFactor).toInt
+ }
+
+ def randomSeed() = {
+ System.getProperty("benchmark.randomSeed", "0").toInt
+ }
+
+ def timeDilation() = {
+ System.getProperty("benchmark.timeDilation", "1").toLong
+ }
+
+ var stat: DescriptiveStatistics = _
+
+ val resultRepository = BenchResultRepository()
+
+ val legendTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")
+
+ type TS <: TradingSystem
+
+ var tradingSystem: TS = _
+ val random: Random = new Random(randomSeed)
+
+ def createTradingSystem(): TS
+
+ def placeOrder(orderReceiver: TS#OR, order: Order): Rsp
+
+ def runScenario(scenario: String, orders: List[Order], repeat: Int, numberOfClients: Int, delayMs: Int)
+
+ @Before
+ def setUp() {
+ stat = new SynchronizedDescriptiveStatistics
+ tradingSystem = createTradingSystem()
+ tradingSystem.start()
+ warmUp()
+ TotalTradeCounter.reset()
+ stat = new SynchronizedDescriptiveStatistics
+ }
+
+ @After
+ def tearDown() {
+ tradingSystem.shutdown()
+ stat = null
+ }
+
+ def warmUp() {
+ val bid = new Bid("A1", 100, 1000)
+ val ask = new Ask("A1", 100, 1000)
+
+ val orderReceiver = tradingSystem.orderReceivers.head
+ val loopCount = if (isWarm) 1 else 10 * warmupRepeatFactor
+
+ for (i ← 1 to loopCount) {
+ placeOrder(orderReceiver, bid)
+ placeOrder(orderReceiver, ask)
+ }
+ isWarm = true
+ }
+
+ /**
+ * To compare two tests with each other you can override this method, in
+ * the test. For example Some("OneWayPerformanceTest")
+ */
+ def compareResultWith: Option[String] = None
+
+ def logMeasurement(scenario: String, numberOfClients: Int, durationNs: Long) {
+
+ val name = getClass.getSimpleName
+ val durationS = durationNs.toDouble / 1000000000.0
+
+ val percentiles = TreeMap[Int, Long](
+ 5 -> (stat.getPercentile(5.0) / 1000).toLong,
+ 25 -> (stat.getPercentile(25.0) / 1000).toLong,
+ 50 -> (stat.getPercentile(50.0) / 1000).toLong,
+ 75 -> (stat.getPercentile(75.0) / 1000).toLong,
+ 95 -> (stat.getPercentile(95.0) / 1000).toLong)
+
+ val stats = Stats(
+ name,
+ load = numberOfClients,
+ timestamp = TestStart.startTime,
+ durationNanos = durationNs,
+ n = stat.getN,
+ min = (stat.getMin / 1000).toLong,
+ max = (stat.getMax / 1000).toLong,
+ mean = (stat.getMean / 1000).toLong,
+ tps = (stat.getN.toDouble / durationS),
+ percentiles)
+
+ resultRepository.add(stats)
+
+ EventHandler.info(this, formatResultsTable(resultRepository.get(name)))
+
+ val chartTitle = name + " Percentiles (microseconds)"
+ val chartUrl = GoogleChartBuilder.percentilChartUrl(resultRepository.get(name), chartTitle, _.load + " clients")
+ EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl)
+
+ for {
+ compareName ← compareResultWith
+ compareStats ← resultRepository.get(compareName, numberOfClients)
+ } {
+ val chartTitle = name + " vs. " + compareName + ", " + numberOfClients + " clients" + ", Percentiles (microseconds)"
+ val chartUrl = GoogleChartBuilder.percentilChartUrl(Seq(compareStats, stats), chartTitle, _.name)
+ EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl)
+ }
+
+ val withHistorical = resultRepository.getWithHistorical(name, numberOfClients)
+ if (withHistorical.size > 1) {
+ val chartTitle = name + " vs. historical, " + numberOfClients + " clients" + ", Percentiles (microseconds)"
+ val chartUrl = GoogleChartBuilder.percentilChartUrl(withHistorical, chartTitle,
+ stats ⇒ legendTimeFormat.format(new Date(stats.timestamp)))
+ EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl)
+ }
+
+ }
+
+ def formatResultsTable(statsSeq: Seq[Stats]): String = {
+
+ val name = statsSeq.head.name
+
+ val spaces = " "
+ val headerScenarioCol = ("Scenario" + spaces).take(name.length)
+
+ val headerLine = (headerScenarioCol :: "clients" :: "TPS" :: "mean" :: "5% " :: "25% " :: "50% " :: "75% " :: "95% " :: "Durat." :: "N" :: Nil)
+ .mkString("\t")
+ val headerLine2 = (spaces.take(name.length) :: " " :: " " :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(s) " :: " " :: Nil)
+ .mkString("\t")
+ val line = List.fill(formatStats(statsSeq.head).replaceAll("\t", " ").length)("-").mkString
+ val formattedStats = "\n" +
+ line.replace('-', '=') + "\n" +
+ headerLine + "\n" +
+ headerLine2 + "\n" +
+ line + "\n" +
+ statsSeq.map(formatStats(_)).mkString("\n") + "\n" +
+ line + "\n"
+
+ formattedStats
+
+ }
+
+ def formatStats(stats: Stats): String = {
+ val durationS = stats.durationNanos.toDouble / 1000000000.0
+ val duration = durationS.formatted("%.0f")
+
+ val tpsStr = stats.tps.formatted("%.0f")
+ val meanStr = stats.mean.formatted("%.0f")
+
+ val summaryLine =
+ stats.name ::
+ stats.load.toString ::
+ tpsStr ::
+ meanStr ::
+ stats.percentiles(5).toString ::
+ stats.percentiles(25).toString ::
+ stats.percentiles(50).toString ::
+ stats.percentiles(75).toString ::
+ stats.percentiles(95).toString ::
+ duration ::
+ stats.n.toString ::
+ Nil
+
+ summaryLine.mkString("\t")
+
+ }
+
+ def delay(delayMs: Int) {
+ val adjustedDelay =
+ if (delayMs >= 5) {
+ val dist = 0.2 * delayMs
+ (delayMs + random.nextGaussian * dist).intValue
+ } else {
+ delayMs
+ }
+
+ if (adjustedDelay > 0) {
+ Thread.sleep(adjustedDelay)
+ }
+ }
+
+}
+
+object TestStart {
+ val startTime = System.currentTimeMillis
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Rsp.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Rsp.scala
new file mode 100755
index 0000000000..683ff3d331
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Rsp.scala
@@ -0,0 +1,3 @@
+package akka.performance.trading.common
+
+case class Rsp(status: Boolean)
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Stats.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Stats.scala
new file mode 100644
index 0000000000..1b1b854cb0
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Stats.scala
@@ -0,0 +1,19 @@
+package akka.performance.trading.common
+
+import scala.collection.immutable.TreeMap
+
+case class Stats(
+ name: String,
+ load: Int,
+ timestamp: Long = System.currentTimeMillis,
+ durationNanos: Long,
+ n: Long,
+ min: Long,
+ max: Long,
+ mean: Double,
+ tps: Double,
+ percentiles: TreeMap[Int, Long]) {
+
+ def median: Long = percentiles(50)
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala
new file mode 100755
index 0000000000..44951879c5
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala
@@ -0,0 +1,111 @@
+package akka.performance.trading.common
+
+import akka.performance.trading.domain.Orderbook
+import akka.performance.trading.domain.OrderbookRepository
+import akka.actor.Actor._
+import akka.actor.ActorRef
+import akka.actor.PoisonPill
+import akka.dispatch.MessageDispatcher
+
+trait TradingSystem {
+ type ME
+ type OR
+
+ val allOrderbookSymbols: List[String] = OrderbookRepository.allOrderbookSymbols
+
+ val orderbooksGroupedByMatchingEngine: List[List[Orderbook]] =
+ for (groupOfSymbols: List[String] ← OrderbookRepository.orderbookSymbolsGroupedByMatchingEngine)
+ yield groupOfSymbols map (s ⇒ Orderbook(s, false))
+
+ def useStandByEngines: Boolean = true
+
+ lazy val matchingEngines: List[MatchingEngineInfo] = createMatchingEngines
+
+ def createMatchingEngines: List[MatchingEngineInfo]
+
+ lazy val orderReceivers: List[OR] = createOrderReceivers
+
+ def createOrderReceivers: List[OR]
+
+ def start()
+
+ def shutdown()
+
+ case class MatchingEngineInfo(primary: ME, standby: Option[ME], orderbooks: List[Orderbook])
+}
+
+class AkkaTradingSystem extends TradingSystem {
+ type ME = ActorRef
+ type OR = ActorRef
+
+ val orDispatcher = createOrderReceiverDispatcher
+ val meDispatcher = createMatchingEngineDispatcher
+
+ // by default we use default-dispatcher that is defined in akka.conf
+ def createOrderReceiverDispatcher: Option[MessageDispatcher] = None
+
+ // by default we use default-dispatcher that is defined in akka.conf
+ def createMatchingEngineDispatcher: Option[MessageDispatcher] = None
+
+ var matchingEngineForOrderbook: Map[String, ActorRef] = Map()
+
+ override def createMatchingEngines: List[MatchingEngineInfo] = {
+ for {
+ (orderbooks, i) ← orderbooksGroupedByMatchingEngine.zipWithIndex
+ n = i + 1
+ } yield {
+ val me = createMatchingEngine("ME" + n, orderbooks)
+ val orderbooksCopy = orderbooks map (o ⇒ Orderbook(o.symbol, true))
+ val standbyOption =
+ if (useStandByEngines) {
+ val meStandby = createMatchingEngine("ME" + n + "s", orderbooksCopy)
+ Some(meStandby)
+ } else {
+ None
+ }
+
+ MatchingEngineInfo(me, standbyOption, orderbooks)
+ }
+ }
+
+ def createMatchingEngine(meId: String, orderbooks: List[Orderbook]) =
+ actorOf(new AkkaMatchingEngine(meId, orderbooks, meDispatcher))
+
+ override def createOrderReceivers: List[ActorRef] = {
+ (1 to 10).toList map (i ⇒ createOrderReceiver())
+ }
+
+ def matchingEngineRouting: Map[ActorRef, List[String]] = {
+ val rules =
+ for {
+ info ← matchingEngines
+ orderbookSymbols = info.orderbooks.map(_.symbol)
+ } yield {
+ (info.primary, orderbookSymbols)
+ }
+
+ Map() ++ rules
+ }
+
+ def createOrderReceiver() =
+ actorOf(new AkkaOrderReceiver(matchingEngineRouting, orDispatcher))
+
+ override def start() {
+ for (MatchingEngineInfo(p, s, o) ← matchingEngines) {
+ p.start()
+ // standby is optional
+ s.foreach(_.start())
+ s.foreach(p ! _)
+ }
+ orderReceivers.foreach(_.start())
+ }
+
+ override def shutdown() {
+ orderReceivers.foreach(_ ! PoisonPill)
+ for (MatchingEngineInfo(p, s, o) ← matchingEngines) {
+ p ! PoisonPill
+ // standby is optional
+ s.foreach(_ ! PoisonPill)
+ }
+ }
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/domain/LatchMessage.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/LatchMessage.scala
new file mode 100644
index 0000000000..375e00f48e
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/LatchMessage.scala
@@ -0,0 +1,16 @@
+package akka.performance.trading.domain
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+trait LatchMessage {
+ val count: Int
+ lazy val latch: CountDownLatch = new CountDownLatch(count)
+}
+
+object LatchOrder {
+ def apply(order: Order) = order match {
+ case bid: Bid ⇒ new Bid(order.orderbookSymbol, order.price, order.volume) with LatchMessage { val count = 2 }
+ case ask: Ask ⇒ new Ask(order.orderbookSymbol, order.price, order.volume) with LatchMessage { val count = 2 }
+ }
+}
\ No newline at end of file
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/domain/Order.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/Order.scala
new file mode 100755
index 0000000000..9007243863
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/Order.scala
@@ -0,0 +1,29 @@
+package akka.performance.trading.domain
+
+trait Order {
+ def orderbookSymbol: String
+ def price: Long
+ def volume: Long
+}
+
+case class Bid(
+ orderbookSymbol: String,
+ price: Long,
+ volume: Long)
+ extends Order {
+
+ def split(newVolume: Long) = {
+ new Bid(orderbookSymbol, price, newVolume)
+ }
+}
+
+case class Ask(
+ orderbookSymbol: String,
+ price: Long,
+ volume: Long)
+ extends Order {
+
+ def split(newVolume: Long) = {
+ new Ask(orderbookSymbol, price, newVolume)
+ }
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/domain/Orderbook.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/Orderbook.scala
new file mode 100755
index 0000000000..a3bd2febc0
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/Orderbook.scala
@@ -0,0 +1,82 @@
+package akka.performance.trading.domain
+
+abstract class Orderbook(val symbol: String) {
+ var bidSide: List[Bid] = Nil
+ var askSide: List[Ask] = Nil
+
+ def addOrder(order: Order) {
+ assert(symbol == order.orderbookSymbol)
+ order match {
+ case bid: Bid ⇒
+ bidSide = (bid :: bidSide).sortWith(_.price > _.price)
+ case ask: Ask ⇒
+ askSide = (ask :: askSide).sortWith(_.price < _.price)
+ }
+ }
+
+ // this is by intention not tuned for performance to simulate some work
+ def matchOrders() {
+ if (!bidSide.isEmpty && !askSide.isEmpty) {
+ val topOfBook = (bidSide.head, askSide.head)
+ topOfBook match {
+ case (bid, ask) if bid.price < ask.price ⇒ // no match
+ case (bid, ask) if bid.price >= ask.price && bid.volume == ask.volume ⇒
+ trade(bid, ask)
+ bidSide = bidSide.tail
+ askSide = askSide.tail
+ matchOrders
+ case (bid, ask) if bid.price >= ask.price && bid.volume < ask.volume ⇒
+ val matchingAsk = ask.split(bid.volume)
+ val remainingAsk = ask.split(ask.volume - bid.volume)
+ trade(bid, matchingAsk)
+ bidSide = bidSide.tail
+ askSide = remainingAsk :: askSide.tail
+ matchOrders
+ case (bid, ask) if bid.price >= ask.price && bid.volume > ask.volume ⇒
+ val matchingBid = bid.split(ask.volume)
+ val remainingBid = bid.split(bid.volume - ask.volume)
+ trade(matchingBid, ask)
+ bidSide = remainingBid :: bidSide.tail
+ askSide = askSide.tail
+ matchOrders
+ }
+ }
+ }
+
+ def trade(bid: Bid, ask: Ask)
+
+}
+
+object Orderbook {
+
+ val useDummyOrderbook = System.getProperty("benchmark.useDummyOrderbook", "false").toBoolean
+
+ def apply(symbol: String, standby: Boolean): Orderbook = standby match {
+ case false if !useDummyOrderbook ⇒ new Orderbook(symbol) with SimpleTradeObserver
+ case true if !useDummyOrderbook ⇒ new Orderbook(symbol) with StandbyTradeObserver
+ case false if useDummyOrderbook ⇒ new DummyOrderbook(symbol) with SimpleTradeObserver
+ case true if useDummyOrderbook ⇒ new DummyOrderbook(symbol) with StandbyTradeObserver
+ }
+}
+
+abstract class DummyOrderbook(symbol: String) extends Orderbook(symbol) {
+ var count = 0
+ var bid: Bid = _
+ var ask: Ask = _
+
+ override def addOrder(order: Order) {
+ count += 1
+ order match {
+ case b: Bid ⇒ bid = b
+ case a: Ask ⇒ ask = a
+ }
+ }
+
+ override def matchOrders() {
+ if (count % 2 == 0)
+ trade(bid, ask)
+ }
+
+ def trade(bid: Bid, ask: Ask)
+
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/domain/OrderbookRepository.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/OrderbookRepository.scala
new file mode 100755
index 0000000000..880d1393ab
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/OrderbookRepository.scala
@@ -0,0 +1,17 @@
+package akka.performance.trading.domain
+
+object OrderbookRepository {
+ def allOrderbookSymbols: List[String] = {
+ val prefix = "A" :: "B" :: "C" :: "D" :: "E" :: "F" :: "G" :: "H" :: "I" :: "J" :: Nil
+ for {
+ p ← prefix
+ i ← 1 to 10
+ } yield p + i
+ }
+
+ def orderbookSymbolsGroupedByMatchingEngine: List[List[String]] = {
+ val groupMap = allOrderbookSymbols groupBy (_.charAt(0))
+ groupMap.map(entry ⇒ entry._2).toList
+ }
+
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/domain/OrderbookTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/OrderbookTest.scala
new file mode 100755
index 0000000000..104d95ec42
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/OrderbookTest.scala
@@ -0,0 +1,94 @@
+package akka.performance.trading.domain
+
+import org.junit._
+import Assert._
+import org.scalatest.junit.JUnitSuite
+import org.mockito.Mockito._
+import org.mockito.Matchers._
+
+class OrderbookTest extends JUnitSuite {
+ var orderbook: Orderbook = null
+ var tradeObserverMock: TradeObserver = null
+
+ @Before
+ def setUp = {
+ tradeObserverMock = mock(classOf[TradeObserver])
+ orderbook = new Orderbook("ERI") with TradeObserver {
+ def trade(bid: Bid, ask: Ask) = tradeObserverMock.trade(bid, ask)
+ }
+ }
+
+ @Test
+ def shouldTradeSamePrice = {
+ val bid = new Bid("ERI", 100, 1000)
+ val ask = new Ask("ERI", 100, 1000)
+ orderbook.addOrder(bid)
+ orderbook.addOrder(ask)
+
+ orderbook.matchOrders()
+ assertEquals(0, orderbook.bidSide.size)
+ assertEquals(0, orderbook.askSide.size)
+
+ verify(tradeObserverMock).trade(bid, ask)
+ }
+
+ @Test
+ def shouldTradeTwoLevels = {
+ val bid1 = new Bid("ERI", 101, 1000)
+ val bid2 = new Bid("ERI", 100, 1000)
+ val bid3 = new Bid("ERI", 99, 1000)
+ orderbook.addOrder(bid1)
+ orderbook.addOrder(bid2)
+ orderbook.addOrder(bid3)
+
+ assertEquals(bid1 :: bid2 :: bid3 :: Nil, orderbook.bidSide)
+
+ val ask1 = new Ask("ERI", 99, 1000)
+ val ask2 = new Ask("ERI", 100, 1000)
+ val ask3 = new Ask("ERI", 101, 1000)
+ orderbook.addOrder(ask1)
+ orderbook.addOrder(ask2)
+ orderbook.addOrder(ask3)
+
+ assertEquals(ask1 :: ask2 :: ask3 :: Nil, orderbook.askSide)
+
+ orderbook.matchOrders()
+ assertEquals(1, orderbook.bidSide.size)
+ assertEquals(bid3, orderbook.bidSide.head)
+ assertEquals(1, orderbook.askSide.size)
+ assertEquals(ask3, orderbook.askSide.head)
+
+ verify(tradeObserverMock, times(2)).trade(any(classOf[Bid]), any(classOf[Ask]))
+ }
+
+ @Test
+ def shouldSplitBid = {
+ val bid = new Bid("ERI", 100, 300)
+ val ask = new Ask("ERI", 100, 1000)
+ orderbook.addOrder(bid)
+ orderbook.addOrder(ask)
+
+ orderbook.matchOrders()
+ assertEquals(0, orderbook.bidSide.size)
+ assertEquals(1, orderbook.askSide.size)
+ assertEquals(700, orderbook.askSide.head.volume)
+
+ verify(tradeObserverMock).trade(any(classOf[Bid]), any(classOf[Ask]))
+ }
+
+ @Test
+ def shouldSplitAsk = {
+ val bid = new Bid("ERI", 100, 1000)
+ val ask = new Ask("ERI", 100, 600)
+ orderbook.addOrder(bid)
+ orderbook.addOrder(ask)
+
+ orderbook.matchOrders()
+ assertEquals(1, orderbook.bidSide.size)
+ assertEquals(0, orderbook.askSide.size)
+ assertEquals(400, orderbook.bidSide.head.volume)
+
+ verify(tradeObserverMock).trade(any(classOf[Bid]), any(classOf[Ask]))
+ }
+
+}
\ No newline at end of file
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/domain/TradeObserver.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/TradeObserver.scala
new file mode 100755
index 0000000000..dec239ae15
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/domain/TradeObserver.scala
@@ -0,0 +1,26 @@
+package akka.performance.trading.domain
+
+import java.util.concurrent.atomic.AtomicInteger
+
+abstract trait TradeObserver {
+ def trade(bid: Bid, ask: Ask)
+}
+
+trait SimpleTradeObserver extends TradeObserver {
+ override def trade(bid: Bid, ask: Ask) {
+ val c = TotalTradeCounter.counter.incrementAndGet
+ }
+}
+
+trait StandbyTradeObserver extends TradeObserver {
+ override def trade(bid: Bid, ask: Ask) {
+ }
+}
+
+object TotalTradeCounter {
+ val counter = new AtomicInteger
+
+ def reset() {
+ counter.set(0)
+ }
+}
\ No newline at end of file
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayMatchingEngine.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayMatchingEngine.scala
new file mode 100755
index 0000000000..2b48107f8d
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayMatchingEngine.scala
@@ -0,0 +1,26 @@
+package akka.performance.trading.oneway
+
+import akka.actor._
+import akka.dispatch.MessageDispatcher
+import akka.event.EventHandler
+import akka.performance.trading.domain.Order
+import akka.performance.trading.domain.Orderbook
+import akka.performance.trading.common.AkkaMatchingEngine
+
+class OneWayMatchingEngine(meId: String, orderbooks: List[Orderbook], disp: Option[MessageDispatcher])
+ extends AkkaMatchingEngine(meId, orderbooks, disp) {
+
+ override def handleOrder(order: Order) {
+ orderbooksMap.get(order.orderbookSymbol) match {
+ case Some(orderbook) ⇒
+ standby.foreach(_ ! order)
+
+ orderbook.addOrder(order)
+ orderbook.matchOrders()
+
+ case None ⇒
+ EventHandler.warning(this, "Orderbook not handled by this MatchingEngine: " + order.orderbookSymbol)
+ }
+ }
+
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala
new file mode 100755
index 0000000000..d64639d3fa
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala
@@ -0,0 +1,21 @@
+package akka.performance.trading.oneway
+
+import akka.actor._
+import akka.dispatch.MessageDispatcher
+import akka.event.EventHandler
+import akka.performance.trading.domain._
+import akka.performance.trading.common.AkkaOrderReceiver
+
+class OneWayOrderReceiver(matchingEngineRouting: Map[ActorRef, List[String]], disp: Option[MessageDispatcher])
+ extends AkkaOrderReceiver(matchingEngineRouting, disp) {
+
+ override def placeOrder(order: Order) = {
+ val matchingEngine = matchingEngineForOrderbook.get(order.orderbookSymbol)
+ matchingEngine match {
+ case Some(m) ⇒
+ m ! order
+ case None ⇒
+ EventHandler.warning(this, "Unknown orderbook: " + order.orderbookSymbol)
+ }
+ }
+}
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayPerformanceTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayPerformanceTest.scala
new file mode 100755
index 0000000000..169dd02ebf
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayPerformanceTest.scala
@@ -0,0 +1,47 @@
+package akka.performance.trading.oneway
+
+import java.util.concurrent.TimeUnit
+
+import org.junit.Test
+
+import akka.actor.Actor.actorOf
+import akka.actor.ActorRef
+import akka.performance.trading.common.AkkaPerformanceTest
+import akka.performance.trading.common.Rsp
+import akka.performance.trading.domain._
+
+class OneWayPerformanceTest extends AkkaPerformanceTest {
+
+ override def createTradingSystem: TS = new OneWayTradingSystem {
+ override def createMatchingEngine(meId: String, orderbooks: List[Orderbook]) =
+ actorOf(new OneWayMatchingEngine(meId, orderbooks, meDispatcher) with LatchMessageCountDown)
+ }
+
+ override def placeOrder(orderReceiver: ActorRef, order: Order): Rsp = {
+ val newOrder = LatchOrder(order)
+ orderReceiver ! newOrder
+ val ok = newOrder.latch.await(10, TimeUnit.SECONDS)
+ new Rsp(ok)
+ }
+
+ // need this so that junit will detect this as a test case
+ @Test
+ def dummy {}
+
+ override def compareResultWith = Some("RspPerformanceTest")
+
+ def createLatchOrder(order: Order) = order match {
+ case bid: Bid ⇒ new Bid(order.orderbookSymbol, order.price, order.volume) with LatchMessage { val count = 2 }
+ case ask: Ask ⇒ new Ask(order.orderbookSymbol, order.price, order.volume) with LatchMessage { val count = 2 }
+ }
+
+}
+
+trait LatchMessageCountDown extends OneWayMatchingEngine {
+
+ override def handleOrder(order: Order) {
+ super.handleOrder(order)
+ order.asInstanceOf[LatchMessage].latch.countDown
+ }
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala
new file mode 100755
index 0000000000..d6fcafbf7c
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala
@@ -0,0 +1,16 @@
+package akka.performance.trading.oneway
+
+import akka.actor.Actor.actorOf
+import akka.actor.ActorRef
+import akka.performance.trading.common.AkkaTradingSystem
+import akka.performance.trading.domain.Orderbook
+
+class OneWayTradingSystem extends AkkaTradingSystem {
+
+ override def createMatchingEngine(meId: String, orderbooks: List[Orderbook]) =
+ actorOf(new OneWayMatchingEngine(meId, orderbooks, meDispatcher))
+
+ override def createOrderReceiver() =
+ actorOf(new OneWayOrderReceiver(matchingEngineRouting, orDispatcher))
+
+}
\ No newline at end of file
diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/response/RspPerformanceTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/response/RspPerformanceTest.scala
new file mode 100755
index 0000000000..652f1e7886
--- /dev/null
+++ b/akka-actor-tests/src/test/scala/akka/performance/trading/response/RspPerformanceTest.scala
@@ -0,0 +1,23 @@
+package akka.performance.trading.response
+
+import org.junit.Test
+
+import akka.actor.ActorRef
+import akka.performance.trading.common.AkkaPerformanceTest
+import akka.performance.trading.domain.Order
+import akka.performance.trading.common.Rsp
+
+class RspPerformanceTest extends AkkaPerformanceTest {
+
+ override def placeOrder(orderReceiver: ActorRef, order: Order): Rsp = {
+ (orderReceiver ? order).get.asInstanceOf[Rsp]
+ }
+
+ // need this so that junit will detect this as a test case
+ @Test
+ def dummy {}
+
+ override def compareResultWith = Some("OneWayPerformanceTest")
+
+}
+
diff --git a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala
index 5ffc36446e..c78016c1f5 100644
--- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala
+++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala
@@ -119,7 +119,7 @@ class RoutingSpec extends WordSpec with MustMatchers {
for (i ← 1 to 500) d ! i
try {
- latch.await(10 seconds)
+ latch.await(20 seconds)
} finally {
// because t1 is much slower and thus has a bigger mailbox all the time
t1Count.get must be < (t2Count.get)
diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala
index 4867a0db23..0bb7319400 100644
--- a/akka-actor/src/main/scala/akka/actor/Actor.scala
+++ b/akka-actor/src/main/scala/akka/actor/Actor.scala
@@ -13,7 +13,7 @@ import ReflectiveAccess._
import akka.remoteinterface.RemoteSupport
import akka.japi.{ Creator, Procedure }
import akka.AkkaException
-import akka.serialization.{ Format, Serializer }
+import akka.serialization.{ Format, Serializer, Serialization }
import akka.cluster.ClusterNode
import akka.event.EventHandler
import scala.collection.immutable.Stack
@@ -58,7 +58,7 @@ case object RevertHotSwap extends AutoReceivedMessage with LifeCycleMessage
case class Restart(reason: Throwable) extends AutoReceivedMessage with LifeCycleMessage
-case class Exit(dead: ActorRef, killer: Throwable) extends AutoReceivedMessage with LifeCycleMessage
+case class Death(dead: ActorRef, killer: Throwable) extends AutoReceivedMessage with LifeCycleMessage
case class Link(child: ActorRef) extends AutoReceivedMessage with LifeCycleMessage
@@ -341,7 +341,7 @@ object Actor extends ListenerManagement {
*
*/
def actorOf[T <: Actor](creator: ⇒ T, address: String): ActorRef = {
- createActor(address, () ⇒ new LocalActorRef(() ⇒ creator, address, Transient))
+ createActor(address, () ⇒ new LocalActorRef(() ⇒ creator, address))
}
/**
@@ -364,7 +364,7 @@ object Actor extends ListenerManagement {
* JAVA API
*/
def actorOf[T <: Actor](creator: Creator[T], address: String): ActorRef = {
- createActor(address, () ⇒ new LocalActorRef(() ⇒ creator.create, address, Transient))
+ createActor(address, () ⇒ new LocalActorRef(() ⇒ creator.create, address))
}
def localActorOf[T <: Actor: Manifest]: ActorRef = {
@@ -384,24 +384,26 @@ object Actor extends ListenerManagement {
}
def localActorOf[T <: Actor](factory: ⇒ T): ActorRef = {
- new LocalActorRef(() ⇒ factory, new UUID().toString, Transient)
+ new LocalActorRef(() ⇒ factory, new UUID().toString)
}
def localActorOf[T <: Actor](factory: ⇒ T, address: String): ActorRef = {
- new LocalActorRef(() ⇒ factory, address, Transient)
+ new LocalActorRef(() ⇒ factory, address)
}
/**
* Use to spawn out a block of code in an event-driven actor. Will shut actor down when
* the block has been executed.
*
+ * Only to be used from Scala code.
+ *
* NOTE: If used from within an Actor then has to be qualified with 'Actor.spawn' since
* there is a method 'spawn[ActorType]' in the Actor trait already.
* Example:
*
* import Actor.spawn
*
- * spawn {
+ * spawn {
* ... // do stuff
* }
*
@@ -416,15 +418,19 @@ object Actor extends ListenerManagement {
}).start() ! Spawn
}
+ /**
+ * Creates an actor according to the deployment plan for the 'address'; local or clustered.
+ * If already created then it just returns it from the registry.
+ */
private[akka] def createActor(address: String, actorFactory: () ⇒ ActorRef): ActorRef = {
Address.validate(address)
registry.actorFor(address) match { // check if the actor for the address is already in the registry
- case Some(actorRef) ⇒ actorRef // it is -> return it
+ case Some(actorRef) ⇒ actorRef // it is -> return it
case None ⇒ // it is not -> create it
try {
Deployer.deploymentFor(address) match {
- case Deploy(_, router, _, Local) ⇒ actorFactory() // create a local actor
- case deploy ⇒ newClusterActorRef(actorFactory, address, deploy)
+ case Deploy(_, router, Local) ⇒ actorFactory() // create a local actor
+ case deploy ⇒ newClusterActorRef(actorFactory, address, deploy)
}
} catch {
case e: DeploymentException ⇒
@@ -451,15 +457,15 @@ object Actor extends ListenerManagement {
"\nif so put it outside the class/trait, f.e. in a companion object," +
"\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.", cause)
}
- }, address, Transient)
+ }, address)
}
private def newClusterActorRef(factory: () ⇒ ActorRef, address: String, deploy: Deploy): ActorRef = {
deploy match {
case Deploy(
- configAdress, router, serializerClassName,
+ configAdress, router,
Clustered(
- home,
+ preferredHomeNodes,
replicas,
replication)) ⇒
@@ -470,22 +476,19 @@ object Actor extends ListenerManagement {
if (!Actor.remote.isRunning) throw new IllegalStateException(
"Remote server is not running")
- val isHomeNode = DeploymentConfig.isHomeNode(home)
+ val isHomeNode = DeploymentConfig.isHomeNode(preferredHomeNodes)
val nrOfReplicas = DeploymentConfig.replicaValueFor(replicas)
- def serializerErrorDueTo(reason: String) =
- throw new akka.config.ConfigurationException(
- "Could not create Serializer object [" + serializerClassName +
- "] for serialization of actor [" + address +
- "] since " + reason)
+ def serializerErrorDueTo(reason: String) = throw new akka.config.ConfigurationException(
+ "Could not create Serializer for actor [" + address + "] due to: " + reason)
val serializer: Serializer =
- akka.serialization.Serialization.serializerFor(this.getClass).fold(x ⇒ serializerErrorDueTo(x.toString), s ⇒ s)
+ Serialization.serializerFor(this.getClass).fold(x ⇒ serializerErrorDueTo(x.toString), s ⇒ s)
def storeActorAndGetClusterRef(replicationScheme: ReplicationScheme, serializer: Serializer): ActorRef = {
// add actor to cluster registry (if not already added)
if (!cluster.isClustered(address))
- cluster.store(factory().start(), nrOfReplicas, replicationScheme, false, serializer)
+ cluster.store(address, factory, nrOfReplicas, replicationScheme, false, serializer)
// remote node (not home node), check out as ClusterActorRef
cluster.ref(address, DeploymentConfig.routerTypeFor(router))
@@ -496,13 +499,16 @@ object Actor extends ListenerManagement {
storeActorAndGetClusterRef(Transient, serializer)
case replication: Replication ⇒
+ if (DeploymentConfig.routerTypeFor(router) != akka.routing.RouterType.Direct) throw new ConfigurationException(
+ "Can't replicate an actor [" + address + "] configured with another router than \"direct\" - found [" + router + "]")
+
if (isHomeNode) { // stateful actor's home node
cluster
.use(address, serializer)
.getOrElse(throw new ConfigurationException(
"Could not check out actor [" + address + "] from cluster registry as a \"local\" actor"))
+
} else {
- // FIXME later manage different 'storage' (data grid) as well
storeActorAndGetClusterRef(replication, serializer)
}
}
@@ -736,7 +742,7 @@ trait Actor {
msg match {
case HotSwap(code, discardOld) ⇒ become(code(self), discardOld)
case RevertHotSwap ⇒ unbecome()
- case Exit(dead, reason) ⇒ self.handleTrapExit(dead, reason)
+ case Death(dead, reason) ⇒ self.handleTrapExit(dead, reason)
case Link(child) ⇒ self.link(child)
case Unlink(child) ⇒ self.unlink(child)
case UnlinkAndStop(child) ⇒ self.unlink(child); child.stop()
diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala
index 7ee17ef223..e8d45ad63f 100644
--- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala
+++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala
@@ -9,10 +9,10 @@ import akka.dispatch._
import akka.config._
import akka.config.Supervision._
import akka.util._
-import akka.serialization.{ Format, Serializer }
+import akka.serialization.{ Format, Serializer, Serialization }
import ReflectiveAccess._
import ClusterModule._
-import DeploymentConfig.{ ReplicationScheme, Replication, Transient, WriteThrough, WriteBehind }
+import DeploymentConfig.{ TransactionLog ⇒ TransactionLogConfig, _ }
import java.net.InetSocketAddress
import java.util.concurrent.atomic.AtomicReference
@@ -416,10 +416,7 @@ trait ActorRef extends ActorRefShared with ForwardableChannel with java.lang.Com
*
* @author Jonas Bonér
*/
-class LocalActorRef private[akka] (
- private[this] val actorFactory: () ⇒ Actor,
- val address: String,
- replicationScheme: ReplicationScheme)
+class LocalActorRef private[akka] (private[this] val actorFactory: () ⇒ Actor, val address: String)
extends ActorRef with ScalaActorRef {
protected[akka] val guard = new ReentrantGuard
@@ -447,52 +444,38 @@ class LocalActorRef private[akka] (
protected[akka] val actorInstance = guard.withGuard { new AtomicReference[Actor](newActor) }
- private val isReplicated: Boolean = replicationScheme match {
- case _: Transient | Transient ⇒ false
- case _ ⇒ true
- }
-
- // FIXME how to get the matching serializerClassName? Now default is used. Needed for transaction log snapshot
- // private val serializer = Actor.serializerFor(address, Format.defaultSerializerName)
-
def serializerErrorDueTo(reason: String) =
throw new akka.config.ConfigurationException(
"Could not create Serializer object [" + this.getClass.getName +
"]")
private val serializer: Serializer =
- akka.serialization.Serialization.serializerFor(this.getClass).fold(x ⇒ serializerErrorDueTo(x.toString), s ⇒ s)
+ Serialization.serializerFor(this.getClass).fold(x ⇒ serializerErrorDueTo(x.toString), s ⇒ s)
+
+ private lazy val replicationScheme: ReplicationScheme =
+ DeploymentConfig.replicationSchemeFor(Deployer.deploymentFor(address)).getOrElse(Transient)
+
+ private lazy val isReplicated: Boolean = DeploymentConfig.isReplicated(replicationScheme)
+
+ private lazy val isWriteBehindReplication: Boolean = DeploymentConfig.isWriteBehindReplication(replicationScheme)
private lazy val replicationStorage: Either[TransactionLog, AnyRef] = {
- replicationScheme match {
- case _: Transient | Transient ⇒
- throw new IllegalStateException("Can not replicate 'transient' actor [" + toString + "]")
+ if (DeploymentConfig.isReplicatedWithTransactionLog(replicationScheme)) {
+ EventHandler.debug(this,
+ "Creating a transaction log for Actor [%s] with replication strategy [%s]"
+ .format(address, replicationScheme))
- case Replication(storage, strategy) ⇒
- val isWriteBehind = strategy match {
- case _: WriteBehind | WriteBehind ⇒ true
- case _: WriteThrough | WriteThrough ⇒ false
- }
+ Left(transactionLog.newLogFor(_uuid.toString, isWriteBehindReplication, replicationScheme))
- storage match {
- case _: DeploymentConfig.TransactionLog | DeploymentConfig.TransactionLog ⇒
- EventHandler.debug(this,
- "Creating a transaction log for Actor [%s] with replication strategy [%s]"
- .format(address, replicationScheme))
- // Left(transactionLog.newLogFor(_uuid.toString, isWriteBehind, replicationScheme, serializer))
- // to fix null
- Left(transactionLog.newLogFor(_uuid.toString, isWriteBehind, replicationScheme, null))
+ } else if (DeploymentConfig.isReplicatedWithDataGrid(replicationScheme)) {
+ throw new ConfigurationException("Replication storage type \"data-grid\" is not yet supported")
- case _: DeploymentConfig.DataGrid | DeploymentConfig.DataGrid ⇒
- throw new ConfigurationException("Replication storage type \"data-grid\" is not yet supported")
-
- case unknown ⇒
- throw new ConfigurationException("Unknown replication storage type [" + unknown + "]")
- }
+ } else {
+ throw new ConfigurationException("Unknown replication storage type [" + replicationScheme + "]")
}
}
- //If it was started inside "newActor", initialize it
+ // If it was started inside "newActor", initialize it
if (isRunning) initializeActorInstance
// used only for deserialization
@@ -504,10 +487,9 @@ class LocalActorRef private[akka] (
__lifeCycle: LifeCycle,
__supervisor: Option[ActorRef],
__hotswap: Stack[PartialFunction[Any, Unit]],
- __factory: () ⇒ Actor,
- __replicationStrategy: ReplicationScheme) = {
+ __factory: () ⇒ Actor) = {
- this(__factory, __address, __replicationStrategy)
+ this(__factory, __address)
_uuid = __uuid
timeout = __timeout
@@ -727,7 +709,7 @@ class LocalActorRef private[akka] (
dead.restart(reason, maxRetries, within)
case _ ⇒
- if (_supervisor.isDefined) notifySupervisorWithMessage(Exit(this, reason))
+ if (_supervisor.isDefined) notifySupervisorWithMessage(Death(this, reason))
else dead.stop()
}
}
@@ -746,8 +728,7 @@ class LocalActorRef private[akka] (
val windowStart = restartTimeWindowStartNanos
val now = System.nanoTime
//We are within the time window if it isn't the first restart, or if the window hasn't closed
- val insideWindow = if (windowStart == 0) false
- else (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(withinTimeRange.get)
+ val insideWindow = if (windowStart == 0) true else (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(withinTimeRange.get)
if (windowStart == 0 || !insideWindow) //(Re-)set the start of the window
restartTimeWindowStartNanos = now
@@ -876,7 +857,7 @@ class LocalActorRef private[akka] (
channel.sendException(reason)
- if (supervisor.isDefined) notifySupervisorWithMessage(Exit(this, reason))
+ if (supervisor.isDefined) notifySupervisorWithMessage(Death(this, reason))
else {
lifeCycle match {
case Temporary ⇒ shutDownTemporaryActor(this)
@@ -1007,7 +988,8 @@ private[akka] case class RemoteActorRef private[akka] (
}
def start(): this.type = synchronized[this.type] {
- _status = ActorRefInternals.RUNNING
+ if (_status == ActorRefInternals.UNSTARTED)
+ _status = ActorRefInternals.RUNNING
this
}
@@ -1196,8 +1178,11 @@ trait ScalaActorRef extends ActorRefShared with ForwardableChannel { ref: ActorR
* Sends a message asynchronously, returning a future which may eventually hold the reply.
*/
def ?(message: Any)(implicit channel: UntypedChannel, timeout: Timeout): Future[Any] = {
+ //todo: so it can happen that a message is posted after the actor has been shut down (the isRunning and postMessageToMailboxAndCreateFutureResultWithTimeout
+ //are not atomic.
if (isRunning) {
postMessageToMailboxAndCreateFutureResultWithTimeout(message, timeout, channel)
+ //todo: there is no after check if the running state is still true.. so no 'repairing'
} else throw new ActorInitializationException(
"Actor has not been started, you need to invoke 'actor.start()' before using it")
}
@@ -1210,8 +1195,11 @@ trait ScalaActorRef extends ActorRefShared with ForwardableChannel { ref: ActorR
* Works with '!' and '?'/'ask'.
*/
def forward(message: Any)(implicit channel: ForwardableChannel) = {
+ //todo: so it can happen that a message is posted after the actor has been shut down (the isRunning and postMessageToMailbox
+ //are not atomic.
if (isRunning) {
postMessageToMailbox(message, channel.channel)
+ //todo: there is no after check if the running state is still true.. so no 'repairing'
} else throw new ActorInitializationException(
"Actor has not been started, you need to invoke 'actor.start()' before using it")
}
diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala
index 0906d2fbca..9dafb5a90e 100644
--- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala
+++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala
@@ -11,6 +11,7 @@ import annotation.tailrec
import java.util.concurrent.{ ConcurrentSkipListSet, ConcurrentHashMap }
import java.util.{ Set ⇒ JSet }
+import akka.util.Index
import akka.util.ReflectiveAccess._
import akka.util.ListenerManagement
import akka.serialization._
@@ -95,17 +96,17 @@ private[actor] final class ActorRegistry private[actor] () extends ListenerManag
/**
* Registers an actor in the Cluster ActorRegistry.
*/
- private[akka] def registerInCluster[T <: Actor](
- address: String, actorRef: ActorRef, replicas: Int, serializeMailbox: Boolean = false)(implicit format: Serializer) {
- ClusterModule.node.store(actorRef, replicas, serializeMailbox, format)
- }
+ // private[akka] def registerInCluster[T <: Actor](
+ // address: String, actorRef: ActorRef, replicas: Int, serializeMailbox: Boolean = false)(implicit format: Serializer) {
+ // // FIXME: implement ActorRegistry.registerInCluster(..)
+ // }
/**
* Unregisters an actor in the Cluster ActorRegistry.
*/
- private[akka] def unregisterInCluster(address: String) {
- ClusterModule.node.remove(address)
- }
+ // private[akka] def unregisterInCluster(address: String) {
+ // ClusterModule.node.remove(address)
+ // }
/**
* Get the typed actor proxy for a given typed actor ref.
@@ -254,119 +255,3 @@ class LocalActorRegistry(
private def typedActorFor(actorRef: ActorRef): Option[AnyRef] =
typedActorFor(actorRef.uuid)
}
-
-/**
- * FIXME move Index to its own file and put in akka.util.
- *
- * An implementation of a ConcurrentMultiMap
- * Adds/remove is serialized over the specified key
- * Reads are fully concurrent <-- el-cheapo
- *
- * @author Viktor Klang
- */
-class Index[K <: AnyRef, V <: AnyRef: Manifest] {
- private val Naught = Array[V]() //Nil for Arrays
- private val container = new ConcurrentHashMap[K, JSet[V]]
- private val emptySet = new ConcurrentSkipListSet[V]
-
- /**
- * Associates the value of type V with the key of type K
- * @return true if the value didn't exist for the key previously, and false otherwise
- */
- def put(key: K, value: V): Boolean = {
- //Tailrecursive spin-locking put
- @tailrec
- def spinPut(k: K, v: V): Boolean = {
- var retry = false
- var added = false
- val set = container get k
-
- if (set ne null) {
- set.synchronized {
- if (set.isEmpty) retry = true //IF the set is empty then it has been removed, so signal retry
- else { //Else add the value to the set and signal that retry is not needed
- added = set add v
- retry = false
- }
- }
- } else {
- val newSet = new ConcurrentSkipListSet[V]
- newSet add v
-
- // Parry for two simultaneous putIfAbsent(id,newSet)
- val oldSet = container.putIfAbsent(k, newSet)
- if (oldSet ne null) {
- oldSet.synchronized {
- if (oldSet.isEmpty) retry = true //IF the set is empty then it has been removed, so signal retry
- else { //Else try to add the value to the set and signal that retry is not needed
- added = oldSet add v
- retry = false
- }
- }
- } else added = true
- }
-
- if (retry) spinPut(k, v)
- else added
- }
-
- spinPut(key, value)
- }
-
- /**
- * @return a _new_ array of all existing values for the given key at the time of the call
- */
- def values(key: K): Array[V] = {
- val set: JSet[V] = container get key
- val result = if (set ne null) set toArray Naught else Naught
- result.asInstanceOf[Array[V]]
- }
-
- /**
- * @return Some(value) for the first matching value where the supplied function returns true for the given key,
- * if no matches it returns None
- */
- def findValue(key: K)(f: (V) ⇒ Boolean): Option[V] = {
- import scala.collection.JavaConversions._
- val set = container get key
- if (set ne null) set.iterator.find(f)
- else None
- }
-
- /**
- * Applies the supplied function to all keys and their values
- */
- def foreach(fun: (K, V) ⇒ Unit) {
- import scala.collection.JavaConversions._
- container.entrySet foreach { e ⇒ e.getValue.foreach(fun(e.getKey, _)) }
- }
-
- /**
- * Disassociates the value of type V from the key of type K
- * @return true if the value was disassociated from the key and false if it wasn't previously associated with the key
- */
- def remove(key: K, value: V): Boolean = {
- val set = container get key
-
- if (set ne null) {
- set.synchronized {
- if (set.remove(value)) { //If we can remove the value
- if (set.isEmpty) //and the set becomes empty
- container.remove(key, emptySet) //We try to remove the key if it's mapped to an empty set
-
- true //Remove succeeded
- } else false //Remove failed
- }
- } else false //Remove failed
- }
-
- /**
- * @return true if the underlying containers is empty, may report false negatives when the last remove is underway
- */
- def isEmpty: Boolean = container.isEmpty
-
- /**
- * Removes all keys and all values
- */
- def clear = foreach { case (k, v) ⇒ remove(k, v) }
-}
diff --git a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala
index de1bffb7d2..0cf3a8fa2e 100644
--- a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala
+++ b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala
@@ -48,6 +48,8 @@ trait BootableActorLoaderService extends Bootable {
abstract override def onLoad = {
super.onLoad
+ applicationLoader foreach Thread.currentThread.setContextClassLoader
+
for (loader ← applicationLoader; clazz ← BOOT_CLASSES) {
loader.loadClass(clazz).newInstance
}
diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala
index 685197820b..f681e8ab50 100644
--- a/akka-actor/src/main/scala/akka/actor/Deployer.scala
+++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala
@@ -16,165 +16,6 @@ import akka.util.ReflectiveAccess._
import akka.serialization._
import akka.AkkaException
-/**
- * Module holding the programmatic deployment configuration classes.
- * Defines the deployment specification.
- * Most values have defaults and can be left out.
- *
- * @author Jonas Bonér
- */
-object DeploymentConfig {
-
- // --------------------------------
- // --- Deploy
- // --------------------------------
- case class Deploy(
- address: String,
- routing: Routing = Direct,
- format: String = Serializer.defaultSerializerName, // Format.defaultSerializerName,
- scope: Scope = Local)
-
- // --------------------------------
- // --- Routing
- // --------------------------------
- sealed trait Routing
- case class CustomRouter(router: AnyRef) extends Routing
-
- // For Java API
- case class Direct() extends Routing
- case class RoundRobin() extends Routing
- case class Random() extends Routing
- case class LeastCPU() extends Routing
- case class LeastRAM() extends Routing
- case class LeastMessages() extends Routing
-
- // For Scala API
- case object Direct extends Routing
- case object RoundRobin extends Routing
- case object Random extends Routing
- case object LeastCPU extends Routing
- case object LeastRAM extends Routing
- case object LeastMessages extends Routing
-
- // --------------------------------
- // --- Scope
- // --------------------------------
- sealed trait Scope
- case class Clustered(
- home: Home = Host("localhost"),
- replicas: Replicas = NoReplicas,
- replication: ReplicationScheme = Transient) extends Scope
-
- // For Java API
- case class Local() extends Scope
-
- // For Scala API
- case object Local extends Scope
-
- // --------------------------------
- // --- Home
- // --------------------------------
- sealed trait Home
- case class Host(hostName: String) extends Home
- case class Node(nodeName: String) extends Home
- case class IP(ipAddress: String) extends Home
-
- // --------------------------------
- // --- Replicas
- // --------------------------------
- sealed trait Replicas
- case class Replicate(factor: Int) extends Replicas {
- if (factor < 1) throw new IllegalArgumentException("Replicas factor can not be negative or zero")
- }
-
- // For Java API
- case class AutoReplicate() extends Replicas
- case class NoReplicas() extends Replicas
-
- // For Scala API
- case object AutoReplicate extends Replicas
- case object NoReplicas extends Replicas
-
- // --------------------------------
- // --- Replication
- // --------------------------------
- sealed trait ReplicationScheme
-
- // For Java API
- case class Transient() extends ReplicationScheme
-
- // For Scala API
- case object Transient extends ReplicationScheme
- case class Replication(
- storage: ReplicationStorage,
- strategy: ReplicationStrategy) extends ReplicationScheme
-
- // --------------------------------
- // --- ReplicationStorage
- // --------------------------------
- sealed trait ReplicationStorage
-
- // For Java API
- case class TransactionLog() extends ReplicationStorage
- case class DataGrid() extends ReplicationStorage
-
- // For Scala API
- case object TransactionLog extends ReplicationStorage
- case object DataGrid extends ReplicationStorage
-
- // --------------------------------
- // --- ReplicationStrategy
- // --------------------------------
- sealed trait ReplicationStrategy
-
- // For Java API
- case class WriteBehind() extends ReplicationStrategy
- case class WriteThrough() extends ReplicationStrategy
-
- // For Scala API
- case object WriteBehind extends ReplicationStrategy
- case object WriteThrough extends ReplicationStrategy
-
- // --------------------------------
- // --- Helper methods for parsing
- // --------------------------------
-
- def isHomeNode(home: Home): Boolean = home match {
- case Host(hostname) ⇒ hostname == Config.hostname
- case IP(address) ⇒ address == "0.0.0.0" || address == "127.0.0.1" // FIXME look up IP address from the system
- case Node(nodename) ⇒ nodename == Config.nodename
- }
-
- def replicaValueFor(replicas: Replicas): Int = replicas match {
- case Replicate(replicas) ⇒ replicas
- case AutoReplicate ⇒ -1
- case AutoReplicate() ⇒ -1
- case NoReplicas ⇒ 0
- case NoReplicas() ⇒ 0
- }
-
- def routerTypeFor(routing: Routing): RouterType = routing match {
- case Direct ⇒ RouterType.Direct
- case Direct() ⇒ RouterType.Direct
- case RoundRobin ⇒ RouterType.RoundRobin
- case RoundRobin() ⇒ RouterType.RoundRobin
- case Random ⇒ RouterType.Random
- case Random() ⇒ RouterType.Random
- case LeastCPU ⇒ RouterType.LeastCPU
- case LeastCPU() ⇒ RouterType.LeastCPU
- case LeastRAM ⇒ RouterType.LeastRAM
- case LeastRAM() ⇒ RouterType.LeastRAM
- case LeastMessages ⇒ RouterType.LeastMessages
- case LeastMessages() ⇒ RouterType.LeastMessages
- case c: CustomRouter ⇒ throw new UnsupportedOperationException("routerTypeFor: " + c)
- }
-
- def isReplicationAsync(strategy: ReplicationStrategy): Boolean = strategy match {
- case _: WriteBehind | WriteBehind ⇒ true
- case _: WriteThrough | WriteThrough ⇒ false
- }
-}
-
/**
* Deployer maps actor deployments to actor addresses.
*
@@ -223,8 +64,8 @@ object Deployer {
}
def isLocal(deployment: Deploy): Boolean = deployment match {
- case Deploy(_, _, _, Local) ⇒ true
- case _ ⇒ false
+ case Deploy(_, _, Local) ⇒ true
+ case _ ⇒ false
}
def isClustered(deployment: Deploy): Boolean = isLocal(deployment)
@@ -245,8 +86,10 @@ object Deployer {
private[akka] def lookupDeploymentFor(address: String): Option[Deploy] = {
val deployment_? = instance.lookupDeploymentFor(address)
+
if (deployment_?.isDefined && (deployment_?.get ne null)) deployment_?
else {
+
val newDeployment =
try {
lookupInConfig(address)
@@ -255,6 +98,7 @@ object Deployer {
EventHandler.error(e, this, e.getMessage)
throw e
}
+
newDeployment foreach { d ⇒
if (d eq null) {
val e = new IllegalStateException("Deployment for address [" + address + "] is null")
@@ -263,6 +107,7 @@ object Deployer {
}
deploy(d) // deploy and cache it
}
+
newDeployment
}
}
@@ -295,7 +140,7 @@ object Deployer {
// --------------------------------
val addressPath = "akka.actor.deployment." + address
Config.config.getSection(addressPath) match {
- case None ⇒ Some(Deploy(address, Direct, Serializer.defaultSerializerName, Local))
+ case None ⇒ Some(Deploy(address, Direct, Local))
case Some(addressConfig) ⇒
// --------------------------------
@@ -319,62 +164,64 @@ object Deployer {
CustomRouter(customRouter)
}
- // --------------------------------
- // akka.actor.deployment..format
- // --------------------------------
- val format = addressConfig.getString("format", Serializer.defaultSerializerName)
-
// --------------------------------
// akka.actor.deployment..clustered
// --------------------------------
addressConfig.getSection("clustered") match {
case None ⇒
- Some(Deploy(address, router, Serializer.defaultSerializerName, Local)) // deploy locally
+ Some(Deploy(address, router, Local)) // deploy locally
case Some(clusteredConfig) ⇒
// --------------------------------
- // akka.actor.deployment..clustered.home
+ // akka.actor.deployment..clustered.preferred-nodes
// --------------------------------
- val home = clusteredConfig.getString("home", "") match {
- case "" ⇒ Host("localhost")
- case home ⇒
+ val preferredNodes = clusteredConfig.getList("preferred-nodes") match {
+ case Nil ⇒ Nil
+ case homes ⇒
def raiseHomeConfigError() = throw new ConfigurationException(
"Config option [" + addressPath +
- ".clustered.home] needs to be on format 'host:', 'ip:'' or 'node:', was [" +
- home + "]")
+ ".clustered.preferred-nodes] needs to be a list with elements on format\n'host:', 'ip:' or 'node:', was [" +
+ homes + "]")
- if (!(home.startsWith("host:") || home.startsWith("node:") || home.startsWith("ip:"))) raiseHomeConfigError()
+ homes map { home ⇒
+ if (!(home.startsWith("host:") || home.startsWith("node:") || home.startsWith("ip:"))) raiseHomeConfigError()
- val tokenizer = new java.util.StringTokenizer(home, ":")
- val protocol = tokenizer.nextElement
- val address = tokenizer.nextElement.asInstanceOf[String]
+ val tokenizer = new java.util.StringTokenizer(home, ":")
+ val protocol = tokenizer.nextElement
+ val address = tokenizer.nextElement.asInstanceOf[String]
- protocol match {
- case "host" ⇒ Host(address)
- case "node" ⇒ Node(address)
- case "ip" ⇒ IP(address)
- case _ ⇒ raiseHomeConfigError()
+ protocol match {
+ case "host" ⇒ Host(address)
+ case "node" ⇒ Node(address)
+ case "ip" ⇒ IP(address)
+ case _ ⇒ raiseHomeConfigError()
+ }
}
}
// --------------------------------
// akka.actor.deployment..clustered.replicas
// --------------------------------
- val replicas = clusteredConfig.getAny("replicas", "0") match {
- case "auto" ⇒ AutoReplicate
- case "0" ⇒ NoReplicas
- case nrOfReplicas: String ⇒
- try {
- Replicate(nrOfReplicas.toInt)
- } catch {
- case e: NumberFormatException ⇒
- throw new ConfigurationException(
- "Config option [" + addressPath +
- ".clustered.replicas] needs to be either [\"auto\"] or [0-N] - was [" +
- nrOfReplicas + "]")
+ val replicas = {
+ if (router == Direct) Replicate(1)
+ else {
+ clusteredConfig.getAny("replicas", "0") match {
+ case "auto" ⇒ AutoReplicate
+ case "0" ⇒ NoReplicas
+ case nrOfReplicas: String ⇒
+ try {
+ Replicate(nrOfReplicas.toInt)
+ } catch {
+ case e: NumberFormatException ⇒
+ throw new ConfigurationException(
+ "Config option [" + addressPath +
+ ".clustered.replicas] needs to be either [\"auto\"] or [0-N] - was [" +
+ nrOfReplicas + "]")
+ }
}
+ }
}
// --------------------------------
@@ -382,7 +229,7 @@ object Deployer {
// --------------------------------
clusteredConfig.getSection("replication") match {
case None ⇒
- Some(Deploy(address, router, format, Clustered(home, replicas, Transient)))
+ Some(Deploy(address, router, Clustered(preferredNodes, replicas, Transient)))
case Some(replicationConfig) ⇒
val storage = replicationConfig.getString("storage", "transaction-log") match {
@@ -401,7 +248,7 @@ object Deployer {
".clustered.replication.strategy] needs to be either [\"write-through\"] or [\"write-behind\"] - was [" +
unknown + "]")
}
- Some(Deploy(address, router, format, Clustered(home, replicas, Replication(storage, strategy))))
+ Some(Deploy(address, router, Clustered(preferredNodes, replicas, Replication(storage, strategy))))
}
}
}
diff --git a/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala b/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala
new file mode 100644
index 0000000000..1d4f23e545
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala
@@ -0,0 +1,217 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.actor
+
+import akka.config.Config
+import akka.routing.RouterType
+import akka.serialization.Serializer
+
+/**
+ * Module holding the programmatic deployment configuration classes.
+ * Defines the deployment specification.
+ * Most values have defaults and can be left out.
+ *
+ * @author Jonas Bonér
+ */
+object DeploymentConfig {
+
+ // --------------------------------
+ // --- Deploy
+ // --------------------------------
+ case class Deploy(
+ address: String,
+ routing: Routing = Direct,
+ scope: Scope = Local)
+
+ // --------------------------------
+ // --- Routing
+ // --------------------------------
+ sealed trait Routing
+ case class CustomRouter(router: AnyRef) extends Routing
+
+ // For Java API
+ case class Direct() extends Routing
+ case class RoundRobin() extends Routing
+ case class Random() extends Routing
+ case class LeastCPU() extends Routing
+ case class LeastRAM() extends Routing
+ case class LeastMessages() extends Routing
+
+ // For Scala API
+ case object Direct extends Routing
+ case object RoundRobin extends Routing
+ case object Random extends Routing
+ case object LeastCPU extends Routing
+ case object LeastRAM extends Routing
+ case object LeastMessages extends Routing
+
+ // --------------------------------
+ // --- Scope
+ // --------------------------------
+ sealed trait Scope
+ case class Clustered(
+ preferredNodes: Iterable[Home] = Vector(Host("localhost")),
+ replicas: Replicas = NoReplicas,
+ replication: ReplicationScheme = Transient) extends Scope
+
+ // For Java API
+ case class Local() extends Scope
+
+ // For Scala API
+ case object Local extends Scope
+
+ // --------------------------------
+ // --- Home
+ // --------------------------------
+ sealed trait Home
+ case class Host(hostName: String) extends Home
+ case class Node(nodeName: String) extends Home
+ case class IP(ipAddress: String) extends Home
+
+ // --------------------------------
+ // --- Replicas
+ // --------------------------------
+ sealed trait Replicas
+ case class Replicate(factor: Int) extends Replicas {
+ if (factor < 1) throw new IllegalArgumentException("Replicas factor can not be negative or zero")
+ }
+
+ // For Java API
+ case class AutoReplicate() extends Replicas
+ case class NoReplicas() extends Replicas
+
+ // For Scala API
+ case object AutoReplicate extends Replicas
+ case object NoReplicas extends Replicas
+
+ // --------------------------------
+ // --- Replication
+ // --------------------------------
+ sealed trait ReplicationScheme
+
+ // For Java API
+ case class Transient() extends ReplicationScheme
+
+ // For Scala API
+ case object Transient extends ReplicationScheme
+ case class Replication(
+ storage: ReplicationStorage,
+ strategy: ReplicationStrategy) extends ReplicationScheme
+
+ // --------------------------------
+ // --- ReplicationStorage
+ // --------------------------------
+ sealed trait ReplicationStorage
+
+ // For Java API
+ case class TransactionLog() extends ReplicationStorage
+ case class DataGrid() extends ReplicationStorage
+
+ // For Scala API
+ case object TransactionLog extends ReplicationStorage
+ case object DataGrid extends ReplicationStorage
+
+ // --------------------------------
+ // --- ReplicationStrategy
+ // --------------------------------
+ sealed trait ReplicationStrategy
+
+ // For Java API
+ case class WriteBehind() extends ReplicationStrategy
+ case class WriteThrough() extends ReplicationStrategy
+
+ // For Scala API
+ case object WriteBehind extends ReplicationStrategy
+ case object WriteThrough extends ReplicationStrategy
+
+ // --------------------------------
+ // --- Helper methods for parsing
+ // --------------------------------
+
+ def nodeNameFor(home: Home): String = home match {
+ case Node(nodename) ⇒ nodename
+ case Host("localhost") ⇒ Config.nodename
+ case IP("0.0.0.0") ⇒ Config.nodename
+ case IP("127.0.0.1") ⇒ Config.nodename
+ case Host(hostname) ⇒ throw new UnsupportedOperationException("Specifying preferred node name by 'hostname' is not yet supported. Use the node name like: preferred-nodes = [\"node:node1\"]")
+ case IP(address) ⇒ throw new UnsupportedOperationException("Specifying preferred node name by 'IP address' is not yet supported. Use the node name like: preferred-nodes = [\"node:node1\"]")
+ }
+
+ def isHomeNode(homes: Iterable[Home]): Boolean = homes exists (home ⇒ nodeNameFor(home) == Config.nodename)
+
+ def replicaValueFor(replicas: Replicas): Int = replicas match {
+ case Replicate(replicas) ⇒ replicas
+ case AutoReplicate ⇒ -1
+ case AutoReplicate() ⇒ -1
+ case NoReplicas ⇒ 0
+ case NoReplicas() ⇒ 0
+ }
+
+ def routerTypeFor(routing: Routing): RouterType = routing match {
+ case Direct ⇒ RouterType.Direct
+ case Direct() ⇒ RouterType.Direct
+ case RoundRobin ⇒ RouterType.RoundRobin
+ case RoundRobin() ⇒ RouterType.RoundRobin
+ case Random ⇒ RouterType.Random
+ case Random() ⇒ RouterType.Random
+ case LeastCPU ⇒ RouterType.LeastCPU
+ case LeastCPU() ⇒ RouterType.LeastCPU
+ case LeastRAM ⇒ RouterType.LeastRAM
+ case LeastRAM() ⇒ RouterType.LeastRAM
+ case LeastMessages ⇒ RouterType.LeastMessages
+ case LeastMessages() ⇒ RouterType.LeastMessages
+ case c: CustomRouter ⇒ throw new UnsupportedOperationException("Unknown Router [" + c + "]")
+ }
+
+ def replicationSchemeFor(deployment: Deploy): Option[ReplicationScheme] = deployment match {
+ case Deploy(_, _, Clustered(_, _, replicationScheme)) ⇒ Some(replicationScheme)
+ case _ ⇒ None
+ }
+
+ def isReplicated(deployment: Deploy): Boolean = replicationSchemeFor(deployment) match {
+ case Some(replicationScheme) ⇒ isReplicated(replicationScheme)
+ case _ ⇒ false
+ }
+
+ def isReplicated(replicationScheme: ReplicationScheme): Boolean =
+ isReplicatedWithTransactionLog(replicationScheme) ||
+ isReplicatedWithDataGrid(replicationScheme)
+
+ def isWriteBehindReplication(replicationScheme: ReplicationScheme): Boolean = replicationScheme match {
+ case _: Transient | Transient ⇒ false
+ case Replication(_, strategy) ⇒
+ strategy match {
+ case _: WriteBehind | WriteBehind ⇒ true
+ case _: WriteThrough | WriteThrough ⇒ false
+ }
+ }
+
+ def isWriteThroughReplication(replicationScheme: ReplicationScheme): Boolean = replicationScheme match {
+ case _: Transient | Transient ⇒ false
+ case Replication(_, strategy) ⇒
+ strategy match {
+ case _: WriteBehind | WriteBehind ⇒ true
+ case _: WriteThrough | WriteThrough ⇒ false
+ }
+ }
+
+ def isReplicatedWithTransactionLog(replicationScheme: ReplicationScheme): Boolean = replicationScheme match {
+ case _: Transient | Transient ⇒ false
+ case Replication(storage, _) ⇒
+ storage match {
+ case _: TransactionLog | TransactionLog ⇒ true
+ case _: DataGrid | DataGrid ⇒ throw new UnsupportedOperationException("ReplicationStorage 'DataGrid' is no supported yet")
+ }
+ }
+
+ def isReplicatedWithDataGrid(replicationScheme: ReplicationScheme): Boolean = replicationScheme match {
+ case _: Transient | Transient ⇒ false
+ case Replication(storage, _) ⇒
+ storage match {
+ case _: TransactionLog | TransactionLog ⇒ false
+ case _: DataGrid | DataGrid ⇒ throw new UnsupportedOperationException("ReplicationStorage 'DataGrid' is no supported yet")
+ }
+ }
+}
diff --git a/akka-actor/src/main/scala/akka/actor/Scheduler.scala b/akka-actor/src/main/scala/akka/actor/Scheduler.scala
index 823333761f..4096188a88 100644
--- a/akka-actor/src/main/scala/akka/actor/Scheduler.scala
+++ b/akka-actor/src/main/scala/akka/actor/Scheduler.scala
@@ -15,12 +15,12 @@
*/
package akka.actor
-import scala.collection.JavaConversions
-
-import java.util.concurrent._
-
import akka.event.EventHandler
import akka.AkkaException
+import java.util.concurrent.atomic.AtomicLong
+import java.lang.ref.WeakReference
+import java.util.concurrent._
+import java.lang.RuntimeException
object Scheduler {
import Actor._
@@ -30,14 +30,28 @@ object Scheduler {
@volatile
private var service = Executors.newSingleThreadScheduledExecutor(SchedulerThreadFactory)
+ private def createSendRunnable(receiver: ActorRef, message: Any, throwWhenReceiverExpired: Boolean): Runnable = {
+ receiver match {
+ case local: LocalActorRef =>
+ val ref = new WeakReference[ActorRef](local)
+ new Runnable {
+ def run = ref.get match {
+ case null => if(throwWhenReceiverExpired) throw new RuntimeException("Receiver not found: GC:ed")
+ case actor => actor ! message
+ }
+ }
+ case other => new Runnable { def run = other ! message }
+ }
+ }
+
/**
- * Schedules to send the specified message to the receiver after initialDelay and then repeated after delay
+ * Schedules to send the specified message to the receiver after initialDelay and then repeated after delay.
+ * The returned java.util.concurrent.ScheduledFuture can be used to cancel the
+ * send of the message.
*/
- def schedule(receiver: ActorRef, message: AnyRef, initialDelay: Long, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] = {
+ def schedule(receiver: ActorRef, message: Any, initialDelay: Long, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] = {
try {
- service.scheduleAtFixedRate(
- new Runnable { def run = receiver ! message },
- initialDelay, delay, timeUnit).asInstanceOf[ScheduledFuture[AnyRef]]
+ service.scheduleAtFixedRate(createSendRunnable(receiver, message, true), initialDelay, delay, timeUnit).asInstanceOf[ScheduledFuture[AnyRef]]
} catch {
case e: Exception ⇒
val error = SchedulerException(message + " could not be scheduled on " + receiver, e)
@@ -48,14 +62,18 @@ object Scheduler {
/**
* Schedules to run specified function to the receiver after initialDelay and then repeated after delay,
- * avoid blocking operations since this is executed in the schedulers thread
+ * avoid blocking operations since this is executed in the schedulers thread.
+ * The returned java.util.concurrent.ScheduledFuture can be used to cancel the
+ * execution of the function.
*/
def schedule(f: () ⇒ Unit, initialDelay: Long, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] =
schedule(new Runnable { def run = f() }, initialDelay, delay, timeUnit)
/**
* Schedules to run specified runnable to the receiver after initialDelay and then repeated after delay,
- * avoid blocking operations since this is executed in the schedulers thread
+ * avoid blocking operations since this is executed in the schedulers thread.
+ * The returned java.util.concurrent.ScheduledFuture can be used to cancel the
+ * execution of the runnable.
*/
def schedule(runnable: Runnable, initialDelay: Long, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] = {
try {
@@ -69,13 +87,13 @@ object Scheduler {
}
/**
- * Schedules to send the specified message to the receiver after delay
+ * Schedules to send the specified message to the receiver after delay.
+ * The returned java.util.concurrent.ScheduledFuture can be used to cancel the
+ * send of the message.
*/
- def scheduleOnce(receiver: ActorRef, message: AnyRef, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] = {
+ def scheduleOnce(receiver: ActorRef, message: Any, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] = {
try {
- service.schedule(
- new Runnable { def run = receiver ! message },
- delay, timeUnit).asInstanceOf[ScheduledFuture[AnyRef]]
+ service.schedule(createSendRunnable(receiver, message, false), delay, timeUnit).asInstanceOf[ScheduledFuture[AnyRef]]
} catch {
case e: Exception ⇒
val error = SchedulerException(message + " could not be scheduleOnce'd on " + receiver, e)
@@ -86,14 +104,18 @@ object Scheduler {
/**
* Schedules a function to be run after delay,
- * avoid blocking operations since the runnable is executed in the schedulers thread
+ * avoid blocking operations since the runnable is executed in the schedulers thread.
+ * The returned java.util.concurrent.ScheduledFuture can be used to cancel the
+ * execution of the function.
*/
def scheduleOnce(f: () ⇒ Unit, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] =
scheduleOnce(new Runnable { def run = f() }, delay, timeUnit)
/**
* Schedules a runnable to be run after delay,
- * avoid blocking operations since the runnable is executed in the schedulers thread
+ * avoid blocking operations since the runnable is executed in the schedulers thread.
+ * The returned java.util.concurrent.ScheduledFuture can be used to cancel the
+ * execution of the runnable.
*/
def scheduleOnce(runnable: Runnable, delay: Long, timeUnit: TimeUnit): ScheduledFuture[AnyRef] = {
try {
@@ -121,12 +143,12 @@ object Scheduler {
}
private object SchedulerThreadFactory extends ThreadFactory {
- private var count = 0
+ private val count = new AtomicLong(0)
val threadFactory = Executors.defaultThreadFactory()
def newThread(r: Runnable): Thread = {
val thread = threadFactory.newThread(r)
- thread.setName("akka:scheduler-" + count)
+ thread.setName("akka:scheduler-" + count.incrementAndGet())
thread.setDaemon(true)
thread
}
diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala
index 622cf1908b..85e206be46 100644
--- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala
+++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala
@@ -41,6 +41,14 @@ class SupervisorException private[akka] (message: String, cause: Throwable = nul
* supervisor.unlink(child)
*
*
+ * If you are using it from Java you have to use Supervisor.apply(..) like in:
+ *
+ * Supervisor supervisor = Supervisor.apply(
+ * SupervisorConfig(
+ * ..
+ * ))
+ *
+ *
* @author Jonas Bonér
*/
object Supervisor {
diff --git a/akka-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-actor/src/main/scala/akka/actor/TypedActor.scala
index ac226a8734..a8cb627b14 100644
--- a/akka-actor/src/main/scala/akka/actor/TypedActor.scala
+++ b/akka-actor/src/main/scala/akka/actor/TypedActor.scala
@@ -12,71 +12,76 @@ import akka.util.{ Duration }
import java.util.concurrent.atomic.{ AtomicReference ⇒ AtomVar }
//TODO Document this class, not only in Scaladoc, but also in a dedicated typed-actor.rst, for both java and scala
+/**
+ * A TypedActor in Akka is an implementation of the Active Objects Pattern, i.e. an object with asynchronous method dispatch
+ *
+ * It consists of 2 parts:
+ * The Interface
+ * The Implementation
+ *
+ * Given a combination of Interface and Implementation, a JDK Dynamic Proxy object with the Interface will be returned
+ *
+ * The semantics is as follows,
+ * any methods in the Interface that returns Unit/void will use fire-and-forget semantics (same as Actor !)
+ * any methods in the Interface that returns Option/JOption will use ask + block-with-timeout-return-none-if-timeout semantics
+ * any methods in the Interface that returns anything else will use ask + block-with-timeout-throw-if-timeout semantics
+ *
+ * TypedActors needs, just like Actors, to be Stopped when they are no longer needed, use TypedActor.stop(proxy)
+ */
object TypedActor {
private val selfReference = new ThreadLocal[AnyRef]
+ /**
+ * Returns the reference to the proxy when called inside a method call in a TypedActor
+ *
+ * Example:
+ *
+ * class FooImpl extends Foo {
+ * def doFoo {
+ * val myself = self[Foo]
+ * }
+ * }
+ *
+ * Useful when you want to send a reference to this TypedActor to someone else.
+ *
+ * NEVER EXPOSE "this" to someone else, always use "self[TypeOfInterface(s)]"
+ *
+ * @throws IllegalStateException if called outside of the scope of a method on this TypedActor
+ * @throws ClassCastException if the supplied type T isn't the type of the proxy associated with this TypedActor
+ */
def self[T <: AnyRef] = selfReference.get.asInstanceOf[T] match {
case null ⇒ throw new IllegalStateException("Calling TypedActor.self outside of a TypedActor implementation method!")
case some ⇒ some
}
- private[akka] class TypedActor[R <: AnyRef, T <: R](val proxyRef: AtomVar[R], createInstance: ⇒ T) extends Actor {
- val me = createInstance
- def receive = {
- case m: MethodCall ⇒
- selfReference set proxyRef.get
- try {
- m match {
- case m if m.isOneWay ⇒ m(me)
- case m if m.returnsFuture_? ⇒ self.senderFuture.get completeWith m(me).asInstanceOf[Future[Any]]
- case m ⇒ self reply m(me)
- }
- } finally { selfReference set null }
- }
- }
-
- case class TypedActorInvocationHandler(actor: ActorRef) extends InvocationHandler {
- def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]): AnyRef = method.getName match {
- case "toString" ⇒ actor.toString
- case "equals" ⇒ (args.length == 1 && (proxy eq args(0)) || actor == getActorRefFor(args(0))).asInstanceOf[AnyRef] //Force boxing of the boolean
- case "hashCode" ⇒ actor.hashCode.asInstanceOf[AnyRef]
- case _ ⇒
- implicit val timeout = Timeout(actor.timeout)
- MethodCall(method, args) match {
- case m if m.isOneWay ⇒
- actor ! m
- null
- case m if m.returnsFuture_? ⇒
- actor ? m
- case m if m.returnsJOption_? || m.returnsOption_? ⇒
- val f = actor ? m
- try { f.await } catch { case _: FutureTimeoutException ⇒ }
- f.value match {
- case None | Some(Right(null)) ⇒ if (m.returnsJOption_?) JOption.none[Any] else None
- case Some(Right(joption: AnyRef)) ⇒ joption
- case Some(Left(ex)) ⇒ throw ex
- }
- case m ⇒
- (actor ? m).get.asInstanceOf[AnyRef]
- }
- }
- }
-
+<<<<<<< HEAD
+ @deprecated("This should be replaced with the same immutable configuration that will be used for ActorRef.actorOf", "!!!")
object Configuration { //TODO: Replace this with the new ActorConfiguration when it exists
val defaultTimeout = Duration(Actor.TIMEOUT, "millis")
val defaultConfiguration = new Configuration(defaultTimeout, Dispatchers.defaultGlobalDispatcher)
def apply(): Configuration = defaultConfiguration
}
+ @deprecated("This should be replaced with the same immutable configuration that will be used for ActorRef.actorOf", "!!!")
case class Configuration(timeout: Duration = Configuration.defaultTimeout, dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher)
+ /**
+ * This class represents a Method call, and has a reference to the Method to be called and the parameters to supply
+ * It's sent to the ActorRef backing the TypedActor and can be serialized and deserialized
+ */
case class MethodCall(method: Method, parameters: Array[AnyRef]) {
+
def isOneWay = method.getReturnType == java.lang.Void.TYPE
def returnsFuture_? = classOf[Future[_]].isAssignableFrom(method.getReturnType)
def returnsJOption_? = classOf[akka.japi.Option[_]].isAssignableFrom(method.getReturnType)
def returnsOption_? = classOf[scala.Option[_]].isAssignableFrom(method.getReturnType)
+ /**
+ * Invokes the Method on the supplied instance
+ *
+ * @throws the underlying exception if there's an InvocationTargetException thrown on the invocation
+ */
def apply(instance: AnyRef): AnyRef = try {
- parameters match { //We do not yet obey Actor.SERIALIZE_MESSAGES
+ parameters match { //TODO: We do not yet obey Actor.SERIALIZE_MESSAGES
case null ⇒ method.invoke(instance)
case args if args.length == 0 ⇒ method.invoke(instance)
case args ⇒ method.invoke(instance, args: _*)
@@ -86,43 +91,103 @@ object TypedActor {
private def writeReplace(): AnyRef = new SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, parameters)
}
+ /**
+ * Represents the serialized form of a MethodCall, uses readResolve and writeReplace to marshall the call
+ */
case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], parameterValues: Array[AnyRef]) {
//TODO implement writeObject and readObject to serialize
//TODO Possible optimization is to special encode the parameter-types to conserve space
private def readResolve(): AnyRef = MethodCall(ownerType.getDeclaredMethod(methodName, parameterTypes: _*), parameterValues)
}
+ /**
+ * Creates a new TypedActor proxy using the supplied configuration,
+ * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or
+ * all interfaces (Class.getInterfaces) if it's not an interface class
+ */
def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Class[T], config: Configuration): R =
createProxyAndTypedActor(interface, impl.newInstance, config, interface.getClassLoader)
+ /**
+ * Creates a new TypedActor proxy using the supplied configuration,
+ * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or
+ * all interfaces (Class.getInterfaces) if it's not an interface class
+ */
def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Creator[T], config: Configuration): R =
createProxyAndTypedActor(interface, impl.create, config, interface.getClassLoader)
def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Class[T], config: Configuration, loader: ClassLoader): R =
createProxyAndTypedActor(interface, impl.newInstance, config, loader)
+ /**
+ * Creates a new TypedActor proxy using the supplied configuration,
+ * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or
+ * all interfaces (Class.getInterfaces) if it's not an interface class
+ */
def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Creator[T], config: Configuration, loader: ClassLoader): R =
createProxyAndTypedActor(interface, impl.create, config, loader)
+ /**
+ * Creates a new TypedActor proxy using the supplied configuration,
+ * the interfaces usable by the returned proxy is the supplied implementation class' interfaces (Class.getInterfaces)
+ */
def typedActorOf[R <: AnyRef, T <: R](impl: Class[T], config: Configuration, loader: ClassLoader): R =
createProxyAndTypedActor(impl, impl.newInstance, config, loader)
+ /**
+ * Creates a new TypedActor proxy using the supplied configuration,
+ * the interfaces usable by the returned proxy is the supplied implementation class' interfaces (Class.getInterfaces)
+ */
def typedActorOf[R <: AnyRef, T <: R](config: Configuration = Configuration(), loader: ClassLoader = null)(implicit m: Manifest[T]): R = {
val clazz = m.erasure.asInstanceOf[Class[T]]
createProxyAndTypedActor(clazz, clazz.newInstance, config, if (loader eq null) clazz.getClassLoader else loader)
}
- def stop(typedActor: AnyRef): Boolean = getActorRefFor(typedActor) match {
+ /**
+ * Stops the underlying ActorRef for the supplied TypedActor proxy, if any, returns whether it could stop it or not
+ */
+ def stop(proxy: AnyRef): Boolean = getActorRefFor(proxy) match {
case null ⇒ false
case ref ⇒ ref.stop; true
}
- def getActorRefFor(typedActor: AnyRef): ActorRef = invocationHandlerFor(typedActor) match {
+ /**
+ * Retrieves the underlying ActorRef for the supplied TypedActor proxy, or null if none found
+ */
+ def getActorRefFor(proxy: AnyRef): ActorRef = invocationHandlerFor(proxy) match {
case null ⇒ null
case handler ⇒ handler.actor
}
- def invocationHandlerFor(typedActor_? : AnyRef): TypedActorInvocationHandler =
+ /**
+ * Returns wether the supplied AnyRef is a TypedActor proxy or not
+ */
+ def isTypedActor(proxyOrNot: AnyRef): Boolean = invocationHandlerFor(proxyOrNot) ne null
+
+ /**
+ * Creates a proxy given the supplied configuration, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself,
+ * to create TypedActor proxies, use typedActorOf
+ */
+ def createProxy[R <: AnyRef](constructor: ⇒ Actor, config: Configuration = Configuration(), loader: ClassLoader = null)(implicit m: Manifest[R]): R =
+ createProxy[R](extractInterfaces(m.erasure), (ref: AtomVar[R]) ⇒ constructor, config, if (loader eq null) m.erasure.getClassLoader else loader)
+
+ /**
+ * Creates a proxy given the supplied configuration, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself,
+ * to create TypedActor proxies, use typedActorOf
+ */
+ def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: Creator[Actor], config: Configuration, loader: ClassLoader): R =
+ createProxy(interfaces, (ref: AtomVar[R]) ⇒ constructor.create, config, loader)
+
+ /**
+ * Creates a proxy given the supplied configuration, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself,
+ * to create TypedActor proxies, use typedActorOf
+ */
+ def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: ⇒ Actor, config: Configuration, loader: ClassLoader): R =
+ createProxy[R](interfaces, (ref: AtomVar[R]) ⇒ constructor, config, loader)
+
+ /* Internal API */
+
+ private[akka] def invocationHandlerFor(typedActor_? : AnyRef): TypedActorInvocationHandler =
if ((typedActor_? ne null) && Proxy.isProxyClass(typedActor_?.getClass)) typedActor_? match {
case null ⇒ null
case other ⇒ Proxy.getInvocationHandler(other) match {
@@ -133,19 +198,6 @@ object TypedActor {
}
else null
- def isTypedActor(typedActor_? : AnyRef): Boolean = invocationHandlerFor(typedActor_?) ne null
-
- def createProxy[R <: AnyRef](constructor: ⇒ Actor, config: Configuration = Configuration(), loader: ClassLoader = null)(implicit m: Manifest[R]): R =
- createProxy[R](extractInterfaces(m.erasure), (ref: AtomVar[R]) ⇒ constructor, config, if (loader eq null) m.erasure.getClassLoader else loader)
-
- def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: Creator[Actor], config: Configuration, loader: ClassLoader): R =
- createProxy(interfaces, (ref: AtomVar[R]) ⇒ constructor.create, config, loader)
-
- def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: ⇒ Actor, config: Configuration, loader: ClassLoader): R =
- createProxy[R](interfaces, (ref: AtomVar[R]) ⇒ constructor, config, loader)
-
- /* Internal API */
-
private[akka] def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: (AtomVar[R]) ⇒ Actor, config: Configuration, loader: ClassLoader): R = {
val proxyRef = new AtomVar[R]
configureAndProxyLocalActorRef[R](interfaces, proxyRef, constructor(proxyRef), config, loader)
@@ -168,4 +220,45 @@ object TypedActor {
}
private[akka] def extractInterfaces(clazz: Class[_]): Array[Class[_]] = if (clazz.isInterface) Array[Class[_]](clazz) else clazz.getInterfaces
+
+ private[akka] class TypedActor[R <: AnyRef, T <: R](val proxyRef: AtomVar[R], createInstance: ⇒ T) extends Actor {
+ val me = createInstance
+ def receive = {
+ case m: MethodCall ⇒
+ selfReference set proxyRef.get
+ try {
+ if (m.isOneWay) m(me)
+ else if (m.returnsFuture_?) self.senderFuture.get completeWith m(me).asInstanceOf[Future[Any]]
+ else self reply m(me)
+
+ } finally { selfReference set null }
+ }
+ }
+
+ private[akka] case class TypedActorInvocationHandler(actor: ActorRef) extends InvocationHandler {
+ def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]): AnyRef = method.getName match {
+ case "toString" ⇒ actor.toString
+ case "equals" ⇒ (args.length == 1 && (proxy eq args(0)) || actor == getActorRefFor(args(0))).asInstanceOf[AnyRef] //Force boxing of the boolean
+ case "hashCode" ⇒ actor.hashCode.asInstanceOf[AnyRef]
+ case _ ⇒
+ implicit val timeout = Actor.Timeout(actor.timeout)
+ MethodCall(method, args) match {
+ case m if m.isOneWay ⇒
+ actor ! m
+ null
+ case m if m.returnsFuture_? ⇒
+ actor ? m
+ case m if m.returnsJOption_? || m.returnsOption_? ⇒
+ val f = actor ? m
+ try { f.await } catch { case _: FutureTimeoutException ⇒ }
+ f.value match {
+ case None | Some(Right(null)) ⇒ if (m.returnsJOption_?) JOption.none[Any] else None
+ case Some(Right(joption: AnyRef)) ⇒ joption
+ case Some(Left(ex)) ⇒ throw ex
+ }
+ case m ⇒
+ (actor ? m).get.asInstanceOf[AnyRef]
+ }
+ }
+ }
}
diff --git a/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala b/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala
index 84c783ed37..714207458c 100644
--- a/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala
+++ b/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala
@@ -122,11 +122,9 @@ object NodeAddress {
trait ClusterNode {
import ChangeListener._
- val isConnected = new Switch(false)
+ val isConnected = new AtomicBoolean(false)
private[cluster] val locallyCachedMembershipNodes = new ConcurrentSkipListSet[String]()
- private[cluster] val nodeNameToAddress: ConcurrentMap[String, InetSocketAddress] = new ConcurrentHashMap[String, InetSocketAddress]
- private[cluster] val locallyCheckedOutActors: ConcurrentMap[UUID, Array[Byte]] = new ConcurrentHashMap[UUID, Array[Byte]]
def membershipNodes: Array[String]
@@ -138,7 +136,7 @@ trait ClusterNode {
def remoteServerAddress: InetSocketAddress
- def isRunning: Boolean = isConnected.isOn
+ def isRunning: Boolean = isConnected.get
def start(): ClusterNode
@@ -173,49 +171,49 @@ trait ClusterNode {
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], serializeMailbox: Boolean, serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
@@ -229,76 +227,75 @@ trait ClusterNode {
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, serializer: Serializer): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, serializer: Serializer): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Needed to have reflection through structural typing work.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode
/**
* Needed to have reflection through structural typing work.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode
/**
* Removes actor from the cluster.
*/
- def remove(actorRef: ActorRef)
+ // def remove(actorRef: ActorRef)
/**
* Removes actor with address from the cluster.
*/
- def remove(address: String): ClusterNode
+ // def remove(address: String): ClusterNode
/**
* Is the actor with uuid clustered or not?
@@ -319,23 +316,28 @@ trait ClusterNode {
* Checks out an actor for use on this node, e.g. checked out as a 'LocalActorRef' but it makes it available
* for remote access through lookup by its UUID.
*/
- def use[T <: Actor](actorAddress: String): Option[ActorRef]
+ def use[T <: Actor](actorAddress: String): Option[LocalActorRef]
/**
* Checks out an actor for use on this node, e.g. checked out as a 'LocalActorRef' but it makes it available
* for remote access through lookup by its UUID.
*/
- def use[T <: Actor](actorAddress: String, serializer: Serializer): Option[ActorRef]
+ def use[T <: Actor](actorAddress: String, serializer: Serializer): Option[LocalActorRef]
/**
- * Using (checking out) all actors with a specific UUID on all nodes in the cluster.
+ * Using (checking out) actor on a specific set of nodes.
*/
- def useActorOnAllNodes(uuid: UUID)
+ def useActorOnNodes(nodes: Array[String], actorAddress: String, replicateFromUuid: Option[UUID])
/**
- * Using (checking out) specific UUID on a specefic node.
+ * Using (checking out) actor on all nodes in the cluster.
*/
- def useActorOnNode(node: String, uuid: UUID)
+ def useActorOnAllNodes(actorAddress: String, replicateFromUuid: Option[UUID])
+
+ /**
+ * Using (checking out) actor on a specific node.
+ */
+ def useActorOnNode(node: String, actorAddress: String, replicateFromUuid: Option[UUID])
/**
* Checks in an actor after done using it on this node.
@@ -352,16 +354,6 @@ trait ClusterNode {
*/
def ref(actorAddress: String, router: RouterType): ActorRef
- /**
- * Migrate the actor from 'this' node to node 'to'.
- */
- def migrate(to: NodeAddress, actorAddress: String)
-
- /**
- * Migrate the actor from node 'from' to node 'to'.
- */
- def migrate(from: NodeAddress, to: NodeAddress, actorAddress: String)
-
/**
* Returns the addresses of all actors checked out on this node.
*/
@@ -436,18 +428,20 @@ trait ClusterNode {
// =============== PRIVATE METHODS ===============
+ // FIXME considering moving all these private[cluster] methods to a separate trait to get them out of the user's view
+
private[cluster] def remoteClientLifeCycleListener: ActorRef
private[cluster] def remoteDaemon: ActorRef
/**
* Removes actor with uuid from the cluster.
*/
- private[cluster] def remove(uuid: UUID)
+ // private[cluster] def remove(uuid: UUID)
/**
* Releases (checking in) all actors with a specific UUID on all nodes in the cluster where the actor is in 'use'.
*/
- private[cluster] def releaseActorOnAllNodes(uuid: UUID)
+ private[cluster] def releaseActorOnAllNodes(actorAddress: String)
/**
* Returns the UUIDs of all actors checked out on this node.
@@ -474,11 +468,6 @@ trait ClusterNode {
*/
private[cluster] def uuidsForActorAddress(actorAddress: String): Array[UUID]
- /**
- * Returns the node names of all actors in use with UUID.
- */
- private[cluster] def nodesForActorsInUseWithUuid(uuid: UUID): Array[String]
-
/**
* Returns the UUIDs of all actors in use registered on a specific node.
*/
@@ -488,50 +477,41 @@ trait ClusterNode {
private[cluster] def publish(change: ChangeNotification)
- private[cluster] def findFailedNodes(nodes: List[String]): List[String]
-
- private[cluster] def findNewlyConnectedMembershipNodes(nodes: List[String]): List[String]
-
- private[cluster] def findNewlyDisconnectedMembershipNodes(nodes: List[String]): List[String]
-
- private[cluster] def findNewlyConnectedAvailableNodes(nodes: List[String]): List[String]
-
- private[cluster] def findNewlyDisconnectedAvailableNodes(nodes: List[String]): List[String]
-
private[cluster] def joinCluster()
private[cluster] def joinLeaderElection: Boolean
- private[cluster] def failOverConnections(from: InetSocketAddress, to: InetSocketAddress)
+ private[cluster] def failOverClusterActorRefConnections(from: InetSocketAddress, to: InetSocketAddress)
- private[cluster] def migrateActorsOnFailedNodes(currentNodes: List[String])
+ private[cluster] def migrateActorsOnFailedNodes(
+ failedNodes: List[String],
+ currentClusterNodes: List[String],
+ oldClusterNodes: List[String],
+ disconnectedConnections: Map[String, InetSocketAddress])
- private[cluster] def membershipPathFor(node: String): String
-
- private[cluster] def configurationPathFor(key: String): String
-
- private[cluster] def actorAddressToUuidsPathFor(actorAddress: String): String
-
- private[cluster] def actorLocationsPathFor(uuid: UUID): String
-
- private[cluster] def actorLocationsPathFor(uuid: UUID, node: NodeAddress): String
-
- private[cluster] def actorsAtNodePathFor(node: String): String
-
- private[cluster] def actorAtNodePathFor(node: String, uuid: UUID): String
-
- private[cluster] def actorRegistryPathFor(uuid: UUID): String
-
- private[cluster] def actorRegistrySerializerPathFor(uuid: UUID): String
-
- private[cluster] def actorRegistryActorAddressPathFor(uuid: UUID): String
-
- private[cluster] def actorRegistryNodePathFor(uuid: UUID): String
-
- private[cluster] def actorRegistryNodePathFor(uuid: UUID, address: InetSocketAddress): String
+ private[cluster] def connectToAllNewlyArrivedMembershipNodesInCluster(
+ newlyConnectedMembershipNodes: Traversable[String],
+ newlyDisconnectedMembershipNodes: Traversable[String]): Map[String, InetSocketAddress]
private[cluster] def remoteSocketAddressForNode(node: String): Option[InetSocketAddress]
- private[cluster] def createActorsAtAddressPath()
+ private[cluster] def membershipPathFor(node: String): String
+ private[cluster] def configurationPathFor(key: String): String
+
+ private[cluster] def actorAddressToNodesPathFor(actorAddress: String): String
+ private[cluster] def actorAddressToNodesPathFor(actorAddress: String, nodeName: String): String
+
+ private[cluster] def nodeToUuidsPathFor(node: String): String
+ private[cluster] def nodeToUuidsPathFor(node: String, uuid: UUID): String
+
+ private[cluster] def actorAddressRegistryPathFor(actorAddress: String): String
+ private[cluster] def actorAddressRegistrySerializerPathFor(actorAddress: String): String
+ private[cluster] def actorAddressRegistryUuidPathFor(actorAddress: String): String
+
+ private[cluster] def actorUuidRegistryPathFor(uuid: UUID): String
+ private[cluster] def actorUuidRegistryNodePathFor(uuid: UUID): String
+ private[cluster] def actorUuidRegistryAddressPathFor(uuid: UUID): String
+
+ private[cluster] def actorAddressToUuidsPathFor(actorAddress: String): String
}
diff --git a/akka-actor/src/main/scala/akka/config/Config.scala b/akka-actor/src/main/scala/akka/config/Config.scala
index 1dc2fa4cf2..68660ef840 100644
--- a/akka-actor/src/main/scala/akka/config/Config.scala
+++ b/akka-actor/src/main/scala/akka/config/Config.scala
@@ -96,6 +96,8 @@ object Config {
val TIME_UNIT = config.getString("akka.time-unit", "seconds")
+ val isClusterEnabled = config.getList("akka.enabled-modules").exists(_ == "cluster")
+
lazy val nodename = System.getProperty("akka.cluster.nodename") match {
case null | "" ⇒ new UUID().toString
case value ⇒ value
@@ -119,12 +121,4 @@ object Config {
val startTime = System.currentTimeMillis
def uptime = (System.currentTimeMillis - startTime) / 1000
-
- val serializers = config.getSection("akka.actor.serializers").map(_.map).getOrElse(Map("default" -> "akka.serialization.JavaSerializer"))
-
- val bindings = config.getSection("akka.actor.serialization-bindings")
- .map(_.map)
- .map(m ⇒ Map() ++ m.map { case (k, v: List[String]) ⇒ Map() ++ v.map((_, k)) }.flatten)
-
- val serializerMap = bindings.map(m ⇒ m.map { case (k, v: String) ⇒ (k, serializers(v)) }).getOrElse(Map())
}
diff --git a/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala
index d00b579610..4516597acf 100644
--- a/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala
+++ b/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala
@@ -67,7 +67,7 @@ class Dispatcher(
val throughput: Int = Dispatchers.THROUGHPUT,
val throughputDeadlineTime: Int = Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS,
val mailboxType: MailboxType = Dispatchers.MAILBOX_TYPE,
- val config: ThreadPoolConfig = ThreadPoolConfig())
+ executorServiceFactoryProvider: ExecutorServiceFactoryProvider = ThreadPoolConfig())
extends MessageDispatcher {
def this(_name: String, throughput: Int, throughputDeadlineTime: Int, mailboxType: MailboxType) =
@@ -79,16 +79,16 @@ class Dispatcher(
def this(_name: String, throughput: Int) =
this(_name, throughput, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE) // Needed for Java API usage
- def this(_name: String, _config: ThreadPoolConfig) =
- this(_name, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE, _config)
+ def this(_name: String, _executorServiceFactoryProvider: ExecutorServiceFactoryProvider) =
+ this(_name, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE, _executorServiceFactoryProvider)
def this(_name: String) =
this(_name, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE) // Needed for Java API usage
val name = "akka:event-driven:dispatcher:" + _name
- private[akka] val threadFactory = new MonitorableThreadFactory(name)
- private[akka] val executorService = new AtomicReference[ExecutorService](config.createLazyExecutorService(threadFactory))
+ private[akka] val executorServiceFactory = executorServiceFactoryProvider.createExecutorServiceFactory(name)
+ private[akka] val executorService = new AtomicReference[ExecutorService](new LazyExecutorServiceWrapper(executorServiceFactory.createExecutorService))
private[akka] def dispatch(invocation: MessageInvocation) = {
val mbox = getMailbox(invocation.receiver)
@@ -134,7 +134,7 @@ class Dispatcher(
private[akka] def start {}
private[akka] def shutdown {
- val old = executorService.getAndSet(config.createLazyExecutorService(threadFactory))
+ val old = executorService.getAndSet(new LazyExecutorServiceWrapper(executorServiceFactory.createExecutorService))
if (old ne null) {
old.shutdownNow()
}
@@ -160,6 +160,8 @@ class Dispatcher(
private[akka] def reRegisterForExecution(mbox: MessageQueue with ExecutableMailbox): Unit =
registerForExecution(mbox)
+ private[akka] def doneProcessingMailbox(mbox: MessageQueue with ExecutableMailbox): Unit = ()
+
protected override def cleanUpMailboxFor(actorRef: ActorRef) {
val m = getMailbox(actorRef)
if (!m.isEmpty) {
@@ -201,8 +203,11 @@ trait ExecutableMailbox extends Runnable { self: MessageQueue ⇒
finally {
dispatcherLock.unlock()
}
+
if (!self.isEmpty)
dispatcher.reRegisterForExecution(this)
+
+ dispatcher.doneProcessingMailbox(this)
}
/**
@@ -271,7 +276,7 @@ class PriorityDispatcher(
throughput: Int = Dispatchers.THROUGHPUT,
throughputDeadlineTime: Int = Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS,
mailboxType: MailboxType = Dispatchers.MAILBOX_TYPE,
- config: ThreadPoolConfig = ThreadPoolConfig()) extends Dispatcher(name, throughput, throughputDeadlineTime, mailboxType, config) with PriorityMailbox {
+ executorServiceFactoryProvider: ExecutorServiceFactoryProvider = ThreadPoolConfig()) extends Dispatcher(name, throughput, throughputDeadlineTime, mailboxType, executorServiceFactoryProvider) with PriorityMailbox {
def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int, throughputDeadlineTime: Int, mailboxType: MailboxType) =
this(name, comparator, throughput, throughputDeadlineTime, mailboxType, ThreadPoolConfig()) // Needed for Java API usage
@@ -282,8 +287,8 @@ class PriorityDispatcher(
def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int) =
this(name, comparator, throughput, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE) // Needed for Java API usage
- def this(name: String, comparator: java.util.Comparator[MessageInvocation], config: ThreadPoolConfig) =
- this(name, comparator, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE, config)
+ def this(name: String, comparator: java.util.Comparator[MessageInvocation], executorServiceFactoryProvider: ExecutorServiceFactoryProvider) =
+ this(name, comparator, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE, executorServiceFactoryProvider)
def this(name: String, comparator: java.util.Comparator[MessageInvocation]) =
this(name, comparator, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE) // Needed for Java API usage
diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala
index 1d26fdea7d..8dd989eab6 100644
--- a/akka-actor/src/main/scala/akka/dispatch/Future.scala
+++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala
@@ -15,12 +15,13 @@ import scala.util.continuations._
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.{ ConcurrentLinkedQueue, TimeUnit, Callable }
import java.util.concurrent.TimeUnit.{ NANOSECONDS ⇒ NANOS, MILLISECONDS ⇒ MILLIS }
-import java.util.concurrent.atomic.{ AtomicBoolean }
import java.lang.{ Iterable ⇒ JIterable }
import java.util.{ LinkedList ⇒ JLinkedList }
import scala.annotation.tailrec
import scala.collection.mutable.Stack
+import akka.util.{ Switch, Duration, BoxedType }
+import java.util.concurrent.atomic.{ AtomicLong, AtomicBoolean }
class FutureTimeoutException(message: String, cause: Throwable = null) extends AkkaException(message, cause)
@@ -97,27 +98,35 @@ object Futures {
} else {
val result = new DefaultPromise[R](timeout)
val results = new ConcurrentLinkedQueue[T]()
+ val done = new Switch(false)
val allDone = futures.size
- val aggregate: Future[T] ⇒ Unit = f ⇒ if (!result.isCompleted) { //TODO: This is an optimization, is it premature?
+ val aggregate: Future[T] ⇒ Unit = f ⇒ if (done.isOff && !result.isCompleted) { //TODO: This is an optimization, is it premature?
f.value.get match {
case r: Right[Throwable, T] ⇒
- results add r.b
- if (results.size == allDone) { //Only one thread can get here
- try {
- result completeWithResult scala.collection.JavaConversions.collectionAsScalaIterable(results).foldLeft(zero)(foldFun)
- } catch {
- case e: Exception ⇒
- EventHandler.error(e, this, e.getMessage)
- result completeWithException e
- }
- finally {
- results.clear
+ val added = results add r.b
+ if (added && results.size == allDone) { //Only one thread can get here
+ if (done.switchOn) {
+ try {
+ val i = results.iterator
+ var currentValue = zero
+ while (i.hasNext) { currentValue = foldFun(currentValue, i.next) }
+ result completeWithResult currentValue
+ } catch {
+ case e: Exception ⇒
+ EventHandler.error(e, this, e.getMessage)
+ result completeWithException e
+ }
+ finally {
+ results.clear
+ }
}
}
case l: Left[Throwable, T] ⇒
- result completeWithException l.a
- results.clear
+ if (done.switchOn) {
+ result completeWithException l.a
+ results.clear
+ }
}
}
@@ -289,12 +298,7 @@ object Future {
*/
def flow[A](body: ⇒ A @cps[Future[Any]])(implicit timeout: Timeout): Future[A] = {
val future = Promise[A](timeout)
- (reset(future.asInstanceOf[Promise[Any]].completeWithResult(body)): Future[Any]) onComplete {
- _.exception match {
- case Some(e) ⇒ future completeWithException e
- case None ⇒
- }
- }
+ (reset(future.asInstanceOf[Promise[Any]].completeWithResult(body)): Future[Any]) onException { case e => future completeWithException e }
future
}
}
diff --git a/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala b/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala
index b1c0f6e747..b52e17d3a2 100644
--- a/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala
+++ b/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala
@@ -11,6 +11,7 @@ import ThreadPoolExecutor.CallerRunsPolicy
import akka.util.Duration
import akka.event.EventHandler
+import concurrent.forkjoin.{ ForkJoinWorkerThread, ForkJoinTask, ForkJoinPool }
object ThreadPoolConfig {
type Bounds = Int
@@ -51,18 +52,24 @@ object ThreadPoolConfig {
}
}
+trait ExecutorServiceFactory {
+ def createExecutorService: ExecutorService
+}
+
+trait ExecutorServiceFactoryProvider {
+ def createExecutorServiceFactory(name: String): ExecutorServiceFactory
+}
+
case class ThreadPoolConfig(allowCorePoolTimeout: Boolean = ThreadPoolConfig.defaultAllowCoreThreadTimeout,
corePoolSize: Int = ThreadPoolConfig.defaultCorePoolSize,
maxPoolSize: Int = ThreadPoolConfig.defaultMaxPoolSize,
threadTimeout: Duration = ThreadPoolConfig.defaultTimeout,
flowHandler: ThreadPoolConfig.FlowHandler = ThreadPoolConfig.defaultFlowHandler,
- queueFactory: ThreadPoolConfig.QueueFactory = ThreadPoolConfig.linkedBlockingQueue()) {
-
- final def createLazyExecutorService(threadFactory: ThreadFactory): ExecutorService =
- new LazyExecutorServiceWrapper(createExecutorService(threadFactory))
-
- final def createExecutorService(threadFactory: ThreadFactory): ExecutorService = {
- flowHandler match {
+ queueFactory: ThreadPoolConfig.QueueFactory = ThreadPoolConfig.linkedBlockingQueue())
+ extends ExecutorServiceFactoryProvider {
+ final def createExecutorServiceFactory(name: String): ExecutorServiceFactory = new ExecutorServiceFactory {
+ val threadFactory = new MonitorableThreadFactory(name)
+ def createExecutorService: ExecutorService = flowHandler match {
case Left(rejectHandler) ⇒
val service = new ThreadPoolExecutor(corePoolSize, maxPoolSize, threadTimeout.length, threadTimeout.unit, queueFactory(), threadFactory, rejectHandler)
service.allowCoreThreadTimeOut(allowCorePoolTimeout)
@@ -144,10 +151,14 @@ case class ThreadPoolConfigDispatcherBuilder(dispatcherFactory: (ThreadPoolConfi
/**
* @author Jonas Bonér
*/
-class MonitorableThreadFactory(val name: String) extends ThreadFactory {
+class MonitorableThreadFactory(val name: String, val daemonic: Boolean = false) extends ThreadFactory {
protected val counter = new AtomicLong
- def newThread(runnable: Runnable) = new MonitorableThread(runnable, name)
+ def newThread(runnable: Runnable) = {
+ val t = new MonitorableThread(runnable, name)
+ t.setDaemon(daemonic)
+ t
+ }
}
/**
diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala
index 8c95b49849..ae56c8d2b1 100644
--- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala
+++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala
@@ -17,19 +17,22 @@ import akka.AkkaException
object Serialization {
case class NoSerializerFoundException(m: String) extends AkkaException(m)
- def serialize(o: AnyRef): Either[Exception, Array[Byte]] =
- serializerFor(o.getClass).fold((ex) ⇒ Left(ex), (ser) ⇒ Right(ser.toBinary(o)))
+ def serialize(o: AnyRef): Either[Exception, Array[Byte]] = serializerFor(o.getClass) match {
+ case Left(ex) ⇒ Left(ex)
+ case Right(serializer) ⇒ Right(serializer.toBinary(o))
+ }
def deserialize(
bytes: Array[Byte],
clazz: Class[_],
classLoader: Option[ClassLoader]): Either[Exception, AnyRef] =
- serializerFor(clazz)
- .fold((ex) ⇒ Left(ex),
- (ser) ⇒ Right(ser.fromBinary(bytes, Some(clazz), classLoader)))
+ serializerFor(clazz) match {
+ case Left(ex) ⇒ Left(ex)
+ case Right(serializer) ⇒ Right(serializer.fromBinary(bytes, Some(clazz), classLoader))
+ }
def serializerFor(clazz: Class[_]): Either[Exception, Serializer] = {
- Config.serializerMap.get(clazz.getName) match {
+ serializerMap.get(clazz.getName) match {
case Some(serializerName: String) ⇒
getClassFor(serializerName) match {
case Right(serializer) ⇒ Right(serializer.newInstance.asInstanceOf[Serializer])
@@ -43,34 +46,40 @@ object Serialization {
}
}
- private def defaultSerializer = {
- Config.serializers.get("default") match {
- case Some(ser: String) ⇒
- getClassFor(ser) match {
- case Right(srializer) ⇒ Some(srializer.newInstance.asInstanceOf[Serializer])
- case Left(exception) ⇒ None
- }
- case None ⇒ None
- }
+ private def defaultSerializer = serializers.get("default") match {
+ case Some(ser: String) ⇒
+ getClassFor(ser) match {
+ case Right(serializer) ⇒ Some(serializer.newInstance.asInstanceOf[Serializer])
+ case Left(exception) ⇒ None
+ }
+ case None ⇒ None
}
- private def getSerializerInstanceForBestMatchClass(
- configMap: collection.mutable.Map[String, String],
- cl: Class[_]) = {
- configMap
- .find {
- case (clazzName, ser) ⇒
- getClassFor(clazzName) match {
- case Right(clazz) ⇒ clazz.isAssignableFrom(cl)
- case _ ⇒ false
- }
- }
- .map {
- case (_, ser) ⇒
- getClassFor(ser) match {
- case Right(s) ⇒ Right(s.newInstance.asInstanceOf[Serializer])
- case _ ⇒ Left(new Exception("Error instantiating " + ser))
- }
- }.getOrElse(Left(NoSerializerFoundException("No mapping serializer found for " + cl)))
+ private def getSerializerInstanceForBestMatchClass(cl: Class[_]) = bindings match {
+ case Some(mappings) ⇒ mappings find {
+ case (clazzName, ser) ⇒
+ getClassFor(clazzName) match {
+ case Right(clazz) ⇒ clazz.isAssignableFrom(cl)
+ case _ ⇒ false
+ }
+ } map {
+ case (_, ser) ⇒
+ getClassFor(ser) match {
+ case Right(s) ⇒ Right(s.newInstance.asInstanceOf[Serializer])
+ case _ ⇒ Left(new Exception("Error instantiating " + ser))
+ }
+ } getOrElse Left(NoSerializerFoundException("No mapping serializer found for " + cl))
+ case None ⇒ Left(NoSerializerFoundException("No mapping serializer found for " + cl))
}
+
+ //TODO: Add type and docs
+ val serializers = config.getSection("akka.actor.serializers").map(_.map).getOrElse(Map("default" -> "akka.serialization.JavaSerializer"))
+
+ //TODO: Add type and docs
+ val bindings = config.getSection("akka.actor.serialization-bindings")
+ .map(_.map)
+ .map(m ⇒ Map() ++ m.map { case (k, v: List[String]) ⇒ Map() ++ v.map((_, k)) }.flatten)
+
+ //TODO: Add type and docs
+ val serializerMap = bindings.map(m ⇒ m.map { case (k, v: String) ⇒ (k, serializers(v)) }).getOrElse(Map())
}
diff --git a/akka-actor/src/main/scala/akka/util/Index.scala b/akka-actor/src/main/scala/akka/util/Index.scala
new file mode 100644
index 0000000000..d7df32efd6
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/util/Index.scala
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.util
+
+import annotation.tailrec
+
+import java.util.concurrent.{ ConcurrentSkipListSet, ConcurrentHashMap }
+import java.util.{ Set ⇒ JSet }
+
+/**
+ * An implementation of a ConcurrentMultiMap
+ * Adds/remove is serialized over the specified key
+ * Reads are fully concurrent <-- el-cheapo
+ *
+ * @author Viktor Klang
+ */
+class Index[K <: AnyRef, V <: AnyRef: Manifest] {
+ private val Naught = Array[V]() //Nil for Arrays
+ private val container = new ConcurrentHashMap[K, JSet[V]]
+ private val emptySet = new ConcurrentSkipListSet[V]
+
+ /**
+ * Associates the value of type V with the key of type K
+ * @return true if the value didn't exist for the key previously, and false otherwise
+ */
+ def put(key: K, value: V): Boolean = {
+ //Tailrecursive spin-locking put
+ @tailrec
+ def spinPut(k: K, v: V): Boolean = {
+ var retry = false
+ var added = false
+ val set = container get k
+
+ if (set ne null) {
+ set.synchronized {
+ if (set.isEmpty) retry = true //IF the set is empty then it has been removed, so signal retry
+ else { //Else add the value to the set and signal that retry is not needed
+ added = set add v
+ retry = false
+ }
+ }
+ } else {
+ val newSet = new ConcurrentSkipListSet[V]
+ newSet add v
+
+ // Parry for two simultaneous putIfAbsent(id,newSet)
+ val oldSet = container.putIfAbsent(k, newSet)
+ if (oldSet ne null) {
+ oldSet.synchronized {
+ if (oldSet.isEmpty) retry = true //IF the set is empty then it has been removed, so signal retry
+ else { //Else try to add the value to the set and signal that retry is not needed
+ added = oldSet add v
+ retry = false
+ }
+ }
+ } else added = true
+ }
+
+ if (retry) spinPut(k, v)
+ else added
+ }
+
+ spinPut(key, value)
+ }
+
+ /**
+ * @return a _new_ array of all existing values for the given key at the time of the call
+ */
+ def values(key: K): Array[V] = {
+ val set: JSet[V] = container get key
+ val result = if (set ne null) set toArray Naught else Naught
+ result.asInstanceOf[Array[V]]
+ }
+
+ /**
+ * @return Some(value) for the first matching value where the supplied function returns true for the given key,
+ * if no matches it returns None
+ */
+ def findValue(key: K)(f: (V) ⇒ Boolean): Option[V] = {
+ import scala.collection.JavaConversions._
+ val set = container get key
+ if (set ne null) set.iterator.find(f)
+ else None
+ }
+
+ /**
+ * Applies the supplied function to all keys and their values
+ */
+ def foreach(fun: (K, V) ⇒ Unit) {
+ import scala.collection.JavaConversions._
+ container.entrySet foreach { e ⇒ e.getValue.foreach(fun(e.getKey, _)) }
+ }
+
+ /**
+ * Disassociates the value of type V from the key of type K
+ * @return true if the value was disassociated from the key and false if it wasn't previously associated with the key
+ */
+ def remove(key: K, value: V): Boolean = {
+ val set = container get key
+
+ if (set ne null) {
+ set.synchronized {
+ if (set.remove(value)) { //If we can remove the value
+ if (set.isEmpty) //and the set becomes empty
+ container.remove(key, emptySet) //We try to remove the key if it's mapped to an empty set
+
+ true //Remove succeeded
+ } else false //Remove failed
+ }
+ } else false //Remove failed
+ }
+
+ /**
+ * @return true if the underlying containers is empty, may report false negatives when the last remove is underway
+ */
+ def isEmpty: Boolean = container.isEmpty
+
+ /**
+ * Removes all keys and all values
+ */
+ def clear = foreach { case (k, v) ⇒ remove(k, v) }
+}
\ No newline at end of file
diff --git a/akka-actor/src/main/scala/akka/util/LockUtil.scala b/akka-actor/src/main/scala/akka/util/LockUtil.scala
index b281ca89be..5a334e44a9 100644
--- a/akka-actor/src/main/scala/akka/util/LockUtil.scala
+++ b/akka-actor/src/main/scala/akka/util/LockUtil.scala
@@ -22,15 +22,6 @@ final class ReentrantGuard {
lock.unlock
}
}
-
- final def tryWithGuard[T](body: ⇒ T): T = {
- while (!lock.tryLock) { Thread.sleep(10) } // wait on the monitor to be unlocked
- try {
- body
- } finally {
- lock.unlock
- }
- }
}
/**
diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala
index 42fd88a78f..b1bfe83466 100644
--- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala
+++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala
@@ -30,12 +30,12 @@ object ReflectiveAccess {
* @author Jonas Bonér
*/
object ClusterModule {
- lazy val isEnabled = clusterInstance.isDefined
+ lazy val isEnabled = Config.isClusterEnabled && clusterInstance.isDefined
def ensureEnabled() {
if (!isEnabled) {
val e = new ModuleNotAvailableException(
- "Can't load the cluster module, make sure that akka-cluster.jar is on the classpath")
+ "Can't load the cluster module, make sure it is enabled in the config ('akka.enabled-modules = [\"cluster\"])' and that akka-cluster.jar is on the classpath")
EventHandler.debug(this, e.toString)
throw e
}
@@ -102,6 +102,7 @@ object ReflectiveAccess {
def dequeue: MessageInvocation
}
+ // FIXME: remove?
type Serializer = {
def toBinary(obj: AnyRef): Array[Byte]
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef
@@ -111,25 +112,24 @@ object ReflectiveAccess {
def newLogFor(
id: String,
isAsync: Boolean,
- replicationScheme: ReplicationScheme,
- format: Serializer): TransactionLog
+ replicationScheme: ReplicationScheme): TransactionLog
def logFor(
id: String,
isAsync: Boolean,
- replicationScheme: ReplicationScheme,
- format: Serializer): TransactionLog
+ replicationScheme: ReplicationScheme): TransactionLog
def shutdown()
}
type TransactionLog = {
- def recordEntry(messageHandle: MessageInvocation, actorRef: ActorRef)
+ def recordEntry(messageHandle: MessageInvocation, actorRef: LocalActorRef)
def recordEntry(entry: Array[Byte])
def recordSnapshot(snapshot: Array[Byte])
def entries: Vector[Array[Byte]]
def entriesFromLatestSnapshot: Tuple2[Array[Byte], Vector[Array[Byte]]]
def entriesInRange(from: Long, to: Long): Vector[Array[Byte]]
+ def latestSnapshotAndSubsequentEntries: (Option[Array[Byte]], Vector[Array[Byte]])
def latestEntryId: Long
def latestSnapshotId: Long
def delete()
diff --git a/akka-actor/src/main/scala/akka/util/package.scala b/akka-actor/src/main/scala/akka/util/duration/package.scala
similarity index 100%
rename from akka-actor/src/main/scala/akka/util/package.scala
rename to akka-actor/src/main/scala/akka/util/duration/package.scala
diff --git a/akka-cluster/src/main/java/akka/cluster/ClusterProtocol.java b/akka-cluster/src/main/java/akka/cluster/ClusterProtocol.java
index 54ca02a15f..8d18fc319b 100644
--- a/akka-cluster/src/main/java/akka/cluster/ClusterProtocol.java
+++ b/akka-cluster/src/main/java/akka/cluster/ClusterProtocol.java
@@ -132,6 +132,11 @@ public final class ClusterProtocol {
// optional bytes payload = 5;
boolean hasPayload();
com.google.protobuf.ByteString getPayload();
+
+ // optional .UuidProtocol replicateActorFromUuid = 6;
+ boolean hasReplicateActorFromUuid();
+ akka.cluster.ClusterProtocol.UuidProtocol getReplicateActorFromUuid();
+ akka.cluster.ClusterProtocol.UuidProtocolOrBuilder getReplicateActorFromUuidOrBuilder();
}
public static final class RemoteDaemonMessageProtocol extends
com.google.protobuf.GeneratedMessage
@@ -227,11 +232,25 @@ public final class ClusterProtocol {
return payload_;
}
+ // optional .UuidProtocol replicateActorFromUuid = 6;
+ public static final int REPLICATEACTORFROMUUID_FIELD_NUMBER = 6;
+ private akka.cluster.ClusterProtocol.UuidProtocol replicateActorFromUuid_;
+ public boolean hasReplicateActorFromUuid() {
+ return ((bitField0_ & 0x00000010) == 0x00000010);
+ }
+ public akka.cluster.ClusterProtocol.UuidProtocol getReplicateActorFromUuid() {
+ return replicateActorFromUuid_;
+ }
+ public akka.cluster.ClusterProtocol.UuidProtocolOrBuilder getReplicateActorFromUuidOrBuilder() {
+ return replicateActorFromUuid_;
+ }
+
private void initFields() {
messageType_ = akka.cluster.ClusterProtocol.RemoteDaemonMessageType.START;
actorUuid_ = akka.cluster.ClusterProtocol.UuidProtocol.getDefaultInstance();
actorAddress_ = "";
payload_ = com.google.protobuf.ByteString.EMPTY;
+ replicateActorFromUuid_ = akka.cluster.ClusterProtocol.UuidProtocol.getDefaultInstance();
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -248,6 +267,12 @@ public final class ClusterProtocol {
return false;
}
}
+ if (hasReplicateActorFromUuid()) {
+ if (!getReplicateActorFromUuid().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
memoizedIsInitialized = 1;
return true;
}
@@ -267,6 +292,9 @@ public final class ClusterProtocol {
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeBytes(5, payload_);
}
+ if (((bitField0_ & 0x00000010) == 0x00000010)) {
+ output.writeMessage(6, replicateActorFromUuid_);
+ }
getUnknownFields().writeTo(output);
}
@@ -292,6 +320,10 @@ public final class ClusterProtocol {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(5, payload_);
}
+ if (((bitField0_ & 0x00000010) == 0x00000010)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(6, replicateActorFromUuid_);
+ }
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -402,13 +434,14 @@ public final class ClusterProtocol {
maybeForceBuilderInitialization();
}
- private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ private Builder(BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
getActorUuidFieldBuilder();
+ getReplicateActorFromUuidFieldBuilder();
}
}
private static Builder create() {
@@ -429,6 +462,12 @@ public final class ClusterProtocol {
bitField0_ = (bitField0_ & ~0x00000004);
payload_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000008);
+ if (replicateActorFromUuidBuilder_ == null) {
+ replicateActorFromUuid_ = akka.cluster.ClusterProtocol.UuidProtocol.getDefaultInstance();
+ } else {
+ replicateActorFromUuidBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000010);
return this;
}
@@ -487,6 +526,14 @@ public final class ClusterProtocol {
to_bitField0_ |= 0x00000008;
}
result.payload_ = payload_;
+ if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
+ to_bitField0_ |= 0x00000010;
+ }
+ if (replicateActorFromUuidBuilder_ == null) {
+ result.replicateActorFromUuid_ = replicateActorFromUuid_;
+ } else {
+ result.replicateActorFromUuid_ = replicateActorFromUuidBuilder_.build();
+ }
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -515,6 +562,9 @@ public final class ClusterProtocol {
if (other.hasPayload()) {
setPayload(other.getPayload());
}
+ if (other.hasReplicateActorFromUuid()) {
+ mergeReplicateActorFromUuid(other.getReplicateActorFromUuid());
+ }
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -530,6 +580,12 @@ public final class ClusterProtocol {
return false;
}
}
+ if (hasReplicateActorFromUuid()) {
+ if (!getReplicateActorFromUuid().isInitialized()) {
+
+ return false;
+ }
+ }
return true;
}
@@ -586,6 +642,15 @@ public final class ClusterProtocol {
payload_ = input.readBytes();
break;
}
+ case 50: {
+ akka.cluster.ClusterProtocol.UuidProtocol.Builder subBuilder = akka.cluster.ClusterProtocol.UuidProtocol.newBuilder();
+ if (hasReplicateActorFromUuid()) {
+ subBuilder.mergeFrom(getReplicateActorFromUuid());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setReplicateActorFromUuid(subBuilder.buildPartial());
+ break;
+ }
}
}
}
@@ -766,6 +831,96 @@ public final class ClusterProtocol {
return this;
}
+ // optional .UuidProtocol replicateActorFromUuid = 6;
+ private akka.cluster.ClusterProtocol.UuidProtocol replicateActorFromUuid_ = akka.cluster.ClusterProtocol.UuidProtocol.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ akka.cluster.ClusterProtocol.UuidProtocol, akka.cluster.ClusterProtocol.UuidProtocol.Builder, akka.cluster.ClusterProtocol.UuidProtocolOrBuilder> replicateActorFromUuidBuilder_;
+ public boolean hasReplicateActorFromUuid() {
+ return ((bitField0_ & 0x00000010) == 0x00000010);
+ }
+ public akka.cluster.ClusterProtocol.UuidProtocol getReplicateActorFromUuid() {
+ if (replicateActorFromUuidBuilder_ == null) {
+ return replicateActorFromUuid_;
+ } else {
+ return replicateActorFromUuidBuilder_.getMessage();
+ }
+ }
+ public Builder setReplicateActorFromUuid(akka.cluster.ClusterProtocol.UuidProtocol value) {
+ if (replicateActorFromUuidBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ replicateActorFromUuid_ = value;
+ onChanged();
+ } else {
+ replicateActorFromUuidBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000010;
+ return this;
+ }
+ public Builder setReplicateActorFromUuid(
+ akka.cluster.ClusterProtocol.UuidProtocol.Builder builderForValue) {
+ if (replicateActorFromUuidBuilder_ == null) {
+ replicateActorFromUuid_ = builderForValue.build();
+ onChanged();
+ } else {
+ replicateActorFromUuidBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000010;
+ return this;
+ }
+ public Builder mergeReplicateActorFromUuid(akka.cluster.ClusterProtocol.UuidProtocol value) {
+ if (replicateActorFromUuidBuilder_ == null) {
+ if (((bitField0_ & 0x00000010) == 0x00000010) &&
+ replicateActorFromUuid_ != akka.cluster.ClusterProtocol.UuidProtocol.getDefaultInstance()) {
+ replicateActorFromUuid_ =
+ akka.cluster.ClusterProtocol.UuidProtocol.newBuilder(replicateActorFromUuid_).mergeFrom(value).buildPartial();
+ } else {
+ replicateActorFromUuid_ = value;
+ }
+ onChanged();
+ } else {
+ replicateActorFromUuidBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000010;
+ return this;
+ }
+ public Builder clearReplicateActorFromUuid() {
+ if (replicateActorFromUuidBuilder_ == null) {
+ replicateActorFromUuid_ = akka.cluster.ClusterProtocol.UuidProtocol.getDefaultInstance();
+ onChanged();
+ } else {
+ replicateActorFromUuidBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000010);
+ return this;
+ }
+ public akka.cluster.ClusterProtocol.UuidProtocol.Builder getReplicateActorFromUuidBuilder() {
+ bitField0_ |= 0x00000010;
+ onChanged();
+ return getReplicateActorFromUuidFieldBuilder().getBuilder();
+ }
+ public akka.cluster.ClusterProtocol.UuidProtocolOrBuilder getReplicateActorFromUuidOrBuilder() {
+ if (replicateActorFromUuidBuilder_ != null) {
+ return replicateActorFromUuidBuilder_.getMessageOrBuilder();
+ } else {
+ return replicateActorFromUuid_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ akka.cluster.ClusterProtocol.UuidProtocol, akka.cluster.ClusterProtocol.UuidProtocol.Builder, akka.cluster.ClusterProtocol.UuidProtocolOrBuilder>
+ getReplicateActorFromUuidFieldBuilder() {
+ if (replicateActorFromUuidBuilder_ == null) {
+ replicateActorFromUuidBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ akka.cluster.ClusterProtocol.UuidProtocol, akka.cluster.ClusterProtocol.UuidProtocol.Builder, akka.cluster.ClusterProtocol.UuidProtocolOrBuilder>(
+ replicateActorFromUuid_,
+ getParentForChildren(),
+ isClean());
+ replicateActorFromUuid_ = null;
+ }
+ return replicateActorFromUuidBuilder_;
+ }
+
// @@protoc_insertion_point(builder_scope:RemoteDaemonMessageProtocol)
}
@@ -1092,7 +1247,7 @@ public final class ClusterProtocol {
maybeForceBuilderInitialization();
}
- private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ private Builder(BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
@@ -1694,7 +1849,7 @@ public final class ClusterProtocol {
maybeForceBuilderInitialization();
}
- private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ private Builder(BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
@@ -1912,23 +2067,24 @@ public final class ClusterProtocol {
descriptor;
static {
java.lang.String[] descriptorData = {
- "\n\025ClusterProtocol.proto\"\225\001\n\033RemoteDaemon" +
+ "\n\025ClusterProtocol.proto\"\304\001\n\033RemoteDaemon" +
"MessageProtocol\022-\n\013messageType\030\001 \002(\0162\030.R" +
"emoteDaemonMessageType\022 \n\tactorUuid\030\002 \001(" +
"\0132\r.UuidProtocol\022\024\n\014actorAddress\030\003 \001(\t\022\017" +
- "\n\007payload\030\005 \001(\014\"\212\001\n\035DurableMailboxMessag" +
- "eProtocol\022\031\n\021ownerActorAddress\030\001 \002(\t\022\032\n\022" +
- "senderActorAddress\030\002 \001(\t\022!\n\nfutureUuid\030\003" +
- " \001(\0132\r.UuidProtocol\022\017\n\007message\030\004 \002(\014\")\n\014" +
- "UuidProtocol\022\014\n\004high\030\001 \002(\004\022\013\n\003low\030\002 \002(\004*" +
- "\232\002\n\027RemoteDaemonMessageType\022\t\n\005START\020\001\022\010",
- "\n\004STOP\020\002\022\007\n\003USE\020\003\022\013\n\007RELEASE\020\004\022\022\n\016MAKE_A" +
- "VAILABLE\020\005\022\024\n\020MAKE_UNAVAILABLE\020\006\022\016\n\nDISC" +
- "ONNECT\020\007\022\r\n\tRECONNECT\020\010\022\n\n\006RESIGN\020\t\022\031\n\025F" +
- "AIL_OVER_CONNECTIONS\020\n\022\026\n\022FUNCTION_FUN0_" +
- "UNIT\020\013\022\025\n\021FUNCTION_FUN0_ANY\020\014\022\032\n\026FUNCTIO" +
- "N_FUN1_ARG_UNIT\020\r\022\031\n\025FUNCTION_FUN1_ARG_A" +
- "NY\020\016B\020\n\014akka.clusterH\001"
+ "\n\007payload\030\005 \001(\014\022-\n\026replicateActorFromUui" +
+ "d\030\006 \001(\0132\r.UuidProtocol\"\212\001\n\035DurableMailbo" +
+ "xMessageProtocol\022\031\n\021ownerActorAddress\030\001 " +
+ "\002(\t\022\032\n\022senderActorAddress\030\002 \001(\t\022!\n\nfutur" +
+ "eUuid\030\003 \001(\0132\r.UuidProtocol\022\017\n\007message\030\004 " +
+ "\002(\014\")\n\014UuidProtocol\022\014\n\004high\030\001 \002(\004\022\013\n\003low",
+ "\030\002 \002(\004*\232\002\n\027RemoteDaemonMessageType\022\t\n\005ST" +
+ "ART\020\001\022\010\n\004STOP\020\002\022\007\n\003USE\020\003\022\013\n\007RELEASE\020\004\022\022\n" +
+ "\016MAKE_AVAILABLE\020\005\022\024\n\020MAKE_UNAVAILABLE\020\006\022" +
+ "\016\n\nDISCONNECT\020\007\022\r\n\tRECONNECT\020\010\022\n\n\006RESIGN" +
+ "\020\t\022\031\n\025FAIL_OVER_CONNECTIONS\020\n\022\026\n\022FUNCTIO" +
+ "N_FUN0_UNIT\020\013\022\025\n\021FUNCTION_FUN0_ANY\020\014\022\032\n\026" +
+ "FUNCTION_FUN1_ARG_UNIT\020\r\022\031\n\025FUNCTION_FUN" +
+ "1_ARG_ANY\020\016B\020\n\014akka.clusterH\001"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -1940,7 +2096,7 @@ public final class ClusterProtocol {
internal_static_RemoteDaemonMessageProtocol_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_RemoteDaemonMessageProtocol_descriptor,
- new java.lang.String[] { "MessageType", "ActorUuid", "ActorAddress", "Payload", },
+ new java.lang.String[] { "MessageType", "ActorUuid", "ActorAddress", "Payload", "ReplicateActorFromUuid", },
akka.cluster.ClusterProtocol.RemoteDaemonMessageProtocol.class,
akka.cluster.ClusterProtocol.RemoteDaemonMessageProtocol.Builder.class);
internal_static_DurableMailboxMessageProtocol_descriptor =
diff --git a/akka-cluster/src/main/protocol/ClusterProtocol.proto b/akka-cluster/src/main/protocol/ClusterProtocol.proto
index 1287c1d9f0..e5d2b5ebf0 100644
--- a/akka-cluster/src/main/protocol/ClusterProtocol.proto
+++ b/akka-cluster/src/main/protocol/ClusterProtocol.proto
@@ -19,6 +19,7 @@ message RemoteDaemonMessageProtocol {
optional UuidProtocol actorUuid = 2;
optional string actorAddress = 3;
optional bytes payload = 5;
+ optional UuidProtocol replicateActorFromUuid = 6;
}
/**
diff --git a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala
index 5b6dd82ab6..772d614264 100644
--- a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala
@@ -1,4 +1,3 @@
-
/**
* Copyright (C) 2009-2011 Scalable Solutions AB
*/
@@ -23,16 +22,13 @@ import scala.collection.immutable.{ HashMap, HashSet }
import scala.collection.mutable.ConcurrentMap
import scala.collection.JavaConversions._
-import ClusterProtocol._
-import RemoteDaemonMessageType._
-
import akka.util._
import Helpers._
import akka.actor._
import Actor._
import Status._
-import DeploymentConfig.{ ReplicationScheme, ReplicationStrategy, Transient, WriteThrough, WriteBehind }
+import DeploymentConfig._
import akka.event.EventHandler
import akka.dispatch.{ Dispatchers, Future }
@@ -43,19 +39,22 @@ import akka.config.{ Config, Supervision }
import Supervision._
import Config._
-import akka.serialization.{ Serialization, Serializer, Compression }
+import akka.serialization.{ Serialization, Serializer, Compression, ActorSerialization }
+import ActorSerialization._
import Compression.LZF
-import akka.AkkaException
import akka.cluster.zookeeper._
-import akka.cluster.ChangeListener._
+import ChangeListener._
+import ClusterProtocol._
+import RemoteDaemonMessageType._
+
+import akka.AkkaException
import com.eaio.uuid.UUID
import com.google.protobuf.ByteString
// FIXME add watch for each node that when the entry for the node is removed then the node shuts itself down
-// FIXME Provisioning data in ZK (file names etc) and files in S3 and on disk
/**
* JMX MBean for the cluster service.
@@ -99,8 +98,6 @@ trait ClusterNodeMBean {
def getAddressesForActorsInUse: Array[String]
- def getNodesForActorInUseWithUuid(uuid: String): Array[String]
-
def getNodesForActorInUseWithAddress(address: String): Array[String]
def getUuidsForActorsInUseOnNode(nodeName: String): Array[String]
@@ -117,7 +114,7 @@ trait ClusterNodeMBean {
}
/**
- * Module for the ClusterNode. Also holds global state such as configuration data etc.
+ * Module for the Cluster. Also holds global state such as configuration data etc.
*
* @author Jonas Bonér
*/
@@ -134,11 +131,24 @@ object Cluster {
val shouldCompressData = config.getBool("akka.cluster.use-compression", false)
val enableJMX = config.getBool("akka.enable-jmx", true)
val remoteDaemonAckTimeout = Duration(config.getInt("akka.cluster.remote-daemon-ack-timeout", 30), TIME_UNIT).toMillis.toInt
- val excludeRefNodeInReplicaSet = config.getBool("akka.cluster.exclude-ref-node-in-replica-set", true)
+ val includeRefNodeInReplicaSet = config.getBool("akka.cluster.include-ref-node-in-replica-set", true)
+ val clusterDirectory = config.getString("akka.cluster.log-directory", "_akka_cluster")
+
+ val clusterDataDirectory = clusterDirectory + "/data"
+ val clusterLogDirectory = clusterDirectory + "/log"
@volatile
private var properties = Map.empty[String, String]
+ /**
+ * Use to override JVM options such as -Dakka.cluster.nodename=node1 etc.
+ * Currently supported options are:
+ *
+ * Cluster setProperty ("akka.cluster.nodename", "node1")
+ * Cluster setProperty ("akka.cluster.hostname", "darkstar.lan")
+ * Cluster setProperty ("akka.cluster.port", "1234")
+ *
+ */
def setProperty(property: (String, String)) {
properties = properties + property
}
@@ -158,7 +168,7 @@ object Cluster {
case None ⇒ Config.remoteServerPort
}
- val defaultSerializer = new SerializableSerializer
+ val defaultZooKeeperSerializer = new SerializableSerializer
private val _zkServer = new AtomicReference[Option[ZkServer]](None)
@@ -172,7 +182,7 @@ object Cluster {
*/
val node = {
if (nodeAddress eq null) throw new IllegalArgumentException("NodeAddress can't be null")
- new DefaultClusterNode(nodeAddress, hostname, port, zooKeeperServers, defaultSerializer)
+ new DefaultClusterNode(nodeAddress, hostname, port, zooKeeperServers, defaultZooKeeperSerializer)
}
/**
@@ -184,19 +194,19 @@ object Cluster {
* Starts up a local ZooKeeper server. Should only be used for testing purposes.
*/
def startLocalCluster(): ZkServer =
- startLocalCluster("_akka_cluster/data", "_akka_cluster/log", 2181, 5000)
+ startLocalCluster(clusterDataDirectory, clusterLogDirectory, 2181, 5000)
/**
* Starts up a local ZooKeeper server. Should only be used for testing purposes.
*/
def startLocalCluster(port: Int, tickTime: Int): ZkServer =
- startLocalCluster("_akka_cluster/data", "_akka_cluster/log", port, tickTime)
+ startLocalCluster(clusterDataDirectory, clusterLogDirectory, port, tickTime)
/**
* Starts up a local ZooKeeper server. Should only be used for testing purposes.
*/
def startLocalCluster(tickTime: Int): ZkServer =
- startLocalCluster("_akka_cluster/data", "_akka_cluster/log", 2181, tickTime)
+ startLocalCluster(clusterDataDirectory, clusterLogDirectory, 2181, tickTime)
/**
* Starts up a local ZooKeeper server. Should only be used for testing purposes.
@@ -233,7 +243,7 @@ object Cluster {
/**
* Creates a new AkkaZkClient.
*/
- def newZkClient(): AkkaZkClient = new AkkaZkClient(zooKeeperServers, sessionTimeout, connectionTimeout, defaultSerializer)
+ def newZkClient(): AkkaZkClient = new AkkaZkClient(zooKeeperServers, sessionTimeout, connectionTimeout, defaultZooKeeperSerializer)
def createQueue(rootPath: String, blocking: Boolean = true) = new ZooKeeperQueue(node.zkClient, rootPath, blocking)
@@ -270,6 +280,27 @@ object Cluster {
/**
* A Cluster is made up by a bunch of jvm's, the ClusterNode.
*
+ * These are the path tree holding the cluster meta-data in ZooKeeper.
+ *
+ * Syntax: foo means a variable string, 'foo' means a symbol that does not change and "data" in foo[data] means the value (in bytes) for the node "foo"
+ *
+ *
+ * /clusterName/'members'/nodeName
+ * /clusterName/'config'/key[bytes]
+ *
+ * /clusterName/'actor-address-to-nodes'/actorAddress/nodeName
+ * /clusterName/'actors-node-to-uuids'/nodeName/actorUuid
+ *
+ * /clusterName/'actor-address-registry'/actorAddress/'serializer'[serializerName]
+ * /clusterName/'actor-address-registry'/actorAddress/'uuid'[actorUuid]
+ *
+ * /clusterName/'actor-uuid-registry'/actorUuid/'node'[nodeName]
+ * /clusterName/'actor-uuid-registry'/actorUuid/'node'/ip:port
+ * /clusterName/'actor-uuid-registry'/actorUuid/'address'[actorAddress]
+ *
+ * /clusterName/'actor-address-to-uuids'/actorAddress/actorUuid
+ *
+ *
* @author Jonas Bonér
*/
class DefaultClusterNode private[akka] (
@@ -296,7 +327,7 @@ class DefaultClusterNode private[akka] (
}
}, "akka.cluster.RemoteClientLifeCycleListener").start()
- private[cluster] lazy val remoteDaemon = localActorOf(new RemoteClusterDaemon(this), RemoteClusterDaemon.ADDRESS).start()
+ private[cluster] lazy val remoteDaemon = localActorOf(new RemoteClusterDaemon(this), RemoteClusterDaemon.Address).start()
private[cluster] lazy val remoteDaemonSupervisor = Supervisor(
SupervisorConfig(
@@ -309,7 +340,7 @@ class DefaultClusterNode private[akka] (
lazy val remoteService: RemoteSupport = {
val remote = new akka.remote.netty.NettyRemoteSupport
remote.start(hostname, port)
- remote.register(RemoteClusterDaemon.ADDRESS, remoteDaemon)
+ remote.register(RemoteClusterDaemon.Address, remoteDaemon)
remote.addListener(remoteClientLifeCycleListener)
remote
}
@@ -321,16 +352,19 @@ class DefaultClusterNode private[akka] (
val MEMBERSHIP_PATH = CLUSTER_PATH + "/members"
val CONFIGURATION_PATH = CLUSTER_PATH + "/config"
val PROVISIONING_PATH = CLUSTER_PATH + "/provisioning"
- val ACTOR_REGISTRY_PATH = CLUSTER_PATH + "/actor-registry"
- val ACTOR_LOCATIONS_PATH = CLUSTER_PATH + "/actor-locations"
+ val ACTOR_ADDRESS_NODES_TO_PATH = CLUSTER_PATH + "/actor-address-to-nodes"
+ val ACTOR_ADDRESS_REGISTRY_PATH = CLUSTER_PATH + "/actor-address-registry"
+ val ACTOR_UUID_REGISTRY_PATH = CLUSTER_PATH + "/actor-uuid-registry"
val ACTOR_ADDRESS_TO_UUIDS_PATH = CLUSTER_PATH + "/actor-address-to-uuids"
- val ACTORS_AT_PATH_PATH = CLUSTER_PATH + "/actors-at-address"
+ val NODE_TO_ACTOR_UUIDS_PATH = CLUSTER_PATH + "/node-to-actors-uuids"
+
val basePaths = List(
CLUSTER_PATH,
MEMBERSHIP_PATH,
- ACTOR_REGISTRY_PATH,
- ACTOR_LOCATIONS_PATH,
- ACTORS_AT_PATH_PATH,
+ ACTOR_ADDRESS_REGISTRY_PATH,
+ ACTOR_UUID_REGISTRY_PATH,
+ ACTOR_ADDRESS_NODES_TO_PATH,
+ NODE_TO_ACTOR_UUIDS_PATH,
ACTOR_ADDRESS_TO_UUIDS_PATH,
CONFIGURATION_PATH,
PROVISIONING_PATH)
@@ -341,8 +375,12 @@ class DefaultClusterNode private[akka] (
def membershipNodes: Array[String] = locallyCachedMembershipNodes.toList.toArray.asInstanceOf[Array[String]]
- private[akka] val nodeConnections: ConcurrentMap[String, Tuple2[InetSocketAddress, ActorRef]] =
- new ConcurrentHashMap[String, Tuple2[InetSocketAddress, ActorRef]]
+ private[akka] val nodeConnections: ConcurrentMap[String, Tuple2[InetSocketAddress, ActorRef]] = {
+ val conns = new ConcurrentHashMap[String, Tuple2[InetSocketAddress, ActorRef]]
+ if (includeRefNodeInReplicaSet)
+ conns.put(nodeAddress.nodeName, (remoteServerAddress, remoteDaemon)) // add the remote connection to 'this' node as well, but as a 'local' actor
+ conns
+ }
// zookeeper listeners
private val stateListener = new StateListener(this)
@@ -354,9 +392,10 @@ class DefaultClusterNode private[akka] (
// Address -> ClusterActorRef
private val clusterActorRefs = new Index[InetSocketAddress, ClusterActorRef]
- // resources
+ // ZooKeeper client
lazy private[cluster] val zkClient = new AkkaZkClient(zkServerAddresses, sessionTimeout, connectionTimeout, serializer)
+ // leader election listener, registered to the 'leaderLock' below
lazy private[cluster] val leaderElectionCallback = new LockListener {
override def lockAcquired() {
EventHandler.info(this, "Node [%s] is the new leader".format(self.nodeAddress.nodeName))
@@ -368,6 +407,7 @@ class DefaultClusterNode private[akka] (
}
}
+ // leader election lock in ZooKeeper
lazy private[cluster] val leaderLock = new WriteLock(
zkClient.connection.getZookeeper,
LEADER_ELECTION_PATH, null,
@@ -380,18 +420,17 @@ class DefaultClusterNode private[akka] (
// =======================================
def start(): ClusterNode = {
- isConnected switchOn {
+ if (isConnected.compareAndSet(false, true)) {
initializeNode()
}
this
}
def shutdown() {
- isConnected switchOff {
+ if (isConnected.compareAndSet(true, false)) {
ignore[ZkNoNodeException](zkClient.deleteRecursive(membershipNodePath))
locallyCachedMembershipNodes.clear()
- locallyCheckedOutActors.clear()
nodeConnections.toList.foreach({
case (_, (address, _)) ⇒
@@ -470,215 +509,194 @@ class DefaultClusterNode private[akka] (
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, 0, Transient, false, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, 0, Transient, false, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, 0, replicationScheme, false, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, 0, replicationScheme, false, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, replicationFactor, Transient, false, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, replicationFactor, Transient, false, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, replicationFactor, replicationScheme, false, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, replicationFactor, replicationScheme, false, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, 0, Transient, serializeMailbox, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, 0, Transient, serializeMailbox, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, 0, replicationScheme, serializeMailbox, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, 0, replicationScheme, serializeMailbox, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, replicationFactor, Transient, serializeMailbox, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, replicationFactor, Transient, serializeMailbox, serializer)
/**
* Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store[T <: Actor](address: String, actorClass: Class[T], replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(Actor.actorOf(actorClass, address).start, replicationFactor, replicationScheme, serializeMailbox, serializer)
+ def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, () ⇒ Actor.actorOf(actorClass, actorAddress).start, replicationFactor, replicationScheme, serializeMailbox, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, serializer: Serializer): ClusterNode =
- store(actorRef, 0, Transient, false, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, 0, Transient, false, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(actorRef, 0, Transient, serializeMailbox, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, 0, Transient, serializeMailbox, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
- store(actorRef, 0, replicationScheme, false, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, 0, replicationScheme, false, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, serializer: Serializer): ClusterNode =
- store(actorRef, replicationFactor, Transient, false, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, replicationFactor, Transient, false, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
- store(actorRef, replicationFactor, replicationScheme, false, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, replicationFactor, replicationScheme, false, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(actorRef, replicationFactor, Transient, serializeMailbox, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, replicationFactor, Transient, serializeMailbox, serializer)
/**
* Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
- def store(actorRef: ActorRef, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
- store(actorRef, 0, replicationScheme, serializeMailbox, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode =
+ store(actorAddress, actorFactory, 0, replicationScheme, serializeMailbox, serializer)
/**
* Needed to have reflection through structural typing work.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode =
- store(actorRef, replicationFactor, replicationScheme, serializeMailbox, serializer.asInstanceOf[Serializer])
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode =
+ store(actorAddress, actorFactory, replicationFactor, replicationScheme, serializeMailbox, serializer.asInstanceOf[Serializer])
/**
* Needed to have reflection through structural typing work.
*/
- def store(actorRef: ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode =
- store(actorRef, replicationFactor, Transient, serializeMailbox, serializer)
+ def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationFactor: Int, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode =
+ store(actorAddress, actorFactory, replicationFactor, Transient, serializeMailbox, serializer)
/**
- * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated
+ * Clusters an actor. If the actor is already clustered then the clustered version will be updated
* with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly
* available durable store.
*/
def store(
- actorRef: ActorRef,
+ actorAddress: String,
+ actorFactory: () ⇒ ActorRef,
replicationFactor: Int,
replicationScheme: ReplicationScheme,
serializeMailbox: Boolean,
- serializer: Serializer): ClusterNode = if (isConnected.isOn) {
-
- import akka.serialization.ActorSerialization._
-
- if (!actorRef.isInstanceOf[LocalActorRef]) throw new IllegalArgumentException(
- "'actorRef' must be an instance of 'LocalActorRef' [" + actorRef.getClass.getName + "]")
+ serializer: Serializer): ClusterNode = if (isConnected.get) {
val serializerClassName = serializer.getClass.getName
- val uuid = actorRef.uuid
EventHandler.debug(this,
- "Storing actor [%s] with UUID [%s] in cluster".format(actorRef.address, uuid))
+ "Storing actor with address [%s] in cluster".format(actorAddress))
- val actorBytes =
- if (shouldCompressData) LZF.compress(toBinary(actorRef, serializeMailbox, replicationScheme))
- else toBinary(actorRef, serializeMailbox, replicationScheme)
+ val actorFactoryBytes =
+ Serialization.serialize(actorFactory) match {
+ case Left(error) ⇒ throw error
+ case Right(bytes) ⇒
+ if (shouldCompressData) LZF.compress(bytes)
+ else bytes
+ }
- val actorRegistryPath = actorRegistryPathFor(uuid)
+ val actorAddressRegistryPath = actorAddressRegistryPathFor(actorAddress)
- // create UUID -> Array[Byte] for actor registry
+ // create ADDRESS -> Array[Byte] for actor registry
try {
- zkClient.writeData(actorRegistryPath, actorBytes) // FIXME Store actor bytes in Data Grid not ZooKeeper
+ zkClient.writeData(actorAddressRegistryPath, actorFactoryBytes)
} catch {
case e: ZkNoNodeException ⇒ // if not stored yet, store the actor
zkClient.retryUntilConnected(new Callable[Either[String, Exception]]() {
def call: Either[String, Exception] = {
try {
- Left(zkClient.connection.create(actorRegistryPath, actorBytes, CreateMode.PERSISTENT))
+ Left(zkClient.connection.create(actorAddressRegistryPath, actorFactoryBytes, CreateMode.PERSISTENT))
} catch {
case e: KeeperException.NodeExistsException ⇒ Right(e)
}
}
}) match {
case Left(path) ⇒ path
- case Right(exception) ⇒ actorRegistryPath
+ case Right(exception) ⇒ actorAddressRegistryPath
}
-
- // create UUID -> serializer class name registry
- try {
- zkClient.createPersistent(actorRegistrySerializerPathFor(uuid), serializerClassName)
- } catch {
- case e: ZkNodeExistsException ⇒ zkClient.writeData(actorRegistrySerializerPathFor(uuid), serializerClassName)
- }
-
- // create UUID -> ADDRESS registry
- try {
- zkClient.createPersistent(actorRegistryActorAddressPathFor(uuid), actorRef.address)
- } catch {
- case e: ZkNodeExistsException ⇒ zkClient.writeData(actorRegistryActorAddressPathFor(uuid), actorRef.address)
- }
-
- // create UUID -> Address registry
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorRegistryNodePathFor(uuid)))
-
- // create UUID -> Node registry
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorLocationsPathFor(uuid)))
-
- // create ADDRESS -> UUIDs registry
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorAddressToUuidsPathFor(actorRef.address)))
- ignore[ZkNodeExistsException](zkClient.createPersistent("%s/%s".format(actorAddressToUuidsPathFor(actorRef.address), uuid)))
-
- // create NODE NAME -> UUID registry
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorAtNodePathFor(nodeAddress.nodeName, uuid)))
}
- import RemoteClusterDaemon._
- val command = RemoteDaemonMessageProtocol.newBuilder
- .setMessageType(USE)
- .setActorUuid(uuidToUuidProtocol(uuid))
- .build
+ // create ADDRESS -> SERIALIZER CLASS NAME mapping
+ try {
+ zkClient.createPersistent(actorAddressRegistrySerializerPathFor(actorAddress), serializerClassName)
+ } catch {
+ case e: ZkNodeExistsException ⇒ zkClient.writeData(actorAddressRegistrySerializerPathFor(actorAddress), serializerClassName)
+ }
- nodeConnectionsForReplicationFactor(replicationFactor) foreach { connection ⇒ sendCommandToReplica(connection, command, async = false) }
+ // create ADDRESS -> NODE mapping
+ ignore[ZkNodeExistsException](zkClient.createPersistent(actorAddressToNodesPathFor(actorAddress)))
+
+ // create ADDRESS -> UUIDs mapping
+ ignore[ZkNodeExistsException](zkClient.createPersistent(actorAddressToUuidsPathFor(actorAddress)))
+
+ useActorOnNodes(nodesForReplicationFactor(replicationFactor, Some(actorAddress)).toArray, actorAddress)
this
} else throw new ClusterException("Not connected to cluster")
@@ -686,45 +704,27 @@ class DefaultClusterNode private[akka] (
/**
* Removes actor from the cluster.
*/
- def remove(actorRef: ActorRef) {
- remove(actorRef.uuid)
- }
+ // def remove(actorRef: ActorRef) {
+ // remove(actorRef.address)
+ // }
/**
* Removes actor with uuid from the cluster.
*/
- def remove(uuid: UUID) {
- releaseActorOnAllNodes(uuid)
-
- locallyCheckedOutActors.remove(uuid)
-
- // warning: ordering matters here
- // FIXME remove ADDRESS to UUID mapping?
- actorAddressForUuid(uuid) foreach (address ⇒ ignore[ZkNoNodeException](zkClient.deleteRecursive(actorAddressToUuidsPathFor(address))))
- ignore[ZkNoNodeException](zkClient.deleteRecursive(actorAtNodePathFor(nodeAddress.nodeName, uuid)))
- ignore[ZkNoNodeException](zkClient.deleteRecursive(actorRegistryPathFor(uuid)))
- ignore[ZkNoNodeException](zkClient.deleteRecursive(actorLocationsPathFor(uuid)))
- }
-
- /**
- * Removes actor with address from the cluster.
- */
- def remove(address: String): ClusterNode = {
- isConnected ifOn {
- EventHandler.debug(this,
- "Removing actor(s) with address [%s] from cluster".format(address))
- uuidsForActorAddress(address) foreach (uuid ⇒ remove(uuid))
- }
- this
- }
+ // def remove(actorAddress: String) {
+ // releaseActorOnAllNodes(actorAddress)
+ // // warning: ordering matters here
+ // // FIXME remove ADDRESS to UUID mapping?
+ // ignore[ZkNoNodeException](zkClient.deleteRecursive(actorAddressToUuidsPathFor(actorAddress)))
+ // ignore[ZkNoNodeException](zkClient.deleteRecursive(actorAddressRegistryPathFor(actorAddress)))
+ // ignore[ZkNoNodeException](zkClient.deleteRecursive(actorAddressToNodesPathFor(actorAddress)))
+ // }
/**
* Is the actor with uuid clustered or not?
*/
- def isClustered(actorAddress: String): Boolean = if (isConnected.isOn) {
- actorUuidsForActorAddress(actorAddress) map { uuid ⇒
- zkClient.exists(actorRegistryPathFor(uuid))
- } exists (_ == true)
+ def isClustered(actorAddress: String): Boolean = if (isConnected.get) {
+ zkClient.exists(actorAddressRegistryPathFor(actorAddress))
} else false
/**
@@ -735,97 +735,137 @@ class DefaultClusterNode private[akka] (
/**
* Is the actor with uuid in use or not?
*/
- def isInUseOnNode(actorAddress: String, node: NodeAddress): Boolean = if (isConnected.isOn) {
- actorUuidsForActorAddress(actorAddress) map { uuid ⇒
- zkClient.exists(actorLocationsPathFor(uuid, node))
- } exists (_ == true)
+ def isInUseOnNode(actorAddress: String, node: NodeAddress): Boolean = if (isConnected.get) {
+ zkClient.exists(actorAddressToNodesPathFor(actorAddress, node.nodeName))
} else false
/**
* Checks out an actor for use on this node, e.g. checked out as a 'LocalActorRef' but it makes it available
* for remote access through lookup by its UUID.
*/
- def use[T <: Actor](actorAddress: String): Option[ActorRef] = use(actorAddress, serializerForActor(actorAddress))
+ def use[T <: Actor](actorAddress: String): Option[LocalActorRef] = use(actorAddress, serializerForActor(actorAddress))
/**
* Checks out an actor for use on this node, e.g. checked out as a 'LocalActorRef' but it makes it available
* for remote access through lookup by its UUID.
*/
- def use[T <: Actor](actorAddress: String, serializer: Serializer): Option[ActorRef] = if (isConnected.isOn) {
+ def use[T <: Actor](actorAddress: String, serializer: Serializer): Option[LocalActorRef] = if (isConnected.get) {
+ val nodeName = nodeAddress.nodeName
- import akka.serialization.ActorSerialization._
+ ignore[ZkNodeExistsException](zkClient.createEphemeral(actorAddressToNodesPathFor(actorAddress, nodeName)))
- actorUuidsForActorAddress(actorAddress) map { uuid ⇒
+ val actorFactoryPath = actorAddressRegistryPathFor(actorAddress)
+ zkClient.retryUntilConnected(new Callable[Either[Exception, () ⇒ LocalActorRef]]() {
+ def call: Either[Exception, () ⇒ LocalActorRef] = {
+ try {
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorAtNodePathFor(nodeAddress.nodeName, uuid), true))
- ignore[ZkNodeExistsException](zkClient.createEphemeral(actorLocationsPathFor(uuid, nodeAddress)))
+ val actorFactoryBytes =
+ if (shouldCompressData) LZF.uncompress(zkClient.connection.readData(actorFactoryPath, new Stat, false))
+ else zkClient.connection.readData(actorFactoryPath, new Stat, false)
- // set home address
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorRegistryNodePathFor(uuid)))
- ignore[ZkNodeExistsException](zkClient.createEphemeral(actorRegistryNodePathFor(uuid, remoteServerAddress)))
+ val actorFactory =
+ Serialization.deserialize(actorFactoryBytes, classOf[() ⇒ LocalActorRef], None) match {
+ case Left(error) ⇒ throw error
+ case Right(instance) ⇒ instance.asInstanceOf[() ⇒ LocalActorRef]
+ }
- val actorPath = actorRegistryPathFor(uuid)
- zkClient.retryUntilConnected(new Callable[Either[Array[Byte], Exception]]() {
- def call: Either[Array[Byte], Exception] = {
- try {
- Left(if (shouldCompressData) LZF.uncompress(zkClient.connection.readData(actorPath, new Stat, false))
- else zkClient.connection.readData(actorPath, new Stat, false))
- } catch {
- case e: KeeperException.NoNodeException ⇒ Right(e)
- }
+ Right(actorFactory)
+ } catch {
+ case e: KeeperException.NoNodeException ⇒ Left(e)
}
- }) match {
- case Left(bytes) ⇒
- locallyCheckedOutActors += (uuid -> bytes)
- val actor = fromBinary[T](bytes, remoteServerAddress)
- EventHandler.debug(this,
- "Checking out actor [%s] to be used on node [%s] as local actor"
- .format(actor, nodeAddress.nodeName))
- actor.start()
- actor
- case Right(exception) ⇒ throw exception
}
- } headOption // FIXME should not be an array at all coming here but an Option[ActorRef]
+ }) match {
+ case Left(exception) ⇒ throw exception
+ case Right(actorFactory) ⇒
+ val actorRef = actorFactory()
+
+ EventHandler.debug(this,
+ "Checking out actor [%s] to be used on node [%s] as local actor"
+ .format(actorAddress, nodeName))
+
+ val uuid = actorRef.uuid
+
+ // create UUID registry
+ ignore[ZkNodeExistsException](zkClient.createPersistent(actorUuidRegistryPathFor(uuid)))
+
+ // create UUID -> NODE mapping
+ try {
+ zkClient.createEphemeral(actorUuidRegistryNodePathFor(uuid), nodeName)
+ } catch {
+ case e: ZkNodeExistsException ⇒ zkClient.writeData(actorUuidRegistryNodePathFor(uuid), nodeName)
+ }
+
+ // create UUID -> ADDRESS
+ try {
+ zkClient.createEphemeral(actorUuidRegistryAddressPathFor(uuid), actorAddress)
+ } catch {
+ case e: ZkNodeExistsException ⇒ zkClient.writeData(actorUuidRegistryAddressPathFor(uuid), actorAddress)
+ }
+
+ // create UUID -> REMOTE ADDRESS (InetSocketAddress) mapping
+ try {
+ zkClient.createEphemeral(actorUuidRegistryRemoteAddressPathFor(uuid), remoteServerAddress)
+ } catch {
+ case e: ZkNodeExistsException ⇒ zkClient.writeData(actorUuidRegistryRemoteAddressPathFor(uuid), remoteServerAddress)
+ }
+
+ // create ADDRESS -> UUID mapping
+ try {
+ zkClient.createPersistent(actorAddressRegistryUuidPathFor(actorAddress), uuid)
+ } catch {
+ case e: ZkNodeExistsException ⇒ zkClient.writeData(actorAddressRegistryUuidPathFor(actorAddress), uuid)
+ }
+
+ // create NODE -> UUID mapping
+ ignore[ZkNodeExistsException](zkClient.createPersistent(nodeToUuidsPathFor(nodeName, uuid), true))
+
+ // create ADDRESS -> UUIDs mapping
+ ignore[ZkNodeExistsException](zkClient.createPersistent(actorAddressToUuidsPathFor(actorAddress, uuid)))
+
+ actorRef.start()
+ actorRef
+ }
} else None
/**
- * Using (checking out) all actors with a specific UUID on all nodes in the cluster.
+ * Using (checking out) actor on a specific set of nodes.
*/
- def useActorOnAllNodes(uuid: UUID) {
- isConnected ifOn {
- EventHandler.debug(this,
- "Using (checking out) all actors with UUID [%s] on all nodes in cluster".format(uuid))
+ def useActorOnNodes(nodes: Array[String], actorAddress: String, replicateFromUuid: Option[UUID] = None) {
+ EventHandler.debug(this,
+ "Sending command to nodes [%s] for checking out actor [%s]".format(nodes.mkString(", "), actorAddress))
- connectToAllNewlyArrivedMembershipNodesInCluster()
+ if (isConnected.get) {
- val command = RemoteDaemonMessageProtocol.newBuilder
+ val builder = RemoteDaemonMessageProtocol.newBuilder
.setMessageType(USE)
- .setActorUuid(uuidToUuidProtocol(uuid))
- .build
+ .setActorAddress(actorAddress)
- membershipNodes foreach { node ⇒
+ // set the UUID to replicated from - if available
+ replicateFromUuid foreach (uuid ⇒ builder.setReplicateActorFromUuid(uuidToUuidProtocol(uuid)))
+
+ val command = builder.build
+
+ nodes foreach { node ⇒
nodeConnections.get(node) foreach {
- case (_, connection) ⇒ sendCommandToReplica(connection, command, async = false)
+ case (_, connection) ⇒
+ sendCommandToNode(connection, command, async = false)
}
}
}
}
/**
- * Using (checking out) specific UUID on a specific node.
+ * Using (checking out) actor on all nodes in the cluster.
*/
- def useActorOnNode(node: String, uuid: UUID) {
- isConnected ifOn {
- connectToAllNewlyArrivedMembershipNodesInCluster()
- nodeConnections.get(node) foreach {
- case (_, connection) ⇒
- val command = RemoteDaemonMessageProtocol.newBuilder
- .setMessageType(USE)
- .setActorUuid(uuidToUuidProtocol(uuid))
- .build
- sendCommandToReplica(connection, command, async = false)
- }
- }
+ def useActorOnAllNodes(actorAddress: String, replicateFromUuid: Option[UUID] = None) {
+ useActorOnNodes(membershipNodes, actorAddress, replicateFromUuid)
+ }
+
+ /**
+ * Using (checking out) actor on a specific node.
+ */
+ def useActorOnNode(node: String, actorAddress: String, replicateFromUuid: Option[UUID] = None) {
+ useActorOnNodes(Array(node), actorAddress, replicateFromUuid)
}
/**
@@ -842,37 +882,35 @@ class DefaultClusterNode private[akka] (
// FIXME 'Cluster.release' needs to notify all existing ClusterActorRef's that are using the instance that it is no longer available. Then what to do? Should we even remove this method?
- isConnected ifOn {
- actorUuidsForActorAddress(actorAddress) foreach { uuid ⇒
+ if (isConnected.get) {
+ ignore[ZkNoNodeException](zkClient.delete(actorAddressToNodesPathFor(actorAddress, nodeAddress.nodeName)))
+
+ uuidsForActorAddress(actorAddress) foreach { uuid ⇒
EventHandler.debug(this,
- "Releasing actor with UUID [%s] after usage".format(uuid))
- locallyCheckedOutActors.remove(uuid)
- ignore[ZkNoNodeException](zkClient.deleteRecursive(actorAtNodePathFor(nodeAddress.nodeName, uuid)))
- ignore[ZkNoNodeException](zkClient.delete(actorAtNodePathFor(nodeAddress.nodeName, uuid)))
- ignore[ZkNoNodeException](zkClient.delete(actorLocationsPathFor(uuid, nodeAddress)))
- ignore[ZkNoNodeException](zkClient.delete(actorRegistryNodePathFor(uuid, remoteServerAddress)))
+ "Releasing actor [%s] with UUID [%s] after usage".format(actorAddress, uuid))
+
+ ignore[ZkNoNodeException](zkClient.deleteRecursive(nodeToUuidsPathFor(nodeAddress.nodeName, uuid)))
+ ignore[ZkNoNodeException](zkClient.delete(actorUuidRegistryRemoteAddressPathFor(uuid)))
}
}
}
/**
- * Releases (checking in) all actors with a specific UUID on all nodes in the cluster where the actor is in 'use'.
+ * Releases (checking in) all actors with a specific address on all nodes in the cluster where the actor is in 'use'.
*/
- private[akka] def releaseActorOnAllNodes(uuid: UUID) {
- isConnected ifOn {
+ private[akka] def releaseActorOnAllNodes(actorAddress: String) {
+ if (isConnected.get) {
EventHandler.debug(this,
- "Releasing (checking in) all actors with UUID [%s] on all nodes in cluster".format(uuid))
-
- connectToAllNewlyArrivedMembershipNodesInCluster()
+ "Releasing (checking in) all actors with address [%s] on all nodes in cluster".format(actorAddress))
val command = RemoteDaemonMessageProtocol.newBuilder
.setMessageType(RELEASE)
- .setActorUuid(uuidToUuidProtocol(uuid))
+ .setActorAddress(actorAddress)
.build
- nodesForActorsInUseWithUuid(uuid) foreach { node ⇒
+ nodesForActorsInUseWithAddress(actorAddress) foreach { node ⇒
nodeConnections.get(node) foreach {
- case (_, connection) ⇒ sendCommandToReplica(connection, command, async = true)
+ case (_, connection) ⇒ sendCommandToNode(connection, command, async = true)
}
}
}
@@ -881,7 +919,7 @@ class DefaultClusterNode private[akka] (
/**
* Creates an ActorRef with a Router to a set of clustered actors.
*/
- def ref(actorAddress: String, router: RouterType): ActorRef = if (isConnected.isOn) {
+ def ref(actorAddress: String, router: RouterType): ActorRef = if (isConnected.get) {
val addresses = addressesForActor(actorAddress)
EventHandler.debug(this,
"Checking out cluster actor ref with address [%s] and router [%s] on [%s] connected to [\n\t%s]"
@@ -893,29 +931,6 @@ class DefaultClusterNode private[akka] (
} else throw new ClusterException("Not connected to cluster")
- /**
- * Migrate the actor from 'this' node to node 'to'.
- */
- def migrate(to: NodeAddress, actorAddress: String) {
- migrate(nodeAddress, to, actorAddress)
- }
-
- /**
- * Migrate the actor from node 'from' to node 'to'.
- */
- def migrate(
- from: NodeAddress, to: NodeAddress, actorAddress: String) {
- isConnected ifOn {
- if (from eq null) throw new IllegalArgumentException("NodeAddress 'from' can not be 'null'")
- if (to eq null) throw new IllegalArgumentException("NodeAddress 'to' can not be 'null'")
- if (isInUseOnNode(actorAddress, from)) {
- migrateWithoutCheckingThatActorResidesOnItsHomeNode(from, to, actorAddress)
- } else {
- throw new ClusterException("Can't move actor from node [" + from + "] since it does not exist on this node")
- }
- }
- }
-
/**
* Returns the UUIDs of all actors checked out on this node.
*/
@@ -929,8 +944,8 @@ class DefaultClusterNode private[akka] (
/**
* Returns the UUIDs of all actors registered in this cluster.
*/
- private[akka] def uuidsForClusteredActors: Array[UUID] = if (isConnected.isOn) {
- zkClient.getChildren(ACTOR_REGISTRY_PATH).toList.map(new UUID(_)).toArray.asInstanceOf[Array[UUID]]
+ private[akka] def uuidsForClusteredActors: Array[UUID] = if (isConnected.get) {
+ zkClient.getChildren(ACTOR_UUID_REGISTRY_PATH).toList.map(new UUID(_)).toArray.asInstanceOf[Array[UUID]]
} else Array.empty[UUID]
/**
@@ -941,9 +956,9 @@ class DefaultClusterNode private[akka] (
/**
* Returns the actor id for the actor with a specific UUID.
*/
- private[akka] def actorAddressForUuid(uuid: UUID): Option[String] = if (isConnected.isOn) {
+ private[akka] def actorAddressForUuid(uuid: UUID): Option[String] = if (isConnected.get) {
try {
- Some(zkClient.readData(actorRegistryActorAddressPathFor(uuid)).asInstanceOf[String])
+ Some(zkClient.readData(actorUuidRegistryAddressPathFor(uuid)).asInstanceOf[String])
} catch {
case e: ZkNoNodeException ⇒ None
}
@@ -958,11 +973,11 @@ class DefaultClusterNode private[akka] (
/**
* Returns the actor UUIDs for actor ID.
*/
- private[akka] def uuidsForActorAddress(actorAddress: String): Array[UUID] = if (isConnected.isOn) {
+ private[akka] def uuidsForActorAddress(actorAddress: String): Array[UUID] = if (isConnected.get) {
try {
- zkClient.getChildren(actorAddressToUuidsPathFor(actorAddress)).toArray map {
+ zkClient.getChildren(actorAddressToUuidsPathFor(actorAddress)).toList.toArray map {
case c: CharSequence ⇒ new UUID(c)
- }
+ } filter (_ ne null)
} catch {
case e: ZkNoNodeException ⇒ Array[UUID]()
}
@@ -971,37 +986,22 @@ class DefaultClusterNode private[akka] (
/**
* Returns the node names of all actors in use with UUID.
*/
- private[akka] def nodesForActorsInUseWithUuid(uuid: UUID): Array[String] = if (isConnected.isOn) {
+ private[akka] def nodesForActorsInUseWithAddress(actorAddress: String): Array[String] = if (isConnected.get) {
try {
- zkClient.getChildren(actorLocationsPathFor(uuid)).toArray.asInstanceOf[Array[String]]
+ zkClient.getChildren(actorAddressToNodesPathFor(actorAddress)).toList.toArray.asInstanceOf[Array[String]]
} catch {
case e: ZkNoNodeException ⇒ Array[String]()
}
} else Array.empty[String]
- /**
- * Returns the node names of all actors in use with address.
- */
- def nodesForActorsInUseWithAddress(address: String): Array[String] = if (isConnected.isOn) {
- flatten {
- actorUuidsForActorAddress(address) map { uuid ⇒
- try {
- zkClient.getChildren(actorLocationsPathFor(uuid)).toArray.asInstanceOf[Array[String]]
- } catch {
- case e: ZkNoNodeException ⇒ Array[String]()
- }
- }
- }
- } else Array.empty[String]
-
/**
* Returns the UUIDs of all actors in use registered on a specific node.
*/
- private[akka] def uuidsForActorsInUseOnNode(nodeName: String): Array[UUID] = if (isConnected.isOn) {
+ private[akka] def uuidsForActorsInUseOnNode(nodeName: String): Array[UUID] = if (isConnected.get) {
try {
- zkClient.getChildren(actorsAtNodePathFor(nodeName)).toArray map {
+ zkClient.getChildren(nodeToUuidsPathFor(nodeName)).toList.toArray map {
case c: CharSequence ⇒ new UUID(c)
- }
+ } filter (_ ne null)
} catch {
case e: ZkNoNodeException ⇒ Array[UUID]()
}
@@ -1010,12 +1010,12 @@ class DefaultClusterNode private[akka] (
/**
* Returns the addresses of all actors in use registered on a specific node.
*/
- def addressesForActorsInUseOnNode(nodeName: String): Array[String] = if (isConnected.isOn) {
+ def addressesForActorsInUseOnNode(nodeName: String): Array[String] = if (isConnected.get) {
val uuids =
try {
- zkClient.getChildren(actorsAtNodePathFor(nodeName)).toArray map {
+ zkClient.getChildren(nodeToUuidsPathFor(nodeName)).toList.toArray map {
case c: CharSequence ⇒ new UUID(c)
- }
+ } filter (_ ne null)
} catch {
case e: ZkNoNodeException ⇒ Array[UUID]()
}
@@ -1026,21 +1026,13 @@ class DefaultClusterNode private[akka] (
* Returns Serializer for actor with specific address.
*/
def serializerForActor(actorAddress: String): Serializer = {
- // FIXME should only be 1 single class name per actor address - FIX IT
-
- val serializerClassNames = actorUuidsForActorAddress(actorAddress) map { uuid ⇒
+ val serializerClassName =
try {
- Some(zkClient.readData(actorRegistrySerializerPathFor(uuid), new Stat).asInstanceOf[String])
+ zkClient.readData(actorAddressRegistrySerializerPathFor(actorAddress), new Stat).asInstanceOf[String]
} catch {
- case e: ZkNoNodeException ⇒ None
+ case e: ZkNoNodeException ⇒ throw new IllegalStateException("No serializer found for actor with address [%s]".format(actorAddress))
}
- } filter (_.isDefined) map (_.get)
- if (serializerClassNames.isEmpty) throw new IllegalStateException("No serializer found for actor with address [%s]".format(actorAddress))
- if (serializerClassNames.forall(_ == serializerClassNames.head) == false)
- throw new IllegalStateException("Multiple serializers found for actor with address [%s]".format(actorAddress))
-
- val serializerClassName = serializerClassNames.head
ReflectiveAccess.getClassFor(serializerClassName) match { // FIXME need to pass in a user provide class loader? Now using default in ReflectiveAccess.
case Right(clazz) ⇒ clazz.newInstance.asInstanceOf[Serializer]
case Left(error) ⇒
@@ -1050,21 +1042,22 @@ class DefaultClusterNode private[akka] (
}
/**
- * Returns home address for actor with UUID.
+ * Returns addresses for nodes that the clustered actor is in use on.
*/
def addressesForActor(actorAddress: String): Array[(UUID, InetSocketAddress)] = {
try {
for {
- uuid ← actorUuidsForActorAddress(actorAddress)
- address ← zkClient.getChildren(actorRegistryNodePathFor(uuid)).toList
+ uuid ← uuidsForActorAddress(actorAddress)
} yield {
- val tokenizer = new java.util.StringTokenizer(address, ":")
- val hostname = tokenizer.nextToken // hostname
- val port = tokenizer.nextToken.toInt // port
- (uuid, new InetSocketAddress(hostname, port))
+ val remoteAddress = zkClient.readData(actorUuidRegistryRemoteAddressPathFor(uuid)).asInstanceOf[InetSocketAddress]
+ (uuid, remoteAddress)
}
} catch {
- case e: ZkNoNodeException ⇒ Array[(UUID, InetSocketAddress)]()
+ case e: ZkNoNodeException ⇒
+ EventHandler.warning(this,
+ "Could not retrieve remote socket address for node hosting actor [%s] due to: %s"
+ .format(actorAddress, e.toString))
+ Array[(UUID, InetSocketAddress)]()
}
}
@@ -1200,7 +1193,7 @@ class DefaultClusterNode private[akka] (
// Private
// =======================================
- private def sendCommandToReplica(connection: ActorRef, command: RemoteDaemonMessageProtocol, async: Boolean = true) {
+ private def sendCommandToNode(connection: ActorRef, command: RemoteDaemonMessageProtocol, async: Boolean = true) {
if (async) {
connection ! command
} else {
@@ -1222,31 +1215,27 @@ class DefaultClusterNode private[akka] (
}
}
- private[cluster] def membershipPathFor(node: String) = "%s/%s".format(MEMBERSHIP_PATH, node)
+ private[cluster] def membershipPathFor(node: String): String = "%s/%s".format(MEMBERSHIP_PATH, node)
+ private[cluster] def configurationPathFor(key: String): String = "%s/%s".format(CONFIGURATION_PATH, key)
- private[cluster] def configurationPathFor(key: String) = "%s/%s".format(CONFIGURATION_PATH, key)
+ private[cluster] def actorAddressToNodesPathFor(actorAddress: String): String = "%s/%s".format(ACTOR_ADDRESS_NODES_TO_PATH, actorAddress)
+ private[cluster] def actorAddressToNodesPathFor(actorAddress: String, nodeName: String): String = "%s/%s".format(actorAddressToNodesPathFor(actorAddress), nodeName)
- private[cluster] def actorAddressToUuidsPathFor(actorAddress: String) = "%s/%s".format(ACTOR_ADDRESS_TO_UUIDS_PATH, actorAddress.replace('.', '_'))
+ private[cluster] def nodeToUuidsPathFor(node: String): String = "%s/%s".format(NODE_TO_ACTOR_UUIDS_PATH, node)
+ private[cluster] def nodeToUuidsPathFor(node: String, uuid: UUID): String = "%s/%s/%s".format(NODE_TO_ACTOR_UUIDS_PATH, node, uuid)
- private[cluster] def actorLocationsPathFor(uuid: UUID) = "%s/%s".format(ACTOR_LOCATIONS_PATH, uuid)
+ private[cluster] def actorAddressRegistryPathFor(actorAddress: String): String = "%s/%s".format(ACTOR_ADDRESS_REGISTRY_PATH, actorAddress)
+ private[cluster] def actorAddressRegistrySerializerPathFor(actorAddress: String): String = "%s/%s".format(actorAddressRegistryPathFor(actorAddress), "serializer")
+ private[cluster] def actorAddressRegistryUuidPathFor(actorAddress: String): String = "%s/%s".format(actorAddressRegistryPathFor(actorAddress), "uuid")
- private[cluster] def actorLocationsPathFor(uuid: UUID, node: NodeAddress) =
- "%s/%s/%s".format(ACTOR_LOCATIONS_PATH, uuid, node.nodeName)
+ private[cluster] def actorUuidRegistryPathFor(uuid: UUID): String = "%s/%s".format(ACTOR_UUID_REGISTRY_PATH, uuid)
+ private[cluster] def actorUuidRegistryNodePathFor(uuid: UUID): String = "%s/%s".format(actorUuidRegistryPathFor(uuid), "node")
+ private[cluster] def actorUuidRegistryAddressPathFor(uuid: UUID): String = "%s/%s".format(actorUuidRegistryPathFor(uuid), "address")
- private[cluster] def actorsAtNodePathFor(node: String) = "%s/%s".format(ACTORS_AT_PATH_PATH, node)
+ private[cluster] def actorUuidRegistryRemoteAddressPathFor(uuid: UUID): String = "%s/%s".format(actorUuidRegistryPathFor(uuid), "remote-address")
- private[cluster] def actorAtNodePathFor(node: String, uuid: UUID) = "%s/%s/%s".format(ACTORS_AT_PATH_PATH, node, uuid)
-
- private[cluster] def actorRegistryPathFor(uuid: UUID) = "%s/%s".format(ACTOR_REGISTRY_PATH, uuid)
-
- private[cluster] def actorRegistrySerializerPathFor(uuid: UUID) = "%s/%s".format(actorRegistryPathFor(uuid), "serializer")
-
- private[cluster] def actorRegistryActorAddressPathFor(uuid: UUID) = "%s/%s".format(actorRegistryPathFor(uuid), "address")
-
- private[cluster] def actorRegistryNodePathFor(uuid: UUID): String = "%s/%s".format(actorRegistryPathFor(uuid), "node")
-
- private[cluster] def actorRegistryNodePathFor(uuid: UUID, address: InetSocketAddress): String =
- "%s/%s:%s".format(actorRegistryNodePathFor(uuid), address.getHostName, address.getPort)
+ private[cluster] def actorAddressToUuidsPathFor(actorAddress: String): String = "%s/%s".format(ACTOR_ADDRESS_TO_UUIDS_PATH, actorAddress.replace('.', '_'))
+ private[cluster] def actorAddressToUuidsPathFor(actorAddress: String, uuid: UUID): String = "%s/%s".format(actorAddressToUuidsPathFor(actorAddress), uuid)
private[cluster] def initializeNode() {
EventHandler.info(this,
@@ -1258,82 +1247,131 @@ class DefaultClusterNode private[akka] (
"\n\tserializer = [%s]")
.format(nodeAddress.clusterName, nodeAddress.nodeName, port, zkServerAddresses, serializer))
EventHandler.info(this, "Starting up remote server [%s]".format(remoteServerAddress.toString))
- createRootClusterNode()
- val isLeader = joinLeaderElection()
- if (isLeader) createNodeStructureIfNeeded()
+ createZooKeeperPathStructureIfNeeded()
registerListeners()
joinCluster()
- createActorsAtAddressPath()
+ joinLeaderElection()
fetchMembershipNodes()
EventHandler.info(this, "Cluster node [%s] started successfully".format(nodeAddress))
}
- private def actorUuidsForActorAddress(actorAddress: String): Array[UUID] =
- uuidsForActorAddress(actorAddress) filter (_ ne null)
+ /**
+ * Returns a random set with node names of size 'replicationFactor'.
+ * Default replicationFactor is 0, which returns the empty Set.
+ */
+ private def nodesForReplicationFactor(replicationFactor: Int = 0, actorAddress: Option[String] = None): Set[String] = {
+ var replicaNames = Set.empty[String]
+ val nrOfClusterNodes = nodeConnections.size
+
+ if (replicationFactor < 1) return replicaNames
+ if (nrOfClusterNodes < replicationFactor) throw new IllegalArgumentException(
+ "Replication factor [" + replicationFactor +
+ "] is greater than the number of available nodeNames [" + nrOfClusterNodes + "]")
+
+ val preferredNodes =
+ if (actorAddress.isDefined) { // use 'preferred-nodes' in deployment config for the actor
+ Deployer.deploymentFor(actorAddress.get) match {
+ case Deploy(_, _, Clustered(nodes, _, _)) ⇒
+ nodes map (node ⇒ DeploymentConfig.nodeNameFor(node)) take replicationFactor
+ case _ ⇒
+ throw new ClusterException("Actor [" + actorAddress.get + "] is not configured as clustered")
+ }
+ } else Vector.empty[String]
+
+ for {
+ nodeName ← preferredNodes
+ key ← nodeConnections.keys
+ if key == nodeName
+ } replicaNames = replicaNames + nodeName
+
+ val nrOfCurrentReplicaNames = replicaNames.size
+
+ val replicaSet =
+ if (nrOfCurrentReplicaNames > replicationFactor) throw new IllegalStateException("Replica set is larger than replication factor")
+ else if (nrOfCurrentReplicaNames == replicationFactor) replicaNames
+ else {
+ val random = new java.util.Random(System.currentTimeMillis)
+ while (replicaNames.size < replicationFactor) {
+ replicaNames = replicaNames + membershipNodes(random.nextInt(nrOfClusterNodes))
+ }
+ replicaNames
+ }
+
+ EventHandler.debug(this,
+ "Picked out replica set [%s] for actor [%s]".format(replicaSet.mkString(", "), actorAddress))
+
+ replicaSet
+ }
/**
* Returns a random set with replica connections of size 'replicationFactor'.
- * Default replicationFactor is 0, which returns the empty set.
+ * Default replicationFactor is 0, which returns the empty Set.
*/
- private def nodeConnectionsForReplicationFactor(replicationFactor: Int = 0): Set[ActorRef] = {
- var replicas = HashSet.empty[ActorRef]
- if (replicationFactor < 1) return replicas
-
- connectToAllNewlyArrivedMembershipNodesInCluster()
-
- val numberOfReplicas = nodeConnections.size
- val nodeConnectionsAsArray = nodeConnections.toList map {
- case (node, (address, actorRef)) ⇒ actorRef
- } // the ActorRefs
-
- if (numberOfReplicas < replicationFactor) {
- throw new IllegalArgumentException(
- "Replication factor [" + replicationFactor +
- "] is greater than the number of available nodes [" + numberOfReplicas + "]")
- } else if (numberOfReplicas == replicationFactor) {
- replicas = replicas ++ nodeConnectionsAsArray
- } else {
- val random = new java.util.Random(System.currentTimeMillis)
- while (replicas.size < replicationFactor) {
- val index = random.nextInt(numberOfReplicas)
- replicas = replicas + nodeConnectionsAsArray(index)
- }
- }
- replicas
+ private def nodeConnectionsForReplicationFactor(replicationFactor: Int = 0, actorAddress: Option[String] = None): Set[ActorRef] = {
+ for {
+ node ← nodesForReplicationFactor(replicationFactor, actorAddress)
+ connectionOption ← nodeConnections.get(node)
+ connection ← connectionOption
+ actorRef ← connection._2
+ } yield actorRef
}
+ private val connectToAllNewlyArrivedMembershipNodesInClusterLock = new AtomicBoolean(false)
+
/**
- * Connect to all available replicas unless already connected).
+ * Update the list of connections to other nodes in the cluster.
+ *
+ * @returns a Map with the remote socket addresses to of disconnected node connections
*/
- private def connectToAllNewlyArrivedMembershipNodesInCluster(currentSetOfClusterNodes: Traversable[String] = membershipNodes) {
- currentSetOfClusterNodes foreach { node ⇒
- if ((node != Config.nodename)) { // no replica on the "home" node of the ref
- if (!nodeConnections.contains(node)) { // only connect to each replica once
- val addressOption = remoteSocketAddressForNode(node)
- if (addressOption.isDefined) {
- val address = addressOption.get
- EventHandler.debug(this,
- "Setting up connection to node with nodename [%s] and address [%s]".format(node, address))
- val clusterDaemon = Actor.remote.actorFor(RemoteClusterDaemon.ADDRESS, address.getHostName, address.getPort)
- nodeConnections.put(node, (address, clusterDaemon))
+ private[cluster] def connectToAllNewlyArrivedMembershipNodesInCluster(
+ newlyConnectedMembershipNodes: Traversable[String],
+ newlyDisconnectedMembershipNodes: Traversable[String]): Map[String, InetSocketAddress] = {
+
+ // cache the disconnected connections in a map, needed for fail-over of these connections later
+ var disconnectedConnections = Map.empty[String, InetSocketAddress]
+ newlyDisconnectedMembershipNodes foreach { node ⇒
+ disconnectedConnections += (node -> (nodeConnections(node) match { case (address, _) ⇒ address }))
+ }
+
+ if (connectToAllNewlyArrivedMembershipNodesInClusterLock.compareAndSet(false, true)) {
+ try {
+ // remove connections to failed nodes
+ newlyDisconnectedMembershipNodes foreach (nodeConnections.remove(_))
+
+ // add connections newly arrived nodes
+ newlyConnectedMembershipNodes foreach { node ⇒
+ if (!nodeConnections.contains(node)) { // only connect to each replica once
+
+ remoteSocketAddressForNode(node) foreach { address ⇒
+ EventHandler.debug(this,
+ "Setting up connection to node with nodename [%s] and address [%s]".format(node, address))
+
+ val clusterDaemon = Actor.remote.actorFor(RemoteClusterDaemon.Address, address.getHostName, address.getPort).start()
+ nodeConnections.put(node, (address, clusterDaemon))
+ }
}
}
+ } finally {
+ connectToAllNewlyArrivedMembershipNodesInClusterLock.set(false)
}
}
+
+ disconnectedConnections
}
private[cluster] def joinCluster() {
- nodeNameToAddress += (nodeAddress.nodeName -> remoteServerAddress)
try {
EventHandler.info(this,
"Joining cluster as membership node [%s] on [%s]".format(nodeAddress, membershipNodePath))
zkClient.createEphemeral(membershipNodePath, remoteServerAddress)
} catch {
case e: ZkNodeExistsException ⇒
- val error = new ClusterException("Can't join the cluster. The node name [" + nodeAddress.nodeName + "] is already in by another node")
+ val error = new ClusterException(
+ "Can't join the cluster. The node name [" + nodeAddress.nodeName + "] is already in by another node")
EventHandler.error(error, this, error.toString)
throw error
}
+ ignore[ZkNodeExistsException](zkClient.createPersistent(nodeToUuidsPathFor(nodeAddress.nodeName)))
}
private[cluster] def joinLeaderElection(): Boolean = {
@@ -1353,54 +1391,72 @@ class DefaultClusterNode private[akka] (
}
}
- private[cluster] def createActorsAtAddressPath() {
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorsAtNodePathFor(nodeAddress.nodeName)))
- }
-
- private[cluster] def failOverConnections(from: InetSocketAddress, to: InetSocketAddress) {
+ private[cluster] def failOverClusterActorRefConnections(from: InetSocketAddress, to: InetSocketAddress) {
clusterActorRefs.values(from) foreach (_.failOver(from, to))
}
- private[cluster] def migrateActorsOnFailedNodes(currentSetOfClusterNodes: List[String]) {
- connectToAllNewlyArrivedMembershipNodesInCluster(currentSetOfClusterNodes)
-
- val failedNodes = findFailedNodes(currentSetOfClusterNodes)
+ private[cluster] def migrateActorsOnFailedNodes(
+ failedNodes: List[String],
+ currentClusterNodes: List[String],
+ oldClusterNodes: List[String],
+ disconnectedConnections: Map[String, InetSocketAddress]) {
failedNodes.foreach { failedNodeName ⇒
- val allNodes = locallyCachedMembershipNodes.toList
- val myIndex = allNodes.indexWhere(_.endsWith(nodeAddress.nodeName))
- val failedNodeIndex = allNodes.indexWhere(_ == failedNodeName)
+ val failedNodeAddress = NodeAddress(nodeAddress.clusterName, failedNodeName)
+
+ val myIndex = oldClusterNodes.indexWhere(_.endsWith(nodeAddress.nodeName))
+ val failedNodeIndex = oldClusterNodes.indexWhere(_ == failedNodeName)
// Migrate to the successor of the failed node (using a sorted circular list of the node names)
- if ((failedNodeIndex == 0 && myIndex == locallyCachedMembershipNodes.size - 1) || // No leftmost successor exists, check the tail
+ if ((failedNodeIndex == 0 && myIndex == oldClusterNodes.size - 1) || // No leftmost successor exists, check the tail
(failedNodeIndex == myIndex + 1)) { // Am I the leftmost successor?
+ // Takes the lead of migrating the actors. Not all to this node.
+ // All to this node except if the actor already resides here, then pick another node it is not already on.
+
// Yes I am the node to migrate the actor to (can only be one in the cluster)
- val actorUuidsForFailedNode = zkClient.getChildren(actorsAtNodePathFor(failedNodeName))
+ val actorUuidsForFailedNode = zkClient.getChildren(nodeToUuidsPathFor(failedNodeName)).toList
- EventHandler.debug(this,
- "Migrating actors from failed node [%s] to node [%s]: Actor UUIDs [%s]"
- .format(failedNodeName, nodeAddress.nodeName, actorUuidsForFailedNode))
-
- actorUuidsForFailedNode.foreach { uuid ⇒
+ actorUuidsForFailedNode.foreach { uuidAsString ⇒
EventHandler.debug(this,
"Cluster node [%s] has failed, migrating actor with UUID [%s] to [%s]"
- .format(failedNodeName, uuid, nodeAddress.nodeName))
+ .format(failedNodeName, uuidAsString, nodeAddress.nodeName))
- val actorAddressOption = actorAddressForUuid(uuidFrom(uuid))
- if (actorAddressOption.isDefined) {
- val actorAddress = actorAddressOption.get
+ val uuid = uuidFrom(uuidAsString)
+ val actorAddress = actorAddressForUuid(uuid).getOrElse(
+ throw new IllegalStateException("No actor address found for UUID [" + uuidAsString + "]"))
- migrateWithoutCheckingThatActorResidesOnItsHomeNode( // since the ephemeral node is already gone, so can't check
- NodeAddress(nodeAddress.clusterName, failedNodeName), nodeAddress, actorAddress)
+ val migrateToNodeAddress =
+ if (isInUseOnNode(actorAddress)) {
+ // already in use on this node, pick another node to instantiate the actor on
+ val replicaNodesForActor = nodesForActorsInUseWithAddress(actorAddress)
+ val nodesAvailableForMigration = (currentClusterNodes.toSet diff failedNodes.toSet) diff replicaNodesForActor.toSet
- use(actorAddress, serializerForActor(actorAddress)) foreach (actor ⇒ remoteService.register(actorAddress, actor))
- }
+ if (nodesAvailableForMigration.isEmpty) throw new ClusterException(
+ "Can not migrate actor to new node since there are not any available nodes left. " +
+ "(However, the actor already has >1 replica in cluster, so we are ok)")
+
+ NodeAddress(nodeAddress.clusterName, nodesAvailableForMigration.head)
+ } else {
+ // actor is not in use on this node, migrate it here
+ nodeAddress
+ }
+
+ // if actor is replicated => pass along the UUID for the actor to replicate from (replay transaction log etc.)
+ val replicateFromUuid =
+ if (isReplicated(actorAddress)) Some(uuid)
+ else None
+
+ migrateWithoutCheckingThatActorResidesOnItsHomeNode(
+ failedNodeAddress,
+ migrateToNodeAddress,
+ actorAddress,
+ replicateFromUuid)
}
// notify all available nodes that they should fail-over all connections from 'from' to 'to'
- val from = nodeNameToAddress(failedNodeName)
+ val from = disconnectedConnections(failedNodeName)
val to = remoteServerAddress
Serialization.serialize((from, to)) match {
@@ -1413,10 +1469,8 @@ class DefaultClusterNode private[akka] (
.build
// FIXME now we are broadcasting to ALL nodes in the cluster even though a fraction might have a reference to the actors - should that be fixed?
- currentSetOfClusterNodes foreach { node ⇒
- nodeConnections.get(node) foreach {
- case (_, connection) ⇒ sendCommandToReplica(connection, command, async = true)
- }
+ nodeConnections.values foreach {
+ case (_, connection) ⇒ sendCommandToNode(connection, command, async = true)
}
}
}
@@ -1424,61 +1478,35 @@ class DefaultClusterNode private[akka] (
}
/**
- * Used when the ephemeral "home" node is already gone, so we can't check.
+ * Used when the ephemeral "home" node is already gone, so we can't check if it is available.
*/
private def migrateWithoutCheckingThatActorResidesOnItsHomeNode(
- from: NodeAddress, to: NodeAddress, actorAddress: String) {
+ from: NodeAddress, to: NodeAddress, actorAddress: String, replicateFromUuid: Option[UUID]) {
EventHandler.debug(this, "Migrating actor [%s] from node [%s] to node [%s]".format(actorAddress, from, to))
+ if (!isInUseOnNode(actorAddress, to)) {
+ release(actorAddress)
- actorUuidsForActorAddress(actorAddress) map { uuid ⇒
- val actorAddressOption = actorAddressForUuid(uuid)
- if (actorAddressOption.isDefined) {
- val actorAddress = actorAddressOption.get
+ val remoteAddress = remoteSocketAddressForNode(to.nodeName).getOrElse(throw new ClusterException("No remote address registered for [" + to.nodeName + "]"))
- if (!isInUseOnNode(actorAddress, to)) {
- release(actorAddress)
+ ignore[ZkNodeExistsException](zkClient.createEphemeral(actorAddressToNodesPathFor(actorAddress, to.nodeName)))
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorRegistryNodePathFor(uuid)))
- ignore[ZkNodeExistsException](zkClient.createEphemeral(actorRegistryNodePathFor(uuid,
- remoteSocketAddressForNode(to.nodeName).getOrElse(throw new ClusterException("No remote address registered for [" + to.nodeName + "]")))))
+ ignore[ZkNoNodeException](zkClient.delete(actorAddressToNodesPathFor(actorAddress, from.nodeName)))
- ignore[ZkNodeExistsException](zkClient.createEphemeral(actorLocationsPathFor(uuid, to)))
- ignore[ZkNodeExistsException](zkClient.createPersistent(actorAtNodePathFor(nodeAddress.nodeName, uuid)))
+ // FIXME who takes care of this line?
+ //ignore[ZkNoNodeException](zkClient.delete(nodeToUuidsPathFor(from.nodeName, uuid)))
- ignore[ZkNoNodeException](zkClient.delete(actorLocationsPathFor(uuid, from)))
- ignore[ZkNoNodeException](zkClient.delete(actorAtNodePathFor(from.nodeName, uuid)))
-
- // 'use' (check out) actor on the remote 'to' node
- useActorOnNode(to.nodeName, uuid)
- }
- }
+ // 'use' (check out) actor on the remote 'to' node
+ useActorOnNode(to.nodeName, actorAddress, replicateFromUuid)
}
}
- private[cluster] def findFailedNodes(nodes: List[String]): List[String] =
- (locallyCachedMembershipNodes.toArray.toSet.asInstanceOf[Set[String]] diff Set(nodes: _*)).toList
-
- private[cluster] def findNewlyConnectedMembershipNodes(nodes: List[String]): List[String] =
- (Set(nodes: _*) diff locallyCachedMembershipNodes.toArray.toSet.asInstanceOf[Set[String]]).toList
-
- private[cluster] def findNewlyDisconnectedMembershipNodes(nodes: List[String]): List[String] =
- (locallyCachedMembershipNodes.toArray.toSet.asInstanceOf[Set[String]] diff Set(nodes: _*)).toList
-
- private[cluster] def findNewlyConnectedAvailableNodes(nodes: List[String]): List[String] =
- (Set(nodes: _*) diff locallyCachedMembershipNodes.toArray.toSet.asInstanceOf[Set[String]]).toList
-
- private[cluster] def findNewlyDisconnectedAvailableNodes(nodes: List[String]): List[String] =
- (locallyCachedMembershipNodes.toArray.toSet.asInstanceOf[Set[String]] diff Set(nodes: _*)).toList
-
- private def createRootClusterNode() {
+ private def createZooKeeperPathStructureIfNeeded() {
ignore[ZkNodeExistsException] {
zkClient.create(CLUSTER_PATH, null, CreateMode.PERSISTENT)
EventHandler.info(this, "Created node [%s]".format(CLUSTER_PATH))
}
- }
- private def createNodeStructureIfNeeded() {
basePaths.foreach { path ⇒
try {
ignore[ZkNodeExistsException](zkClient.create(path, null, CreateMode.PERSISTENT))
@@ -1506,8 +1534,11 @@ class DefaultClusterNode private[akka] (
val membershipChildren = zkClient.getChildren(MEMBERSHIP_PATH)
locallyCachedMembershipNodes.clear()
membershipChildren.iterator.foreach(locallyCachedMembershipNodes.add)
+ connectToAllNewlyArrivedMembershipNodesInCluster(membershipNodes, Nil)
}
+ private def isReplicated(actorAddress: String): Boolean = DeploymentConfig.isReplicated(Deployer.deploymentFor(actorAddress))
+
private def createMBean = {
val clusterMBean = new StandardMBean(classOf[ClusterNodeMBean]) with ClusterNodeMBean {
@@ -1523,7 +1554,7 @@ class DefaultClusterNode private[akka] (
override def resign(): Unit = self.resign()
- override def isConnected = self.isConnected.isOn
+ override def isConnected = self.isConnected.get
override def getRemoteServerHostname = self.hostname
@@ -1547,9 +1578,7 @@ class DefaultClusterNode private[akka] (
override def getAddressesForClusteredActors = self.addressesForClusteredActors.map(_.toString).toArray
- override def getNodesForActorInUseWithUuid(uuid: String) = self.nodesForActorsInUseWithUuid(stringToUuid(uuid))
-
- override def getNodesForActorInUseWithAddress(id: String) = self.nodesForActorsInUseWithAddress(id)
+ override def getNodesForActorInUseWithAddress(address: String) = self.nodesForActorsInUseWithAddress(address)
override def getUuidsForActorsInUseOnNode(nodeName: String) = self.uuidsForActorsInUseOnNode(nodeName).map(_.toString).toArray
@@ -1578,25 +1607,29 @@ class MembershipChildListener(self: ClusterNode) extends IZkChildListener with E
def handleChildChange(parentPath: String, currentChilds: JList[String]) {
withErrorHandler {
if (currentChilds ne null) {
- val childList = currentChilds.toList
- if (!childList.isEmpty) EventHandler.debug(this,
+ val currentClusterNodes = currentChilds.toList
+ if (!currentClusterNodes.isEmpty) EventHandler.debug(this,
"MembershipChildListener at [%s] has children [%s]"
- .format(self.nodeAddress.nodeName, childList.mkString(" ")))
-
- self.migrateActorsOnFailedNodes(currentChilds.toList)
-
- self.findNewlyConnectedMembershipNodes(childList) foreach { name ⇒
- self.remoteSocketAddressForNode(name) foreach (address ⇒ self.nodeNameToAddress += (name -> address)) // update 'nodename-address' map
- self.publish(NodeConnected(name))
- }
-
- self.findNewlyDisconnectedMembershipNodes(childList) foreach { name ⇒
- self.nodeNameToAddress - name // update 'nodename-address' map
- self.publish(NodeDisconnected(name))
- }
+ .format(self.nodeAddress.nodeName, currentClusterNodes.mkString(" ")))
+ // take a snapshot of the old cluster nodes and then update the list with the current connected nodes in the cluster
+ val oldClusterNodes = self.locallyCachedMembershipNodes.toArray.toSet.asInstanceOf[Set[String]]
self.locallyCachedMembershipNodes.clear()
- childList.foreach(self.locallyCachedMembershipNodes.add)
+ currentClusterNodes foreach (self.locallyCachedMembershipNodes.add)
+
+ val newlyConnectedMembershipNodes = (Set(currentClusterNodes: _*) diff oldClusterNodes).toList
+ val newlyDisconnectedMembershipNodes = (oldClusterNodes diff Set(currentClusterNodes: _*)).toList
+
+ // update the connections with the new set of cluster nodes
+ val disconnectedConnections = self.connectToAllNewlyArrivedMembershipNodesInCluster(newlyConnectedMembershipNodes, newlyDisconnectedMembershipNodes)
+
+ // if node(s) left cluster then migrate actors residing on the failed node
+ if (!newlyDisconnectedMembershipNodes.isEmpty)
+ self.migrateActorsOnFailedNodes(newlyDisconnectedMembershipNodes, currentClusterNodes, oldClusterNodes.toList, disconnectedConnections)
+
+ // publish NodeConnected and NodeDisconnect events to the listeners
+ newlyConnectedMembershipNodes foreach (node ⇒ self.publish(NodeConnected(node)))
+ newlyDisconnectedMembershipNodes foreach (node ⇒ self.publish(NodeDisconnected(node)))
}
}
}
@@ -1636,7 +1669,7 @@ class StateListener(self: ClusterNode) extends IZkStateListener {
trait ErrorHandler {
def withErrorHandler[T](body: ⇒ T) = {
try {
- ignore[ZkInterruptedException](body)
+ ignore[ZkInterruptedException](body) // FIXME Is it good to ignore ZkInterruptedException? If not, how should we handle it?
} catch {
case e: Throwable ⇒
EventHandler.error(e, this, e.toString)
@@ -1649,13 +1682,15 @@ trait ErrorHandler {
* @author Jonas Bonér
*/
object RemoteClusterDaemon {
- val ADDRESS = "akka-cluster-daemon".intern
+ val Address = "akka-cluster-daemon".intern
// FIXME configure computeGridDispatcher to what?
- val computeGridDispatcher = Dispatchers.newDispatcher("akka:cloud:cluster:compute-grid").build
+ val computeGridDispatcher = Dispatchers.newDispatcher("akka:compute-grid").build
}
/**
+ * Internal "daemon" actor for cluster internal communication.
+ *
* @author Jonas Bonér
*/
class RemoteClusterDaemon(cluster: ClusterNode) extends Actor {
@@ -1671,26 +1706,94 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor {
def receive: Receive = {
case message: RemoteDaemonMessageProtocol ⇒
- EventHandler.debug(this, "Received command to RemoteClusterDaemon [%s]".format(message))
+ EventHandler.debug(this,
+ "Received command [\n%s] to RemoteClusterDaemon on node [%s]"
+ .format(message, cluster.nodeAddress.nodeName))
message.getMessageType match {
case USE ⇒
try {
- if (message.hasActorUuid) {
- for {
- address ← cluster.actorAddressForUuid(uuidProtocolToUuid(message.getActorUuid))
- serializer ← cluster.serializerForActor(address)
- } cluster.use(address, serializer)
+ if (message.hasActorAddress) {
+ val actorAddress = message.getActorAddress
+ cluster.serializerForActor(actorAddress) foreach { serializer ⇒
+ cluster.use(actorAddress, serializer) foreach { newActorRef ⇒
+ cluster.remoteService.register(actorAddress, newActorRef)
- } else if (message.hasActorAddress) {
- val address = message.getActorAddress
- cluster.serializerForActor(address) foreach (serializer ⇒ cluster.use(address, serializer))
+ if (message.hasReplicateActorFromUuid) {
+ // replication is used - fetch the messages and replay them
+ import akka.remote.protocol.RemoteProtocol._
+ import akka.remote.MessageSerializer
+ val replicateFromUuid = uuidProtocolToUuid(message.getReplicateActorFromUuid)
+ val deployment = Deployer.deploymentFor(actorAddress)
+ val replicationScheme = DeploymentConfig.replicationSchemeFor(deployment).getOrElse(
+ throw new IllegalStateException(
+ "Actor [" + actorAddress + "] should have been configured as a replicated actor but could not find its ReplicationScheme"))
+ val isWriteBehind = DeploymentConfig.isWriteBehindReplication(replicationScheme)
+
+ try {
+ // get the transaction log for the actor UUID
+ val txLog = TransactionLog.logFor(replicateFromUuid.toString, isWriteBehind, replicationScheme)
+
+ // get the latest snapshot (Option[Array[Byte]]) and all the subsequent messages (Array[Byte])
+ val (snapshotAsBytes, entriesAsBytes) = txLog.latestSnapshotAndSubsequentEntries
+
+ // deserialize and restore actor snapshot
+ val actorRefToUseForReplay =
+ snapshotAsBytes match {
+
+ // we have a new actor ref - the snapshot
+ case Some(bytes) ⇒
+ // stop the new actor ref and use the snapshot instead
+ cluster.remoteService.unregister(actorAddress)
+
+ // deserialize the snapshot actor ref and register it as remote actor
+ val uncompressedBytes =
+ if (Cluster.shouldCompressData) LZF.uncompress(bytes)
+ else bytes
+
+ val snapshotActorRef = fromBinary(uncompressedBytes, newActorRef.uuid).start()
+ cluster.remoteService.register(actorAddress, snapshotActorRef)
+
+ // FIXME we should call 'stop()' here (to GC the actor), but can't since that will currently shut down the TransactionLog for this UUID - since both this actor and the new snapshotActorRef have the same UUID (which they should)
+ //newActorRef.stop()
+
+ snapshotActorRef
+
+ // we have no snapshot - use the new actor ref
+ case None ⇒
+ newActorRef
+ }
+
+ // deserialize the messages
+ val messages: Vector[AnyRef] = entriesAsBytes map { bytes ⇒
+ val messageBytes =
+ if (Cluster.shouldCompressData) LZF.uncompress(bytes)
+ else bytes
+ MessageSerializer.deserialize(MessageProtocol.parseFrom(messageBytes), None)
+ }
+
+ EventHandler.info(this, "Replaying [%s] messages to actor [%s]".format(messages.size, actorAddress))
+
+ // replay all messages
+ messages foreach { message ⇒
+ EventHandler.debug(this, "Replaying message [%s] to actor [%s]".format(message, actorAddress))
+
+ // FIXME how to handle '?' messages? We can *not* replay them with the correct semantics. Should we: 1. Ignore/drop them and log warning? 2. Throw exception when about to log them? 3. Other?
+ actorRefToUseForReplay ! message
+ }
+
+ } catch {
+ case e: Throwable ⇒
+ EventHandler.error(e, this, e.toString)
+ throw e
+ }
+ }
+ }
+ }
} else {
- EventHandler.warning(this,
- "None of 'uuid', or 'address' is specified, ignoring remote cluster daemon command [%s]"
- .format(message))
+ EventHandler.error(this, "Actor 'address' is not defined, ignoring remote cluster daemon command [%s]".format(message))
}
self.reply(Success)
@@ -1725,7 +1828,7 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor {
case FAIL_OVER_CONNECTIONS ⇒
val (from, to) = payloadFor(message, classOf[(InetSocketAddress, InetSocketAddress)])
- cluster.failOverConnections(from, to)
+ cluster.failOverClusterActorRefConnections(from, to)
case FUNCTION_FUN0_UNIT ⇒
localActorOf(new Actor() {
diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterActorRef.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterActorRef.scala
index a957dcaa29..b9aeb1c460 100644
--- a/akka-cluster/src/main/scala/akka/cluster/ClusterActorRef.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/ClusterActorRef.scala
@@ -21,6 +21,9 @@ import java.util.{ Map ⇒ JMap }
import com.eaio.uuid.UUID
/**
+ * ActorRef representing a one or many instances of a clustered, load-balanced and sometimes replicated actor
+ * where the instances can reside on other nodes in the cluster.
+ *
* @author Jonas Bonér
*/
class ClusterActorRef private[akka] (
@@ -28,7 +31,6 @@ class ClusterActorRef private[akka] (
val address: String,
_timeout: Long)
extends ActorRef with ScalaActorRef { this: Router.Router ⇒
-
timeout = _timeout
private[akka] val inetSocketAddressToActorRefMap = new AtomicReference[Map[InetSocketAddress, ActorRef]](
@@ -37,7 +39,6 @@ class ClusterActorRef private[akka] (
})
ClusterModule.ensureEnabled()
- start()
def connections: Map[InetSocketAddress, ActorRef] = inetSocketAddressToActorRefMap.get
@@ -83,42 +84,64 @@ class ClusterActorRef private[akka] (
if (_status == ActorRefInternals.RUNNING) {
_status = ActorRefInternals.SHUTDOWN
postMessageToMailbox(RemoteActorSystemMessage.Stop, None)
+
+ // FIXME here we need to fire off Actor.cluster.remove(address) (which needs to be properly implemented first, see ticket)
+
+ inetSocketAddressToActorRefMap.get.values foreach (_.stop()) // shut down all remote connections
}
}
}
+ // ========================================================================
// ==== NOT SUPPORTED ====
+ // ========================================================================
+
// FIXME move these methods and the same ones in RemoteActorRef to a base class - now duplicated
def dispatcher_=(md: MessageDispatcher) {
unsupported
}
+
def dispatcher: MessageDispatcher = unsupported
+
def link(actorRef: ActorRef) {
unsupported
}
+
def unlink(actorRef: ActorRef) {
unsupported
}
+
def startLink(actorRef: ActorRef): ActorRef = unsupported
+
def supervisor: Option[ActorRef] = unsupported
+
def linkedActors: JMap[Uuid, ActorRef] = unsupported
+
protected[akka] def mailbox: AnyRef = unsupported
+
protected[akka] def mailbox_=(value: AnyRef): AnyRef = unsupported
+
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable) {
unsupported
}
+
protected[akka] def restart(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]) {
unsupported
}
+
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]) {
unsupported
}
+
protected[akka] def invoke(messageHandle: MessageInvocation) {
unsupported
}
+
protected[akka] def supervisor_=(sup: Option[ActorRef]) {
unsupported
}
+
protected[akka] def actorInstance: AtomicReference[Actor] = unsupported
- private def unsupported = throw new UnsupportedOperationException("Not supported for RemoteActorRef")
+
+ private def unsupported = throw new UnsupportedOperationException("Not supported for ClusterActorRef")
}
diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterDeployer.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterDeployer.scala
index e24fb43367..6f251eb593 100644
--- a/akka-cluster/src/main/scala/akka/cluster/ClusterDeployer.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/ClusterDeployer.scala
@@ -41,9 +41,9 @@ object ClusterDeployer {
val deploymentCoordinationPath = clusterPath + "/deployment-coordination"
val deploymentInProgressLockPath = deploymentCoordinationPath + "/in-progress"
- val isDeploymentCompletedInClusterLockPath = deploymentCoordinationPath + "/completed" // should not be part of baseNodes
+ val isDeploymentCompletedInClusterLockPath = deploymentCoordinationPath + "/completed" // should not be part of basePaths
- val baseNodes = List(clusterPath, deploymentPath, deploymentCoordinationPath, deploymentInProgressLockPath)
+ val basePaths = List(clusterPath, deploymentPath, deploymentCoordinationPath, deploymentInProgressLockPath)
private val isConnected = new Switch(false)
private val deploymentCompleted = new CountDownLatch(1)
@@ -52,7 +52,7 @@ object ClusterDeployer {
Cluster.zooKeeperServers,
Cluster.sessionTimeout,
Cluster.connectionTimeout,
- Cluster.defaultSerializer)
+ Cluster.defaultZooKeeperSerializer)
private val deploymentInProgressLockListener = new LockListener {
def lockAcquired() {
@@ -123,7 +123,7 @@ object ClusterDeployer {
val deployments = addresses map { address ⇒
zkClient.readData(deploymentAddressPath.format(address)).asInstanceOf[Deploy]
}
- EventHandler.info(this, "Fetched clustered deployments [\n\t%s\n]" format deployments.mkString("\n\t"))
+ EventHandler.info(this, "Fetched deployment plan from cluster [\n\t%s\n]" format deployments.mkString("\n\t"))
deployments
}
@@ -131,10 +131,10 @@ object ClusterDeployer {
isConnected switchOn {
EventHandler.info(this, "Initializing cluster deployer")
- baseNodes foreach { path ⇒
+ basePaths foreach { path ⇒
try {
ignore[ZkNodeExistsException](zkClient.create(path, null, CreateMode.PERSISTENT))
- EventHandler.debug(this, "Created node [%s]".format(path))
+ EventHandler.debug(this, "Created ZooKeeper path for deployment [%s]".format(path))
} catch {
case e ⇒
val error = new DeploymentException(e.toString)
@@ -148,7 +148,7 @@ object ClusterDeployer {
if (!isDeploymentCompletedInCluster) {
if (deploymentInProgressLock.lock()) {
// try to be the one doing the clustered deployment
- EventHandler.info(this, "Deploying to cluster [\n" + allDeployments.mkString("\n\t") + "\n]")
+ EventHandler.info(this, "Pushing deployment plan cluster [\n\t" + allDeployments.mkString("\n\t") + "\n]")
allDeployments foreach (deploy(_)) // deploy
markDeploymentCompletedInCluster()
deploymentInProgressLock.unlock() // signal deployment complete
@@ -167,7 +167,7 @@ object ClusterDeployer {
ensureRunning {
LocalDeployer.deploy(deployment)
deployment match {
- case Deploy(_, _, _, Local) ⇒ {} // local deployment, do nothing here
+ case Deploy(_, _, Local) ⇒ {} // local deployment, do nothing here
case _ ⇒ // cluster deployment
val path = deploymentAddressPath.format(deployment.address)
try {
diff --git a/akka-cluster/src/main/scala/akka/cluster/Routing.scala b/akka-cluster/src/main/scala/akka/cluster/Routing.scala
index 838efc729f..1c9c1f5043 100644
--- a/akka-cluster/src/main/scala/akka/cluster/Routing.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/Routing.scala
@@ -90,7 +90,7 @@ object Router {
if (connections.isEmpty) {
EventHandler.warning(this, "Router has no replica connections")
None
- } else Some(connections.valuesIterator.drop(random.nextInt(connections.size)).next)
+ } else Some(connections.valuesIterator.drop(random.nextInt(connections.size)).next())
}
/**
diff --git a/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala b/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala
index 89a9c811d9..7a15673754 100644
--- a/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala
@@ -18,9 +18,8 @@ import DeploymentConfig.{ ReplicationScheme, ReplicationStrategy, Transient, Wri
import akka.event.EventHandler
import akka.dispatch.{ DefaultPromise, Promise, MessageInvocation }
import akka.remote.MessageSerializer
-import akka.serialization.ActorSerialization._
import akka.cluster.zookeeper._
-import akka.serialization.{ Serializer, Compression }
+import akka.serialization.{ Serializer, Serialization, Compression }
import Compression.LZF
import akka.serialization.ActorSerialization._
@@ -33,15 +32,11 @@ import java.util.concurrent.atomic.AtomicLong
// FIXME delete tx log after migration of actor has been made and create a new one
/**
- * TODO: Improved documentation,
- *
* @author Jonas Bonér
*/
class ReplicationException(message: String) extends AkkaException(message)
/**
- * TODO: Improved documentation.
- *
* TODO: Explain something about threadsafety.
*
* A TransactionLog makes chunks of data durable.
@@ -52,39 +47,34 @@ class TransactionLog private (
ledger: LedgerHandle,
val id: String,
val isAsync: Boolean,
- replicationScheme: ReplicationScheme,
- format: Serializer) {
+ replicationScheme: ReplicationScheme) {
import TransactionLog._
val logId = ledger.getId
val txLogPath = transactionLogNode + "/" + id
val snapshotPath = txLogPath + "/snapshot"
- val nrOfEntries = new AtomicLong(0)
private val isOpen = new Switch(true)
/**
- * TODO document method
+ * Record an Actor message invocation.
*/
- def recordEntry(messageHandle: MessageInvocation, actorRef: ActorRef) {
- if (nrOfEntries.incrementAndGet % snapshotFrequency == 0) {
- val snapshot =
- // FIXME ReplicationStrategy Transient is always used
- if (Cluster.shouldCompressData) LZF.compress(toBinary(actorRef, false, replicationScheme))
- else toBinary(actorRef, false, replicationScheme)
- recordSnapshot(snapshot)
- }
- recordEntry(MessageSerializer.serialize(messageHandle.message.asInstanceOf[AnyRef]).toByteArray)
+ def recordEntry(messageHandle: MessageInvocation, actorRef: LocalActorRef) {
+ val entryId = ledger.getLastAddPushed + 1
+ if (entryId != 0 && (entryId % snapshotFrequency) == 0) {
+ recordSnapshot(toBinary(actorRef, false, replicationScheme))
+ } else recordEntry(MessageSerializer.serialize(messageHandle.message.asInstanceOf[AnyRef]).toByteArray)
}
/**
- * TODO document method
+ * Record an entry.
*/
def recordEntry(entry: Array[Byte]) {
if (isOpen.isOn) {
- val bytes = if (Cluster.shouldCompressData) LZF.compress(entry)
- else entry
+ val bytes =
+ if (Cluster.shouldCompressData) LZF.compress(entry)
+ else entry
try {
if (isAsync) {
ledger.asyncAddEntry(
@@ -96,8 +86,7 @@ class TransactionLog private (
entryId: Long,
ctx: AnyRef) {
handleReturnCode(returnCode)
- EventHandler.debug(this,
- "Writing entry [%s] to log [%s]".format(entryId, logId))
+ EventHandler.debug(this, "Writing entry [%s] to log [%s]".format(entryId, logId))
}
},
null)
@@ -113,12 +102,13 @@ class TransactionLog private (
}
/**
- * TODO document method
+ * Record a snapshot.
*/
def recordSnapshot(snapshot: Array[Byte]) {
if (isOpen.isOn) {
- val bytes = if (Cluster.shouldCompressData) LZF.compress(snapshot)
- else snapshot
+ val bytes =
+ if (Cluster.shouldCompressData) LZF.compress(snapshot)
+ else snapshot
try {
if (isAsync) {
ledger.asyncAddEntry(
@@ -127,16 +117,20 @@ class TransactionLog private (
def addComplete(
returnCode: Int,
ledgerHandle: LedgerHandle,
- entryId: Long,
+ snapshotId: Long,
ctx: AnyRef) {
handleReturnCode(returnCode)
- storeSnapshotMetaDataInZooKeeper(entryId)
+ EventHandler.debug(this, "Writing snapshot to log [%s]".format(snapshotId))
+ storeSnapshotMetaDataInZooKeeper(snapshotId)
}
},
null)
} else {
handleReturnCode(ledger.addEntry(bytes))
- storeSnapshotMetaDataInZooKeeper(ledger.getLastAddPushed)
+ val snapshotId = ledger.getLastAddPushed
+
+ EventHandler.debug(this, "Writing snapshot to log [%s]".format(snapshotId))
+ storeSnapshotMetaDataInZooKeeper(snapshotId)
}
} catch {
case e ⇒ handleError(e)
@@ -145,22 +139,36 @@ class TransactionLog private (
}
/**
- * TODO document method
+ * Get all the entries for this transaction log.
*/
def entries: Vector[Array[Byte]] = entriesInRange(0, ledger.getLastAddConfirmed)
/**
- * TODO document method
+ * Get the latest snapshot and all subsequent entries from this snapshot.
*/
- def toByteArraysLatestSnapshot: (Array[Byte], Vector[Array[Byte]]) = {
- val snapshotId = latestSnapshotId
- EventHandler.debug(this,
- "Reading entries from snapshot id [%s] for log [%s]".format(snapshotId, logId))
- (entriesInRange(snapshotId, snapshotId).head, entriesInRange(snapshotId + 1, ledger.getLastAddConfirmed))
+ def latestSnapshotAndSubsequentEntries: (Option[Array[Byte]], Vector[Array[Byte]]) = {
+ latestSnapshotId match {
+ case Some(snapshotId) ⇒
+ EventHandler.debug(this, "Reading entries from snapshot id [%s] for log [%s]".format(snapshotId, logId))
+
+ val cursor = snapshotId + 1
+ val lastIndex = ledger.getLastAddConfirmed
+
+ val snapshot = Some(entriesInRange(snapshotId, snapshotId).head)
+
+ val entries =
+ if ((cursor - lastIndex) == 0) Vector.empty[Array[Byte]]
+ else entriesInRange(cursor, lastIndex)
+
+ (snapshot, entries)
+
+ case None ⇒
+ (None, entries)
+ }
}
/**
- * TODO document method
+ * Get a range of entries from 'from' to 'to' for this transaction log.
*/
def entriesInRange(from: Long, to: Long): Vector[Array[Byte]] = if (isOpen.isOn) {
try {
@@ -180,8 +188,10 @@ class TransactionLog private (
ledgerHandle: LedgerHandle,
enumeration: Enumeration[LedgerEntry],
ctx: AnyRef) {
+
val future = ctx.asInstanceOf[Promise[Vector[Array[Byte]]]]
val entries = toByteArrays(enumeration)
+
if (returnCode == BKException.Code.OK) future.completeWithResult(entries)
else future.completeWithException(BKException.create(returnCode))
}
@@ -197,29 +207,27 @@ class TransactionLog private (
} else transactionClosedError
/**
- * TODO document method
+ * Get the last entry written to this transaction log.
*/
def latestEntryId: Long = ledger.getLastAddConfirmed
/**
- * TODO document method
+ * Get the id for the last snapshot written to this transaction log.
*/
- def latestSnapshotId: Long = {
+ def latestSnapshotId: Option[Long] = {
try {
val snapshotId = zkClient.readData(snapshotPath).asInstanceOf[Long]
EventHandler.debug(this,
"Retrieved latest snapshot id [%s] from transaction log [%s]".format(snapshotId, logId))
- snapshotId
+ Some(snapshotId)
} catch {
- case e: ZkNoNodeException ⇒
- handleError(new ReplicationException(
- "Transaction log for UUID [" + id + "] does not have a snapshot recorded in ZooKeeper"))
- case e ⇒ handleError(e)
+ case e: ZkNoNodeException ⇒ None
+ case e ⇒ handleError(e)
}
}
/**
- * TODO document method
+ * Delete all entries for this transaction log.
*/
def delete() {
if (isOpen.isOn) {
@@ -244,7 +252,7 @@ class TransactionLog private (
}
/**
- * TODO document method
+ * Close this transaction log.
*/
def close() {
if (isOpen.switchOff) {
@@ -344,7 +352,7 @@ object TransactionLog {
Cluster.zooKeeperServers,
Cluster.sessionTimeout,
Cluster.connectionTimeout,
- Cluster.defaultSerializer)
+ Cluster.defaultZooKeeperSerializer)
try {
zk.create(transactionLogNode, null, CreateMode.PERSISTENT)
@@ -371,9 +379,8 @@ object TransactionLog {
ledger: LedgerHandle,
id: String,
isAsync: Boolean,
- replicationScheme: ReplicationScheme,
- format: Serializer) =
- new TransactionLog(ledger, id, isAsync, replicationScheme, format)
+ replicationScheme: ReplicationScheme) =
+ new TransactionLog(ledger, id, isAsync, replicationScheme)
/**
* Shuts down the transaction log.
@@ -392,13 +399,12 @@ object TransactionLog {
}
/**
- * TODO document method
+ * Creates a new transaction log for the 'id' specified.
*/
def newLogFor(
id: String,
isAsync: Boolean,
- replicationScheme: ReplicationScheme,
- format: Serializer): TransactionLog = {
+ replicationScheme: ReplicationScheme): TransactionLog = {
val txLogPath = transactionLogNode + "/" + id
@@ -443,17 +449,16 @@ object TransactionLog {
}
EventHandler.info(this, "Created new transaction log [%s] for UUID [%s]".format(logId, id))
- TransactionLog(ledger, id, isAsync, replicationScheme, format)
+ TransactionLog(ledger, id, isAsync, replicationScheme)
}
/**
- * TODO document method
+ * Fetches an existing transaction log for the 'id' specified.
*/
def logFor(
id: String,
isAsync: Boolean,
- replicationScheme: ReplicationScheme,
- format: Serializer): TransactionLog = {
+ replicationScheme: ReplicationScheme): TransactionLog = {
val txLogPath = transactionLogNode + "/" + id
@@ -493,7 +498,7 @@ object TransactionLog {
case e ⇒ handleError(e)
}
- TransactionLog(ledger, id, isAsync, replicationScheme, format)
+ TransactionLog(ledger, id, isAsync, replicationScheme)
}
private[akka] def await[T](future: Promise[T]): T = {
diff --git a/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala b/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala
new file mode 100755
index 0000000000..5718d41fe5
--- /dev/null
+++ b/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala
@@ -0,0 +1,358 @@
+package akka.cluster.storage
+
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+import akka.cluster.zookeeper.AkkaZkClient
+import akka.AkkaException
+import org.apache.zookeeper.{ KeeperException, CreateMode }
+import org.apache.zookeeper.data.Stat
+import java.util.concurrent.ConcurrentHashMap
+import annotation.tailrec
+import java.lang.{ RuntimeException, UnsupportedOperationException }
+
+/**
+ * Simple abstraction to store an Array of bytes based on some String key.
+ *
+ * Nothing is being said about ACID, transactions etc. It depends on the implementation
+ * of this Storage interface of what is and isn't done on the lowest level.
+ *
+ * The amount of data that is allowed to be insert/updated is implementation specific. The InMemoryStorage
+ * has no limits, but the ZooKeeperStorage has a maximum size of 1 mb.
+ *
+ * TODO: Class is up for better names.
+ * TODO: Instead of a String as key, perhaps also a byte-array.
+ */
+trait Storage {
+
+ /**
+ * Loads the VersionedData for the given key.
+ *
+ * This call doesn't care about the actual version of the data.
+ *
+ * @param key: the key of the VersionedData to load.
+ * @return the VersionedData for the given entry.
+ * @throws MissingDataException if the entry with the given key doesn't exist.
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def load(key: String): VersionedData
+
+ /**
+ * Loads the VersionedData for the given key and expectedVersion.
+ *
+ * This call can be used for optimistic locking since the version is included.
+ *
+ * @param key: the key of the VersionedData to load
+ * @param expectedVersion the version the data to load should have.
+ * @throws MissingDataException if the data with the given key doesn't exist.
+ * @throws BadVersionException if the version is not the expected version.
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def load(key: String, expectedVersion: Long): VersionedData
+
+ /**
+ * Checks if a VersionedData with the given key exists.
+ *
+ * @param key the key to check the existence for.
+ * @return true if exists, false if not.
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def exists(key: String): Boolean
+
+ /**
+ * Inserts a byte-array based on some key.
+ *
+ * @param key the key of the Data to insert.
+ * @param bytes the data to insert.
+ * @return the version of the written data (can be used for optimistic locking).
+ * @throws DataExistsException when VersionedData with the given Key already exists.
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def insert(key: String, bytes: Array[Byte]): Long
+
+ /**
+ * Inserts the data if there is no data for that key, or overwrites it if it is there.
+ *
+ * This is the method you want to call if you just want to save something and don't
+ * care about any lost update issues.
+ *
+ * @param key the key of the data
+ * @param bytes the data to insert
+ * @return the version of the written data (can be used for optimistic locking).
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def insertOrOverwrite(key: String, bytes: Array[Byte]): Long
+
+ /**
+ * Overwrites the current data for the given key. This call doesn't care about the version of the existing data.
+ *
+ * @param key the key of the data to overwrite
+ * @param bytes the data to insert.
+ * @return the version of the written data (can be used for optimistic locking).
+ * @throws MissingDataException when the entry with the given key doesn't exist.
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def overwrite(key: String, bytes: Array[Byte]): Long
+
+ /**
+ * Updates an existing value using an optimistic lock. So it expect the current data to have the expectedVersion
+ * and only then, it will do the update.
+ *
+ * @param key the key of the data to update
+ * @param bytes the content to write for the given key
+ * @param expectedVersion the version of the content that is expected to be there.
+ * @return the version of the written data (can be used for optimistic locking).
+ * @throws MissingDataException if no data for the given key exists
+ * @throws BadVersionException if the version if the found data doesn't match the expected version. So essentially
+ * if another update was already done.
+ * @throws StorageException if anything goes wrong while accessing the storage
+ */
+ def update(key: String, bytes: Array[Byte], expectedVersion: Long): Long
+}
+
+/**
+ * The VersionedData is a container of data (some bytes) and a version (a Long).
+ */
+class VersionedData(val data: Array[Byte], val version: Long) {}
+
+/**
+ * An AkkaException thrown by the Storage module.
+ */
+class StorageException(msg: String = null, cause: java.lang.Throwable = null) extends AkkaException(msg, cause)
+
+/**
+ * *
+ * A StorageException thrown when an operation is done on a non existing node.
+ */
+class MissingDataException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause)
+
+/**
+ * A StorageException thrown when an operation is done on an existing node, but no node was expected.
+ */
+class DataExistsException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause)
+
+/**
+ * A StorageException thrown when an operation causes an optimistic locking failure.
+ */
+class BadVersionException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause)
+
+/**
+ * A Storage implementation based on ZooKeeper.
+ *
+ * The store method is atomic:
+ * - so everything is written or nothing is written
+ * - is isolated, so threadsafe,
+ * but it will not participate in any transactions.
+ *
+ */
+class ZooKeeperStorage(zkClient: AkkaZkClient, root: String = "/peter/storage") extends Storage {
+
+ var path = ""
+
+ //makes sure that the complete root exists on zookeeper.
+ root.split("/").foreach(
+ item ⇒ if (item.size > 0) {
+
+ path = path + "/" + item
+
+ if (!zkClient.exists(path)) {
+ //it could be that another thread is going to create this root node as well, so ignore it when it happens.
+ try {
+ zkClient.create(path, "".getBytes, CreateMode.PERSISTENT)
+ } catch {
+ case ignore: KeeperException.NodeExistsException ⇒
+ }
+ }
+ })
+
+ def toZkPath(key: String): String = {
+ root + "/" + key
+ }
+
+ def load(key: String) = try {
+ val stat = new Stat
+ val arrayOfBytes = zkClient.connection.readData(root + "/" + key, stat, false)
+ new VersionedData(arrayOfBytes, stat.getVersion)
+ } catch {
+ case e: KeeperException.NoNodeException ⇒ throw new MissingDataException(
+ String.format("Failed to load key [%s]: no data was found", key), e)
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to load key [%s]", key), e)
+ }
+
+ def load(key: String, expectedVersion: Long) = try {
+ val stat = new Stat
+ val arrayOfBytes = zkClient.connection.readData(root + "/" + key, stat, false)
+
+ if (stat.getVersion != expectedVersion) throw new BadVersionException(
+ "Failed to update key [" + key + "]: version mismatch, expected [" + expectedVersion + "]" +
+ " but found [" + stat.getVersion + "]")
+
+ new VersionedData(arrayOfBytes, stat.getVersion)
+ } catch {
+ case e: KeeperException.NoNodeException ⇒ throw new MissingDataException(
+ String.format("Failed to load key [%s]: no data was found", key), e)
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to load key [%s]", key), e)
+ }
+
+ def insertOrOverwrite(key: String, bytes: Array[Byte]) = {
+ try {
+ throw new UnsupportedOperationException()
+ } catch {
+ case e: KeeperException.NodeExistsException ⇒ throw new DataExistsException(
+ String.format("Failed to insert key [%s]: an entry already exists with the same key", key), e)
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to insert key [%s]", key), e)
+ }
+ }
+
+ def insert(key: String, bytes: Array[Byte]): Long = {
+ try {
+ zkClient.connection.create(root + "/" + key, bytes, CreateMode.PERSISTENT)
+ //todo: how to get hold of the version.
+ val version: Long = 0
+ version
+ } catch {
+ case e: KeeperException.NodeExistsException ⇒ throw new DataExistsException(
+ String.format("Failed to insert key [%s]: an entry already exists with the same key", key), e)
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to insert key [%s]", key), e)
+ }
+ }
+
+ def exists(key: String) = try {
+ zkClient.connection.exists(toZkPath(key), false)
+ } catch {
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to check existance for key [%s]", key), e)
+ }
+
+ def update(key: String, bytes: Array[Byte], expectedVersion: Long): Long = {
+ try {
+ zkClient.connection.writeData(root + "/" + key, bytes, expectedVersion.asInstanceOf[Int])
+ throw new RuntimeException()
+ } catch {
+ case e: KeeperException.BadVersionException ⇒ throw new BadVersionException(
+ String.format("Failed to update key [%s]: version mismatch", key), e)
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to update key [%s]", key), e)
+ }
+ }
+
+ def overwrite(key: String, bytes: Array[Byte]): Long = {
+ try {
+ zkClient.connection.writeData(root + "/" + key, bytes)
+ -1L
+ } catch {
+ case e: KeeperException.NoNodeException ⇒ throw new MissingDataException(
+ String.format("Failed to overwrite key [%s]: a previous entry already exists", key), e)
+ case e: KeeperException ⇒ throw new StorageException(
+ String.format("Failed to overwrite key [%s]", key), e)
+ }
+ }
+}
+
+object InMemoryStorage {
+ val InitialVersion = 0;
+}
+
+/**
+ * An in memory {@link RawStore} implementation. Useful for testing purposes.
+ */
+final class InMemoryStorage extends Storage {
+
+ private val map = new ConcurrentHashMap[String, VersionedData]()
+
+ def load(key: String) = {
+ val result = map.get(key)
+
+ if (result == null) throw new MissingDataException(
+ String.format("Failed to load key [%s]: no data was found", key))
+
+ result
+ }
+
+ def load(key: String, expectedVersion: Long) = {
+ val result = load(key)
+
+ if (result.version != expectedVersion) throw new BadVersionException(
+ "Failed to load key [" + key + "]: version mismatch, expected [" + result.version + "] " +
+ "but found [" + expectedVersion + "]")
+
+ result
+ }
+
+ def exists(key: String) = map.containsKey(key)
+
+ def insert(key: String, bytes: Array[Byte]): Long = {
+ val version: Long = InMemoryStorage.InitialVersion
+ val result = new VersionedData(bytes, version)
+
+ val previous = map.putIfAbsent(key, result)
+ if (previous != null) throw new DataExistsException(
+ String.format("Failed to insert key [%s]: the key already has been inserted previously", key))
+
+ version
+ }
+
+ @tailrec
+ def update(key: String, bytes: Array[Byte], expectedVersion: Long): Long = {
+ val found = map.get(key)
+
+ if (found == null) throw new MissingDataException(
+ String.format("Failed to update key [%s], no previous entry exist", key))
+
+ if (expectedVersion != found.version) throw new BadVersionException(
+ "Failed to update key [" + key + "]: version mismatch, expected [" + expectedVersion + "]" +
+ " but found [" + found.version + "]")
+
+ val newVersion: Long = expectedVersion + 1
+
+ if (map.replace(key, found, new VersionedData(bytes, newVersion))) newVersion
+ else update(key, bytes, expectedVersion)
+ }
+
+ @tailrec
+ def overwrite(key: String, bytes: Array[Byte]): Long = {
+ val current = map.get(key)
+
+ if (current == null) throw new MissingDataException(
+ String.format("Failed to overwrite key [%s], no previous entry exist", key))
+
+ val update = new VersionedData(bytes, current.version + 1)
+
+ if (map.replace(key, current, update)) update.version
+ else overwrite(key, bytes)
+ }
+
+ def insertOrOverwrite(key: String, bytes: Array[Byte]): Long = {
+ val version = InMemoryStorage.InitialVersion
+ val result = new VersionedData(bytes, version)
+
+ val previous = map.putIfAbsent(key, result)
+
+ if (previous == null) result.version
+ else overwrite(key, bytes)
+ }
+}
+
+//TODO: To minimize the number of dependencies, should the Storage not be placed in a seperate module?
+//class VoldemortRawStorage(storeClient: StoreClient) extends Storage {
+//
+// def load(Key: String) = {
+// try {
+//
+// } catch {
+// case
+// }
+// }
+//
+// override def insert(key: String, bytes: Array[Byte]) {
+// throw new UnsupportedOperationException()
+// }
+//
+// def update(key: String, bytes: Array[Byte]) {
+// throw new UnsupportedOperationException()
+// }
+//}
\ No newline at end of file
diff --git a/akka-cluster/src/main/scala/akka/cluster/untitled b/akka-cluster/src/main/scala/akka/cluster/untitled
new file mode 100644
index 0000000000..ec128ad190
--- /dev/null
+++ b/akka-cluster/src/main/scala/akka/cluster/untitled
@@ -0,0 +1,41 @@
+
+diff --git a/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala b/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala
+index b7183ca..c267bc6 100644
+--- a/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala
++++ b/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala
+@@ -107,7 +107,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
+
+ val txlog2 = TransactionLog.logFor(uuid, false, null)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
++ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
+
+ val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
+ entries.size must equal(4)
+@@ -136,7 +136,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
+
+ val txlog2 = TransactionLog.logFor(uuid, false, null)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
++ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
+
+ val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
+ entries.size must equal(2)
+@@ -251,7 +251,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
+ Thread.sleep(200)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+ Thread.sleep(200)
+- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
++ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
+
+ val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
+ Thread.sleep(200)
+@@ -290,7 +290,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
+ Thread.sleep(200)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+ Thread.sleep(200)
+- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
++ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
+ val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
+ Thread.sleep(200)
+ entries.size must equal(2)
\ No newline at end of file
diff --git a/akka-cluster/src/main/scala/akka/cluster/zookeeper/AkkaZkClient.scala b/akka-cluster/src/main/scala/akka/cluster/zookeeper/AkkaZkClient.scala
index c168de4022..42df10ee63 100644
--- a/akka-cluster/src/main/scala/akka/cluster/zookeeper/AkkaZkClient.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/zookeeper/AkkaZkClient.scala
@@ -7,8 +7,6 @@ import org.I0Itec.zkclient._
import org.I0Itec.zkclient.serialize._
import org.I0Itec.zkclient.exception._
-//import akka.event.EventHandler
-
/**
* ZooKeeper client. Holds the ZooKeeper connection and manages its session.
*/
@@ -17,7 +15,6 @@ class AkkaZkClient(zkServers: String,
connectionTimeout: Int,
zkSerializer: ZkSerializer = new SerializableSerializer)
extends ZkClient(zkServers, sessionTimeout, connectionTimeout, zkSerializer) {
- // EventHandler.debug(this, "Connecting to ZooKeeper ensamble [%s]" format zkServers)
def connection: ZkConnection = _connection.asInstanceOf[ZkConnection]
diff --git a/akka-cluster/src/main/scala/akka/cluster/zookeeper/ZooKeeperBarrier.scala b/akka-cluster/src/main/scala/akka/cluster/zookeeper/ZooKeeperBarrier.scala
index e6e44c6520..3128d525b0 100644
--- a/akka-cluster/src/main/scala/akka/cluster/zookeeper/ZooKeeperBarrier.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/zookeeper/ZooKeeperBarrier.scala
@@ -65,6 +65,14 @@ class ZooKeeperBarrier(zkClient: ZkClient, name: String, node: String, count: In
leave()
}
+ /**
+ * An await does a enter/leave making this barrier a 'single' barrier instead of a double barrier.
+ */
+ def await() {
+ enter
+ leave()
+ }
+
def enter = {
zkClient.createEphemeral(entry)
if (zkClient.countChildren(barrier) >= count)
diff --git a/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
index e7eb7d6b95..4a2d84a80a 100644
--- a/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
+++ b/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
@@ -12,7 +12,6 @@ import akka.serialization.RemoteActorSerialization._
import akka.remoteinterface._
import akka.actor.{
PoisonPill,
- Index,
LocalActorRef,
Actor,
RemoteActorRef,
@@ -21,7 +20,7 @@ import akka.actor.{
RemoteActorSystemMessage,
uuidFrom,
Uuid,
- Exit,
+ Death,
LifeCycleMessage
}
import akka.actor.Actor._
@@ -209,7 +208,7 @@ abstract class RemoteClient private[akka] (
senderFuture: Option[Promise[T]]): Option[Promise[T]] = {
if (isRunning) {
- EventHandler.debug(this, "Sending to connection [%s] message [%s]".format(remoteAddress, request))
+ EventHandler.debug(this, "Sending to connection [%s] message [\n%s]".format(remoteAddress, request))
if (request.getOneWay) {
try {
@@ -576,18 +575,25 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with
}
class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, val port: Int, val loader: Option[ClassLoader]) {
-
+ import RemoteServerSettings._
val name = "NettyRemoteServer@" + host + ":" + port
val address = new InetSocketAddress(host, port)
private val factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)
private val bootstrap = new ServerBootstrap(factory)
+ private val executor = new ExecutionHandler(
+ new OrderedMemoryAwareThreadPoolExecutor(
+ EXECUTION_POOL_SIZE,
+ MAX_CHANNEL_MEMORY_SIZE,
+ MAX_TOTAL_MEMORY_SIZE,
+ EXECUTION_POOL_KEEPALIVE.length,
+ EXECUTION_POOL_KEEPALIVE.unit))
// group of open channels, used for clean-up
private val openChannels: ChannelGroup = new DefaultDisposableChannelGroup("akka-remote-server")
- val pipelineFactory = new RemoteServerPipelineFactory(name, openChannels, loader, serverModule)
+ val pipelineFactory = new RemoteServerPipelineFactory(name, openChannels, executor, loader, serverModule)
bootstrap.setPipelineFactory(pipelineFactory)
bootstrap.setOption("backlog", RemoteServerSettings.BACKLOG)
bootstrap.setOption("child.tcpNoDelay", true)
@@ -611,6 +617,7 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String,
openChannels.disconnect
openChannels.close.awaitUninterruptibly
bootstrap.releaseExternalResources()
+ executor.releaseExternalResources()
serverModule.notifyListeners(RemoteServerShutdown(serverModule))
} catch {
case e: Exception ⇒
@@ -740,6 +747,7 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule
class RemoteServerPipelineFactory(
val name: String,
val openChannels: ChannelGroup,
+ val executor: ExecutionHandler,
val loader: Option[ClassLoader],
val server: NettyRemoteServerModule) extends ChannelPipelineFactory {
import RemoteServerSettings._
@@ -753,16 +761,9 @@ class RemoteServerPipelineFactory(
case "zlib" ⇒ (new ZlibEncoder(ZLIB_COMPRESSION_LEVEL) :: Nil, new ZlibDecoder :: Nil)
case _ ⇒ (Nil, Nil)
}
- val execution = new ExecutionHandler(
- new OrderedMemoryAwareThreadPoolExecutor(
- EXECUTION_POOL_SIZE,
- MAX_CHANNEL_MEMORY_SIZE,
- MAX_TOTAL_MEMORY_SIZE,
- EXECUTION_POOL_KEEPALIVE.length,
- EXECUTION_POOL_KEEPALIVE.unit))
val authenticator = if (REQUIRE_COOKIE) new RemoteServerAuthenticationHandler(SECURE_COOKIE) :: Nil else Nil
val remoteServer = new RemoteServerHandler(name, openChannels, loader, server)
- val stages: List[ChannelHandler] = dec ::: lenDec :: protobufDec :: enc ::: lenPrep :: protobufEnc :: execution :: authenticator ::: remoteServer :: Nil
+ val stages: List[ChannelHandler] = dec ::: lenDec :: protobufDec :: enc ::: lenPrep :: protobufEnc :: executor :: authenticator ::: remoteServer :: Nil
new StaticChannelPipeline(stages: _*)
}
}
@@ -950,7 +951,7 @@ class RemoteServerHandler(
val address = actorInfo.getAddress
EventHandler.debug(this,
- "Creating an remotely available actor for address [%s] on node [%s]"
+ "Looking up a remotely available actor for address [%s] on node [%s]"
.format(address, Config.nodename))
val actorRef = Actor.createActor(address, () ⇒ createSessionActor(actorInfo, channel))
diff --git a/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala
index c481ac899e..cd64a83067 100644
--- a/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala
+++ b/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala
@@ -20,6 +20,8 @@ import java.net.InetSocketAddress
import com.google.protobuf.ByteString
+import com.eaio.uuid.UUID
+
/**
* Module for local actor serialization.
*/
@@ -27,10 +29,13 @@ object ActorSerialization {
implicit val defaultSerializer = akka.serialization.JavaSerializer // Format.Default
def fromBinary[T <: Actor](bytes: Array[Byte], homeAddress: InetSocketAddress): ActorRef =
- fromBinaryToLocalActorRef(bytes, Some(homeAddress))
+ fromBinaryToLocalActorRef(bytes, None, Some(homeAddress))
+
+ def fromBinary[T <: Actor](bytes: Array[Byte], uuid: UUID): ActorRef =
+ fromBinaryToLocalActorRef(bytes, Some(uuid), None)
def fromBinary[T <: Actor](bytes: Array[Byte]): ActorRef =
- fromBinaryToLocalActorRef(bytes, None)
+ fromBinaryToLocalActorRef(bytes, None, None)
def toBinary[T <: Actor](
a: ActorRef,
@@ -126,13 +131,16 @@ object ActorSerialization {
private def fromBinaryToLocalActorRef[T <: Actor](
bytes: Array[Byte],
+ uuid: Option[UUID],
homeAddress: Option[InetSocketAddress]): ActorRef = {
val builder = SerializedActorRefProtocol.newBuilder.mergeFrom(bytes)
- fromProtobufToLocalActorRef(builder.build, None)
+ fromProtobufToLocalActorRef(builder.build, uuid, None)
}
private[akka] def fromProtobufToLocalActorRef[T <: Actor](
- protocol: SerializedActorRefProtocol, loader: Option[ClassLoader]): ActorRef = {
+ protocol: SerializedActorRefProtocol,
+ overriddenUuid: Option[UUID],
+ loader: Option[ClassLoader]): ActorRef = {
val lifeCycle =
if (protocol.hasLifeCycle) {
@@ -147,28 +155,27 @@ object ActorSerialization {
if (protocol.hasSupervisor) Some(RemoteActorSerialization.fromProtobufToRemoteActorRef(protocol.getSupervisor, loader))
else None
- import ReplicationStorageType._
- import ReplicationStrategyType._
-
- val replicationScheme =
- if (protocol.hasReplicationStorage) {
- protocol.getReplicationStorage match {
- case TRANSIENT ⇒ Transient
- case store ⇒
- val storage = store match {
- case TRANSACTION_LOG ⇒ TransactionLog
- case DATA_GRID ⇒ DataGrid
- }
- val strategy = if (protocol.hasReplicationStrategy) {
- protocol.getReplicationStrategy match {
- case WRITE_THROUGH ⇒ WriteThrough
- case WRITE_BEHIND ⇒ WriteBehind
- }
- } else throw new IllegalActorStateException(
- "Expected replication strategy for replication storage [" + storage + "]")
- Replication(storage, strategy)
- }
- } else Transient
+ // import ReplicationStorageType._
+ // import ReplicationStrategyType._
+ // val replicationScheme =
+ // if (protocol.hasReplicationStorage) {
+ // protocol.getReplicationStorage match {
+ // case TRANSIENT ⇒ Transient
+ // case store ⇒
+ // val storage = store match {
+ // case TRANSACTION_LOG ⇒ TransactionLog
+ // case DATA_GRID ⇒ DataGrid
+ // }
+ // val strategy = if (protocol.hasReplicationStrategy) {
+ // protocol.getReplicationStrategy match {
+ // case WRITE_THROUGH ⇒ WriteThrough
+ // case WRITE_BEHIND ⇒ WriteBehind
+ // }
+ // } else throw new IllegalActorStateException(
+ // "Expected replication strategy for replication storage [" + storage + "]")
+ // Replication(storage, strategy)
+ // }
+ // } else Transient
val hotswap =
try {
@@ -197,16 +204,20 @@ object ActorSerialization {
}
}
+ val actorUuid = overriddenUuid match {
+ case Some(uuid) ⇒ uuid
+ case None ⇒ uuidFrom(protocol.getUuid.getHigh, protocol.getUuid.getLow)
+ }
+
val ar = new LocalActorRef(
- uuidFrom(protocol.getUuid.getHigh, protocol.getUuid.getLow),
+ actorUuid,
protocol.getAddress,
if (protocol.hasTimeout) protocol.getTimeout else Actor.TIMEOUT,
if (protocol.hasReceiveTimeout) Some(protocol.getReceiveTimeout) else None,
lifeCycle,
supervisor,
hotswap,
- factory,
- replicationScheme)
+ factory)
val messages = protocol.getMessagesList.toArray.toList.asInstanceOf[List[RemoteMessageProtocol]]
messages.foreach(message ⇒ ar ! MessageSerializer.deserialize(message.getMessage, Some(classLoader)))
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/ClusterTestNode.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/ClusterTestNode.scala
new file mode 100644
index 0000000000..a8b6489ca1
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/ClusterTestNode.scala
@@ -0,0 +1,140 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster
+
+import org.scalatest.WordSpec
+import org.scalatest.matchers.MustMatchers
+import org.scalatest.BeforeAndAfterAll
+
+import akka.util.duration._
+import akka.util.Duration
+import System.{ currentTimeMillis ⇒ now }
+
+import java.io.File
+
+trait MasterClusterTestNode extends WordSpec with MustMatchers with BeforeAndAfterAll {
+ def testNodes: Int
+
+ override def beforeAll() = {
+ Cluster.startLocalCluster()
+ onReady()
+ ClusterTestNode.ready(getClass.getName)
+ }
+
+ def onReady() = {}
+
+ override def afterAll() = {
+ ClusterTestNode.waitForExits(getClass.getName, testNodes - 1)
+ ClusterTestNode.cleanUp(getClass.getName)
+ onShutdown()
+ Cluster.shutdownLocalCluster()
+ }
+
+ def onShutdown() = {}
+}
+
+trait ClusterTestNode extends WordSpec with MustMatchers with BeforeAndAfterAll {
+ override def beforeAll() = {
+ ClusterTestNode.waitForReady(getClass.getName)
+ }
+
+ override def afterAll() = {
+ ClusterTestNode.exit(getClass.getName)
+ }
+}
+
+object ClusterTestNode {
+ val TestMarker = "MultiJvm"
+ val HomeDir = "_akka_cluster"
+ val TestDir = "multi-jvm"
+ val Sleep = 100.millis
+ val Timeout = 1.minute
+
+ def ready(className: String) = {
+ println("ClusterTest: READY")
+ readyFile(className).createNewFile()
+ }
+
+ def waitForReady(className: String) = {
+ if (!waitExists(readyFile(className))) {
+ cleanUp(className)
+ sys.error("Timeout waiting for cluster ready")
+ }
+ println("ClusterTest: GO")
+ }
+
+ def exit(className: String) = {
+ println("ClusterTest: EXIT")
+ exitFile(className).createNewFile()
+ }
+
+ def waitForExits(className: String, nodes: Int) = {
+ if (!waitCount(exitDir(className), nodes)) {
+ cleanUp(className)
+ sys.error("Timeout waiting for node exits")
+ }
+ println("ClusterTest: SHUTDOWN")
+ }
+
+ def cleanUp(className: String) = {
+ deleteRecursive(testDir(className))
+ }
+
+ def testName(name: String) = {
+ val i = name.indexOf(TestMarker)
+ if (i >= 0) name.substring(0, i) else name
+ }
+
+ def nodeName(name: String) = {
+ val i = name.indexOf(TestMarker)
+ if (i >= 0) name.substring(i + TestMarker.length) else name
+ }
+
+ def testDir(className: String) = {
+ val home = new File(HomeDir)
+ val tests = new File(home, TestDir)
+ val dir = new File(tests, testName(className))
+ dir.mkdirs()
+ dir
+ }
+
+ def readyFile(className: String) = {
+ new File(testDir(className), "ready")
+ }
+
+ def exitDir(className: String) = {
+ val dir = new File(testDir(className), "exit")
+ dir.mkdirs()
+ dir
+ }
+
+ def exitFile(className: String) = {
+ new File(exitDir(className), nodeName(className))
+ }
+
+ def waitExists(file: File) = waitFor(file.exists)
+
+ def waitCount(file: File, n: Int) = waitFor(file.list.size >= n)
+
+ def waitFor(test: ⇒ Boolean, sleep: Duration = Sleep, timeout: Duration = Timeout): Boolean = {
+ val start = now
+ val limit = start + timeout.toMillis
+ var passed = test
+ var expired = false
+ while (!passed && !expired) {
+ if (now > limit) expired = true
+ else {
+ Thread.sleep(sleep.toMillis)
+ passed = test
+ }
+ }
+ passed
+ }
+
+ def deleteRecursive(file: File): Boolean = {
+ if (file.isDirectory) file.listFiles.foreach(deleteRecursive)
+ file.delete()
+ }
+}
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmSpec.scala
similarity index 80%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmSpec.scala
index eba50ab6a2..d296926653 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/newleader/NewLeaderChangeListenerMultiJvmSpec.scala
@@ -18,9 +18,11 @@ object NewLeaderChangeListenerMultiJvmSpec {
var NrOfNodes = 2
}
-class NewLeaderChangeListenerMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class NewLeaderChangeListenerMultiJvmNode1 extends MasterClusterTestNode {
import NewLeaderChangeListenerMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A NewLeader change listener" must {
"be invoked after leader election is completed" in {
@@ -43,17 +45,9 @@ class NewLeaderChangeListenerMultiJvmNode1 extends WordSpec with MustMatchers wi
node.shutdown()
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
}
-class NewLeaderChangeListenerMultiJvmNode2 extends WordSpec with MustMatchers {
+class NewLeaderChangeListenerMultiJvmNode2 extends ClusterTestNode {
import NewLeaderChangeListenerMultiJvmSpec._
"A NewLeader change listener" must {
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmSpec.scala
similarity index 80%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmSpec.scala
index d8bd90b8fd..f77f14b568 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodeconnected/NodeConnectedChangeListenerMultiJvmSpec.scala
@@ -18,9 +18,11 @@ object NodeConnectedChangeListenerMultiJvmSpec {
var NrOfNodes = 2
}
-class NodeConnectedChangeListenerMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class NodeConnectedChangeListenerMultiJvmNode1 extends MasterClusterTestNode {
import NodeConnectedChangeListenerMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A NodeConnected change listener" must {
"be invoked when a new node joins the cluster" in {
@@ -42,17 +44,9 @@ class NodeConnectedChangeListenerMultiJvmNode1 extends WordSpec with MustMatcher
node.shutdown()
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
}
-class NodeConnectedChangeListenerMultiJvmNode2 extends WordSpec with MustMatchers {
+class NodeConnectedChangeListenerMultiJvmNode2 extends ClusterTestNode {
import NodeConnectedChangeListenerMultiJvmSpec._
"A NodeConnected change listener" must {
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmSpec.scala
similarity index 79%
rename from akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmSpec.scala
index 30ca68946d..b59c95de8d 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/changelisteners/nodedisconnected/NodeDisconnectedChangeListenerMultiJvmSpec.scala
@@ -18,9 +18,11 @@ object NodeDisconnectedChangeListenerMultiJvmSpec {
var NrOfNodes = 2
}
-class NodeDisconnectedChangeListenerMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class NodeDisconnectedChangeListenerMultiJvmNode1 extends MasterClusterTestNode {
import NodeDisconnectedChangeListenerMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A NodeDisconnected change listener" must {
"be invoked when a new node leaves the cluster" in {
@@ -39,21 +41,13 @@ class NodeDisconnectedChangeListenerMultiJvmNode1 extends WordSpec with MustMatc
}
latch.await(10, TimeUnit.SECONDS) must be === true
+
+ node.shutdown()
}
-
- node.shutdown()
- }
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
}
}
-class NodeDisconnectedChangeListenerMultiJvmNode2 extends WordSpec with MustMatchers {
+class NodeDisconnectedChangeListenerMultiJvmNode2 extends ClusterTestNode {
import NodeDisconnectedChangeListenerMultiJvmSpec._
"A NodeDisconnected change listener" must {
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmSpec.scala
similarity index 86%
rename from akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmSpec.scala
index 2352bd942c..1a32184054 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/configuration/ConfigurationStorageMultiJvmSpec.scala
@@ -15,9 +15,11 @@ object ConfigurationStorageMultiJvmSpec {
var NrOfNodes = 2
}
-class ConfigurationStorageMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class ConfigurationStorageMultiJvmNode1 extends MasterClusterTestNode {
import ConfigurationStorageMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A cluster" must {
"be able to store, read and remove custom configuration data" in {
@@ -50,17 +52,9 @@ class ConfigurationStorageMultiJvmNode1 extends WordSpec with MustMatchers with
node.shutdown()
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
}
-class ConfigurationStorageMultiJvmNode2 extends WordSpec with MustMatchers {
+class ConfigurationStorageMultiJvmNode2 extends ClusterTestNode {
import ConfigurationStorageMultiJvmSpec._
"A cluster" must {
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmSpec.scala
similarity index 82%
rename from akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmSpec.scala
index b87dcd07ac..493fe57d6e 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/leader/election/LeaderElectionMultiJvmSpec.scala
@@ -18,9 +18,11 @@ object LeaderElectionMultiJvmSpec {
var NrOfNodes = 2
}
/*
-class LeaderElectionMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class LeaderElectionMultiJvmNode1 extends MasterClusterTestNode {
import LeaderElectionMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A cluster" must {
"be able to elect a single leader in the cluster and perform re-election if leader resigns" in {
@@ -39,17 +41,9 @@ class LeaderElectionMultiJvmNode1 extends WordSpec with MustMatchers with Before
}
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
}
-class LeaderElectionMultiJvmNode2 extends WordSpec with MustMatchers {
+class LeaderElectionMultiJvmNode2 extends ClusterTestNode {
import LeaderElectionMultiJvmSpec._
"A cluster" must {
@@ -73,4 +67,4 @@ class LeaderElectionMultiJvmNode2 extends WordSpec with MustMatchers {
}
}
}
-*/
\ No newline at end of file
+*/
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala
similarity index 69%
rename from akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala
index a0d46ad000..792924f338 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala
@@ -40,9 +40,11 @@ object RegistryStoreMultiJvmSpec {
}
}
-class RegistryStoreMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class RegistryStoreMultiJvmNode1 extends MasterClusterTestNode {
import RegistryStoreMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A cluster" must {
"be able to store an ActorRef in the cluster without a replication strategy and retrieve it with 'use'" in {
@@ -56,7 +58,7 @@ class RegistryStoreMultiJvmNode1 extends WordSpec with MustMatchers with BeforeA
barrier("store-1-in-node-1", NrOfNodes) {
val serializer = Serialization.serializerFor(classOf[HelloWorld1]).fold(x ⇒ fail("No serializer found"), s ⇒ s)
- node.store(actorOf[HelloWorld1]("hello-world-1"), serializer)
+ node.store("hello-world-1", classOf[HelloWorld1], serializer)
}
barrier("use-1-in-node-2", NrOfNodes) {
@@ -70,34 +72,12 @@ class RegistryStoreMultiJvmNode1 extends WordSpec with MustMatchers with BeforeA
barrier("use-2-in-node-2", NrOfNodes) {
}
- barrier("store-3-in-node-1", NrOfNodes) {
- val serializer = Serialization.serializerFor(classOf[HelloWorld2]).fold(x ⇒ fail("No serializer found"), s ⇒ s)
- val actor = actorOf[HelloWorld2]("hello-world-3").start
- actor ! "Hello"
- actor ! "Hello"
- actor ! "Hello"
- actor ! "Hello"
- actor ! "Hello"
- node.store(actor, true, serializer)
- }
-
- barrier("use-3-in-node-2", NrOfNodes) {
- }
-
node.shutdown()
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
}
-class RegistryStoreMultiJvmNode2 extends WordSpec with MustMatchers {
+class RegistryStoreMultiJvmNode2 extends ClusterTestNode {
import RegistryStoreMultiJvmSpec._
"A cluster" must {
@@ -137,19 +117,6 @@ class RegistryStoreMultiJvmNode2 extends WordSpec with MustMatchers {
(actorRef ? "Hello").as[String].get must be("World from node [node2]")
}
- barrier("store-3-in-node-1", NrOfNodes) {
- }
-
- barrier("use-3-in-node-2", NrOfNodes) {
- val actorOrOption = node.use("hello-world-3")
- if (actorOrOption.isEmpty) fail("Actor could not be retrieved")
-
- val actorRef = actorOrOption.get
- actorRef.address must be("hello-world-3")
-
- (actorRef ? ("Count", 30000)).as[Int].get must be >= (2) // be conservative - can by 5 but also 2 if slow system
- }
-
node.shutdown()
}
}
diff --git a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf
similarity index 69%
rename from akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf
index 83ba804ad1..6f117d6ce2 100644
--- a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf
@@ -1,4 +1,4 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
akka.actor.deployment.service-hello.router = "round-robin"
-akka.actor.deployment.service-hello.clustered.home = "node:node1"
akka.actor.deployment.service-hello.clustered.replicas = 1
diff --git a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf
similarity index 69%
rename from akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf
index 83ba804ad1..6f117d6ce2 100644
--- a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf
@@ -1,4 +1,4 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
akka.actor.deployment.service-hello.router = "round-robin"
-akka.actor.deployment.service-hello.clustered.home = "node:node1"
akka.actor.deployment.service-hello.clustered.replicas = 1
diff --git a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmSpec.scala
similarity index 76%
rename from akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmSpec.scala
index 9b3f1eb562..a511681732 100644
--- a/akka-cluster/src/test/scala/akka/cluster/deployment/DeploymentMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmSpec.scala
@@ -17,9 +17,11 @@ object DeploymentMultiJvmSpec {
var NrOfNodes = 2
}
-class DeploymentMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class DeploymentMultiJvmNode1 extends MasterClusterTestNode {
import DeploymentMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A ClusterDeployer" must {
"be able to deploy deployments in akka.conf and lookup the deployments by 'address'" in {
@@ -33,9 +35,6 @@ class DeploymentMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndA
barrier("perform-deployment-on-node-1", NrOfNodes) {
Deployer.start()
- // val deployments = Deployer.deploymentsInConfig
- // deployments must not equal (Nil)
- // ClusterDeployer.init(deployments)
}
barrier("lookup-deployment-node-2", NrOfNodes) {
@@ -44,18 +43,9 @@ class DeploymentMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndA
node.shutdown()
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- // ClusterDeployer.shutdown()
- }
}
-class DeploymentMultiJvmNode2 extends WordSpec with MustMatchers {
+class DeploymentMultiJvmNode2 extends ClusterTestNode {
import DeploymentMultiJvmSpec._
"A cluster" must {
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf
new file mode 100644
index 0000000000..7d8a1476ad
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf
@@ -0,0 +1,4 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf
new file mode 100644
index 0000000000..7d8a1476ad
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf
@@ -0,0 +1,4 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf
new file mode 100644
index 0000000000..7d8a1476ad
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf
@@ -0,0 +1,4 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode3.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode3.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmSpec.scala
similarity index 63%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmSpec.scala
index 5ab7b8726a..c929fdeb6f 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmSpec.scala
@@ -1,24 +1,19 @@
-/**
- * Copyright (C) 2009-2011 Scalable Solutions AB
+/*
+ * Copyright (C) 2009-2011 Scalable Solutions AB
*/
-package akka.cluster.api.migration.automatic
+package akka.cluster.migration.automatic
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import org.scalatest.BeforeAndAfterAll
import akka.actor._
-import Actor._
import akka.cluster._
-import ChangeListener._
import Cluster._
-import DeploymentConfig._
import akka.config.Config
import akka.serialization.Serialization
-import java.util.concurrent._
-
object MigrationAutomaticMultiJvmSpec {
var NrOfNodes = 3
@@ -30,26 +25,25 @@ object MigrationAutomaticMultiJvmSpec {
}
}
-class MigrationAutomaticMultiJvmNode1 extends WordSpec with MustMatchers {
+class MigrationAutomaticMultiJvmNode1 extends ClusterTestNode {
import MigrationAutomaticMultiJvmSpec._
"A cluster" must {
"be able to migrate an actor from one node to another" in {
- barrier("start-node3", NrOfNodes) {
- }
-
- barrier("start-node2", NrOfNodes) {
- }
-
barrier("start-node1", NrOfNodes) {
node.start()
}
- barrier("store-actor-in-node1", NrOfNodes) {
- val serializer = Serialization.serializerFor(classOf[HelloWorld]).fold(x ⇒ fail("No serializer found"), s ⇒ s)
- node.store(actorOf[HelloWorld]("hello-world"), 1, serializer)
+ barrier("create-actor-on-node1", NrOfNodes) {
+ val actorRef = Actor.actorOf[HelloWorld]("hello-world").start()
+ node.isInUseOnNode("hello-world") must be(true)
+ actorRef.address must be("hello-world")
+ (actorRef ? "Hello").as[String].get must be("World from node [node1]")
+ }
+
+ barrier("start-node2", NrOfNodes) {
}
node.shutdown()
@@ -57,7 +51,7 @@ class MigrationAutomaticMultiJvmNode1 extends WordSpec with MustMatchers {
}
}
-class MigrationAutomaticMultiJvmNode2 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class MigrationAutomaticMultiJvmNode2 extends ClusterTestNode {
import MigrationAutomaticMultiJvmSpec._
var isFirstReplicaNode = false
@@ -66,22 +60,19 @@ class MigrationAutomaticMultiJvmNode2 extends WordSpec with MustMatchers with Be
"be able to migrate an actor from one node to another" in {
- barrier("start-node3", NrOfNodes) {
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
}
barrier("start-node2", NrOfNodes) {
node.start()
}
- barrier("start-node1", NrOfNodes) {
- }
+ Thread.sleep(2000) // wait for fail-over from node1 to node2
- barrier("store-actor-in-node1", NrOfNodes) {
- }
-
- Thread.sleep(2000) // wait for fail-over
-
- barrier("check-fail-over", NrOfNodes - 1) {
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
// both remaining nodes should now have the replica
node.isInUseOnNode("hello-world") must be(true)
val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
@@ -89,34 +80,42 @@ class MigrationAutomaticMultiJvmNode2 extends WordSpec with MustMatchers with Be
(actorRef ? "Hello").as[String].get must be("World from node [node2]")
}
+ barrier("start-node3", NrOfNodes - 1) {
+ }
+
node.shutdown()
}
}
}
-class MigrationAutomaticMultiJvmNode3 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+class MigrationAutomaticMultiJvmNode3 extends MasterClusterTestNode {
import MigrationAutomaticMultiJvmSpec._
+ val testNodes = NrOfNodes
+
"A cluster" must {
"be able to migrate an actor from one node to another" in {
- barrier("start-node3", NrOfNodes) {
- node.start()
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
}
barrier("start-node2", NrOfNodes) {
}
- barrier("start-node1", NrOfNodes) {
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
}
- barrier("store-actor-in-node1", NrOfNodes) {
+ barrier("start-node3", NrOfNodes - 1) {
+ node.start()
}
- Thread.sleep(2000) // wait for fail-over
+ Thread.sleep(2000) // wait for fail-over from node2 to node3
- barrier("check-fail-over", NrOfNodes - 1) {
+ barrier("check-fail-over-to-node3", NrOfNodes - 2) {
// both remaining nodes should now have the replica
node.isInUseOnNode("hello-world") must be(true)
val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
@@ -127,12 +126,4 @@ class MigrationAutomaticMultiJvmNode3 extends WordSpec with MustMatchers with Be
node.shutdown()
}
}
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
}
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode1.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode1.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode1.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode1.opts
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode2.conf
similarity index 50%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode2.conf
index 480c30c09d..762f32d92a 100644
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode2.conf
@@ -1 +1,2 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmNode2.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmSpec.scala
new file mode 100644
index 0000000000..0772b7798a
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/explicit/MigrationExplicitMultiJvmSpec.scala
@@ -0,0 +1,111 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ *
+ *
+ * package akka.cluster.migration.explicit
+ *
+ * import org.scalatest.WordSpec
+ * import org.scalatest.matchers.MustMatchers
+ * import org.scalatest.BeforeAndAfterAll
+ *
+ * import akka.actor._
+ * import Actor._
+ * import akka.cluster._
+ * import ChangeListener._
+ * import Cluster._
+ * import akka.config.Config
+ * import akka.serialization.Serialization
+ *
+ * import java.util.concurrent._
+ *
+ * object MigrationExplicitMultiJvmSpec {
+ * var NrOfNodes = 2
+ *
+ * class HelloWorld extends Actor with Serializable {
+ * def receive = {
+ * case "Hello" ⇒
+ * self.reply("World from node [" + Config.nodename + "]")
+ * }
+ * }
+ * }
+ *
+ * class MigrationExplicitMultiJvmNode1 extends MasterClusterTestNode {
+ * import MigrationExplicitMultiJvmSpec._
+ *
+ * val testNodes = NrOfNodes
+ *
+ * "A cluster" must {
+ *
+ * "be able to migrate an actor from one node to another" in {
+ *
+ * barrier("start-node-1", NrOfNodes) {
+ * node.start()
+ * }
+ *
+ * barrier("start-node-2", NrOfNodes) {
+ * }
+ *
+ * barrier("store-1-in-node-1", NrOfNodes) {
+ * val serializer = Serialization.serializerFor(classOf[HelloWorld]).fold(x ⇒ fail("No serializer found"), s ⇒ s)
+ * node.store("hello-world", classOf[HelloWorld], serializer)
+ * }
+ *
+ * barrier("use-1-in-node-2", NrOfNodes) {
+ * }
+ *
+ * barrier("migrate-from-node2-to-node1", NrOfNodes) {
+ * }
+ *
+ * barrier("check-actor-is-moved-to-node1", NrOfNodes) {
+ * node.isInUseOnNode("hello-world") must be(true)
+ *
+ * val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
+ * actorRef.address must be("hello-world")
+ * (actorRef ? "Hello").as[String].get must be("World from node [node1]")
+ * }
+ *
+ * node.shutdown()
+ * }
+ * }
+ * }
+ *
+ * class MigrationExplicitMultiJvmNode2 extends ClusterTestNode {
+ * import MigrationExplicitMultiJvmSpec._
+ *
+ * "A cluster" must {
+ *
+ * "be able to migrate an actor from one node to another" in {
+ *
+ * barrier("start-node-1", NrOfNodes) {
+ * }
+ *
+ * barrier("start-node-2", NrOfNodes) {
+ * node.start()
+ * }
+ *
+ * barrier("store-1-in-node-1", NrOfNodes) {
+ * }
+ *
+ * barrier("use-1-in-node-2", NrOfNodes) {
+ * val actorOrOption = node.use("hello-world")
+ * if (actorOrOption.isEmpty) fail("Actor could not be retrieved")
+ *
+ * val actorRef = actorOrOption.get
+ * actorRef.address must be("hello-world")
+ *
+ * (actorRef ? "Hello").as[String].get must be("World from node [node2]")
+ * }
+ *
+ * barrier("migrate-from-node2-to-node1", NrOfNodes) {
+ * node.migrate(NodeAddress(node.nodeAddress.clusterName, "node1"), "hello-world")
+ * Thread.sleep(2000)
+ * }
+ *
+ * barrier("check-actor-is-moved-to-node1", NrOfNodes) {
+ * }
+ *
+ * node.shutdown()
+ * }
+ * }
+ * }
+ */
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf
new file mode 100644
index 0000000000..d8bee0cb07
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind"
+akka.cluster.replication.snapshot-frequency = 1000
diff --git a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf
new file mode 100644
index 0000000000..d8bee0cb07
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind"
+akka.cluster.replication.snapshot-frequency = 1000
diff --git a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec.scala
new file mode 100644
index 0000000000..7ed05307ae
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec.scala
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.replication.transactionlog.writebehind.nosnapshot
+
+import akka.actor._
+import akka.cluster._
+import Cluster._
+import akka.config.Config
+
+object ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec {
+ var NrOfNodes = 2
+
+ sealed trait TransactionLogMessage extends Serializable
+ case class Count(nr: Int) extends TransactionLogMessage
+ case class Log(full: String) extends TransactionLogMessage
+ case object GetLog extends TransactionLogMessage
+
+ class HelloWorld extends Actor with Serializable {
+ var log = ""
+ def receive = {
+ case Count(nr) ⇒
+ log += nr.toString
+ self.reply("World from node [" + Config.nodename + "]")
+ case GetLog ⇒
+ self.reply(Log(log))
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1 extends ClusterTestNode {
+ import ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec._
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ node.start()
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ val actorRef = Actor.actorOf[HelloWorld]("hello-world").start()
+ node.isInUseOnNode("hello-world") must be(true)
+ actorRef.address must be("hello-world")
+ var counter = 0
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ }
+
+ node.shutdown()
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2 extends MasterClusterTestNode {
+ import ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ node.start()
+ }
+
+ Thread.sleep(5000) // wait for fail-over from node1 to node2
+
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
+ // both remaining nodes should now have the replica
+ node.isInUseOnNode("hello-world") must be(true)
+ val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
+ actorRef.address must be("hello-world")
+ (actorRef ? GetLog).as[Log].get must be(Log("0123456789"))
+ }
+
+ node.shutdown()
+ }
+ }
+
+ override def onReady() {
+ LocalBookKeeperEnsemble.start()
+ }
+
+ override def onShutdown() {
+ TransactionLog.shutdown()
+ LocalBookKeeperEnsemble.shutdown()
+ }
+}
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf
new file mode 100644
index 0000000000..8aeaf3135f
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind"
+akka.cluster.replication.snapshot-frequency = 7
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf
new file mode 100644
index 0000000000..8aeaf3135f
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind"
+akka.cluster.replication.snapshot-frequency = 7
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmSpec.scala
new file mode 100644
index 0000000000..c37a863ba0
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmSpec.scala
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.replication.transactionlog.writebehind.snapshot
+
+import akka.actor._
+import akka.cluster._
+import Cluster._
+import akka.config.Config
+
+object ReplicationTransactionLogWriteBehindSnapshotMultiJvmSpec {
+ var NrOfNodes = 2
+
+ sealed trait TransactionLogMessage extends Serializable
+ case class Count(nr: Int) extends TransactionLogMessage
+ case class Log(full: String) extends TransactionLogMessage
+ case object GetLog extends TransactionLogMessage
+
+ class HelloWorld extends Actor with Serializable {
+ var log = ""
+ println("Creating HelloWorld log =======> " + log)
+ def receive = {
+ case Count(nr) ⇒
+ log += nr.toString
+ println("Message to HelloWorld log =======> " + log)
+ self.reply("World from node [" + Config.nodename + "]")
+ case GetLog ⇒
+ self.reply(Log(log))
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1 extends ClusterTestNode {
+ import ReplicationTransactionLogWriteBehindSnapshotMultiJvmSpec._
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ node.start()
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ val actorRef = Actor.actorOf[HelloWorld]("hello-world").start()
+ node.isInUseOnNode("hello-world") must be(true)
+ actorRef.address must be("hello-world")
+ var counter = 0
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ }
+
+ node.shutdown()
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2 extends MasterClusterTestNode {
+ import ReplicationTransactionLogWriteBehindSnapshotMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ node.start()
+ }
+
+ Thread.sleep(5000) // wait for fail-over from node1 to node2
+
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
+ // both remaining nodes should now have the replica
+ node.isInUseOnNode("hello-world") must be(true)
+ val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
+ actorRef.address must be("hello-world")
+ (actorRef ? GetLog).as[Log].get must be(Log("0123456789"))
+ }
+
+ node.shutdown()
+ }
+ }
+
+ override def onReady() {
+ LocalBookKeeperEnsemble.start()
+ }
+
+ override def onShutdown() {
+ TransactionLog.shutdown()
+ LocalBookKeeperEnsemble.shutdown()
+ }
+}
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf
new file mode 100644
index 0000000000..470c4c7a33
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf
@@ -0,0 +1,8 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through"
+akka.cluster.replication.snapshot-frequency = 1000
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf
new file mode 100644
index 0000000000..5fb92ab01f
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through"
+akka.cluster.replication.snapshot-frequency = 1000
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec.scala
new file mode 100644
index 0000000000..c9c53a9a25
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec.scala
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.replication.transactionlog.writethrough.nosnapshot
+
+import akka.actor._
+import akka.cluster._
+import Cluster._
+import akka.config.Config
+
+object ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec {
+ var NrOfNodes = 2
+
+ sealed trait TransactionLogMessage extends Serializable
+ case class Count(nr: Int) extends TransactionLogMessage
+ case class Log(full: String) extends TransactionLogMessage
+ case object GetLog extends TransactionLogMessage
+
+ class HelloWorld extends Actor with Serializable {
+ var log = ""
+ def receive = {
+ case Count(nr) ⇒
+ log += nr.toString
+ self.reply("World from node [" + Config.nodename + "]")
+ case GetLog ⇒
+ self.reply(Log(log))
+ }
+ }
+}
+
+/*
+class ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1 extends ClusterTestNode {
+ import ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec._
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" ignore {
+
+ barrier("start-node1", NrOfNodes) {
+ node.start()
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ val actorRef = Actor.actorOf[HelloWorld]("hello-world").start()
+ node.isInUseOnNode("hello-world") must be(true)
+ actorRef.address must be("hello-world")
+ var counter = 0
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ }
+
+ node.shutdown()
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2 extends MasterClusterTestNode {
+ import ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ node.start()
+ }
+
+ Thread.sleep(5000) // wait for fail-over from node1 to node2
+
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
+ // both remaining nodes should now have the replica
+ node.isInUseOnNode("hello-world") must be(true)
+ val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
+ actorRef.address must be("hello-world")
+ (actorRef ? GetLog).as[Log].get must be(Log("0123456789"))
+ }
+
+ node.shutdown()
+ }
+ }
+
+ override def onReady() {
+ LocalBookKeeperEnsemble.start()
+ }
+
+ override def onShutdown() {
+ TransactionLog.shutdown()
+ LocalBookKeeperEnsemble.shutdown()
+ }
+}*/
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf
new file mode 100644
index 0000000000..470c4c7a33
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf
@@ -0,0 +1,8 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through"
+akka.cluster.replication.snapshot-frequency = 1000
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf
new file mode 100644
index 0000000000..5fb92ab01f
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through"
+akka.cluster.replication.snapshot-frequency = 1000
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmSpec.scala
new file mode 100644
index 0000000000..10fc3883dc
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmSpec.scala
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.replication.transactionlog.writethrough.nosnapshot
+
+import akka.actor._
+import akka.cluster._
+import Cluster._
+import akka.config.Config
+
+object ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmSpec {
+ var NrOfNodes = 2
+
+ sealed trait TransactionLogMessage extends Serializable
+ case class Count(nr: Int) extends TransactionLogMessage
+ case class Log(full: String) extends TransactionLogMessage
+ case object GetLog extends TransactionLogMessage
+
+ class HelloWorld extends Actor with Serializable {
+ var log = ""
+ def receive = {
+ case Count(nr) ⇒
+ log += nr.toString
+ self.reply("World from node [" + Config.nodename + "]")
+ case GetLog ⇒
+ self.reply(Log(log))
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1 extends ClusterTestNode {
+ import ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmSpec._
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ node.start()
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ val actorRef = Actor.actorOf[HelloWorld]("hello-world").start()
+ node.isInUseOnNode("hello-world") must be(true)
+ actorRef.address must be("hello-world")
+ var counter = 0
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ }
+
+ node.shutdown()
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2 extends MasterClusterTestNode {
+ import ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ node.start()
+ }
+
+ Thread.sleep(5000) // wait for fail-over from node1 to node2
+
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
+ // both remaining nodes should now have the replica
+ node.isInUseOnNode("hello-world") must be(true)
+ val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
+ actorRef.address must be("hello-world")
+ (actorRef ? GetLog).as[Log].get must be(Log("0123456789"))
+ }
+
+ node.shutdown()
+ }
+ }
+
+ override def onReady() {
+ LocalBookKeeperEnsemble.start()
+ }
+
+ override def onShutdown() {
+ TransactionLog.shutdown()
+ LocalBookKeeperEnsemble.shutdown()
+ }
+}
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf
new file mode 100644
index 0000000000..1d332847b6
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through"
+akka.cluster.replication.snapshot-frequency = 7
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf
new file mode 100644
index 0000000000..1d332847b6
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf
@@ -0,0 +1,7 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.hello-world.router = "direct"
+akka.actor.deployment.hello-world.clustered.replicas = 1
+akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log"
+akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through"
+akka.cluster.replication.snapshot-frequency = 7
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmSpec.scala
new file mode 100644
index 0000000000..a7fbc7b4f1
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmSpec.scala
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.replication.transactionlog.writethrough.snapshot
+
+import akka.actor._
+import akka.cluster._
+import Cluster._
+import akka.config.Config
+
+object ReplicationTransactionLogWriteThroughSnapshotMultiJvmSpec {
+ var NrOfNodes = 2
+
+ sealed trait TransactionLogMessage extends Serializable
+ case class Count(nr: Int) extends TransactionLogMessage
+ case class Log(full: String) extends TransactionLogMessage
+ case object GetLog extends TransactionLogMessage
+
+ class HelloWorld extends Actor with Serializable {
+ var log = ""
+ println("Creating HelloWorld log =======> " + log)
+ def receive = {
+ case Count(nr) ⇒
+ log += nr.toString
+ println("Message to HelloWorld log =======> " + log)
+ self.reply("World from node [" + Config.nodename + "]")
+ case GetLog ⇒
+ self.reply(Log(log))
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1 extends ClusterTestNode {
+ import ReplicationTransactionLogWriteThroughSnapshotMultiJvmSpec._
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ node.start()
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ val actorRef = Actor.actorOf[HelloWorld]("hello-world").start()
+ node.isInUseOnNode("hello-world") must be(true)
+ actorRef.address must be("hello-world")
+ var counter = 0
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ counter += 1
+ (actorRef ? Count(counter)).as[String].get must be("World from node [node1]")
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ }
+
+ node.shutdown()
+ }
+ }
+}
+
+class ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2 extends MasterClusterTestNode {
+ import ReplicationTransactionLogWriteThroughSnapshotMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "A cluster" must {
+
+ "be able to replicate an actor with a transaction log and replay transaction log after actor migration" in {
+
+ barrier("start-node1", NrOfNodes) {
+ }
+
+ barrier("create-actor-on-node1", NrOfNodes) {
+ }
+
+ barrier("start-node2", NrOfNodes) {
+ node.start()
+ }
+
+ Thread.sleep(5000) // wait for fail-over from node1 to node2
+
+ barrier("check-fail-over-to-node2", NrOfNodes - 1) {
+ // both remaining nodes should now have the replica
+ node.isInUseOnNode("hello-world") must be(true)
+ val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
+ actorRef.address must be("hello-world")
+ (actorRef ? GetLog).as[Log].get must be(Log("0123456789"))
+ }
+
+ node.shutdown()
+ }
+ }
+
+ override def onReady() {
+ LocalBookKeeperEnsemble.start()
+ }
+
+ override def onShutdown() {
+ TransactionLog.shutdown()
+ LocalBookKeeperEnsemble.shutdown()
+ }
+}
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.conf
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.conf
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmSpec.scala
new file mode 100644
index 0000000000..6df40132c6
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmSpec.scala
@@ -0,0 +1,43 @@
+package akka.cluster.routing.direct.bad_address
+
+import akka.cluster.{ Cluster, MasterClusterTestNode }
+import akka.actor.Actor
+import akka.config.Config
+
+object BadAddressDirectRoutingMultiJvmSpec {
+
+ val NrOfNodes = 1
+
+ class SomeActor extends Actor with Serializable {
+ println("---------------------------------------------------------------------------")
+ println("SomeActor has been created on node [" + Config.nodename + "]")
+ println("---------------------------------------------------------------------------")
+
+ def receive = {
+ case "identify" ⇒ {
+ println("The node received the 'identify' command: " + Config.nodename)
+ self.reply(Config.nodename)
+ }
+ }
+ }
+
+}
+
+/*
+class BadAddressDirectRoutingMultiJvmNode1 extends MasterClusterTestNode {
+
+ import BadAddressDirectRoutingMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "node" must {
+ "participate in cluster" in {
+ Cluster.node.start()
+
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ Cluster.node.shutdown()
+ }
+ }
+}*/
+
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode1.conf
new file mode 100644
index 0000000000..150095d5bf
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode1.conf
@@ -0,0 +1,2 @@
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "direct"
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode2.conf
new file mode 100644
index 0000000000..0bac6e8004
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode2.conf
@@ -0,0 +1,2 @@
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "direct"
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmSpec.scala
new file mode 100644
index 0000000000..dd9207ac17
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/multiple_replicas/MultiReplicaDirectRoutingMultiJvmSpec.scala
@@ -0,0 +1,68 @@
+package akka.cluster.routing.direct.multiple_replicas
+
+import akka.actor.Actor
+import akka.cluster.{ MasterClusterTestNode, Cluster, ClusterTestNode }
+import akka.config.Config
+
+object MultiReplicaDirectRoutingMultiJvmSpec {
+ val NrOfNodes = 2
+
+ class SomeActor extends Actor with Serializable {
+ println("---------------------------------------------------------------------------")
+ println("SomeActor has been created on node [" + Config.nodename + "]")
+ println("---------------------------------------------------------------------------")
+
+ def receive = {
+ case "identify" ⇒ {
+ println("The node received the 'identify' command: " + Config.nodename)
+ self.reply(Config.nodename)
+ }
+ }
+ }
+
+}
+
+/*
+class MultiReplicaDirectRoutingMultiJvmNode2 extends ClusterTestNode {
+
+ import MultiReplicaDirectRoutingMultiJvmSpec._
+
+ "when node send message to existing node using direct routing it" must {
+ "communicate with that node" in {
+ Cluster.node.start()
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+
+ //Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes).await()
+
+ val actor = Actor.actorOf[SomeActor]("service-hello")
+ actor.start()
+
+ //actor.start()
+ val name: String = (actor ? "identify").get.asInstanceOf[String]
+
+ println("The name of the actor was " + name)
+
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ Cluster.node.shutdown()
+ }
+ }
+}
+
+class MultiReplicaDirectRoutingMultiJvmNode1 extends MasterClusterTestNode {
+
+ import MultiReplicaDirectRoutingMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "node" must {
+ "participate in cluster" in {
+ Cluster.node.start()
+
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ Cluster.node.shutdown()
+ }
+ }
+}
+*/
+
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf
new file mode 100644
index 0000000000..81b5034354
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf
@@ -0,0 +1,3 @@
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "direct"
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode2.conf
new file mode 100644
index 0000000000..150095d5bf
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode2.conf
@@ -0,0 +1,2 @@
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "direct"
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmSpec.scala
new file mode 100644
index 0000000000..707f6e6c26
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmSpec.scala
@@ -0,0 +1,60 @@
+package akka.cluster.routing.direct.single_replica
+
+import akka.actor.Actor
+import akka.config.Config
+import akka.cluster.{ ClusterTestNode, MasterClusterTestNode, Cluster }
+
+object SingleReplicaDirectRoutingMultiJvmSpec {
+ val NrOfNodes = 2
+
+ class SomeActor extends Actor with Serializable {
+ println("---------------------------------------------------------------------------")
+ println("SomeActor has been created on node [" + Config.nodename + "]")
+ println("---------------------------------------------------------------------------")
+
+ def receive = {
+ case "identify" ⇒ {
+ println("The node received the 'identify' command: " + Config.nodename)
+ self.reply(Config.nodename)
+ }
+ }
+ }
+
+}
+
+/*
+class SingleReplicaDirectRoutingMultiJvmNode1 extends MasterClusterTestNode {
+
+ import SingleReplicaDirectRoutingMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "when node send message to existing node using direct routing it" must {
+ "communicate with that node" in {
+ Cluster.node.start()
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+
+ val actor = Actor.actorOf[SomeActor]("service-hello").start()
+ actor.isRunning must be(true)
+
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ Cluster.node.shutdown()
+ }
+ }
+}
+
+class SingleReplicaDirectRoutingMultiJvmNode2 extends ClusterTestNode {
+
+ import SingleReplicaDirectRoutingMultiJvmSpec._
+
+ "___" must {
+ "___" in {
+ Cluster.node.start()
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ Cluster.node.shutdown()
+ }
+ }
+}*/
+
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf
new file mode 100644
index 0000000000..0a5f18c2b9
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf
@@ -0,0 +1,5 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf
new file mode 100644
index 0000000000..0a5f18c2b9
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf
@@ -0,0 +1,5 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmSpec.scala
new file mode 100644
index 0000000000..e4aae69f8f
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmSpec.scala
@@ -0,0 +1,59 @@
+package akka.cluster.routing.homenode
+
+import akka.config.Config
+import akka.actor.{ ActorRef, Actor }
+import akka.cluster.{ ClusterTestNode, MasterClusterTestNode, Cluster }
+import Cluster._
+
+object HomeNodeMultiJvmSpec {
+
+ val NrOfNodes = 2
+
+ class SomeActor extends Actor with Serializable {
+ def receive = {
+ case "identify" ⇒ {
+ self.reply(Config.nodename)
+ }
+ }
+ }
+}
+
+class HomeNodeMultiJvmNode1 extends MasterClusterTestNode {
+
+ import HomeNodeMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "A Router" must {
+ "obey 'home-node' config option when instantiated actor in cluster" in {
+
+ node.start()
+ barrier("waiting-for-begin", NrOfNodes).await()
+
+ barrier("get-ref-to-actor-on-node2", NrOfNodes).await()
+
+ node.shutdown()
+ }
+ }
+}
+
+class HomeNodeMultiJvmNode2 extends ClusterTestNode {
+
+ import HomeNodeMultiJvmSpec._
+
+ "A Router" must {
+ "obey 'home-node' config option when instantiated actor in cluster" in {
+
+ node.start()
+ barrier("waiting-for-begin", NrOfNodes).await()
+
+ barrier("get-ref-to-actor-on-node2", NrOfNodes) {
+ val actor = Actor.actorOf[SomeActor]("service-hello")
+ val name = (actor ? "identify").get.asInstanceOf[String]
+ name must equal("node1")
+ }
+
+ node.shutdown()
+ }
+ }
+}
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf
similarity index 69%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf
index 7b2ecc1583..221ccd25ae 100644
--- a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf
@@ -1,4 +1,4 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
akka.actor.deployment.service-hello.router = "round-robin"
-akka.actor.deployment.service-hello.clustered.home = "node:node1"
akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmSpec.scala
new file mode 100644
index 0000000000..6f756ffef6
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmSpec.scala
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.routing.roundrobin_1_replica
+
+import org.scalatest.WordSpec
+import org.scalatest.matchers.MustMatchers
+import org.scalatest.BeforeAndAfterAll
+
+import akka.cluster._
+import Cluster._
+import akka.actor._
+import akka.actor.Actor._
+import akka.config.Config
+
+/**
+ * Test that if a single node is used with a round robin router with replication factor then the actor is instantiated on the single node.
+ */
+object RoundRobin1ReplicaMultiJvmSpec {
+
+ class HelloWorld extends Actor with Serializable {
+ def receive = {
+ case "Hello" ⇒
+ self.reply("World from node [" + Config.nodename + "]")
+ }
+ }
+}
+
+class RoundRobin1ReplicaMultiJvmNode1 extends MasterClusterTestNode {
+ import RoundRobin1ReplicaMultiJvmSpec._
+
+ val testNodes = 1
+
+ "A cluster" must {
+
+ "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" in {
+ node.start()
+
+ var hello: ActorRef = null
+ hello = Actor.actorOf[HelloWorld]("service-hello")
+ hello must not equal (null)
+ hello.address must equal("service-hello")
+ hello.isInstanceOf[ClusterActorRef] must be(true)
+
+ hello must not equal (null)
+ val reply = (hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1"))
+ reply must equal("World from node [node1]")
+
+ node.shutdown()
+ }
+ }
+}
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf
similarity index 54%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf
index b96297f0c4..401a5bd8e4 100644
--- a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf
@@ -1,5 +1,5 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
akka.actor.deployment.service-hello.router = "round-robin"
-akka.actor.deployment.service-hello.clustered.home = "node:node1"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1","node:node2"]
akka.actor.deployment.service-hello.clustered.replicas = 2
-akka.actor.deployment.service-hello.clustered.stateless = on
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf
similarity index 54%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf
index 36795796c2..401a5bd8e4 100644
--- a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf
@@ -1,5 +1,5 @@
+akka.enabled-modules = ["cluster"]
akka.event-handler-level = "DEBUG"
akka.actor.deployment.service-hello.router = "round-robin"
-akka.actor.deployment.service-hello.clustered.home = "node:node1"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1","node:node2"]
akka.actor.deployment.service-hello.clustered.replicas = 2
-akka.actor.deployment.service-hello.clustered.stateless = on
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmSpec.scala
similarity index 55%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmSpec.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmSpec.scala
index a65abd2b1c..c1c76e61a9 100644
--- a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmSpec.scala
@@ -8,66 +8,71 @@ import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import org.scalatest.BeforeAndAfterAll
-import org.apache.bookkeeper.client.{ BookKeeper, BKException }
-import BKException._
-
import akka.cluster._
+import Cluster._
import akka.actor._
import akka.actor.Actor._
import akka.config.Config
+/**
+ * When a MultiJvmNode is started, will it automatically be part of the cluster (so will it automatically be eligible
+ * for running actors, or will it be just a 'client' talking to the cluster.
+ */
object RoundRobin2ReplicasMultiJvmSpec {
- val NrOfNodes = 3
+ val NrOfNodes = 2
class HelloWorld extends Actor with Serializable {
def receive = {
case "Hello" ⇒
- println("Received message on [" + Config.nodename + "]")
self.reply("World from node [" + Config.nodename + "]")
}
}
}
+/**
+ * What is the purpose of this node? Is this just a node for the cluster to make use of?
+ */
+/*
class RoundRobin2ReplicasMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
import RoundRobin2ReplicasMultiJvmSpec._
- private var bookKeeper: BookKeeper = _
- private var localBookKeeper: LocalBookKeeper = _
-
"A cluster" must {
"create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" in {
System.getProperty("akka.cluster.nodename", "") must be("node1")
System.getProperty("akka.cluster.port", "") must be("9991")
- Cluster.barrier("start-node1", NrOfNodes) {
- Cluster.node.start()
+ //wait till node 1 has started.
+ barrier("start-node1", NrOfNodes) {
+ node.start()
}
- Cluster.barrier("start-node2", NrOfNodes) {}
+ //wait till ndoe 2 has started.
+ barrier("start-node2", NrOfNodes) {}
- Cluster.barrier("start-node3", NrOfNodes) {}
+ //wait till node 3 has started.
+ barrier("start-node3", NrOfNodes) {}
- Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes) {}
+ //wait till an actor reference on node 2 has become available.
+ barrier("get-ref-to-actor-on-node2", NrOfNodes) {}
- Cluster.barrier("send-message-from-node2-to-replicas", NrOfNodes) {}
+ //wait till the node 2 has send a message to the replica's.
+ barrier("send-message-from-node2-to-replicas", NrOfNodes) {}
- Cluster.node.shutdown()
+ node.shutdown()
}
}
- override def beforeAll() = {
- Cluster.startLocalCluster()
- LocalBookKeeperEnsemble.start()
+ override def beforeAll() {
+ startLocalCluster()
}
- override def afterAll() = {
- Cluster.shutdownLocalCluster()
- TransactionLog.shutdown()
- LocalBookKeeperEnsemble.shutdown()
+ override def afterAll() {
+ shutdownLocalCluster()
}
-}
+}*/
+/*
class RoundRobin2ReplicasMultiJvmNode2 extends WordSpec with MustMatchers {
import RoundRobin2ReplicasMultiJvmSpec._
@@ -77,23 +82,28 @@ class RoundRobin2ReplicasMultiJvmNode2 extends WordSpec with MustMatchers {
System.getProperty("akka.cluster.nodename", "") must be("node2")
System.getProperty("akka.cluster.port", "") must be("9992")
- Cluster.barrier("start-node1", NrOfNodes) {}
+ //wait till node 1 has started.
+ barrier("start-node1", NrOfNodes) {}
- Cluster.barrier("start-node2", NrOfNodes) {
- Cluster.node.start()
+ //wait till node 2 has started.
+ barrier("start-node2", NrOfNodes) {
+ node.start()
}
- Cluster.barrier("start-node3", NrOfNodes) {}
+ //wait till node 3 has started.
+ barrier("start-node3", NrOfNodes) {}
+ //check if the actorRef is the expected remoteActorRef.
var hello: ActorRef = null
- Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes) {
+ barrier("get-ref-to-actor-on-node2", NrOfNodes) {
hello = Actor.actorOf[HelloWorld]("service-hello")
hello must not equal (null)
hello.address must equal("service-hello")
hello.isInstanceOf[ClusterActorRef] must be(true)
}
- Cluster.barrier("send-message-from-node2-to-replicas", NrOfNodes) {
+ barrier("send-message-from-node2-to-replicas", NrOfNodes) {
+ //todo: is there a reason to check for null again since it already has been done in the previous block.
hello must not equal (null)
val replies = collection.mutable.Map.empty[String, Int]
@@ -103,45 +113,20 @@ class RoundRobin2ReplicasMultiJvmNode2 extends WordSpec with MustMatchers {
}
count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
- count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
- count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
- count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
- count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
replies("World from node [node1]") must equal(4)
- replies("World from node [node3]") must equal(4)
+ replies("World from node [node2]") must equal(4)
}
- Cluster.node.shutdown()
- }
- }
-}
-
-class RoundRobin2ReplicasMultiJvmNode3 extends WordSpec with MustMatchers {
- import RoundRobin2ReplicasMultiJvmSpec._
-
- "A cluster" must {
-
- "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" in {
- System.getProperty("akka.cluster.nodename", "") must be("node3")
- System.getProperty("akka.cluster.port", "") must be("9993")
-
- Cluster.barrier("start-node1", NrOfNodes) {}
-
- Cluster.barrier("start-node2", NrOfNodes) {}
-
- Cluster.barrier("start-node3", NrOfNodes) {
- Cluster.node.start()
- }
-
- Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes) {}
-
- Cluster.barrier("send-message-from-node2-to-replicas", NrOfNodes) {}
-
- Cluster.node.shutdown()
+ node.shutdown()
}
}
}
+*/
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf
new file mode 100644
index 0000000000..851d7a98e8
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf
@@ -0,0 +1,4 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.replicas = 3
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf
new file mode 100644
index 0000000000..851d7a98e8
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf
@@ -0,0 +1,4 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.replicas = 3
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf
new file mode 100644
index 0000000000..851d7a98e8
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf
@@ -0,0 +1,4 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.replicas = 3
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode3.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.opts
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode3.opts
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.opts
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmSpec.scala
new file mode 100644
index 0000000000..eee003409d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmSpec.scala
@@ -0,0 +1,155 @@
+/**
+ * Copyright (C) 2009-2011 Scalable Solutions AB
+ */
+
+package akka.cluster.routing.roundrobin_3_replicas
+
+import org.scalatest.WordSpec
+import org.scalatest.matchers.MustMatchers
+import org.scalatest.BeforeAndAfterAll
+
+import akka.cluster._
+import akka.actor._
+import akka.actor.Actor._
+import akka.config.Config
+import Cluster._
+
+/**
+ * When a MultiJvmNode is started, will it automatically be part of the cluster (so will it automatically be eligible
+ * for running actors, or will it be just a 'client' talking to the cluster.
+ */
+object RoundRobin3ReplicasMultiJvmSpec {
+ val NrOfNodes = 3
+
+ class HelloWorld extends Actor with Serializable {
+ def receive = {
+ case "Hello" ⇒
+ self.reply("World from node [" + Config.nodename + "]")
+ }
+ }
+}
+
+/**
+ * What is the purpose of this node? Is this just a node for the cluster to make use of?
+ */
+class RoundRobin3ReplicasMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
+ import RoundRobin3ReplicasMultiJvmSpec._
+
+ "A cluster" must {
+
+ "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" ignore {
+
+ //wait till node 1 has started.
+ barrier("start-node1", NrOfNodes) {
+ node.start()
+ }
+
+ //wait till ndoe 2 has started.
+ barrier("start-node2", NrOfNodes) {}
+
+ //wait till node 3 has started.
+ barrier("start-node3", NrOfNodes) {}
+
+ //wait till an actor reference on node 2 has become available.
+ barrier("get-ref-to-actor-on-node2", NrOfNodes) {}
+
+ //wait till the node 2 has send a message to the replica's.
+ barrier("send-message-from-node2-to-replicas", NrOfNodes) {}
+
+ node.shutdown()
+ }
+ }
+
+ override def beforeAll() {
+ startLocalCluster()
+ }
+
+ override def afterAll() {
+ shutdownLocalCluster()
+ }
+}
+
+class RoundRobin3ReplicasMultiJvmNode2 extends WordSpec with MustMatchers {
+ import RoundRobin3ReplicasMultiJvmSpec._
+ import Cluster._
+
+ "A cluster" must {
+
+ "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" ignore {
+
+ //wait till node 1 has started.
+ barrier("start-node1", NrOfNodes) {}
+
+ //wait till node 2 has started.
+ barrier("start-node2", NrOfNodes) {
+ node.start()
+ }
+
+ //wait till node 3 has started.
+ barrier("start-node3", NrOfNodes) {}
+
+ //check if the actorRef is the expected remoteActorRef.
+ var hello: ActorRef = null
+ barrier("get-ref-to-actor-on-node2", NrOfNodes) {
+ hello = Actor.actorOf[HelloWorld]("service-hello")
+ hello must not equal (null)
+ hello.address must equal("service-hello")
+ hello.isInstanceOf[ClusterActorRef] must be(true)
+ }
+
+ barrier("send-message-from-node2-to-replicas", NrOfNodes) {
+ //todo: is there a reason to check for null again since it already has been done in the previous block.
+ hello must not equal (null)
+
+ val replies = collection.mutable.Map.empty[String, Int]
+ def count(reply: String) = {
+ if (replies.get(reply).isEmpty) replies.put(reply, 1)
+ else replies.put(reply, replies(reply) + 1)
+ }
+
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node2")))
+ count((hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node3")))
+
+ replies("World from node [node1]") must equal(4)
+ replies("World from node [node2]") must equal(4)
+ replies("World from node [node3]") must equal(4)
+ }
+
+ node.shutdown()
+ }
+ }
+}
+
+class RoundRobin3ReplicasMultiJvmNode3 extends WordSpec with MustMatchers {
+ import RoundRobin3ReplicasMultiJvmSpec._
+ import Cluster._
+
+ "A cluster" must {
+
+ "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" ignore {
+ barrier("start-node1", NrOfNodes) {}
+
+ barrier("start-node2", NrOfNodes) {}
+
+ barrier("start-node3", NrOfNodes) {
+ node.start()
+ }
+
+ barrier("get-ref-to-actor-on-node2", NrOfNodes) {}
+
+ barrier("send-message-from-node2-to-replicas", NrOfNodes) {}
+
+ node.shutdown()
+ }
+ }
+}
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf
new file mode 100644
index 0000000000..0a5f18c2b9
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf
@@ -0,0 +1,5 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.opts
new file mode 100644
index 0000000000..a88c260d8c
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf
new file mode 100644
index 0000000000..0a5f18c2b9
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf
@@ -0,0 +1,5 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.opts
new file mode 100644
index 0000000000..f1e01f253d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node2 -Dakka.cluster.port=9992
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf
new file mode 100644
index 0000000000..0a5f18c2b9
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf
@@ -0,0 +1,5 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.opts
new file mode 100644
index 0000000000..202496ad31
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node3 -Dakka.cluster.port=9993
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf
new file mode 100644
index 0000000000..0a5f18c2b9
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf
@@ -0,0 +1,5 @@
+akka.enabled-modules = ["cluster"]
+akka.event-handler-level = "DEBUG"
+akka.actor.deployment.service-hello.router = "round-robin"
+akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+akka.actor.deployment.service-hello.clustered.replicas = 1
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.opts b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.opts
new file mode 100644
index 0000000000..8c875faf53
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.opts
@@ -0,0 +1 @@
+-Dakka.cluster.nodename=node4 -Dakka.cluster.port=9994
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmSpec.scala
new file mode 100644
index 0000000000..30a1903096
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmSpec.scala
@@ -0,0 +1,144 @@
+package akka.cluster.routing.roundrobin_failover
+
+import akka.config.Config
+import akka.cluster._
+import akka.actor.{ ActorRef, Actor }
+
+object RoundRobinFailoverMultiJvmSpec {
+
+ val NrOfNodes = 2
+
+ class SomeActor extends Actor with Serializable {
+ println("---------------------------------------------------------------------------")
+ println("SomeActor has been created on node [" + Config.nodename + "]")
+ println("---------------------------------------------------------------------------")
+
+ def receive = {
+ case "identify" ⇒ {
+ println("The node received the 'identify' command")
+ self.reply(Config.nodename)
+ }
+ case "shutdown" ⇒ {
+ println("The node received the 'shutdown' command")
+ Cluster.node.shutdown()
+ }
+ }
+ }
+}
+
+class RoundRobinFailoverMultiJvmNode1 extends MasterClusterTestNode {
+
+ import RoundRobinFailoverMultiJvmSpec._
+
+ val testNodes = NrOfNodes
+
+ "foo" must {
+ "bla" in {
+ Cluster.node.start()
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+
+ println("Getting reference to service-hello actor")
+ var hello: ActorRef = null
+ Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes) {
+ hello = Actor.actorOf[SomeActor]("service-hello")
+ }
+
+ println("Successfully acquired reference")
+
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ Cluster.node.shutdown()
+ }
+ }
+}
+
+class RoundRobinFailoverMultiJvmNode2 extends ClusterTestNode {
+
+ import RoundRobinFailoverMultiJvmSpec._
+
+ "foo" must {
+ "bla" in {
+ println("Started Zookeeper Node")
+ Cluster.node.start()
+ println("Waiting to begin")
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+ println("Begin!")
+
+ Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes).await()
+
+ // ============= the real testing =================
+ /*
+ val actor = Actor.actorOf[SomeActor]("service-hello")
+ val firstTimeResult = (actor ? "identify").get
+ val secondTimeResult = (actor ? "identify").get
+ //since there are only 2 nodes, the identity should not have changed.
+ assert(firstTimeResult == secondTimeResult)
+
+ //if we now terminate the node that
+ actor ! "shutdown"
+
+ //todo: do some waiting
+ println("Doing some sleep")
+ try {
+ Thread.sleep(4000) //nasty.. but ok for now.
+ println("Finished doing sleep")
+ } finally {
+ println("Ended the Thread.sleep method somehow..")
+ }
+
+ //now we should get a different node that responds to us since there was a failover.
+ val thirdTimeResult = (actor ? "identify").get
+ assert(!(firstTimeResult == thirdTimeResult)) */
+ // ==================================================
+
+ println("Waiting to end")
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ println("Shutting down ClusterNode")
+ Cluster.node.shutdown()
+ }
+ }
+}
+
+/*
+class RoundRobinFailoverMultiJvmNode3 extends SlaveNode {
+
+ import RoundRobinFailoverMultiJvmSpec._
+
+ "foo" must {
+ "bla" in {
+ println("Started Zookeeper Node")
+ Cluster.node.start()
+ println("Waiting to begin")
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+ println("Begin!")
+
+ Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes).await()
+
+ println("Waiting to end")
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ println("Shutting down ClusterNode")
+ Cluster.node.shutdown()
+ }
+ }
+}
+
+class RoundRobinFailoverMultiJvmNode4 extends SlaveNode {
+
+ import RoundRobinFailoverMultiJvmSpec._
+
+ "foo" must {
+ "bla" in {
+ println("Started Zookeeper Node")
+ Cluster.node.start()
+ println("Waiting to begin")
+ Cluster.barrier("waiting-for-begin", NrOfNodes).await()
+ println("Begin!")
+
+ Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes).await()
+
+ println("Waiting to end")
+ Cluster.barrier("waiting-to-end", NrOfNodes).await()
+ println("Shutting down ClusterNode")
+ Cluster.node.shutdown()
+ }
+ }
+} */
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/questions.txt b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/questions.txt
new file mode 100644
index 0000000000..b02272c30d
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/questions.txt
@@ -0,0 +1,6 @@
+What does clustered home mean?
+
+akka.actor.deployment.service-hello.clustered.home = "node:node1"
+
+If a node fails, it should transparently be redeployed on a different node. So actors imho are homeless.. they run
+wherever the grid deploys them.
\ No newline at end of file
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/testing-design-improvements.txt b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/testing-design-improvements.txt
new file mode 100644
index 0000000000..823410999f
--- /dev/null
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/testing-design-improvements.txt
@@ -0,0 +1,54 @@
+- It would be nice if the .conf files somehow could be integrated in the scala file
+
+object SomeNode extends ClusterNodeWithConf{
+ def config() = "
+ akka.event-handler-level = "DEBUG"
+ akka.actor.deployment.service-hello.router = "round-robin"
+ akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"]
+ akka.actor.deployment.service-hello.clustered.replicas = 1"
+ }
+}
+
+- It would be nice if the .opts file somehow could be integrated in the scala file.
+
+object SomeNode extends ClusterNodeWithOpts{
+ def opts() = -Dakka.cluster.nodename=node1 -Dakka.cluster.port=9991
+}
+
+- It should be transparent which node starts/stops the cluster. Perhaps some kind of 'before the world starts' and
+'after the world ended' logic could be added. The consequence is that there are mixed responsibilities in a node.
+
+- A node has the mixed responsibity of being part of the grid and doing checks. It would be nice if one could create
+cluster nodes very easily (just spawn a jvm and everything will be copied on them) and if one could create 'client nodes'
+that communicate with the grid and do their validations.
+
+- Each node has been expressed in code, so it is very hard to either use a large number of nodes (lots of code) of to change
+the number of nodes without changes all the code. It would be nice if one could say: I want 100 jvm instances with this
+specification.
+
+- There is a lot of waiting for each other, but it would be nice if each node could say this:
+ waitForGo.
+
+so you get something like:
+
+object God{
+ def beforeBegin(){
+ ZooKeeper.start()
+ }
+
+ def afterEnd{
+ ZooKeeper.stop()
+ }
+}
+
+class SomeNode extends ClusterTestNode{
+ "foo" must {
+ "bla" in {
+ waitForGo()
+
+ ..now do testing logic.
+ }
+ }
+}
+
+
diff --git a/akka-cluster/src/test/scala/akka/cluster/sample/PingPongMultiJvmExample.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/sample/PingPongMultiJvmExample.scala
similarity index 100%
rename from akka-cluster/src/test/scala/akka/cluster/sample/PingPongMultiJvmExample.scala
rename to akka-cluster/src/multi-jvm/scala/akka/cluster/sample/PingPongMultiJvmExample.scala
diff --git a/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala b/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala
index e0b7452af4..c267bc6f98 100644
--- a/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala
+++ b/akka-cluster/src/test/scala/akka/cluster/TransactionLogSpec.scala
@@ -32,31 +32,31 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
"A Transaction Log" should {
"be able to record entries - synchronous" in {
val uuid = (new UUID).toString
- val txlog = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog = TransactionLog.newLogFor(uuid, false, null)
val entry = "hello".getBytes("UTF-8")
txlog.recordEntry(entry)
}
"be able to record and delete entries - synchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, false, null)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
txlog1.recordEntry(entry)
txlog1.delete
txlog1.close
- intercept[BKNoSuchLedgerExistsException](TransactionLog.logFor(uuid, false, null, JavaSerializer))
+ intercept[BKNoSuchLedgerExistsException](TransactionLog.logFor(uuid, false, null))
}
"be able to record entries and read entries with 'entriesInRange' - synchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, false, null)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
txlog1.recordEntry(entry)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, false, null, JavaSerializer)
+ val txlog2 = TransactionLog.logFor(uuid, false, null)
val entries = txlog2.entriesInRange(0, 1).map(bytes ⇒ new String(bytes, "UTF-8"))
entries.size must equal(2)
entries(0) must equal("hello")
@@ -66,15 +66,15 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
"be able to record entries and read entries with 'entries' - synchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, false, null)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
txlog1.recordEntry(entry)
txlog1.recordEntry(entry)
txlog1.recordEntry(entry)
- txlog1.close
+ // txlog1.close // should work without txlog.close
- val txlog2 = TransactionLog.logFor(uuid, false, null, JavaSerializer)
+ val txlog2 = TransactionLog.logFor(uuid, false, null)
val entries = txlog2.entries.map(bytes ⇒ new String(bytes, "UTF-8"))
entries.size must equal(4)
entries(0) must equal("hello")
@@ -86,7 +86,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
"be able to record a snapshot - synchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, false, null)
val snapshot = "snapshot".getBytes("UTF-8")
txlog1.recordSnapshot(snapshot)
txlog1.close
@@ -94,7 +94,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
"be able to record and read a snapshot and following entries - synchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, false, null)
val snapshot = "snapshot".getBytes("UTF-8")
txlog1.recordSnapshot(snapshot)
@@ -105,9 +105,9 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
txlog1.recordEntry(entry)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, false, null, JavaSerializer)
- val (snapshotAsBytes, entriesAsBytes) = txlog2.toByteArraysLatestSnapshot
- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
+ val txlog2 = TransactionLog.logFor(uuid, false, null)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
entries.size must equal(4)
@@ -120,7 +120,7 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
"be able to record entries then a snapshot then more entries - and then read from the snapshot and the following entries - synchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, false, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, false, null)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
@@ -134,9 +134,9 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
txlog1.recordEntry(entry)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, false, null, JavaSerializer)
- val (snapshotAsBytes, entriesAsBytes) = txlog2.toByteArraysLatestSnapshot
- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
+ val txlog2 = TransactionLog.logFor(uuid, false, null)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
entries.size must equal(2)
@@ -149,123 +149,154 @@ class TransactionLogSpec extends WordSpec with MustMatchers with BeforeAndAfterA
"A Transaction Log" should {
"be able to record entries - asynchronous" in {
val uuid = (new UUID).toString
- val txlog = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog = TransactionLog.newLogFor(uuid, true, null)
val entry = "hello".getBytes("UTF-8")
txlog.recordEntry(entry)
- Thread.sleep(100)
+ Thread.sleep(200)
txlog.close
}
"be able to record and delete entries - asynchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, true, null)
+ Thread.sleep(200)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.delete
- Thread.sleep(100)
- intercept[BKNoSuchLedgerExistsException](TransactionLog.logFor(uuid, true, null, JavaSerializer))
+ Thread.sleep(200)
+ intercept[BKNoSuchLedgerExistsException](TransactionLog.logFor(uuid, true, null))
}
"be able to record entries and read entries with 'entriesInRange' - asynchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, true, null)
+ Thread.sleep(200)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
- Thread.sleep(100)
+ Thread.sleep(200)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, true, null, JavaSerializer)
+ val txlog2 = TransactionLog.logFor(uuid, true, null)
+ Thread.sleep(200)
val entries = txlog2.entriesInRange(0, 1).map(bytes ⇒ new String(bytes, "UTF-8"))
+ Thread.sleep(200)
entries.size must equal(2)
entries(0) must equal("hello")
entries(1) must equal("hello")
- Thread.sleep(100)
+ Thread.sleep(200)
txlog2.close
}
"be able to record entries and read entries with 'entries' - asynchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, true, null)
+ Thread.sleep(200)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
- Thread.sleep(100)
+ Thread.sleep(200)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, true, null, JavaSerializer)
+ val txlog2 = TransactionLog.logFor(uuid, true, null)
val entries = txlog2.entries.map(bytes ⇒ new String(bytes, "UTF-8"))
+ Thread.sleep(200)
entries.size must equal(4)
entries(0) must equal("hello")
entries(1) must equal("hello")
entries(2) must equal("hello")
entries(3) must equal("hello")
- Thread.sleep(100)
+ Thread.sleep(200)
txlog2.close
}
"be able to record a snapshot - asynchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, true, null)
+ Thread.sleep(200)
val snapshot = "snapshot".getBytes("UTF-8")
txlog1.recordSnapshot(snapshot)
- Thread.sleep(100)
+ Thread.sleep(200)
txlog1.close
}
"be able to record and read a snapshot and following entries - asynchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, true, null)
+ Thread.sleep(200)
val snapshot = "snapshot".getBytes("UTF-8")
txlog1.recordSnapshot(snapshot)
+ Thread.sleep(200)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
- Thread.sleep(100)
+ Thread.sleep(200)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, true, null, JavaSerializer)
- val (snapshotAsBytes, entriesAsBytes) = txlog2.toByteArraysLatestSnapshot
- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
+ val txlog2 = TransactionLog.logFor(uuid, true, null)
+ Thread.sleep(200)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+ Thread.sleep(200)
+ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
+ Thread.sleep(200)
entries.size must equal(4)
entries(0) must equal("hello")
entries(1) must equal("hello")
entries(2) must equal("hello")
entries(3) must equal("hello")
- Thread.sleep(100)
+ Thread.sleep(200)
txlog2.close
}
"be able to record entries then a snapshot then more entries - and then read from the snapshot and the following entries - asynchronous" in {
val uuid = (new UUID).toString
- val txlog1 = TransactionLog.newLogFor(uuid, true, null, JavaSerializer)
+ val txlog1 = TransactionLog.newLogFor(uuid, true, null)
+ Thread.sleep(200)
val entry = "hello".getBytes("UTF-8")
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
+
val snapshot = "snapshot".getBytes("UTF-8")
txlog1.recordSnapshot(snapshot)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
+ Thread.sleep(200)
txlog1.recordEntry(entry)
- Thread.sleep(100)
+ Thread.sleep(200)
txlog1.close
- val txlog2 = TransactionLog.logFor(uuid, true, null, JavaSerializer)
- val (snapshotAsBytes, entriesAsBytes) = txlog2.toByteArraysLatestSnapshot
- new String(snapshotAsBytes, "UTF-8") must equal("snapshot")
+ val txlog2 = TransactionLog.logFor(uuid, true, null)
+ Thread.sleep(200)
+ val (snapshotAsBytes, entriesAsBytes) = txlog2.latestSnapshotAndSubsequentEntries
+ Thread.sleep(200)
+ new String(snapshotAsBytes.getOrElse(fail("No snapshot")), "UTF-8") must equal("snapshot")
val entries = entriesAsBytes.map(bytes ⇒ new String(bytes, "UTF-8"))
+ Thread.sleep(200)
entries.size must equal(2)
entries(0) must equal("hello")
entries(1) must equal("hello")
- Thread.sleep(100)
+ Thread.sleep(200)
txlog2.close
}
}
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode1.conf b/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode1.conf
deleted file mode 100644
index 480c30c09d..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode1.conf
+++ /dev/null
@@ -1 +0,0 @@
-akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode2.conf b/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode2.conf
deleted file mode 100644
index 480c30c09d..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode2.conf
+++ /dev/null
@@ -1 +0,0 @@
-akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode3.conf b/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode3.conf
deleted file mode 100644
index 480c30c09d..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/automatic/MigrationAutomaticMultiJvmNode3.conf
+++ /dev/null
@@ -1 +0,0 @@
-akka.event-handler-level = "DEBUG"
diff --git a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmSpec.scala b/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmSpec.scala
deleted file mode 100644
index a887328745..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/api/migration/explicit/MigrationExplicitMultiJvmSpec.scala
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * Copyright (C) 2009-2011 Scalable Solutions AB
- */
-
-package akka.cluster.api.migration.explicit
-
-import org.scalatest.WordSpec
-import org.scalatest.matchers.MustMatchers
-import org.scalatest.BeforeAndAfterAll
-
-import akka.actor._
-import Actor._
-import akka.cluster._
-import ChangeListener._
-import Cluster._
-import akka.config.Config
-import akka.serialization.Serialization
-
-import java.util.concurrent._
-
-object MigrationExplicitMultiJvmSpec {
- var NrOfNodes = 2
-
- class HelloWorld extends Actor with Serializable {
- def receive = {
- case "Hello" ⇒
- self.reply("World from node [" + Config.nodename + "]")
- }
- }
-}
-
-class MigrationExplicitMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
- import MigrationExplicitMultiJvmSpec._
-
- "A cluster" must {
-
- "be able to migrate an actor from one node to another" in {
-
- barrier("start-node-1", NrOfNodes) {
- node.start()
- }
-
- barrier("start-node-2", NrOfNodes) {
- }
-
- barrier("store-1-in-node-1", NrOfNodes) {
- val serializer = Serialization.serializerFor(classOf[HelloWorld]).fold(x ⇒ fail("No serializer found"), s ⇒ s)
- node.store(actorOf[HelloWorld]("hello-world"), serializer)
- }
-
- barrier("use-1-in-node-2", NrOfNodes) {
- }
-
- barrier("migrate-from-node2-to-node1", NrOfNodes) {
- }
-
- barrier("check-actor-is-moved-to-node1", NrOfNodes) {
- node.isInUseOnNode("hello-world") must be(true)
-
- val actorRef = Actor.registry.local.actorFor("hello-world").getOrElse(fail("Actor should have been in the local actor registry"))
- actorRef.address must be("hello-world")
- (actorRef ? "Hello").as[String].get must be("World from node [node1]")
- }
-
- node.shutdown()
- }
- }
-
- override def beforeAll() = {
- startLocalCluster()
- }
-
- override def afterAll() = {
- shutdownLocalCluster()
- }
-}
-
-class MigrationExplicitMultiJvmNode2 extends WordSpec with MustMatchers {
- import MigrationExplicitMultiJvmSpec._
-
- "A cluster" must {
-
- "be able to migrate an actor from one node to another" in {
-
- barrier("start-node-1", NrOfNodes) {
- }
-
- barrier("start-node-2", NrOfNodes) {
- node.start()
- }
-
- barrier("store-1-in-node-1", NrOfNodes) {
- }
-
- barrier("use-1-in-node-2", NrOfNodes) {
- val actorOrOption = node.use("hello-world")
- if (actorOrOption.isEmpty) fail("Actor could not be retrieved")
-
- val actorRef = actorOrOption.get
- actorRef.address must be("hello-world")
-
- (actorRef ? "Hello").as[String].get must be("World from node [node2]")
- }
-
- barrier("migrate-from-node2-to-node1", NrOfNodes) {
- node.migrate(NodeAddress(node.nodeAddress.clusterName, "node1"), "hello-world")
- Thread.sleep(2000)
- }
-
- barrier("check-actor-is-moved-to-node1", NrOfNodes) {
- }
-
- node.shutdown()
- }
- }
-}
diff --git a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode1.conf b/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode1.conf
deleted file mode 100644
index 946238d603..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode1.conf
+++ /dev/null
@@ -1 +0,0 @@
-test.name = "node1"
diff --git a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode2.conf b/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode2.conf
deleted file mode 100644
index deeeb05a48..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmNode2.conf
+++ /dev/null
@@ -1 +0,0 @@
-test.name = "node2"
diff --git a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmSpec.scala b/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmSpec.scala
deleted file mode 100644
index e3980dc44b..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/multijvmtestsample/SampleMultiJvmSpec.scala
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Copyright (C) 2009-2011 Scalable Solutions AB
- */
-
-package akka.cluster.multijvmtestsample
-
-import org.scalatest.WordSpec
-import org.scalatest.matchers.MustMatchers
-import org.scalatest.BeforeAndAfterAll
-
-import akka.cluster._
-
-object SampleMultiJvmSpec {
- val NrOfNodes = 2
-}
-
-class SampleMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
- import SampleMultiJvmSpec._
-
- override def beforeAll() = {
- Cluster.startLocalCluster()
- }
-
- override def afterAll() = {
- Cluster.shutdownLocalCluster()
- }
-
- def resetCluster(): Unit = {
- import akka.cluster.zookeeper._
- import akka.util.Helpers.ignore
- import org.I0Itec.zkclient.exception.ZkNoNodeException
- val zkClient = Cluster.newZkClient
- ignore[ZkNoNodeException](zkClient.deleteRecursive("/" + Cluster.name))
- ignore[ZkNoNodeException](zkClient.deleteRecursive(ZooKeeperBarrier.BarriersNode))
- zkClient.close
- }
-
- "A cluster" must {
-
- "have jvm options" in {
- System.getProperty("akka.cluster.nodename", "") must be("node1")
- System.getProperty("akka.cluster.port", "") must be("9991")
- akka.config.Config.config.getString("test.name", "") must be("node1")
- }
-
- "be able to start all nodes" in {
- Cluster.barrier("start", NrOfNodes) {
- Cluster.node.start()
- }
- Cluster.node.isRunning must be(true)
- Cluster.node.shutdown()
- }
- }
-}
-
-class SampleMultiJvmNode2 extends WordSpec with MustMatchers {
- import SampleMultiJvmSpec._
-
- "A cluster" must {
-
- "have jvm options" in {
- System.getProperty("akka.cluster.nodename", "") must be("node2")
- System.getProperty("akka.cluster.port", "") must be("9992")
- akka.config.Config.config.getString("test.name", "") must be("node2")
- }
-
- "be able to start all nodes" in {
- Cluster.barrier("start", NrOfNodes) {
- Cluster.node.start()
- }
- Cluster.node.isRunning must be(true)
- Cluster.node.shutdown()
- }
- }
-}
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmSpec.scala b/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmSpec.scala
deleted file mode 100644
index 668acb3376..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmSpec.scala
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * Copyright (C) 2009-2011 Scalable Solutions AB
- */
-
-package akka.cluster.routing.roundrobin_1_replica
-
-import org.scalatest.WordSpec
-import org.scalatest.matchers.MustMatchers
-import org.scalatest.BeforeAndAfterAll
-
-import org.apache.bookkeeper.client.{ BookKeeper, BKException }
-import BKException._
-
-import akka.cluster._
-import akka.actor._
-import akka.actor.Actor._
-import akka.config.Config
-
-object RoundRobin1ReplicaMultiJvmSpec {
- val NrOfNodes = 2
-
- class HelloWorld extends Actor with Serializable {
- def receive = {
- case "Hello" ⇒
- self.reply("World from node [" + Config.nodename + "]")
- }
- }
-}
-
-class RoundRobin1ReplicaMultiJvmNode1 extends WordSpec with MustMatchers with BeforeAndAfterAll {
- import RoundRobin1ReplicaMultiJvmSpec._
-
- private var bookKeeper: BookKeeper = _
- // private var localBookKeeper: LocalBookKeeper = _
-
- "A cluster" must {
-
- "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" in {
- System.getProperty("akka.cluster.nodename", "") must be("node1")
- System.getProperty("akka.cluster.port", "") must be("9991")
-
- Cluster.barrier("start-node1", NrOfNodes) {
- Cluster.node.start()
- }
-
- Cluster.barrier("start-node2", NrOfNodes) {}
-
- Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes) {}
-
- Cluster.barrier("send-message-from-node2-to-node1", NrOfNodes) {}
-
- Cluster.node.shutdown()
- }
- }
-
- override def beforeAll() = {
- Cluster.startLocalCluster()
- // LocalBookKeeperEnsemble.start()
- }
-
- override def afterAll() = {
- Cluster.shutdownLocalCluster()
- // TransactionLog.shutdown()
- // LocalBookKeeperEnsemble.shutdown()
- }
-}
-
-class RoundRobin1ReplicaMultiJvmNode2 extends WordSpec with MustMatchers {
- import RoundRobin1ReplicaMultiJvmSpec._
-
- "A cluster" must {
-
- "create clustered actor, get a 'local' actor on 'home' node and a 'ref' to actor on remote node" in {
- System.getProperty("akka.cluster.nodename", "") must be("node2")
- System.getProperty("akka.cluster.port", "") must be("9992")
-
- Cluster.barrier("start-node1", NrOfNodes) {}
-
- Cluster.barrier("start-node2", NrOfNodes) {
- Cluster.node.start()
- }
-
- var hello: ActorRef = null
- Cluster.barrier("get-ref-to-actor-on-node2", NrOfNodes) {
- hello = Actor.actorOf[HelloWorld]("service-hello")
- hello must not equal (null)
- hello.address must equal("service-hello")
- hello.isInstanceOf[ClusterActorRef] must be(true)
- }
-
- Cluster.barrier("send-message-from-node2-to-node1", NrOfNodes) {
- hello must not equal (null)
- val reply = (hello ? "Hello").as[String].getOrElse(fail("Should have recieved reply from node1"))
- reply must equal("World from node [node1]")
- }
-
- Cluster.node.shutdown()
- }
- }
-}
diff --git a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode3.conf b/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode3.conf
deleted file mode 100644
index b96297f0c4..0000000000
--- a/akka-cluster/src/test/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode3.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-akka.event-handler-level = "DEBUG"
-akka.actor.deployment.service-hello.router = "round-robin"
-akka.actor.deployment.service-hello.clustered.home = "node:node1"
-akka.actor.deployment.service-hello.clustered.replicas = 2
-akka.actor.deployment.service-hello.clustered.stateless = on
diff --git a/akka-cluster/src/test/scala/akka/cluster/storage/InMemoryStorageSpec.scala b/akka-cluster/src/test/scala/akka/cluster/storage/InMemoryStorageSpec.scala
new file mode 100755
index 0000000000..4f92684ba0
--- /dev/null
+++ b/akka-cluster/src/test/scala/akka/cluster/storage/InMemoryStorageSpec.scala
@@ -0,0 +1,241 @@
+package akka.cluster.storage
+
+import org.scalatest.matchers.MustMatchers
+import org.scalatest.WordSpec
+import akka.cluster.storage.StorageTestUtils._
+
+class InMemoryStorageSpec extends WordSpec with MustMatchers {
+
+ "unversioned load" must {
+ "throw MissingDataException if non existing key" in {
+ val store = new InMemoryStorage()
+
+ try {
+ store.load("foo")
+ fail()
+ } catch {
+ case e: MissingDataException ⇒
+ }
+ }
+
+ "return VersionedData if key existing" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val value = "somevalue".getBytes
+ storage.insert(key, value)
+
+ val result = storage.load(key)
+ //todo: strange that the implicit store is not found
+ assertContent(key, value, result.version)(storage)
+ }
+ }
+
+ "exist" must {
+ "return true if value exists" in {
+ val store = new InMemoryStorage()
+ val key = "somekey"
+ store.insert(key, "somevalue".getBytes)
+ store.exists(key) must be(true)
+ }
+
+ "return false if value not exists" in {
+ val store = new InMemoryStorage()
+ store.exists("somekey") must be(false)
+ }
+ }
+
+ "versioned load" must {
+ "throw MissingDataException if non existing key" in {
+ val store = new InMemoryStorage()
+
+ try {
+ store.load("foo", 1)
+ fail()
+ } catch {
+ case e: MissingDataException ⇒
+ }
+ }
+
+ "return VersionedData if key existing and exact version match" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val value = "somevalue".getBytes
+ val storedVersion = storage.insert(key, value)
+
+ val loaded = storage.load(key, storedVersion)
+ assert(loaded.version == storedVersion)
+ org.junit.Assert.assertArrayEquals(value, loaded.data)
+ }
+
+ "throw BadVersionException is version too new" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val value = "somevalue".getBytes
+ val version = storage.insert(key, value)
+
+ try {
+ storage.load(key, version + 1)
+ fail()
+ } catch {
+ case e: BadVersionException ⇒
+ }
+ }
+
+ "throw BadVersionException is version too old" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val value = "somevalue".getBytes
+ val version = storage.insert(key, value)
+
+ try {
+ storage.load(key, version - 1)
+ fail()
+ } catch {
+ case e: BadVersionException ⇒
+ }
+ }
+ }
+
+ "insert" must {
+
+ "place a new value when non previously existed" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val oldValue = "oldvalue".getBytes
+ storage.insert(key, oldValue)
+
+ val result = storage.load(key)
+ assertContent(key, oldValue)(storage)
+ assert(InMemoryStorage.InitialVersion == result.version)
+ }
+
+ "throw MissingDataException when there already exists an entry with the same key" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val initialValue = "oldvalue".getBytes
+ val initialVersion = storage.insert(key, initialValue)
+
+ val newValue = "newValue".getBytes
+
+ try {
+ storage.insert(key, newValue)
+ fail()
+ } catch {
+ case e: DataExistsException ⇒
+ }
+
+ assertContent(key, initialValue, initialVersion)(storage)
+ }
+ }
+
+ "update" must {
+
+ "throw MissingDataException when no node exists" in {
+ val storage = new InMemoryStorage()
+
+ val key = "somekey"
+
+ try {
+ storage.update(key, "somevalue".getBytes, 1)
+ fail()
+ } catch {
+ case e: MissingDataException ⇒
+ }
+ }
+
+ "replace if previous value exists and no other updates have been done" in {
+ val storage = new InMemoryStorage()
+
+ //do the initial insert
+ val key = "foo"
+ val oldValue = "insert".getBytes
+ val initialVersion = storage.insert(key, oldValue)
+
+ //do the update the will be the cause of the conflict.
+ val newValue: Array[Byte] = "update".getBytes
+ val newVersion = storage.update(key, newValue, initialVersion)
+
+ assertContent(key, newValue, newVersion)(storage)
+ }
+
+ "throw BadVersionException when already overwritten" in {
+ val storage = new InMemoryStorage()
+
+ //do the initial insert
+ val key = "foo"
+ val oldValue = "insert".getBytes
+ val initialVersion = storage.insert(key, oldValue)
+
+ //do the update the will be the cause of the conflict.
+ val newValue = "otherupdate".getBytes
+ val newVersion = storage.update(key, newValue, initialVersion)
+
+ try {
+ storage.update(key, "update".getBytes, initialVersion)
+ fail()
+ } catch {
+ case e: BadVersionException ⇒
+ }
+
+ assertContent(key, newValue, newVersion)(storage)
+ }
+ }
+
+ "overwrite" must {
+
+ "throw MissingDataException when no node exists" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+
+ try {
+ storage.overwrite(key, "somevalue".getBytes)
+ fail()
+ } catch {
+ case e: MissingDataException ⇒
+ }
+
+ storage.exists(key) must be(false)
+ }
+
+ "succeed if previous value exist" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val oldValue = "oldvalue".getBytes
+ val newValue = "somevalue".getBytes
+
+ val initialVersion = storage.insert(key, oldValue)
+ val overwriteVersion = storage.overwrite(key, newValue)
+
+ assert(overwriteVersion == initialVersion + 1)
+ assertContent(key, newValue, overwriteVersion)(storage)
+ }
+ }
+
+ "insertOrOverwrite" must {
+ "insert if nothing was inserted before" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val value = "somevalue".getBytes
+
+ val version = storage.insertOrOverwrite(key, value)
+
+ assert(version == InMemoryStorage.InitialVersion)
+ assertContent(key, value, version)(storage)
+ }
+
+ "overwrite of something existed before" in {
+ val storage = new InMemoryStorage()
+ val key = "somekey"
+ val oldValue = "oldvalue".getBytes
+ val newValue = "somevalue".getBytes
+
+ val initialVersion = storage.insert(key, oldValue)
+
+ val overwriteVersion = storage.insertOrOverwrite(key, newValue)
+
+ assert(overwriteVersion == initialVersion + 1)
+ assertContent(key, newValue, overwriteVersion)(storage)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/akka-cluster/src/test/scala/akka/cluster/storage/StorageTestUtils.scala b/akka-cluster/src/test/scala/akka/cluster/storage/StorageTestUtils.scala
new file mode 100644
index 0000000000..ec83be5fc0
--- /dev/null
+++ b/akka-cluster/src/test/scala/akka/cluster/storage/StorageTestUtils.scala
@@ -0,0 +1,15 @@
+package akka.cluster.storage
+
+object StorageTestUtils {
+
+ def assertContent(key: String, expectedData: Array[Byte], expectedVersion: Long)(implicit storage: Storage) {
+ val found = storage.load(key)
+ assert(found.version == expectedVersion, "versions should match, found[" + found.version + "], expected[" + expectedVersion + "]")
+ org.junit.Assert.assertArrayEquals(expectedData, found.data)
+ }
+
+ def assertContent(key: String, expectedData: Array[Byte])(implicit storage: Storage) {
+ val found = storage.load(key)
+ org.junit.Assert.assertArrayEquals(expectedData, found.data)
+ }
+}
\ No newline at end of file
diff --git a/akka-cluster/src/test/scala/akka/cluster/storage/ZooKeeperStorageSpec.scala b/akka-cluster/src/test/scala/akka/cluster/storage/ZooKeeperStorageSpec.scala
new file mode 100644
index 0000000000..125556b254
--- /dev/null
+++ b/akka-cluster/src/test/scala/akka/cluster/storage/ZooKeeperStorageSpec.scala
@@ -0,0 +1,132 @@
+package akka.cluster.storage
+
+import org.scalatest.matchers.MustMatchers
+import akka.actor.Actor
+import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll, WordSpec }
+import org.I0Itec.zkclient.ZkServer
+//import zookeeper.AkkaZkClient
+import akka.cluster.storage.StorageTestUtils._
+import java.io.File
+import java.util.concurrent.atomic.AtomicLong
+
+class ZooKeeperStorageSpec extends WordSpec with MustMatchers with BeforeAndAfterAll with BeforeAndAfterEach {
+ val dataPath = "_akka_cluster/data"
+ val logPath = "_akka_cluster/log"
+ var zkServer: ZkServer = _
+ //var zkClient: AkkaZkClient = _
+ val idGenerator = new AtomicLong
+
+ def generateKey: String = {
+ "foo" + idGenerator.incrementAndGet()
+ }
+
+ override def beforeAll() {
+ /*new File(dataPath).delete()
+ new File(logPath).delete()
+
+ try {
+ zkServer = Cluster.startLocalCluster(dataPath, logPath)
+ Thread.sleep(5000)
+ Actor.cluster.start()
+ zkClient = Cluster.newZkClient()
+ } catch {
+ case e ⇒ e.printStackTrace()
+ }*/
+ }
+
+ override def afterAll() {
+ /*zkClient.close()
+ Actor.cluster.shutdown()
+ ClusterDeployer.shutdown()
+ Cluster.shutdownLocalCluster()
+ Actor.registry.local.shutdownAll() */
+ }
+
+ /*
+ "unversioned load" must {
+ "throw MissingDataException if non existing key" in {
+ val storage = new ZooKeeperStorage(zkClient)
+
+ try {
+ storage.load(generateKey)
+ fail()
+ } catch {
+ case e: MissingDataException ⇒
+ }
+ }
+
+ "return VersionedData if key existing" in {
+ val storage = new ZooKeeperStorage(zkClient)
+ val key = generateKey
+ val value = "somevalue".getBytes
+ storage.insert(key, value)
+
+ val result = storage.load(key)
+ //todo: strange that the implicit store is not found
+ assertContent(key, value, result.version)(storage)
+ }
+ } */
+
+ /*"overwrite" must {
+
+ "throw MissingDataException when there doesn't exist an entry to overwrite" in {
+ val storage = new ZooKeeperStorage(zkClient)
+ val key = generateKey
+ val value = "value".getBytes
+
+ try {
+ storage.overwrite(key, value)
+ fail()
+ } catch {
+ case e: MissingDataException ⇒
+ }
+
+ assert(!storage.exists(key))
+ }
+
+ "overwrite if there is an existing value" in {
+ val storage = new ZooKeeperStorage(zkClient)
+ val key = generateKey
+ val oldValue = "oldvalue".getBytes
+
+ storage.insert(key, oldValue)
+ val newValue = "newValue".getBytes
+
+ val result = storage.overwrite(key, newValue)
+ //assertContent(key, newValue, result.version)(storage)
+ }
+ }
+
+ "insert" must {
+
+ "place a new value when non previously existed" in {
+ val storage = new ZooKeeperStorage(zkClient)
+ val key = generateKey
+ val oldValue = "oldvalue".getBytes
+ storage.insert(key, oldValue)
+
+ val result = storage.load(key)
+ assertContent(key, oldValue)(storage)
+ assert(InMemoryStorage.InitialVersion == result.version)
+ }
+
+ "throw DataExistsException when there already exists an entry with the same key" in {
+ val storage = new ZooKeeperStorage(zkClient)
+ val key = generateKey
+ val oldValue = "oldvalue".getBytes
+
+ val initialVersion = storage.insert(key, oldValue)
+ val newValue = "newValue".getBytes
+
+ try {
+ storage.insert(key, newValue)
+ fail()
+ } catch {
+ case e: DataExistsException ⇒
+ }
+
+ assertContent(key, oldValue, initialVersion)(storage)
+ }
+ } */
+
+}
\ No newline at end of file
diff --git a/akka-docs/general/jmm.rst b/akka-docs/general/jmm.rst
index fd65ce3f28..df25f983c4 100644
--- a/akka-docs/general/jmm.rst
+++ b/akka-docs/general/jmm.rst
@@ -1,36 +1,55 @@
Akka and the Java Memory Model
================================
-Prior to Java 5, the Java Memory Model (JMM) was broken. It was possible to get all kinds of strange results like unpredictable merged writes made by concurrent executing threads, unexpected reordering of instructions, and even final fields were not guaranteed to be final. With Java 5 and JSR-133, the Java Memory Model is clearly specified. This specification makes it possible to write code that performs, but doesn't cause concurrency problems. The Java Memory Model is specified in 'happens before'-rules, e.g.:
+A major benefit of using the Typesafe Stack, including Scala and Akka, is that it simplifies the process of writing
+concurrent software. This article discusses how the Typesafe Stack, and Akka in particular, approaches shared memory
+in concurrent applications.
-* **monitor lock rule**: a release of a lock happens before every subsequent acquire of the same lock.
-* **volatile variable rule**: a write of a volatile variable happens before every subsequent read of the same volatile variable
+The Java Memory Model
+---------------------
+Prior to Java 5, the Java Memory Model (JMM) was ill defined. It was possible to get all kinds of strange results when
+shared memory was accessed by multiple threads, such as:
+* a thread not seeing values written by other threads: a visibility problem
+* a thread observing 'impossible' behavior of other threads, caused by instructions not being executed in the order
+ expected: an instruction reordering problem.
-The 'happens before'-rules clearly specify which visibility guarantees are provided on memory and which re-orderings are allowed. Without these rules it would not be possible to write concurrent and performant code in Java.
+With the implementation of JSR 133 in Java 5, a lot of these issues have been resolved. The JMM is a set of rules based
+on the "happens-before" relation, which constrain when one memory access must happen before another, and conversely,
+when they are allowed to happen out of order. Two examples of these rules are:
+* **The monitor lock rule:** a release of a lock happens before every subsequent acquire of the same lock.
+* **The volatile variable rule:** a write of a volatile variable happens before every subsequent read of the same volatile variable
+Although the JMM can seem complicated, the specification tries to find a balance between ease of use and the ability to
+write performant and scalable concurrent data structures.
Actors and the Java Memory Model
--------------------------------
+With the Actors implementation in Akka, there are two ways multiple threads can execute actions on shared memory:
+* if a message is sent to an actor (e.g. by another actor). In most cases messages are immutable, but if that message
+ is not a properly constructed immutable object, without a "happens before" rule, it would be possible for the receiver
+ to see partially initialized data structures and possibly even values out of thin air (longs/doubles).
+* if an actor makes changes to its internal state while processing a message, and accesses that state while processing
+ another message moments later. It is important to realize that with the actor model you don't get any guarantee that
+ the same thread will be executing the same actor for different messages.
-With the Actors implementation in Akka, there are 2 ways multiple threads can execute actions on shared memory over time:
-
-* if a message is send to an actor (e.g. by another actor). In most cases messages are immutable, but if that message is not a properly constructed immutable object, without happens before rules, the system still could be subject to instruction re-orderings and visibility problems (so a possible source of concurrency errors).
-* if an actor makes changes to its internal state in one 'receive' method and access that state while processing another message. With the actors model you don't get any guarantee that the same thread will be executing the same actor for different messages. Without a happens before relation between these actions, there could be another source of concurrency errors.
-
-To solve the 2 problems above, Akka adds the following 2 'happens before'-rules to the JMM:
-
-* **the actor send rule**: where the send of the message to an actor happens before the receive of the **same** actor.
-* **the actor subsequent processing rule**: where processing of one message happens before processing of the next message by the **same** actor.
-
+To prevent visibility and reordering problems on actors, Akka guarantees the following two "happens before" rules:
+* **The actor send rule:** the send of the message to an actor happens before the receive of that message by the same actor.
+* **The actor subsequent processing rule:** processing of one message happens before processing of the next message by the same actor.
Both rules only apply for the same actor instance and are not valid if different actors are used.
STM and the Java Memory Model
-----------------------------
+Akka's Software Transactional Memory (STM) also provides a "happens before" rule:
-The Akka STM also provides a happens before rule called:
-
-* **the transaction rule**: a commit on a transaction happens before every subsequent start of a transaction where there is at least 1 shared reference.
-
-How these rules are realized in Akka, is an implementation detail and can change over time (the exact details could even depend on the used configuration) but they will lift on the other JMM rules like the monitor lock rule or the volatile variable rule. Essentially this means that you, the Akka user, do not need to worry about adding synchronization to provide such a happens before relation, because it is the responsibility of Akka. So you have your hands free to deal with your problems and not that of the framework.
-
+* **The transactional reference rule:** a successful write during commit, on an transactional reference, happens before every
+ subsequent read of the same transactional reference.
+This rule looks a lot like the 'volatile variable' rule from the JMM. Currently the Akka STM only supports deferred writes,
+so the actual writing to shared memory is deferred until the transaction commits. Writes during the transaction are placed
+in a local buffer (the writeset of the transaction) and are not visible to other transactions. That is why dirty reads are
+not possible.
+How these rules are realized in Akka is an implementation detail and can change over time, and the exact details could
+even depend on the used configuration. But they will build on the other JMM rules like the monitor lock rule or the
+volatile variable rule. This means that you, the Akka user, do not need to worry about adding synchronization to provide
+such a "happens before" relation, because it is the responsibility of Akka. So you have your hands free to deal with your
+business logic, and the Akka framework makes sure that those rules are guaranteed on your behalf.
\ No newline at end of file
diff --git a/akka-docs/java/fault-tolerance.rst b/akka-docs/java/fault-tolerance.rst
index b89b3978b4..512e914d2b 100644
--- a/akka-docs/java/fault-tolerance.rst
+++ b/akka-docs/java/fault-tolerance.rst
@@ -117,7 +117,7 @@ The Actor’s supervision can be declaratively defined by creating a ‘Supervis
import static akka.config.Supervision.*;
import static akka.actor.Actors.*;
- Supervisor supervisor = new Supervisor(
+ Supervisor supervisor = Supervisor.apply(
new SupervisorConfig(
new AllForOneStrategy(new Class[]{Exception.class}, 3, 5000),
new Supervise[] {
@@ -141,13 +141,14 @@ MaximumNumberOfRestartsWithinTimeRangeReached message.
import static akka.actor.Actors.*;
import akka.event.JavaEventHandler;
- Procedure2 handler = new Procedure2() {
- public void apply(ActorRef ref, MaximumNumberOfRestartsWithinTimeRangeReached max) {
- JavaEventHandler.error(ref, max);
- }
- };
+ Procedure2 handler =
+ new Procedure2() {
+ public void apply(ActorRef ref, MaximumNumberOfRestartsWithinTimeRangeReached max) {
+ JavaEventHandler.error(ref, max);
+ }
+ };
- Supervisor supervisor = new Supervisor(
+ Supervisor supervisor = Supervisor.apply(
new SupervisorConfig(
new AllForOneStrategy(new Class[]{Exception.class}, 3, 5000),
new Supervise[] {
@@ -165,7 +166,7 @@ You can link and unlink actors from a declaratively defined supervisor using the
.. code-block:: java
- Supervisor supervisor = new Supervisor(...);
+ Supervisor supervisor = Supervisor.apply(...);
supervisor.link(..);
supervisor.unlink(..);
@@ -209,7 +210,7 @@ Here is an example:
import static akka.config.Supervision.*;
import static akka.actor.Actors.*;
- Supervisor supervisor = new Supervisor(
+ Supervisor supervisor = Supervisor.apply(
new SupervisorConfig(
new AllForOneStrategy(new Class[]{Exception.class}, 3, 5000),
new Supervise[] {
diff --git a/akka-docs/project/issue-tracking.rst b/akka-docs/project/issue-tracking.rst
index f5d43699f3..6afb23308f 100644
--- a/akka-docs/project/issue-tracking.rst
+++ b/akka-docs/project/issue-tracking.rst
@@ -29,10 +29,11 @@ In order to create tickets you need to do the following:
`Register here `_ then log in
+Then you also need to become a "Watcher" of the Akka space.
+
For Akka tickets:
`Link to create new ticket `__
-
For Akka Modules tickets:
`Link to create new ticket `__
@@ -49,8 +50,8 @@ Please submit a failing test on the following format:
import org.scalatest.matchers.MustMatchers
class Ticket001Spec extends WordSpec with MustMatchers {
-
- "An XXX" should {
+
+ "An XXX" must {
"do YYY" in {
1 must be (1)
}
diff --git a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableDispatcher.scala b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableDispatcher.scala
index de54b3fb16..42332ab205 100644
--- a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableDispatcher.scala
+++ b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableDispatcher.scala
@@ -30,10 +30,6 @@ sealed abstract class DurableMailboxStorage(mailboxFQN: String) {
//TODO take into consideration a mailboxConfig parameter so one can have bounded mboxes and capacity etc
def createFor(actor: ActorRef): AnyRef = {
EventHandler.debug(this, "Creating durable mailbox [%s] for [%s]".format(mailboxClass.getName, actor))
- val ctor = mailboxClass.getDeclaredConstructor(constructorSignature: _*)
- ctor.setAccessible(true)
- Some(ctor.newInstance(Array[AnyRef](actor): _*).asInstanceOf[AnyRef])
-
ReflectiveAccess.createInstance[AnyRef](mailboxClass, constructorSignature, Array[AnyRef](actor)) match {
case Right(instance) => instance
case Left(exception) =>
diff --git a/akka-samples/akka-sample-camel/config/akka.conf b/akka-samples/akka-sample-camel/config/akka.conf
new file mode 100644
index 0000000000..0bd7bd16a2
--- /dev/null
+++ b/akka-samples/akka-sample-camel/config/akka.conf
@@ -0,0 +1,20 @@
+####################
+# Akka Config File #
+####################
+
+akka {
+ version = "2.0-SNAPSHOT"
+
+ enabled-modules = ["camel", "http"]
+
+ time-unit = "seconds"
+
+ event-handlers = ["akka.event.EventHandler$DefaultListener"]
+
+ boot = ["sample.camel.Boot"]
+
+ http {
+ hostname = "localhost"
+ port = 9998
+ }
+}
diff --git a/akka-samples/akka-sample-camel/config/microkernel-server.xml b/akka-samples/akka-sample-camel/config/microkernel-server.xml
new file mode 100644
index 0000000000..6be6beec33
--- /dev/null
+++ b/akka-samples/akka-sample-camel/config/microkernel-server.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 300000
+ 2
+ false
+ 8443
+ 20000
+ 5000
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+ /
+
+ akka.http.AkkaRestServlet
+ /*
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ true
+ 1000
+
+
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/BeanImpl.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/BeanImpl.java
new file mode 100644
index 0000000000..6a5a064629
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/BeanImpl.java
@@ -0,0 +1,12 @@
+package sample.camel;
+
+/**
+ * @author Martin Krasser
+ */
+public class BeanImpl implements BeanIntf {
+
+ public String foo(String s) {
+ return "hello " + s;
+ }
+
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/BeanIntf.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/BeanIntf.java
new file mode 100644
index 0000000000..a7b2e6e6a4
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/BeanIntf.java
@@ -0,0 +1,10 @@
+package sample.camel;
+
+/**
+ * @author Martin Krasser
+ */
+public interface BeanIntf {
+
+ public String foo(String s);
+
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer1.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer1.java
new file mode 100644
index 0000000000..3e8ce1e20f
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer1.java
@@ -0,0 +1,15 @@
+package sample.camel;
+
+import org.apache.camel.Body;
+import org.apache.camel.Header;
+
+import akka.camel.consume;
+
+/**
+ * @author Martin Krasser
+ */
+public interface RemoteTypedConsumer1 {
+
+ @consume("jetty:http://localhost:6644/camel/remote-typed-actor-1")
+ public String foo(@Body String body, @Header("name") String header);
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer1Impl.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer1Impl.java
new file mode 100644
index 0000000000..3321ea08c0
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer1Impl.java
@@ -0,0 +1,13 @@
+package sample.camel;
+
+import akka.actor.TypedActor;
+
+/**
+ * @author Martin Krasser
+ */
+public class RemoteTypedConsumer1Impl implements RemoteTypedConsumer1 {
+
+ public String foo(String body, String header) {
+ return String.format("remote1: body=%s header=%s", body, header);
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer2.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer2.java
new file mode 100644
index 0000000000..ba093a1d96
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer2.java
@@ -0,0 +1,15 @@
+package sample.camel;
+
+import org.apache.camel.Body;
+import org.apache.camel.Header;
+import akka.camel.consume;
+
+/**
+ * @author Martin Krasser
+ */
+public interface RemoteTypedConsumer2 {
+
+ @consume("jetty:http://localhost:6644/camel/remote-typed-actor-2")
+ public String foo(@Body String body, @Header("name") String header);
+
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer2Impl.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer2Impl.java
new file mode 100644
index 0000000000..01420ffbee
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/RemoteTypedConsumer2Impl.java
@@ -0,0 +1,12 @@
+package sample.camel;
+
+/**
+ * @author Martin Krasser
+ */
+public class RemoteTypedConsumer2Impl implements RemoteTypedConsumer2 {
+
+ public String foo(String body, String header) {
+ return String.format("remote2: body=%s header=%s", body, header);
+ }
+
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer1.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer1.java
new file mode 100644
index 0000000000..6213fb8f09
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer1.java
@@ -0,0 +1,17 @@
+package sample.camel;
+
+import org.apache.camel.Body;
+import org.apache.camel.Header;
+
+import akka.camel.consume;
+
+/**
+ * @author Martin Krasser
+ */
+public interface TypedConsumer1 {
+ @consume("file:data/input/typed-actor")
+ public void foo(String body);
+
+ @consume("jetty:http://0.0.0.0:8877/camel/typed-actor")
+ public String bar(@Body String body, @Header("name") String header);
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer1Impl.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer1Impl.java
new file mode 100644
index 0000000000..b354872a27
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer1Impl.java
@@ -0,0 +1,21 @@
+package sample.camel;
+
+import org.apache.camel.Body;
+import org.apache.camel.Header;
+
+import akka.actor.TypedActor;
+
+/**
+ * @author Martin Krasser
+ */
+public class TypedConsumer1Impl implements TypedConsumer1 {
+
+ public void foo(String body) {
+ System.out.println("Received message:");
+ System.out.println(body);
+ }
+
+ public String bar(@Body String body, @Header("name") String header) {
+ return String.format("body=%s header=%s", body, header);
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer2.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer2.java
new file mode 100644
index 0000000000..9a39b534b5
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer2.java
@@ -0,0 +1,14 @@
+package sample.camel;
+
+import org.apache.camel.Body;
+import org.apache.camel.Header;
+import akka.camel.consume;
+
+/**
+ * @author Martin Krasser
+ */
+public interface TypedConsumer2 {
+
+ @consume("direct:default")
+ public String foo(String body);
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer2Impl.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer2Impl.java
new file mode 100644
index 0000000000..603c32b803
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/TypedConsumer2Impl.java
@@ -0,0 +1,11 @@
+package sample.camel;
+
+/**
+ * @author Martin Krasser
+ */
+public class TypedConsumer2Impl implements TypedConsumer2 {
+
+ public String foo(String body) {
+ return String.format("default: %s", body);
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java
new file mode 100644
index 0000000000..39d910fc28
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java
@@ -0,0 +1,20 @@
+package sample.camel;
+
+import akka.camel.Message;
+import akka.camel.UntypedConsumerActor;
+
+/**
+ * @author Martin Krasser
+ */
+public class UntypedConsumer1 extends UntypedConsumerActor {
+
+ public String getEndpointUri() {
+ return "direct:untyped-consumer-1";
+ }
+
+ public void onReceive(Object message) {
+ Message msg = (Message)message;
+ String body = msg.getBodyAs(String.class);
+ getContext().replySafe(String.format("received %s", body));
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/main/resources/context-jms.xml b/akka-samples/akka-sample-camel/src/main/resources/context-jms.xml
new file mode 100644
index 0000000000..12e4541be3
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/resources/context-jms.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml b/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml
new file mode 100644
index 0000000000..e4edcbc350
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/resources/context-standalone.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala
new file mode 100644
index 0000000000..98e8462ec4
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala
@@ -0,0 +1,161 @@
+package sample.camel
+
+import org.apache.camel.Exchange
+
+import akka.actor.{Actor, ActorRef, ActorRegistry}
+import akka.camel.{Ack, Failure, Producer, Message, Consumer}
+
+/**
+ * Client-initiated remote actor.
+ */
+class RemoteActor1 extends Actor with Consumer {
+ def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-1"
+
+ protected def receive = {
+ case msg: Message => self.reply(Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote1")))
+ }
+}
+
+/**
+ * Server-initiated remote actor.
+ */
+class RemoteActor2 extends Actor with Consumer {
+ def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-2"
+
+ protected def receive = {
+ case msg: Message => self.reply(Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote2")))
+ }
+}
+
+class Producer1 extends Actor with Producer {
+ def endpointUri = "direct:welcome"
+ override def oneway = false // default
+}
+
+class Consumer1 extends Actor with Consumer {
+ def endpointUri = "file:data/input/actor"
+
+ def receive = {
+ case msg: Message => println("received %s" format msg.bodyAs[String])
+ }
+}
+
+class Consumer2 extends Actor with Consumer {
+ def endpointUri = "jetty:http://0.0.0.0:8877/camel/default"
+
+ def receive = {
+ case msg: Message => self.reply("Hello %s" format msg.bodyAs[String])
+ }
+}
+
+class Consumer3(transformer: ActorRef) extends Actor with Consumer {
+ def endpointUri = "jetty:http://0.0.0.0:8877/camel/welcome"
+
+ def receive = {
+ case msg: Message => transformer.forward(msg.setBodyAs[String])
+ }
+}
+
+class Consumer4 extends Actor with Consumer {
+ def endpointUri = "jetty:http://0.0.0.0:8877/camel/stop"
+
+ def receive = {
+ case msg: Message => msg.bodyAs[String] match {
+ case "stop" => {
+ self.reply("Consumer4 stopped")
+ self.stop
+ }
+ case body => self.reply(body)
+ }
+ }
+}
+
+class Consumer5 extends Actor with Consumer {
+ def endpointUri = "jetty:http://0.0.0.0:8877/camel/start"
+
+ def receive = {
+ case _ => {
+ Actor.actorOf[Consumer4].start
+ self.reply("Consumer4 started")
+ }
+ }
+}
+
+class Transformer(producer: ActorRef) extends Actor {
+ protected def receive = {
+ case msg: Message => producer.forward(msg.transformBody( (body: String) => "- %s -" format body))
+ }
+}
+
+class Subscriber(name:String, uri: String) extends Actor with Consumer {
+ def endpointUri = uri
+
+ protected def receive = {
+ case msg: Message => println("%s received: %s" format (name, msg.body))
+ }
+}
+
+class Publisher(uri: String) extends Actor with Producer {
+ def endpointUri = uri
+ override def oneway = true
+}
+
+class PublisherBridge(uri: String, publisher: ActorRef) extends Actor with Consumer {
+ def endpointUri = uri
+
+ protected def receive = {
+ case msg: Message => {
+ publisher ! msg.bodyAs[String]
+ self.reply("message published")
+ }
+ }
+}
+
+class HttpConsumer(producer: ActorRef) extends Actor with Consumer {
+ def endpointUri = "jetty:http://0.0.0.0:8875/"
+
+ protected def receive = {
+ case msg => producer forward msg
+ }
+}
+
+class HttpProducer(transformer: ActorRef) extends Actor with Producer {
+ def endpointUri = "jetty://http://akka.io/?bridgeEndpoint=true"
+
+ override protected def receiveBeforeProduce = {
+ // only keep Exchange.HTTP_PATH message header (which needed by bridge endpoint)
+ case msg: Message => msg.setHeaders(msg.headers(Set(Exchange.HTTP_PATH)))
+ }
+
+ override protected def receiveAfterProduce = {
+ // do not reply but forward result to transformer
+ case msg => transformer forward msg
+ }
+}
+
+class HttpTransformer extends Actor {
+ protected def receive = {
+ case msg: Message => self.reply(msg.transformBody {body: String => body replaceAll ("Akka ", "AKKA ")})
+ case msg: Failure => self.reply(msg)
+ }
+}
+
+class FileConsumer extends Actor with Consumer {
+ def endpointUri = "file:data/input/actor?delete=true"
+ override def autoack = false
+
+ var counter = 0
+
+ def receive = {
+ case msg: Message => {
+ if (counter == 2) {
+ println("received %s" format msg.bodyAs[String])
+ self.reply(Ack)
+ } else {
+ println("rejected %s" format msg.bodyAs[String])
+ counter += 1
+ self.reply(Failure(new Exception("message number %s not accepted" format counter)))
+ }
+ }
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala
new file mode 100644
index 0000000000..5bf50a5e2c
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala
@@ -0,0 +1,108 @@
+package sample.camel
+
+import org.apache.camel.{Exchange, Processor}
+import org.apache.camel.builder.RouteBuilder
+import org.apache.camel.impl.DefaultCamelContext
+import org.apache.camel.spring.spi.ApplicationContextRegistry
+import org.springframework.context.support.ClassPathXmlApplicationContext
+
+import akka.actor.Actor._
+import akka.actor.{TypedActor, Supervisor}
+import akka.camel.CamelContextManager
+import akka.config.Supervision._
+
+/**
+ * @author Martin Krasser
+ */
+class Boot {
+
+ // -----------------------------------------------------------------------
+ // Basic example
+ // -----------------------------------------------------------------------
+
+ actorOf[Consumer1].start
+ actorOf[Consumer2].start
+
+ // Alternatively, use a supervisor for these actors
+ //val supervisor = Supervisor(
+ // SupervisorConfig(
+ // RestartStrategy(OneForOne, 3, 100, List(classOf[Exception])),
+ // Supervise(actorOf[Consumer1], Permanent) ::
+ // Supervise(actorOf[Consumer2], Permanent) :: Nil))
+
+ // -----------------------------------------------------------------------
+ // Custom Camel route example
+ // -----------------------------------------------------------------------
+
+ // Create CamelContext and a Spring-based registry
+ val context = new ClassPathXmlApplicationContext("/context-jms.xml", getClass)
+ val registry = new ApplicationContextRegistry(context)
+
+ // Use a custom Camel context and a custom touter builder
+ CamelContextManager.init(new DefaultCamelContext(registry))
+ CamelContextManager.mandatoryContext.addRoutes(new CustomRouteBuilder)
+
+ val producer = actorOf[Producer1]
+ val mediator = actorOf(new Transformer(producer))
+ val consumer = actorOf(new Consumer3(mediator))
+
+ producer.start
+ mediator.start
+ consumer.start
+
+ // -----------------------------------------------------------------------
+ // Asynchronous consumer-producer example (Akka homepage transformation)
+ // -----------------------------------------------------------------------
+
+ val httpTransformer = actorOf(new HttpTransformer).start
+ val httpProducer = actorOf(new HttpProducer(httpTransformer)).start
+ val httpConsumer = actorOf(new HttpConsumer(httpProducer)).start
+
+ // -----------------------------------------------------------------------
+ // Publish subscribe examples
+ // -----------------------------------------------------------------------
+
+ //
+ // Cometd example commented out because camel-cometd is broken since Camel 2.3
+ //
+
+ //val cometdUri = "cometd://localhost:8111/test/abc?baseResource=file:target"
+ //val cometdSubscriber = actorOf(new Subscriber("cometd-subscriber", cometdUri)).start
+ //val cometdPublisher = actorOf(new Publisher("cometd-publisher", cometdUri)).start
+
+ val jmsUri = "jms:topic:test"
+ val jmsSubscriber1 = actorOf(new Subscriber("jms-subscriber-1", jmsUri)).start
+ val jmsSubscriber2 = actorOf(new Subscriber("jms-subscriber-2", jmsUri)).start
+ val jmsPublisher = actorOf(new Publisher(jmsUri), "jms-publisher").start
+
+ //val cometdPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/cometd", cometdPublisher)).start
+ val jmsPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/jms", jmsPublisher)).start
+
+ // -----------------------------------------------------------------------
+ // Actor un-publishing and re-publishing example
+ // -----------------------------------------------------------------------
+
+ actorOf[Consumer4].start // POSTing "stop" to http://0.0.0.0:8877/camel/stop stops and unpublishes this actor
+ actorOf[Consumer5].start // POSTing any msg to http://0.0.0.0:8877/camel/start starts and published Consumer4 again.
+
+ // -----------------------------------------------------------------------
+ // Active object example
+ // -----------------------------------------------------------------------
+
+ //TypedActor.newInstance(classOf[TypedConsumer1], classOf[TypedConsumer1Impl])
+}
+
+/**
+ * @author Martin Krasser
+ */
+class CustomRouteBuilder extends RouteBuilder {
+ def configure {
+ val actorUri = "actor:%s" format classOf[Consumer2].getName
+ from("jetty:http://0.0.0.0:8877/camel/custom").to(actorUri)
+ from("direct:welcome").process(new Processor() {
+ def process(exchange: Exchange) {
+ exchange.getOut.setBody("Welcome %s" format exchange.getIn.getBody)
+ }
+ })
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala
new file mode 100644
index 0000000000..5c257f2ada
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala
@@ -0,0 +1,29 @@
+package sample.camel
+
+import akka.actor.Actor._
+import akka.actor.TypedActor
+import akka.camel.Message
+
+/**
+ * @author Martin Krasser
+ */
+object ClientApplication extends App {
+
+ /* TODO: fix remote example
+
+ val actor1 = remote.actorOf[RemoteActor1]("localhost", 7777).start
+ val actor2 = remote.actorFor("remote2", "localhost", 7777)
+
+ val typedActor1 =
+ TypedActor.newRemoteInstance(classOf[RemoteTypedConsumer1],classOf[RemoteTypedConsumer1Impl], "localhost", 7777)
+
+ val typedActor2 = remote.typedActorFor(classOf[RemoteTypedConsumer2], "remote3", "localhost", 7777)
+
+ println(actor1 !! Message("actor1")) // activates and publishes actor remotely
+ println(actor2 !! Message("actor2")) // actor already activated and published remotely
+
+ println(typedActor1.foo("x1", "y1")) // activates and publishes typed actor methods remotely
+ println(typedActor2.foo("x2", "y2")) // typed actor methods already activated and published remotely
+
+ */
+}
diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala
new file mode 100644
index 0000000000..af3d97eb13
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala
@@ -0,0 +1,28 @@
+package sample.camel
+
+import akka.actor.Actor._
+import akka.camel.CamelServiceManager
+import akka.actor.TypedActor
+import akka.actor.TypedActor.Configuration._
+
+/**
+ * @author Martin Krasser
+ */
+object ServerApplication extends App {
+ import CamelServiceManager._
+
+ /* TODO: fix remote example
+
+ startCamelService
+
+ val ua = actorOf[RemoteActor2].start
+ val ta = TypedActor.typedActorOf(
+ classOf[RemoteTypedConsumer2],
+ classOf[RemoteTypedConsumer2Impl], defaultConfiguration)
+
+ remote.start("localhost", 7777)
+ remote.register("remote2", ua)
+ remote.registerTypedActor("remote3", ta)
+
+ */
+}
diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala
new file mode 100644
index 0000000000..812d7f065a
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala
@@ -0,0 +1,129 @@
+package sample.camel
+
+import org.apache.camel.impl.{DefaultCamelContext, SimpleRegistry}
+import org.apache.camel.builder.RouteBuilder
+import org.apache.camel.spring.spi.ApplicationContextRegistry
+import org.springframework.context.support.ClassPathXmlApplicationContext
+
+import akka.actor.{Actor, TypedActor}
+import akka.actor.TypedActor.Configuration._
+import akka.camel._
+
+/**
+ * @author Martin Krasser
+ */
+object StandaloneApplication extends App {
+ import CamelContextManager._
+ import CamelServiceManager._
+
+ // 'externally' register typed actors
+ val registry = new SimpleRegistry
+ registry.put("sample", TypedActor.typedActorOf(classOf[BeanIntf], classOf[BeanImpl], defaultConfiguration))
+
+ // customize CamelContext
+ CamelContextManager.init(new DefaultCamelContext(registry))
+ CamelContextManager.mandatoryContext.addRoutes(new StandaloneApplicationRoute)
+
+ startCamelService
+
+ // access 'externally' registered typed actors
+ assert("hello msg1" == mandatoryContext.createProducerTemplate.requestBody("direct:test", "msg1"))
+
+ mandatoryService.awaitEndpointActivation(1) {
+ // 'internally' register typed actor (requires CamelService)
+ TypedActor.typedActorOf(classOf[TypedConsumer2], classOf[TypedConsumer2Impl], defaultConfiguration)
+ }
+
+ // access 'internally' (automatically) registered typed-actors
+ // (see @consume annotation value at TypedConsumer2.foo method)
+ assert("default: msg3" == mandatoryContext.createProducerTemplate.requestBody("direct:default", "msg3"))
+
+ stopCamelService
+
+ Actor.registry.local.shutdownAll
+}
+
+class StandaloneApplicationRoute extends RouteBuilder {
+ def configure = {
+ // route to typed actors (in SimpleRegistry)
+ from("direct:test").to("typed-actor:sample?method=foo")
+ }
+}
+
+object StandaloneSpringApplication extends App {
+ import CamelContextManager._
+
+ // load Spring application context
+ val appctx = new ClassPathXmlApplicationContext("/context-standalone.xml")
+
+ // We cannot use the CamelServiceManager to wait for endpoint activation
+ // because CamelServiceManager is started by the Spring application context.
+ // (and hence is not available for setting expectations on activations). This
+ // will be improved/enabled in upcoming releases.
+ Thread.sleep(1000)
+
+ // access 'externally' registered typed actors with typed-actor component
+ assert("hello msg3" == mandatoryTemplate.requestBody("direct:test3", "msg3"))
+
+ // access auto-started untyped consumer
+ assert("received msg3" == mandatoryTemplate.requestBody("direct:untyped-consumer-1", "msg3"))
+
+ appctx.close
+
+ Actor.registry.local.shutdownAll
+}
+
+class StandaloneSpringApplicationRoute extends RouteBuilder {
+ def configure = {
+ // routes to typed actor (in ApplicationContextRegistry)
+ from("direct:test3").to("typed-actor:ta?method=foo")
+ }
+}
+
+object StandaloneJmsApplication extends App {
+ import CamelServiceManager._
+
+ val context = new ClassPathXmlApplicationContext("/context-jms.xml")
+ val registry = new ApplicationContextRegistry(context)
+
+ // Init CamelContextManager with custom CamelContext
+ CamelContextManager.init(new DefaultCamelContext(registry))
+
+ startCamelService
+
+ val jmsUri = "jms:topic:test"
+ val jmsPublisher = Actor.actorOf(new Publisher(jmsUri), "jms-publisher").start
+
+ mandatoryService.awaitEndpointActivation(2) {
+ Actor.actorOf(new Subscriber("jms-subscriber-1", jmsUri)).start
+ Actor.actorOf(new Subscriber("jms-subscriber-2", jmsUri)).start
+ }
+
+ // Send 10 messages to via publisher actor
+ for(i <- 1 to 10) {
+ jmsPublisher ! ("Akka rocks (%d)" format i)
+ }
+
+ // Send 10 messages to JMS topic directly
+ for(i <- 1 to 10) {
+ CamelContextManager.mandatoryTemplate.sendBody(jmsUri, "Camel rocks (%d)" format i)
+ }
+
+ // Wait a bit for subscribes to receive messages
+ Thread.sleep(1000)
+
+ stopCamelService
+ Actor.registry.local.shutdownAll
+}
+
+object StandaloneFileApplication {
+ import CamelServiceManager._
+
+ def main(args: Array[String]) {
+ startCamelService
+ mandatoryService.awaitEndpointActivation(1) {
+ Actor.actorOf(new FileConsumer).start
+ }
+ }
+}
+
diff --git a/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java b/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java
new file mode 100644
index 0000000000..5dea328e59
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java
@@ -0,0 +1,21 @@
+package sample.camel;
+
+import akka.camel.Message;
+import akka.camel.UntypedConsumerActor;
+
+/**
+ * @author Martin Krasser
+ */
+public class SampleRemoteUntypedConsumer extends UntypedConsumerActor {
+ public String getEndpointUri() {
+ return "direct:remote-untyped-consumer";
+ }
+
+ public void onReceive(Object message) {
+ Message msg = (Message)message;
+ String body = msg.getBodyAs(String.class);
+ String header = msg.getHeaderAs("test", String.class);
+ getContext().replySafe(String.format("%s %s", body, header));
+ }
+
+}
diff --git a/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala
new file mode 100644
index 0000000000..255a6262c0
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala
@@ -0,0 +1,99 @@
+package sample.camel
+
+import collection.mutable.Set
+
+import java.util.concurrent.CountDownLatch
+
+import org.junit._
+import org.scalatest.junit.JUnitSuite
+
+import akka.actor.Actor._
+import akka.actor.{ActorRegistry, ActorRef, Actor}
+import akka.camel._
+import akka.camel.CamelServiceManager._
+import akka.routing.CyclicIterator
+import akka.routing.Routing._
+
+/**
+ * @author Martin Krasser
+ */
+class HttpConcurrencyTestStress extends JUnitSuite {
+ import HttpConcurrencyTestStress._
+
+ @Test def shouldProcessMessagesConcurrently = {
+ val num = 50
+ val latch1 = new CountDownLatch(num)
+ val latch2 = new CountDownLatch(num)
+ val latch3 = new CountDownLatch(num)
+ val client1 = actorOf(new HttpClientActor("client1", latch1)).start
+ val client2 = actorOf(new HttpClientActor("client2", latch2)).start
+ val client3 = actorOf(new HttpClientActor("client3", latch3)).start
+ for (i <- 1 to num) {
+ client1 ! Message("client1", Map(Message.MessageExchangeId -> i))
+ client2 ! Message("client2", Map(Message.MessageExchangeId -> i))
+ client3 ! Message("client3", Map(Message.MessageExchangeId -> i))
+ }
+ latch1.await
+ latch2.await
+ latch3.await
+ assert(num == (client1 ? "getCorrelationIdCount").as[Int].get)
+ assert(num == (client2 ? "getCorrelationIdCount").as[Int].get)
+ assert(num == (client3 ? "getCorrelationIdCount").as[Int].get)
+ }
+}
+
+object HttpConcurrencyTestStress {
+ @BeforeClass
+ def beforeClass: Unit = {
+ startCamelService
+
+ val workers = for (i <- 1 to 8) yield actorOf[HttpServerWorker].start
+ val balancer = loadBalancerActor(new CyclicIterator(workers.toList))
+
+ service.get.awaitEndpointActivation(1) {
+ actorOf(new HttpServerActor(balancer)).start
+ }
+ }
+
+ @AfterClass
+ def afterClass = {
+ stopCamelService
+ Actor.registry.local.shutdownAll
+ }
+
+ class HttpClientActor(label: String, latch: CountDownLatch) extends Actor with Producer {
+ def endpointUri = "jetty:http://0.0.0.0:8855/echo"
+ var correlationIds = Set[Any]()
+
+ override protected def receive = {
+ case "getCorrelationIdCount" => self.reply(correlationIds.size)
+ case msg => super.receive(msg)
+ }
+
+ override protected def receiveAfterProduce = {
+ case msg: Message => {
+ val corr = msg.headers(Message.MessageExchangeId)
+ val body = msg.bodyAs[String]
+ correlationIds += corr
+ assert(label == body)
+ latch.countDown
+ print(".")
+ }
+ }
+ }
+
+ class HttpServerActor(balancer: ActorRef) extends Actor with Consumer {
+ def endpointUri = "jetty:http://0.0.0.0:8855/echo"
+ var counter = 0
+
+ def receive = {
+ case msg => balancer forward msg
+ }
+ }
+
+ class HttpServerWorker extends Actor {
+ protected def receive = {
+ case msg => self.reply(msg)
+ }
+ }
+}
diff --git a/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala
new file mode 100644
index 0000000000..31586311fa
--- /dev/null
+++ b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala
@@ -0,0 +1,101 @@
+package sample.camel
+
+import org.scalatest.{GivenWhenThen, BeforeAndAfterAll, FeatureSpec}
+
+import akka.actor.Actor._
+import akka.actor._
+import akka.camel._
+import akka.remote.netty.NettyRemoteSupport
+import akka.remoteinterface.RemoteServerModule
+
+/**
+ * @author Martin Krasser
+ */
+class RemoteConsumerTest /*extends FeatureSpec with BeforeAndAfterAll with GivenWhenThen*/ {
+ /* TODO: fix remote test
+
+ import CamelServiceManager._
+ import RemoteConsumerTest._
+
+ var server: RemoteServerModule = _
+
+ override protected def beforeAll = {
+ registry.shutdownAll
+
+ startCamelService
+
+ remote.shutdown
+ remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false)
+
+ server = remote.start(host,port)
+ }
+
+ override protected def afterAll = {
+ remote.shutdown
+
+ stopCamelService
+
+ registry.shutdownAll
+ remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(true)
+ }
+
+ feature("Publish consumer on remote node") {
+ scenario("access published remote consumer") {
+ given("a consumer actor")
+ val consumer = Actor.actorOf[RemoteConsumer]
+
+ when("registered at the server")
+ assert(mandatoryService.awaitEndpointActivation(1) {
+ remote.register(consumer)
+ })
+
+ then("the published consumer is accessible via its endpoint URI")
+ val response = CamelContextManager.mandatoryTemplate.requestBody("direct:remote-consumer", "test")
+ assert(response === "remote actor: test")
+ }
+ }
+
+ feature("Publish typed consumer on remote node") {
+ scenario("access published remote consumer method") {
+ given("a typed consumer actor")
+ when("registered at the server")
+ assert(mandatoryService.awaitEndpointActivation(1) {
+ remote.registerTypedActor("whatever", TypedActor.newInstance(
+ classOf[SampleRemoteTypedConsumer],
+ classOf[SampleRemoteTypedConsumerImpl]))
+ })
+ then("the published method is accessible via its endpoint URI")
+ val response = CamelContextManager.mandatoryTemplate.requestBody("direct:remote-typed-consumer", "test")
+ assert(response === "remote typed actor: test")
+ }
+ }
+
+ feature("Publish untyped consumer on remote node") {
+ scenario("access published remote untyped consumer") {
+ given("an untyped consumer actor")
+ val consumer = Actor.actorOf(classOf[SampleRemoteUntypedConsumer])
+
+ when("registered at the server")
+ assert(mandatoryService.awaitEndpointActivation(1) {
+ remote.register(consumer)
+ })
+ then("the published untyped consumer is accessible via its endpoint URI")
+ val response = CamelContextManager.mandatoryTemplate.requestBodyAndHeader("direct:remote-untyped-consumer", "a", "test", "b")
+ assert(response === "a b")
+ }
+ }*/
+}
+
+object RemoteConsumerTest {
+ val host = "localhost"
+ val port = 7774
+
+ class RemoteConsumer extends Actor with Consumer {
+ def endpointUri = "direct:remote-consumer"
+
+ protected def receive = {
+ case "init" => self.reply("done")
+ case m: Message => self.reply("remote actor: %s" format m.body)
+ }
+ }
+}
diff --git a/akka-spring/src/main/scala/akka/spring/ActorFactoryBean.scala b/akka-spring/src/main/scala/akka/spring/ActorFactoryBean.scala
index fd31ed074c..ea5bb755e7 100644
--- a/akka-spring/src/main/scala/akka/spring/ActorFactoryBean.scala
+++ b/akka-spring/src/main/scala/akka/spring/ActorFactoryBean.scala
@@ -36,7 +36,6 @@ class AkkaBeansException(message: String, cause: Throwable) extends BeansExcepti
* @author Jonas Bonér
*/
class ActorFactoryBean extends AbstractFactoryBean[AnyRef] with ApplicationContextAware {
- import StringReflect._
import AkkaSpringConfigurationTags._
@BeanProperty
var id: String = ""
@@ -242,7 +241,6 @@ class ActorFactoryBean extends AbstractFactoryBean[AnyRef] with ApplicationConte
* @author michaelkober
*/
class ActorForFactoryBean extends AbstractFactoryBean[AnyRef] with ApplicationContextAware {
- import StringReflect._
import AkkaSpringConfigurationTags._
@BeanProperty
diff --git a/akka-spring/src/main/scala/akka/spring/StringReflect.scala b/akka-spring/src/main/scala/akka/spring/StringReflect.scala
deleted file mode 100644
index 2b77f8caa6..0000000000
--- a/akka-spring/src/main/scala/akka/spring/StringReflect.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (C) 2009-2010 Scalable Solutions AB
- */
-
-package akka.spring
-
-object StringReflect {
-
- /**
- * Implicit conversion from String to StringReflect.
- */
- implicit def string2StringReflect(x: String) = new StringReflect(x)
-}
-
-/**
- * Reflection helper class.
- * @author michaelkober
- */
-class StringReflect(val self: String) {
- if ((self eq null) || self == "") throw new IllegalArgumentException("Class name can't be null or empty string [" + self + "]")
- def toClass[T <: AnyRef]: Class[T] = {
- val clazz = Class.forName(self)
- clazz.asInstanceOf[Class[T]]
- }
-}
diff --git a/akka-spring/src/main/scala/akka/spring/SupervisionBeanDefinitionParser.scala b/akka-spring/src/main/scala/akka/spring/SupervisionBeanDefinitionParser.scala
index 60705cddc6..fc258d44f2 100644
--- a/akka-spring/src/main/scala/akka/spring/SupervisionBeanDefinitionParser.scala
+++ b/akka-spring/src/main/scala/akka/spring/SupervisionBeanDefinitionParser.scala
@@ -71,7 +71,6 @@ class SupervisionBeanDefinitionParser extends AbstractSingleBeanDefinitionParser
}
private def parseTrapExits(element: Element): Array[Class[_ <: Throwable]] = {
- import StringReflect._
val trapExits = DomUtils.getChildElementsByTagName(element, TRAP_EXIT_TAG).toArray.toList.asInstanceOf[List[Element]]
trapExits.map(DomUtils.getTextValue(_).toClass.asInstanceOf[Class[_ <: Throwable]]).toArray
}
diff --git a/akka-spring/src/main/scala/akka/spring/SupervisionFactoryBean.scala b/akka-spring/src/main/scala/akka/spring/SupervisionFactoryBean.scala
index d138a4f98e..00aa4e9157 100644
--- a/akka-spring/src/main/scala/akka/spring/SupervisionFactoryBean.scala
+++ b/akka-spring/src/main/scala/akka/spring/SupervisionFactoryBean.scala
@@ -55,7 +55,6 @@ class SupervisionFactoryBean extends AbstractFactoryBean[AnyRef] {
* Create configuration for TypedActor
*/
private[akka] def createComponent(props: ActorProperties): SuperviseTypedActor = {
- import StringReflect._
val lifeCycle = if (!props.lifecycle.isEmpty && props.lifecycle.equalsIgnoreCase(VAL_LIFECYCYLE_TEMPORARY)) Temporary else Permanent
val isRemote = (props.host ne null) && (!props.host.isEmpty)
val withInterface = (props.interface ne null) && (!props.interface.isEmpty)
@@ -80,7 +79,6 @@ class SupervisionFactoryBean extends AbstractFactoryBean[AnyRef] {
* Create configuration for UntypedActor
*/
private[akka] def createSupervise(props: ActorProperties): Server = {
- import StringReflect._
val lifeCycle = if (!props.lifecycle.isEmpty && props.lifecycle.equalsIgnoreCase(VAL_LIFECYCYLE_TEMPORARY)) Temporary else Permanent
val isRemote = (props.host ne null) && (!props.host.isEmpty)
val actorRef = Actor.actorOf(props.target.toClass)
diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala
index d859f695ea..753ea97bf7 100644
--- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala
+++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala
@@ -18,8 +18,7 @@ import com.eaio.uuid.UUID
* @author Roland Kuhn
* @since 1.1
*/
-class TestActorRef[T <: Actor](factory: () ⇒ T, address: String)
- extends LocalActorRef(factory, address, DeploymentConfig.Transient) {
+class TestActorRef[T <: Actor](factory: () ⇒ T, address: String) extends LocalActorRef(factory, address) {
dispatcher = CallingThreadDispatcher.global
receiveTimeout = None
diff --git a/config/akka-reference.conf b/config/akka-reference.conf
index 0ca0a661ff..d224fbe42d 100644
--- a/config/akka-reference.conf
+++ b/config/akka-reference.conf
@@ -40,30 +40,32 @@ akka {
service-ping { # stateless actor with replication factor 3 and round-robin load-balancer
- format = "akka.serialization.Format$Default$" # serializer for messages and actor instance
-
router = "least-cpu" # routing (load-balance) scheme to use
# available: "direct", "round-robin", "random",
# "least-cpu", "least-ram", "least-messages"
# or: fully qualified class name of the router class
# default is "direct";
+ # if 'replication' is used then the only available router is "direct"
clustered { # makes the actor available in the cluster registry
# default (if omitted) is local non-clustered actor
- home = "node:node1" # hostname, IP-address or node name of the "home" node for clustered actor
+ preferred-nodes = ["node:node1"] # a list of preferred nodes for instantiating the actor instances on
+ # defined as: hostname, IP-address or node name
# available: "host:", "ip:" and "node:"
# default is "host:localhost"
- replicas = 3 # number of actor replicas in the cluster
- # available: positivoe integer (0-N) or the string "auto" for auto-scaling
+ replicas = 3 # number of actor instances in the cluster
+ # available: positive integer (0-N) or the string "auto" for auto-scaling
# if "auto" is used then 'home' has no meaning
# default is '0', meaning no replicas;
+ # if the "direct" router is used then this element is ignored (always '1')
- replication { # use replication or not?
+ replication { # use replication or not? only makes sense for a stateful actor
# FIXME should we have this config option here? If so, implement it all through.
- serialize-mailbox = on # should the actor mailbox be part of the serialized snapshot?
+ serialize-mailbox = off # should the actor mailbox be part of the serialized snapshot?
+ # default is 'off'
storage = "transaction-log" # storage model for replication
# available: "transaction-log" and "data-grid"
@@ -101,9 +103,9 @@ akka {
debug {
receive = "false" # enable function of Actor.loggable(), which is
- # to log any received message at DEBUG level
+ # to log any received message at DEBUG level
autoreceive = "false" # enable DEBUG logging of all AutoReceiveMessages
- # (Kill, PoisonPill and the like)
+ # (Kill, PoisonPill and the like)
lifecycle = "false" # enable DEBUG logging of actor lifecycle changes
}
@@ -176,8 +178,7 @@ akka {
connection-timeout = 60
use-compression = off
remote-daemon-ack-timeout = 30 # Timeout for ACK of cluster operations, lik checking actor out etc.
- exclude-ref-node-in-replica-set = on # Should a replica be instantiated on the same node as the
- # cluster reference to the actor
+ include-ref-node-in-replica-set = on # Can a replica be instantiated on the same node as the cluster reference to the actor
# Default: on
compression-scheme = "zlib" # Options: "zlib" (lzf to come), leave out for no compression
zlib-compression-level = 6 # Options: 0-9 (1 being fastest and 9 being the most compressed), default is 6
@@ -188,6 +189,8 @@ akka {
secure-cookie = "" # Generate your own with '$AKKA_HOME/scripts/generate_config_with_secure_cookie.sh'
# or using 'akka.util.Crypt.generateSecureCookie'
+ log-directory = "_akka_cluster" # Where ZooKeeper should store the logs and data files
+
replication {
digest-type = "MAC" # Options: CRC32 (cheap & unsafe), MAC (expensive & secure using password)
password = "secret" # FIXME: store open in file?
@@ -254,6 +257,6 @@ akka {
}
test {
- timefactor = "1.0" # factor by which to scale timeouts during tests, e.g. to account for shared build system load
+ timefactor = "1.0" # factor by which to scale timeouts during tests, e.g. to account for shared build system load
}
}
diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala
new file mode 100644
index 0000000000..ee8ed6d9b8
--- /dev/null
+++ b/project/AkkaBuild.scala
@@ -0,0 +1,418 @@
+import sbt._
+import Keys._
+import MultiJvmPlugin.{ MultiJvm, extraOptions }
+
+object AkkaBuild extends Build {
+ lazy val buildSettings = Seq(
+ organization := "se.scalablesolutions.akka",
+ version := "2.0-SNAPSHOT",
+ scalaVersion := "2.9.0-1"
+ )
+
+ lazy val akka = Project(
+ id = "akka",
+ base = file("."),
+ settings = parentSettings ++ Unidoc.settings ++ rstdocSettings ++ Seq(
+ Unidoc.unidocExclude := Seq(samples.id, tutorials.id),
+ rstdocDirectory <<= baseDirectory / "akka-docs"
+ ),
+ aggregate = Seq(actor, testkit, actorTests, stm, cluster, http, slf4j, mailboxes, camel, camelTyped, samples, tutorials)
+ )
+
+ lazy val actor = Project(
+ id = "akka-actor",
+ base = file("akka-actor"),
+ settings = defaultSettings ++ Seq(
+ autoCompilerPlugins := true,
+ libraryDependencies <+= scalaVersion { v => compilerPlugin("org.scala-lang.plugins" % "continuations" % v) },
+ scalacOptions += "-P:continuations:enable"
+ )
+ )
+
+ lazy val testkit = Project(
+ id = "akka-testkit",
+ base = file("akka-testkit"),
+ dependencies = Seq(actor),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.testkit
+ )
+ )
+
+ lazy val actorTests = Project(
+ id = "akka-actor-tests",
+ base = file("akka-actor-tests"),
+ dependencies = Seq(testkit),
+ settings = defaultSettings ++ Seq(
+ autoCompilerPlugins := true,
+ libraryDependencies <+= scalaVersion { v => compilerPlugin("org.scala-lang.plugins" % "continuations" % v) },
+ scalacOptions += "-P:continuations:enable",
+ libraryDependencies ++= Dependencies.actorTests
+ )
+ )
+
+ lazy val stm = Project(
+ id = "akka-stm",
+ base = file("akka-stm"),
+ dependencies = Seq(actor),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.stm
+ )
+ )
+
+ lazy val cluster = Project(
+ id = "akka-cluster",
+ base = file("akka-cluster"),
+ dependencies = Seq(stm, actorTests % "test->test"),
+ settings = defaultSettings ++ MultiJvmPlugin.settings ++ Seq(
+ libraryDependencies ++= Dependencies.cluster,
+ extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src =>
+ (name: String) => (src ** (name + ".conf")).get.headOption.map("-Dakka.config=" + _.absolutePath).toSeq
+ },
+ // TODO: use dependsOn once updated to sbt 0.10.1 -- currently doesn't fail on error
+ // test in Test <<= (test in Test) dependsOn (test in MultiJvm)
+ test in Test <<= (test in MultiJvm, (test in Test).task) flatMap { (mj, t) => t }
+ )
+ ) configs (MultiJvm)
+
+ lazy val http = Project(
+ id = "akka-http",
+ base = file("akka-http"),
+ dependencies = Seq(actor),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.http
+ )
+ )
+
+ lazy val slf4j = Project(
+ id = "akka-slf4j",
+ base = file("akka-slf4j"),
+ dependencies = Seq(actor),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.slf4j
+ )
+ )
+
+ lazy val mailboxes = Project(
+ id = "akka-durable-mailboxes",
+ base = file("akka-durable-mailboxes"),
+ settings = parentSettings,
+ aggregate = Seq(mailboxesCommon, beanstalkMailbox, fileMailbox, redisMailbox, zookeeperMailbox)
+ )
+
+ lazy val mailboxesCommon = Project(
+ id = "akka-mailboxes-common",
+ base = file("akka-durable-mailboxes/akka-mailboxes-common"),
+ dependencies = Seq(cluster),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.mailboxes
+ )
+ )
+
+ val testBeanstalkMailbox = SettingKey[Boolean]("test-beanstalk-mailbox")
+
+ lazy val beanstalkMailbox = Project(
+ id = "akka-beanstalk-mailbox",
+ base = file("akka-durable-mailboxes/akka-beanstalk-mailbox"),
+ dependencies = Seq(mailboxesCommon % "compile;test->test"),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.beanstalkMailbox,
+ testBeanstalkMailbox := false,
+ testOptions in Test <+= testBeanstalkMailbox map { test => Tests.Filter(s => test) }
+ )
+ )
+
+ lazy val fileMailbox = Project(
+ id = "akka-file-mailbox",
+ base = file("akka-durable-mailboxes/akka-file-mailbox"),
+ dependencies = Seq(mailboxesCommon % "compile;test->test"),
+ settings = defaultSettings
+ )
+
+ val testRedisMailbox = SettingKey[Boolean]("test-redis-mailbox")
+
+ lazy val redisMailbox = Project(
+ id = "akka-redis-mailbox",
+ base = file("akka-durable-mailboxes/akka-redis-mailbox"),
+ dependencies = Seq(mailboxesCommon % "compile;test->test"),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.redisMailbox,
+ testRedisMailbox := false,
+ testOptions in Test <+= testRedisMailbox map { test => Tests.Filter(s => test) }
+ )
+ )
+
+ lazy val zookeeperMailbox = Project(
+ id = "akka-zookeeper-mailbox",
+ base = file("akka-durable-mailboxes/akka-zookeeper-mailbox"),
+ dependencies = Seq(mailboxesCommon % "compile;test->test"),
+ settings = defaultSettings
+ )
+
+ lazy val camel = Project(
+ id = "akka-camel",
+ base = file("akka-camel"),
+ dependencies = Seq(actor, slf4j),
+ settings = defaultSettings ++ Seq(
+ libraryDependencies ++= Dependencies.camel
+ )
+ )
+
+ // can be merged back into akka-camel
+ lazy val camelTyped = Project(
+ id = "akka-camel-typed",
+ base = file("akka-camel-typed"),
+ dependencies = Seq(camel % "compile;test->test"),
+ settings = defaultSettings
+ )
+
+ // lazy val spring = Project(
+ // id = "akka-spring",
+ // base = file("akka-spring"),
+ // dependencies = Seq(cluster, camel),
+ // settings = defaultSettings ++ Seq(
+ // libraryDependencies ++= Dependencies.spring
+ // )
+ // )
+
+ // lazy val kernel = Project(
+ // id = "akka-kernel",
+ // base = file("akka-kernel"),
+ // dependencies = Seq(cluster, http, slf4j, spring),
+ // settings = defaultSettings ++ Seq(
+ // libraryDependencies ++= Dependencies.kernel
+ // )
+ // )
+
+ lazy val samples = Project(
+ id = "akka-samples",
+ base = file("akka-samples"),
+ settings = parentSettings,
+ aggregate = Seq(fsmSample)
+ )
+
+ // lazy val antsSample = Project(
+ // id = "akka-sample-ants",
+ // base = file("akka-samples/akka-sample-ants"),
+ // dependencies = Seq(stm),
+ // settings = defaultSettings
+ // )
+
+ // lazy val chatSample = Project(
+ // id = "akka-sample-chat",
+ // base = file("akka-samples/akka-sample-chat"),
+ // dependencies = Seq(cluster),
+ // settings = defaultSettings
+ // )
+
+ lazy val fsmSample = Project(
+ id = "akka-sample-fsm",
+ base = file("akka-samples/akka-sample-fsm"),
+ dependencies = Seq(actor),
+ settings = defaultSettings
+ )
+
+ // lazy val helloSample = Project(
+ // id = "akka-sample-hello",
+ // base = file("akka-samples/akka-sample-hello"),
+ // dependencies = Seq(kernel),
+ // settings = defaultSettings
+ // )
+
+ // lazy val remoteSample = Project(
+ // id = "akka-sample-remote",
+ // base = file("akka-samples/akka-sample-remote"),
+ // dependencies = Seq(cluster),
+ // settings = defaultSettings
+ // )
+
+ lazy val tutorials = Project(
+ id = "akka-tutorials",
+ base = file("akka-tutorials"),
+ settings = parentSettings,
+ aggregate = Seq(firstTutorial, secondTutorial)
+ )
+
+ lazy val firstTutorial = Project(
+ id = "akka-tutorial-first",
+ base = file("akka-tutorials/akka-tutorial-first"),
+ dependencies = Seq(actor),
+ settings = defaultSettings
+ )
+
+ lazy val secondTutorial = Project(
+ id = "akka-tutorial-second",
+ base = file("akka-tutorials/akka-tutorial-second"),
+ dependencies = Seq(actor),
+ settings = defaultSettings
+ )
+
+ // Settings
+
+ override lazy val settings = super.settings ++ buildSettings ++ Publish.versionSettings
+
+ lazy val baseSettings = Defaults.defaultSettings ++ Publish.settings
+
+ lazy val parentSettings = baseSettings ++ Seq(
+ publishArtifact in Compile := false
+ )
+
+ lazy val defaultSettings = baseSettings ++ Seq(
+ resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/",
+
+ // compile options
+ scalacOptions ++= Seq("-encoding", "UTF-8", "-optimise", "-deprecation", "-unchecked"),
+ javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
+
+ // add config dir to classpaths
+ unmanagedClasspath in Runtime <+= (baseDirectory in LocalProject("akka")) map { base => Attributed.blank(base / "config") },
+ unmanagedClasspath in Test <+= (baseDirectory in LocalProject("akka")) map { base => Attributed.blank(base / "config") },
+
+ // disable parallel tests
+ parallelExecution in Test := false
+ )
+
+ // reStructuredText docs
+
+ val rstdocDirectory = SettingKey[File]("rstdoc-directory")
+ val rstdoc = TaskKey[File]("rstdoc", "Build the reStructuredText documentation.")
+
+ lazy val rstdocSettings = Seq(rstdoc <<= rstdocTask)
+
+ def rstdocTask = (rstdocDirectory, streams) map {
+ (dir, s) => {
+ s.log.info("Building reStructuredText documentation...")
+ val exitCode = Process(List("make", "clean", "html", "pdf"), dir) ! s.log
+ if (exitCode != 0) error("Failed to build docs.")
+ s.log.info("Done building docs.")
+ dir
+ }
+ }
+}
+
+// Dependencies
+
+object Dependencies {
+ import Dependency._
+
+ val testkit = Seq(Test.scalatest)
+
+ val actorTests = Seq(
+ Test.junit, Test.scalatest, Test.multiverse, Test.commonsMath, Test.mockito,
+ protobuf, jacksonMapper, sjson
+ )
+
+ val stm = Seq(multiverse, Test.junit, Test.scalatest)
+
+ val cluster = Seq(
+ bookkeeper, commonsCodec, commonsIo, guice, h2Lzf, jacksonCore, jacksonMapper, log4j, netty,
+ protobuf, sjson, zkClient, zookeeper, zookeeperLock, Test.junit, Test.scalatest
+ )
+
+ val http = Seq(
+ jsr250, Provided.javaxServlet, Provided.jetty, Provided.jerseyServer, jsr311, commonsCodec,
+ Test.junit, Test.scalatest, Test.mockito
+ )
+
+ val slf4j = Seq(slf4jApi)
+
+ val mailboxes = Seq(Test.scalatest)
+
+ val beanstalkMailbox = Seq(beanstalk)
+
+ val redisMailbox = Seq(redis)
+
+ val camel = Seq(camelCore, Test.junit, Test.scalatest, Test.logback)
+
+ val spring = Seq(springBeans, springContext, Test.camelSpring, Test.junit, Test.scalatest)
+
+ val kernel = Seq(
+ jettyUtil, jettyXml, jettyServlet, jerseyCore, jerseyJson, jerseyScala,
+ jacksonCore, staxApi, Provided.jerseyServer
+ )
+}
+
+object Dependency {
+
+ // Versions
+
+ object V {
+ val Camel = "2.7.1"
+ val CamelPatch = "2.7.1.1"
+ val Jackson = "1.8.0"
+ val JavaxServlet = "3.0"
+ val Jersey = "1.3"
+ val Jetty = "7.4.0.v20110414"
+ val Logback = "0.9.28"
+ val Multiverse = "0.6.2"
+ val Netty = "3.2.4.Final"
+ val Protobuf = "2.4.1"
+ val Scalatest = "1.4.1"
+ val Slf4j = "1.6.0"
+ val Spring = "3.0.5.RELEASE"
+ val Zookeeper = "3.4.0"
+ }
+
+ // Compile
+
+ val beanstalk = "beanstalk" % "beanstalk_client" % "1.4.5" // New BSD
+ val bookkeeper = "org.apache.hadoop.zookeeper" % "bookkeeper" % V.Zookeeper // ApacheV2
+ val camelCore = "org.apache.camel" % "camel-core" % V.CamelPatch // ApacheV2
+ val commonsCodec = "commons-codec" % "commons-codec" % "1.4" // ApacheV2
+ val commonsIo = "commons-io" % "commons-io" % "2.0.1" // ApacheV2
+ val guice = "org.guiceyfruit" % "guice-all" % "2.0" // ApacheV2
+ val h2Lzf = "voldemort.store.compress" % "h2-lzf" % "1.0" // ApacheV2
+ val jacksonCore = "org.codehaus.jackson" % "jackson-core-asl" % V.Jackson // ApacheV2
+ val jacksonMapper = "org.codehaus.jackson" % "jackson-mapper-asl" % V.Jackson // ApacheV2
+ val jerseyCore = "com.sun.jersey" % "jersey-core" % V.Jersey // CDDL v1
+ val jerseyJson = "com.sun.jersey" % "jersey-json" % V.Jersey // CDDL v1
+ val jerseyScala = "com.sun.jersey.contribs" % "jersey-scala" % V.Jersey // CDDL v1
+ val jettyUtil = "org.eclipse.jetty" % "jetty-util" % V.Jetty // Eclipse license
+ val jettyXml = "org.eclipse.jetty" % "jetty-xml" % V.Jetty // Eclipse license
+ val jettyServlet = "org.eclipse.jetty" % "jetty-servlet" % V.Jetty // Eclipse license
+ val jsr250 = "javax.annotation" % "jsr250-api" % "1.0" // CDDL v1
+ val jsr311 = "javax.ws.rs" % "jsr311-api" % "1.1" // CDDL v1
+ val log4j = "log4j" % "log4j" % "1.2.15" // ApacheV2
+ val multiverse = "org.multiverse" % "multiverse-alpha" % V.Multiverse // ApacheV2
+ val netty = "org.jboss.netty" % "netty" % V.Netty // ApacheV2
+ val osgi = "org.osgi" % "org.osgi.core" % "4.2.0" // ApacheV2
+ val protobuf = "com.google.protobuf" % "protobuf-java" % V.Protobuf // New BSD
+ val redis = "net.debasishg" % "redisclient_2.9.0" % "2.3.1" // ApacheV2
+ val sjson = "net.debasishg" % "sjson_2.9.0" % "0.11" // ApacheV2
+ val slf4jApi = "org.slf4j" % "slf4j-api" % V.Slf4j // MIT
+ val springBeans = "org.springframework" % "spring-beans" % V.Spring // ApacheV2
+ val springContext = "org.springframework" % "spring-context" % V.Spring // ApacheV2
+ val staxApi = "javax.xml.stream" % "stax-api" % "1.0-2" // ApacheV2
+ val zkClient = "zkclient" % "zkclient" % "0.3" // ApacheV2
+ val zookeeper = "org.apache.hadoop.zookeeper" % "zookeeper" % V.Zookeeper // ApacheV2
+ val zookeeperLock = "org.apache.hadoop.zookeeper" % "zookeeper-recipes-lock" % V.Zookeeper // ApacheV2
+
+ // Provided
+
+ object Provided {
+ val javaxServlet = "org.glassfish" % "javax.servlet" % V.JavaxServlet % "provided" // CDDL v1
+ val jerseyServer = "com.sun.jersey" % "jersey-server" % V.Jersey % "provided" // CDDL v1
+ val jetty = "org.eclipse.jetty" % "jetty-server" % V.Jetty % "provided" // Eclipse license
+ }
+
+ // Runtime
+
+ object Runtime {
+ val logback = "ch.qos.logback" % "logback-classic" % V.Logback % "runtime" // MIT
+ }
+
+ // Test
+
+ object Test {
+ val camelSpring = "org.apache.camel" % "camel-spring" % V.Camel % "test" // ApacheV2
+ val commonsColl = "commons-collections" % "commons-collections" % "3.2.1" % "test" // ApacheV2
+ val commonsMath = "org.apache.commons" % "commons-math" % "2.1" % "test" // ApacheV2
+ val jetty = "org.eclipse.jetty" % "jetty-server" % V.Jetty % "test" // Eclipse license
+ val jettyWebapp = "org.eclipse.jetty" % "jetty-webapp" % V.Jetty % "test" // Eclipse license
+ val junit = "junit" % "junit" % "4.5" % "test" // Common Public License 1.0
+ val logback = "ch.qos.logback" % "logback-classic" % V.Logback % "test" // EPL 1.0 / LGPL 2.1
+ val mockito = "org.mockito" % "mockito-all" % "1.8.1" % "test" // MIT
+ val multiverse = "org.multiverse" % "multiverse-alpha" % V.Multiverse % "test" // ApacheV2
+ val scalatest = "org.scalatest" % "scalatest_2.9.0" % V.Scalatest % "test" // ApacheV2
+ val sjsonTest = "net.debasishg" %% "sjson" % "0.11" % "test" // ApacheV2
+ }
+}
diff --git a/project/Publish.scala b/project/Publish.scala
new file mode 100644
index 0000000000..a0add06443
--- /dev/null
+++ b/project/Publish.scala
@@ -0,0 +1,69 @@
+import sbt._
+import Keys._
+import java.io.File
+
+object Publish {
+ final val Snapshot = "-SNAPSHOT"
+
+ lazy val settings = Seq(
+ crossPaths := false,
+ pomExtra := akkaPomExtra,
+ publishTo := akkaPublishTo,
+ credentials ++= akkaCredentials
+ )
+
+ lazy val versionSettings = Seq(
+ commands += stampVersion
+ )
+
+ def akkaPomExtra = {
+ 2009
+ http://akka.io
+
+ Scalable Solutions AB
+ http://scalablesolutions.se
+
+
+
+ Apache 2
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+ }
+
+ def akkaPublishTo: Option[Resolver] = {
+ val property = Option(System.getProperty("akka.publish.repository"))
+ val repo = property map { "Akka Publish Repository" at _ }
+ val m2repo = Path.userHome / ".m2" /"repository"
+ repo orElse Some(Resolver.file("Local Maven Repository", m2repo))
+ }
+
+ def akkaCredentials: Seq[Credentials] = {
+ val property = Option(System.getProperty("akka.publish.credentials"))
+ property map (f => Credentials(new File(f))) toSeq
+ }
+
+ def stampVersion = Command.command("stamp-version") { state =>
+ append((version in ThisBuild ~= stamp) :: Nil, state)
+ }
+
+ // TODO: replace with extracted.append when updated to sbt 0.10.1
+ def append(settings: Seq[Setting[_]], state: State): State = {
+ val extracted = Project.extract(state)
+ import extracted._
+ val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
+ val newStructure = Load.reapply(session.original ++ append, structure)
+ Project.setProject(session, newStructure, state)
+ }
+
+ def stamp(version: String): String = {
+ if (version endsWith Snapshot) (version stripSuffix Snapshot) + "-" + timestamp(System.currentTimeMillis)
+ else version
+ }
+
+ def timestamp(time: Long): String = {
+ val format = new java.text.SimpleDateFormat("yyyyMMdd-HHmmss")
+ format.format(new java.util.Date(time))
+ }
+}
diff --git a/project/Unidoc.scala b/project/Unidoc.scala
new file mode 100644
index 0000000000..2673e602fe
--- /dev/null
+++ b/project/Unidoc.scala
@@ -0,0 +1,51 @@
+import sbt._
+import Keys._
+import Project.Initialize
+
+object Unidoc {
+ val unidocDirectory = SettingKey[File]("unidoc-directory")
+ val unidocExclude = SettingKey[Seq[String]]("unidoc-exclude")
+ val unidocAllSources = TaskKey[Seq[Seq[File]]]("unidoc-all-sources")
+ val unidocSources = TaskKey[Seq[File]]("unidoc-sources")
+ val unidocAllClasspaths = TaskKey[Seq[Classpath]]("unidoc-all-classpaths")
+ val unidocClasspath = TaskKey[Seq[File]]("unidoc-classpath")
+ val unidoc = TaskKey[File]("unidoc", "Create unified scaladoc for all aggregates")
+
+ lazy val settings = Seq(
+ unidocDirectory <<= crossTarget / "unidoc",
+ unidocExclude := Seq.empty,
+ unidocAllSources <<= (thisProjectRef, buildStructure, unidocExclude) flatMap allSources,
+ unidocSources <<= unidocAllSources map { _.flatten },
+ unidocAllClasspaths <<= (thisProjectRef, buildStructure, unidocExclude) flatMap allClasspaths,
+ unidocClasspath <<= unidocAllClasspaths map { _.flatten.map(_.data).distinct },
+ unidoc <<= unidocTask
+ )
+
+ def allSources(projectRef: ProjectRef, structure: Load.BuildStructure, exclude: Seq[String]): Task[Seq[Seq[File]]] = {
+ val projects = aggregated(projectRef, structure, exclude)
+ projects flatMap { sources in Compile in LocalProject(_) get structure.data } join
+ }
+
+ def allClasspaths(projectRef: ProjectRef, structure: Load.BuildStructure, exclude: Seq[String]): Task[Seq[Classpath]] = {
+ val projects = aggregated(projectRef, structure, exclude)
+ projects flatMap { dependencyClasspath in Compile in LocalProject(_) get structure.data } join
+ }
+
+ def aggregated(projectRef: ProjectRef, structure: Load.BuildStructure, exclude: Seq[String]): Seq[String] = {
+ val aggregate = Project.getProject(projectRef, structure).toSeq.flatMap(_.aggregate)
+ aggregate flatMap { ref =>
+ if (exclude contains ref.project) Seq.empty
+ else ref.project +: aggregated(ref, structure, exclude)
+ }
+ }
+
+ def unidocTask: Initialize[Task[File]] = {
+ (compilers, cacheDirectory, unidocSources, unidocClasspath, unidocDirectory, scaladocOptions in Compile, streams) map {
+ (compilers, cache, sources, classpath, target, options, s) => {
+ val scaladoc = new Scaladoc(100, compilers.scalac)
+ scaladoc.cached(cache / "unidoc", "main", sources, classpath, target, options, s.log)
+ target
+ }
+ }
+ }
+}
diff --git a/project/build.properties b/project/build.properties
index d9cc9584b6..35e164f667 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1,6 +1 @@
-project.organization=se.scalablesolutions.akka
-project.name=akka
-project.version=2.0-SNAPSHOT
-build.scala.versions=2.9.0
-sbt.version=0.7.7
-
+sbt.version=0.10.0
diff --git a/project/plugins/build.sbt b/project/plugins/build.sbt
new file mode 100644
index 0000000000..7e787bb85d
--- /dev/null
+++ b/project/plugins/build.sbt
@@ -0,0 +1,4 @@
+
+resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"
+
+libraryDependencies += "com.typesafe" %% "sbt-multi-jvm" % "0.1"
diff --git a/project/plugins/embedded-repo/de/tuxed/codefellow-core_2.8.0.RC5/0.3/codefellow-core_2.8.0.RC5-0.3.jar b/project/plugins/embedded-repo/de/tuxed/codefellow-core_2.8.0.RC5/0.3/codefellow-core_2.8.0.RC5-0.3.jar
deleted file mode 100644
index ce21e31ad9..0000000000
Binary files a/project/plugins/embedded-repo/de/tuxed/codefellow-core_2.8.0.RC5/0.3/codefellow-core_2.8.0.RC5-0.3.jar and /dev/null differ
diff --git a/project/plugins/embedded-repo/de/tuxed/codefellow-core_2.8.0.RC5/0.3/codefellow-core_2.8.0.RC5-0.3.pom b/project/plugins/embedded-repo/de/tuxed/codefellow-core_2.8.0.RC5/0.3/codefellow-core_2.8.0.RC5-0.3.pom
deleted file mode 100644
index e63e756c45..0000000000
--- a/project/plugins/embedded-repo/de/tuxed/codefellow-core_2.8.0.RC5/0.3/codefellow-core_2.8.0.RC5-0.3.pom
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- 4.0.0
- de.tuxed
- codefellow-core_2.8.0.RC5
- jar
- 0.3
-
-
- org.apache.bcel
- bcel
- 5.2
- compile
-
-
- org.scala-lang
- scala-library
- 2.8.0.RC5
- compile
-
-
- org.scala-lang
- scala-compiler
- 2.8.0.RC5
- compile
-
-
-
-
- ScalaToolsMaven2Repository
- Scala-Tools Maven2 Repository
- http://scala-tools.org/repo-releases/
-
-
-
\ No newline at end of file
diff --git a/project/plugins/embedded-repo/de/tuxed/codefellow-plugin/0.3/codefellow-plugin-0.3.jar b/project/plugins/embedded-repo/de/tuxed/codefellow-plugin/0.3/codefellow-plugin-0.3.jar
deleted file mode 100644
index 6bb2942ccd..0000000000
Binary files a/project/plugins/embedded-repo/de/tuxed/codefellow-plugin/0.3/codefellow-plugin-0.3.jar and /dev/null differ
diff --git a/project/plugins/embedded-repo/de/tuxed/codefellow-plugin/0.3/codefellow-plugin-0.3.pom b/project/plugins/embedded-repo/de/tuxed/codefellow-plugin/0.3/codefellow-plugin-0.3.pom
deleted file mode 100644
index 638a882ceb..0000000000
--- a/project/plugins/embedded-repo/de/tuxed/codefellow-plugin/0.3/codefellow-plugin-0.3.pom
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- 4.0.0
- de.tuxed
- codefellow-plugin
- jar
- 0.3
-
-
- de.tuxed
- codefellow-core_2.8.0.RC5
- 0.3
- compile
-
-
- org.scala-lang
- scala-library
- 2.7.7
- compile
-
-
-
-
- ScalaToolsMaven2Repository
- Scala-Tools Maven2 Repository
- http://scala-tools.org/repo-releases/
-
-
-
\ No newline at end of file
diff --git a/project/sbt7/build.properties b/project/sbt7/build.properties
new file mode 100644
index 0000000000..d9cc9584b6
--- /dev/null
+++ b/project/sbt7/build.properties
@@ -0,0 +1,6 @@
+project.organization=se.scalablesolutions.akka
+project.name=akka
+project.version=2.0-SNAPSHOT
+build.scala.versions=2.9.0
+sbt.version=0.7.7
+
diff --git a/project/build/AkkaProject.scala b/project/sbt7/build/AkkaProject.scala
similarity index 96%
rename from project/build/AkkaProject.scala
rename to project/sbt7/build/AkkaProject.scala
index 91f7415c0b..e2a33013de 100644
--- a/project/build/AkkaProject.scala
+++ b/project/sbt7/build/AkkaProject.scala
@@ -77,7 +77,8 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
lazy val zookeeperModuleConfig = ModuleConfiguration("org.apache.hadoop.zookeeper", AkkaRepo)
lazy val protobufModuleConfig = ModuleConfiguration("com.google.protobuf", AkkaRepo)
lazy val zkclientModuleConfig = ModuleConfiguration("zkclient", AkkaRepo)
- lazy val camelModuleConfig = ModuleConfiguration("org.apache.camel", "camel-core", AkkaRepo)
+ lazy val camelCoreModuleConfig = ModuleConfiguration("org.apache.camel", "camel-core", AkkaRepo)
+ lazy val camelJettyModuleConfig = ModuleConfiguration("org.apache.camel", "camel-jetty", AkkaRepo)
// -------------------------------------------------------------------------------------------------------------------
// Versions
@@ -102,10 +103,13 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
object Dependencies {
// Compile
+ lazy val activemq = "org.apache.activemq" % "activemq-core" % "5.4.2" % "compile" // ApacheV2
lazy val beanstalk = "beanstalk" % "beanstalk_client" % "1.4.5" //New BSD
lazy val bookkeeper = "org.apache.hadoop.zookeeper" % "bookkeeper" % ZOOKEEPER_VERSION //ApacheV2
lazy val camel_core = "org.apache.camel" % "camel-core" % CAMEL_PATCH_VERSION % "compile" //ApacheV2
+ lazy val camel_jetty = "org.apache.camel" % "camel-jetty" % CAMEL_PATCH_VERSION % "compile" // ApacheV2
+ lazy val camel_jms = "org.apache.camel" % "camel-jms" % CAMEL_VERSION % "compile" //ApacheV2
lazy val commons_codec = "commons-codec" % "commons-codec" % "1.4" % "compile" //ApacheV2
lazy val commons_io = "commons-io" % "commons-io" % "2.0.1" % "compile" //ApacheV2
lazy val javax_servlet_30 = "org.glassfish" % "javax.servlet" % JAVAX_SERVLET_VERSION % "provided" //CDDL v1
@@ -136,6 +140,7 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
lazy val spring_beans = "org.springframework" % "spring-beans" % SPRING_VERSION % "compile" //ApacheV2
lazy val spring_context = "org.springframework" % "spring-context" % SPRING_VERSION % "compile" //ApacheV2
+ lazy val spring_jms = "org.springframework" % "spring-jms" % SPRING_VERSION % "compile" //ApacheV2
lazy val stax_api = "javax.xml.stream" % "stax-api" % "1.0-2" % "compile" //ApacheV2
lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" % "runtime" //MIT
lazy val log4j = "log4j" % "log4j" % "1.2.15" //ApacheV2
@@ -154,6 +159,7 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
lazy val scalacheck = "org.scala-tools.testing" %% "scalacheck" % "1.9" % "test" //New BSD
lazy val testLogback = "ch.qos.logback" % "logback-classic" % LOGBACK_VERSION % "test" // EPL 1.0 / LGPL 2.1
lazy val camel_spring = "org.apache.camel" % "camel-spring" % CAMEL_VERSION % "test" //ApacheV2
+ lazy val commonsMath = "org.apache.commons" % "commons-math" % "2.1" % "test" //ApacheV2
}
@@ -173,7 +179,7 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
lazy val akka_camel = project("akka-camel", "akka-camel", new AkkaCamelProject(_), akka_actor, akka_slf4j)
lazy val akka_camel_typed = project("akka-camel-typed", "akka-camel-typed", new AkkaCamelTypedProject(_), akka_actor, akka_slf4j, akka_camel)
//lazy val akka_spring = project("akka-spring", "akka-spring", new AkkaSpringProject(_), akka_cluster, akka_camel)
- //lazy val akka_kernel = project("akka-kernel", "akka-kernel", new AkkaKernelProject(_), akka_cluster, akka_http, akka_slf4j, akka_camel)
+ lazy val akka_kernel = project("akka-kernel", "akka-kernel", new AkkaKernelProject(_), akka_cluster, akka_http, akka_slf4j, akka_camel_typed)
lazy val akka_sbt_plugin = project("akka-sbt-plugin", "akka-sbt-plugin", new AkkaSbtPluginProject(_))
lazy val akka_tutorials = project("akka-tutorials", "akka-tutorials", new AkkaTutorialsParentProject(_), akka_actor)
@@ -598,6 +604,23 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
class AkkaSampleFSMProject(info: ProjectInfo) extends AkkaDefaultProject(info)
+ class AkkaSampleCamelProject(info: ProjectInfo) extends AkkaDefaultProject(info) {
+ val activemq = Dependencies.activemq
+ val camel_jetty = Dependencies.camel_jetty
+ val camel_jms = Dependencies.camel_jms
+ val spring_jms = Dependencies.spring_jms
+ val commons_codec = Dependencies.commons_codec
+
+ override def ivyXML = {
+
+
+
+
+ }
+
+ override def testOptions = createTestFilter( _.endsWith("Test"))
+ }
+
class AkkaSampleHelloProject(info: ProjectInfo) extends AkkaDefaultProject(info)
class AkkaSampleOsgiProject(info: ProjectInfo) extends AkkaDefaultProject(info) with BNDPlugin {
@@ -623,6 +646,8 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
new AkkaSampleOsgiProject(_), akka_actor)
// lazy val akka_sample_remote = project("akka-sample-remote", "akka-sample-remote",
// new AkkaSampleRemoteProject(_), akka_cluster)
+ lazy val akka_sample_camel = project("akka-sample-camel", "akka-sample-camel",
+ new AkkaSampleCamelProject(_), akka_actor, akka_kernel)
lazy val publishRelease = {
val releaseConfiguration = new DefaultPublishConfiguration(localReleaseRepository, "release", false)
@@ -685,6 +710,8 @@ class AkkaParentProject(info: ProjectInfo) extends ParentProject(info) with Exec
val protobuf = Dependencies.protobuf
val jackson = Dependencies.jackson
val sjson = Dependencies.sjson
+ val commonsMath = Dependencies.commonsMath
+ val mockito = Dependencies.mockito
override def compileOptions = super.compileOptions ++ compileOptions("-P:continuations:enable")
}
diff --git a/project/build/DistProject.scala b/project/sbt7/build/DistProject.scala
similarity index 100%
rename from project/build/DistProject.scala
rename to project/sbt7/build/DistProject.scala
diff --git a/project/build/DocParentProject.scala b/project/sbt7/build/DocParentProject.scala
similarity index 100%
rename from project/build/DocParentProject.scala
rename to project/sbt7/build/DocParentProject.scala
diff --git a/project/build/MultiJvmTests.scala b/project/sbt7/build/MultiJvmTests.scala
similarity index 88%
rename from project/build/MultiJvmTests.scala
rename to project/sbt7/build/MultiJvmTests.scala
index 4cf83e3076..46d3956d62 100644
--- a/project/build/MultiJvmTests.scala
+++ b/project/sbt7/build/MultiJvmTests.scala
@@ -2,13 +2,13 @@ import sbt._
import sbt.Process
import java.io.File
import java.lang.{ProcessBuilder => JProcessBuilder}
-import java.io.{BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream}
-import java.io.{PipedInputStream, PipedOutputStream}
-import scala.concurrent.SyncVar
+import java.io.{BufferedReader, InputStream, InputStreamReader, OutputStream}
trait MultiJvmTests extends DefaultProject {
def multiJvmTestName = "MultiJvm"
+
def multiJvmOptions: Seq[String] = Seq.empty
+
def multiJvmExtraOptions(className: String): Seq[String] = Seq.empty
val MultiJvmTestName = multiJvmTestName
@@ -29,13 +29,16 @@ trait MultiJvmTests extends DefaultProject {
lazy val multiJvmTestAll = multiJvmTestAllAction
def multiJvmTestAction = multiJvmMethod(getMultiJvmTests, testScalaOptions)
+
def multiJvmRunAction = multiJvmMethod(getMultiJvmApps, runScalaOptions)
+
def multiJvmTestAllAction = multiJvmTask(Nil, getMultiJvmTests, testScalaOptions)
def multiJvmMethod(getMultiTestsMap: => Map[String, Seq[String]], scalaOptions: String => Seq[String]) = {
- task { args =>
- multiJvmTask(args.toList, getMultiTestsMap, scalaOptions)
- } completeWith(getMultiTestsMap.keys.toList)
+ task {
+ args =>
+ multiJvmTask(args.toList, getMultiTestsMap, scalaOptions)
+ } completeWith (getMultiTestsMap.keys.toList)
}
def multiJvmTask(tests: List[String], getMultiTestsMap: => Map[String, Seq[String]], scalaOptions: String => Seq[String]) = {
@@ -58,17 +61,26 @@ trait MultiJvmTests extends DefaultProject {
} dependsOn (testCompile)
}
+ /**
+ * todo: Documentation
+ */
def getMultiJvmTests(): Map[String, Seq[String]] = {
val allTests = testCompileConditional.analysis.allTests.toList.map(_.className)
filterMultiJvmTests(allTests)
}
+ /**
+ * todo: Documentation
+ */
def getMultiJvmApps(): Map[String, Seq[String]] = {
val allApps = (mainCompileConditional.analysis.allApplications.toSeq ++
- testCompileConditional.analysis.allApplications.toSeq)
+ testCompileConditional.analysis.allApplications.toSeq)
filterMultiJvmTests(allApps)
}
+ /**
+ * todo: Documentation
+ */
def filterMultiJvmTests(allTests: Seq[String]): Map[String, Seq[String]] = {
val multiJvmTests = allTests filter (_.contains(MultiJvmTestName))
val names = multiJvmTests map { fullName =>
@@ -81,16 +93,25 @@ trait MultiJvmTests extends DefaultProject {
Map(testPairs: _*)
}
+ /**
+ * todo: Documentation
+ */
def testIdentifier(className: String) = {
val i = className.indexOf(MultiJvmTestName)
val l = MultiJvmTestName.length
className.substring(i + l)
}
+ /**
+ * todo: Documentation
+ */
def testSimpleName(className: String) = {
className.split("\\.").last
}
+ /**
+ * todo: Documentation
+ */
def testScalaOptions(testClass: String) = {
val scalaTestJars = testClasspath.get.filter(_.name.contains("scalatest"))
val cp = Path.makeString(scalaTestJars)
@@ -98,13 +119,23 @@ trait MultiJvmTests extends DefaultProject {
Seq("-cp", cp, ScalaTestRunner, ScalaTestOptions, "-s", testClass, "-p", paths)
}
+ /**
+ * todo: Documentation
+ */
def runScalaOptions(appClass: String) = {
val cp = Path.makeString(testClasspath.get)
Seq("-cp", cp, appClass)
}
- def runMulti(testName: String, testClasses: Seq[String], scalaOptions: String => Seq[String]) = {
+ /**
+ * Runs all the test. This method blocks until all processes have completed.
+ *
+ * @return an option that return an error message if one of the tests failed, or a None in case of a success.
+ */
+ def runMulti(testName: String, testClasses: Seq[String], scalaOptions: String => Seq[String]): Option[String] = {
log.control(ControlEvent.Start, "%s multi-jvm / %s %s" format (HeaderStart, testName, HeaderEnd))
+
+ //spawns all the processes.
val processes = testClasses.toList.zipWithIndex map {
case (testClass, index) => {
val jvmName = "JVM-" + testIdentifier(testClass)
@@ -128,18 +159,28 @@ trait MultiJvmTests extends DefaultProject {
(testClass, startJvm(jvmOptions, scalaOptions(testClass), jvmLogger, index == 0))
}
}
+
+ //places the exit code of the process belonging to a specific textClass in the exitCodes map.
val exitCodes = processes map {
case (testClass, process) => (testClass, process.exitValue)
}
+
+ //Checks if there are any processes that failed with an error.
val failures = exitCodes flatMap {
case (testClass, exit) if exit > 0 => Some("%s failed with exit code %s" format (testClass, exit))
case _ => None
}
+
+ //log the failures (if there are any).
failures foreach (log.error(_))
log.control(ControlEvent.Finish, "%s multi-jvm / %s %s" format (HeaderStart, testName, HeaderEnd))
+
if (!failures.isEmpty) Some("Some processes failed") else None
}
+ /**
+ * Starts a JVM with the given options.
+ */
def startJvm(jvmOptions: Seq[String], scalaOptions: Seq[String], logger: Logger, connectInput: Boolean) = {
val si = buildScalaInstance
val scalaJars = Seq(si.libraryJar, si.compilerJar)
diff --git a/project/plugins/Plugins.scala b/project/sbt7/plugins/Plugins.scala
similarity index 100%
rename from project/plugins/Plugins.scala
rename to project/sbt7/plugins/Plugins.scala