Merge branch 'master' into wip-merge-to-master-patriknw
This commit is contained in:
commit
54f5b836fc
91 changed files with 5343 additions and 2232 deletions
|
|
@ -118,6 +118,23 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterAll {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"log with MDC" in {
|
||||||
|
new TestKit(appLogging) {
|
||||||
|
system.eventStream.subscribe(testActor, classOf[Logging.Debug])
|
||||||
|
val myMDC = Map("hello" → "mdc")
|
||||||
|
val a = system.actorOf(Props(new Actor with DiagnosticActorLogging {
|
||||||
|
override def mdc(currentMessage: Any) = myMDC
|
||||||
|
def receive = LoggingReceive {
|
||||||
|
case "hello" ⇒
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
a ! "hello"
|
||||||
|
expectMsgPF(hint = "Logging.Debug2") {
|
||||||
|
case m: Logging.Debug2 if m.mdc == myMDC ⇒ ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"An Actor" must {
|
"An Actor" must {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@
|
||||||
package akka.io
|
package akka.io
|
||||||
|
|
||||||
import java.io.{ File, IOException }
|
import java.io.{ File, IOException }
|
||||||
import java.net.{ ServerSocket, URLClassLoader, InetSocketAddress }
|
import java.net.{ InetSocketAddress, ServerSocket, URLClassLoader }
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels._
|
import java.nio.channels._
|
||||||
import java.nio.channels.spi.SelectorProvider
|
import java.nio.channels.spi.SelectorProvider
|
||||||
import java.nio.channels.SelectionKey._
|
import java.nio.channels.SelectionKey._
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
@ -21,8 +22,8 @@ import akka.io.Tcp._
|
||||||
import akka.io.SelectionHandler._
|
import akka.io.SelectionHandler._
|
||||||
import akka.io.Inet.SocketOption
|
import akka.io.Inet.SocketOption
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import akka.testkit.{ AkkaSpec, EventFilter, TestActorRef, TestProbe }
|
import akka.testkit.{ AkkaSpec, EventFilter, SocketUtil, TestActorRef, TestProbe }
|
||||||
import akka.util.{ Helpers, ByteString }
|
import akka.util.{ ByteString, Helpers }
|
||||||
import akka.testkit.SocketUtil._
|
import akka.testkit.SocketUtil._
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
|
|
||||||
|
|
@ -826,7 +827,8 @@ class TcpConnectionSpec extends AkkaSpec("""
|
||||||
"report abort before handler is registered (reproducer from #15033)" in {
|
"report abort before handler is registered (reproducer from #15033)" in {
|
||||||
// This test needs the OP_CONNECT workaround on Windows, see original report #15033 and parent ticket #15766
|
// This test needs the OP_CONNECT workaround on Windows, see original report #15033 and parent ticket #15766
|
||||||
|
|
||||||
val bindAddress = new InetSocketAddress(23402)
|
val port = SocketUtil.temporaryServerAddress().getPort
|
||||||
|
val bindAddress = new InetSocketAddress(port)
|
||||||
val serverSocket = new ServerSocket(bindAddress.getPort, 100, bindAddress.getAddress)
|
val serverSocket = new ServerSocket(bindAddress.getPort, 100, bindAddress.getAddress)
|
||||||
val connectionProbe = TestProbe()
|
val connectionProbe = TestProbe()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,19 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers {
|
||||||
ByteString1.fromString("ab").drop(2) should ===(ByteString(""))
|
ByteString1.fromString("ab").drop(2) should ===(ByteString(""))
|
||||||
ByteString1.fromString("ab").drop(3) should ===(ByteString(""))
|
ByteString1.fromString("ab").drop(3) should ===(ByteString(""))
|
||||||
}
|
}
|
||||||
|
"take" in {
|
||||||
|
ByteString1.empty.take(-1) should ===(ByteString(""))
|
||||||
|
ByteString1.empty.take(0) should ===(ByteString(""))
|
||||||
|
ByteString1.empty.take(1) should ===(ByteString(""))
|
||||||
|
ByteString1.fromString("a").take(1) should ===(ByteString("a"))
|
||||||
|
ByteString1.fromString("ab").take(-1) should ===(ByteString(""))
|
||||||
|
ByteString1.fromString("ab").take(0) should ===(ByteString(""))
|
||||||
|
ByteString1.fromString("ab").take(1) should ===(ByteString("a"))
|
||||||
|
ByteString1.fromString("ab").take(2) should ===(ByteString("ab"))
|
||||||
|
ByteString1.fromString("ab").take(3) should ===(ByteString("ab"))
|
||||||
|
ByteString1.fromString("0123456789").take(3).drop(1) should ===(ByteString("12"))
|
||||||
|
ByteString1.fromString("0123456789").take(10).take(8).drop(3).take(5) should ===(ByteString("34567"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"ByteString1C" must {
|
"ByteString1C" must {
|
||||||
"drop(0)" in {
|
"drop(0)" in {
|
||||||
|
|
@ -415,6 +428,9 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers {
|
||||||
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(3) should ===(ByteString(""))
|
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(3) should ===(ByteString(""))
|
||||||
}
|
}
|
||||||
"take" in {
|
"take" in {
|
||||||
|
ByteString.empty.take(-1) should ===(ByteString(""))
|
||||||
|
ByteString.empty.take(0) should ===(ByteString(""))
|
||||||
|
ByteString.empty.take(1) should ===(ByteString(""))
|
||||||
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(0) should ===(ByteString(""))
|
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(0) should ===(ByteString(""))
|
||||||
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-1) should ===(ByteString(""))
|
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-1) should ===(ByteString(""))
|
||||||
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-2) should ===(ByteString(""))
|
ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-2) should ===(ByteString(""))
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
|
||||||
private var watchedBy: Set[ActorRef] = ActorCell.emptyActorRefSet
|
private var watchedBy: Set[ActorRef] = ActorCell.emptyActorRefSet
|
||||||
private var terminatedQueued: Set[ActorRef] = ActorCell.emptyActorRefSet
|
private var terminatedQueued: Set[ActorRef] = ActorCell.emptyActorRefSet
|
||||||
|
|
||||||
|
def isWatching(ref: ActorRef): Boolean = watching contains ref
|
||||||
|
|
||||||
override final def watch(subject: ActorRef): ActorRef = subject match {
|
override final def watch(subject: ActorRef): ActorRef = subject match {
|
||||||
case a: InternalActorRef ⇒
|
case a: InternalActorRef ⇒
|
||||||
if (a != self && !watchingContains(a)) {
|
if (a != self && !watchingContains(a)) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import language.existentials
|
||||||
import akka.actor.Actor.Receive
|
import akka.actor.Actor.Receive
|
||||||
import akka.actor.ActorContext
|
import akka.actor.ActorContext
|
||||||
import akka.actor.ActorCell
|
import akka.actor.ActorCell
|
||||||
|
import akka.actor.DiagnosticActorLogging
|
||||||
import akka.event.Logging.Debug
|
import akka.event.Logging.Debug
|
||||||
|
|
||||||
object LoggingReceive {
|
object LoggingReceive {
|
||||||
|
|
@ -52,13 +53,18 @@ class LoggingReceive(source: Option[AnyRef], r: Receive, label: Option[String])(
|
||||||
def isDefinedAt(o: Any): Boolean = {
|
def isDefinedAt(o: Any): Boolean = {
|
||||||
val handled = r.isDefinedAt(o)
|
val handled = r.isDefinedAt(o)
|
||||||
if (context.system.eventStream.logLevel >= Logging.DebugLevel) {
|
if (context.system.eventStream.logLevel >= Logging.DebugLevel) {
|
||||||
val (str, clazz) = LogSource.fromAnyRef(source getOrElse context.asInstanceOf[ActorCell].actor)
|
val src = source getOrElse context.asInstanceOf[ActorCell].actor
|
||||||
context.system.eventStream.publish(Debug(str, clazz, "received " + (if (handled) "handled" else "unhandled") + " message " + o
|
val (str, clazz) = LogSource.fromAnyRef(src)
|
||||||
+ " from " + context.sender()
|
val message = "received " + (if (handled) "handled" else "unhandled") + " message " + o + " from " + context.sender() +
|
||||||
+ (label match {
|
(label match {
|
||||||
case Some(l) ⇒ " in state " + l
|
case Some(l) ⇒ " in state " + l
|
||||||
case _ ⇒ ""
|
case _ ⇒ ""
|
||||||
})))
|
})
|
||||||
|
val event = src match {
|
||||||
|
case a: DiagnosticActorLogging ⇒ Debug(str, clazz, message, a.log.mdc)
|
||||||
|
case _ ⇒ Debug(str, clazz, message)
|
||||||
|
}
|
||||||
|
context.system.eventStream.publish(event)
|
||||||
}
|
}
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -253,8 +253,11 @@ object ByteString {
|
||||||
}
|
}
|
||||||
|
|
||||||
override def take(n: Int): ByteString =
|
override def take(n: Int): ByteString =
|
||||||
if (n <= 0) ByteString.empty
|
if (n <= 0) ByteString.empty else take1(n)
|
||||||
else ByteString1(bytes, startIndex, Math.min(n, length))
|
|
||||||
|
private[akka] def take1(n: Int): ByteString1 =
|
||||||
|
if (n >= length) this
|
||||||
|
else ByteString1(bytes, startIndex, n)
|
||||||
|
|
||||||
override def slice(from: Int, until: Int): ByteString =
|
override def slice(from: Int, until: Int): ByteString =
|
||||||
drop(from).take(until - Math.max(0, from))
|
drop(from).take(until - Math.max(0, from))
|
||||||
|
|
@ -436,18 +439,23 @@ object ByteString {
|
||||||
bytestrings.foreach(_.writeToOutputStream(os))
|
bytestrings.foreach(_.writeToOutputStream(os))
|
||||||
}
|
}
|
||||||
|
|
||||||
override def take(n: Int): ByteString = {
|
override def take(n: Int): ByteString =
|
||||||
@tailrec def take0(n: Int, b: ByteStringBuilder, bs: Vector[ByteString1]): ByteString =
|
|
||||||
if (bs.isEmpty || n <= 0) b.result
|
|
||||||
else {
|
|
||||||
val head = bs.head
|
|
||||||
if (n <= head.length) b.append(head.take(n)).result
|
|
||||||
else take0(n - head.length, b.append(head), bs.tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n <= 0) ByteString.empty
|
if (n <= 0) ByteString.empty
|
||||||
else if (n >= length) this
|
else if (n >= length) this
|
||||||
else take0(n, ByteString.newBuilder, bytestrings)
|
else take0(n)
|
||||||
|
|
||||||
|
private[akka] def take0(n: Int): ByteString = {
|
||||||
|
@tailrec def go(last: Int, restToTake: Int): (Int, Int) = {
|
||||||
|
val bs = bytestrings(last)
|
||||||
|
if (bs.length > restToTake) (last, restToTake)
|
||||||
|
else go(last + 1, restToTake - bs.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (last, restToTake) = go(0, n)
|
||||||
|
|
||||||
|
if (last == 0) bytestrings(last).take(restToTake)
|
||||||
|
else if (restToTake == 0) new ByteStrings(bytestrings.take(last), n)
|
||||||
|
else new ByteStrings(bytestrings.take(last) :+ bytestrings(last).take1(restToTake), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def dropRight(n: Int): ByteString =
|
override def dropRight(n: Int): ByteString =
|
||||||
|
|
@ -473,7 +481,7 @@ object ByteString {
|
||||||
|
|
||||||
override def drop(n: Int): ByteString =
|
override def drop(n: Int): ByteString =
|
||||||
if (n <= 0) this
|
if (n <= 0) this
|
||||||
else if (n > length) ByteString.empty
|
else if (n >= length) ByteString.empty
|
||||||
else drop0(n)
|
else drop0(n)
|
||||||
|
|
||||||
private def drop0(n: Int): ByteString = {
|
private def drop0(n: Int): ByteString = {
|
||||||
|
|
|
||||||
|
|
@ -5,36 +5,36 @@ import akka.testkit.AkkaSpec
|
||||||
|
|
||||||
import scala.concurrent.{ Await, Future }
|
import scala.concurrent.{ Await, Future }
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.language.postfixOps
|
|
||||||
|
|
||||||
class ConstantRateEntityRecoveryStrategySpec extends AkkaSpec {
|
class ConstantRateEntityRecoveryStrategySpec extends AkkaSpec {
|
||||||
|
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
val strategy = EntityRecoveryStrategy.constantStrategy(system, 500 millis, 2)
|
val strategy = EntityRecoveryStrategy.constantStrategy(system, 1.second, 2)
|
||||||
|
|
||||||
"ConstantRateEntityRecoveryStrategy" must {
|
"ConstantRateEntityRecoveryStrategy" must {
|
||||||
"recover entities" in {
|
"recover entities" in {
|
||||||
val entities = Set[EntityId]("1", "2", "3", "4", "5")
|
val entities = Set[EntityId]("1", "2", "3", "4", "5")
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.nanoTime()
|
||||||
val resultWithTimes = strategy.recoverEntities(entities).map(
|
val resultWithTimes = strategy.recoverEntities(entities).map(
|
||||||
_.map(entityIds ⇒ (entityIds, System.currentTimeMillis() - startTime))
|
_.map(entityIds ⇒ (entityIds → (System.nanoTime() - startTime).nanos)))
|
||||||
)
|
|
||||||
|
|
||||||
val result = Await.result(Future.sequence(resultWithTimes), 4 seconds).toList.sortWith(_._2 < _._2)
|
val result = Await.result(Future.sequence(resultWithTimes), 6.seconds)
|
||||||
|
.toVector.sortBy { case (_, duration) ⇒ duration }
|
||||||
result.size should ===(3)
|
result.size should ===(3)
|
||||||
|
|
||||||
val scheduledEntities = result.map(_._1)
|
val scheduledEntities = result.map(_._1)
|
||||||
scheduledEntities.head.size should ===(2)
|
scheduledEntities(0).size should ===(2)
|
||||||
scheduledEntities(1).size should ===(2)
|
scheduledEntities(1).size should ===(2)
|
||||||
scheduledEntities(2).size should ===(1)
|
scheduledEntities(2).size should ===(1)
|
||||||
scheduledEntities.foldLeft(Set[EntityId]())(_ ++ _) should ===(entities)
|
scheduledEntities.flatten.toSet should ===(entities)
|
||||||
|
|
||||||
val times = result.map(_._2)
|
val timesMillis = result.map(_._2.toMillis)
|
||||||
|
|
||||||
times.head should ===(500L +- 30L)
|
// scheduling will not happen too early
|
||||||
times(1) should ===(1000L +- 30L)
|
timesMillis(0) should ===(1400L +- 500)
|
||||||
times(2) should ===(1500L +- 30L)
|
timesMillis(1) should ===(2400L +- 500L)
|
||||||
|
timesMillis(2) should ===(3400L +- 500L)
|
||||||
}
|
}
|
||||||
|
|
||||||
"not recover when no entities to recover" in {
|
"not recover when no entities to recover" in {
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,4 @@ Additional Information
|
||||||
../common/binary-compatibility-rules
|
../common/binary-compatibility-rules
|
||||||
faq
|
faq
|
||||||
books
|
books
|
||||||
language-bindings
|
|
||||||
osgi
|
osgi
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
Other Language Bindings
|
|
||||||
=======================
|
|
||||||
|
|
||||||
JRuby
|
|
||||||
-----
|
|
||||||
|
|
||||||
Read more here: `<https://github.com/iconara/mikka>`_.
|
|
||||||
|
|
||||||
Groovy/Groovy++
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Read more here: `<https://gist.github.com/620439>`_.
|
|
||||||
|
|
||||||
Clojure
|
|
||||||
-------
|
|
||||||
|
|
||||||
Read more here: `<http://blog.darevay.com/2011/06/clojure-and-akka-a-match-made-in/>`_.
|
|
||||||
|
|
@ -187,7 +187,7 @@ Callbacks
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Sometimes you just want to listen to a ``Future`` being completed, and react to that not by creating a new Future, but by side-effecting.
|
Sometimes you just want to listen to a ``Future`` being completed, and react to that not by creating a new Future, but by side-effecting.
|
||||||
For this Scala supports ``onComplete``, ``onSuccess`` and ``onFailure``, of which the latter two are specializations of the first.
|
For this Scala supports ``onComplete``, ``onSuccess`` and ``onFailure``, of which the last two are specializations of the first.
|
||||||
|
|
||||||
.. includecode:: code/docs/future/FutureDocTest.java
|
.. includecode:: code/docs/future/FutureDocTest.java
|
||||||
:include: onSuccess
|
:include: onSuccess
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,39 @@ mechanism when ``persist()`` is used. Notice the early stop behaviour that occur
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown-example-bad
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown-example-bad
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown-example-good
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#safe-shutdown-example-good
|
||||||
|
|
||||||
|
|
||||||
|
.. _replay-filter-java:
|
||||||
|
|
||||||
|
Replay Filter
|
||||||
|
-------------
|
||||||
|
There could be cases where event streams are corrupted and multiple writers (i.e. multiple persistent actor instances)
|
||||||
|
journaled different messages with the same sequence number.
|
||||||
|
In such a case, you can configure how you filter replayed messages from multiple writers, upon recovery.
|
||||||
|
|
||||||
|
In your configuration, under the ``akka.persistence.journal.xxx.replay-filter`` section (where ``xxx`` is your journal plugin id),
|
||||||
|
you can select the replay filter ``mode`` from one of the following values:
|
||||||
|
|
||||||
|
* repair-by-discard-old
|
||||||
|
* fail
|
||||||
|
* warn
|
||||||
|
* off
|
||||||
|
|
||||||
|
For example, if you configure the replay filter for leveldb plugin, it looks like this::
|
||||||
|
|
||||||
|
# The replay filter can detect a corrupt event stream by inspecting
|
||||||
|
# sequence numbers and writerUuid when replaying events.
|
||||||
|
akka.persistence.journal.leveldb.replay-filter {
|
||||||
|
# What the filter should do when detecting invalid events.
|
||||||
|
# Supported values:
|
||||||
|
# `repair-by-discard-old` : discard events from old writers,
|
||||||
|
# warning is logged
|
||||||
|
# `fail` : fail the replay, error is logged
|
||||||
|
# `warn` : log warning but emit events untouched
|
||||||
|
# `off` : disable this feature completely
|
||||||
|
mode = repair-by-discard-old
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.. _persistent-views-java:
|
.. _persistent-views-java:
|
||||||
|
|
||||||
Persistent Views
|
Persistent Views
|
||||||
|
|
|
||||||
|
|
@ -1230,6 +1230,16 @@ returned value downstream.
|
||||||
|
|
||||||
**completes** when any upstream completes
|
**completes** when any upstream completes
|
||||||
|
|
||||||
|
zipWithIndex
|
||||||
|
^^^^^^^
|
||||||
|
Zips elements of current flow with its indices.
|
||||||
|
|
||||||
|
**emits** upstream emits an element and is paired with their index
|
||||||
|
|
||||||
|
**backpressures** when downstream backpressures
|
||||||
|
|
||||||
|
**completes** when upstream completes
|
||||||
|
|
||||||
concat
|
concat
|
||||||
^^^^^^
|
^^^^^^
|
||||||
After completion of the original upstream the elements of the given source will be emitted.
|
After completion of the original upstream the elements of the given source will be emitted.
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ Callbacks
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Sometimes you just want to listen to a ``Future`` being completed, and react to that not by creating a new ``Future``, but by side-effecting.
|
Sometimes you just want to listen to a ``Future`` being completed, and react to that not by creating a new ``Future``, but by side-effecting.
|
||||||
For this Scala supports ``onComplete``, ``onSuccess`` and ``onFailure``, of which the latter two are specializations of the first.
|
For this Scala supports ``onComplete``, ``onSuccess`` and ``onFailure``, of which the last two are specializations of the first.
|
||||||
|
|
||||||
.. includecode:: code/docs/future/FutureDocSpec.scala
|
.. includecode:: code/docs/future/FutureDocSpec.scala
|
||||||
:include: onSuccess
|
:include: onSuccess
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,8 @@ It contains instructions on how to run the ``PersistentActorExample``.
|
||||||
Note that when using ``become`` from ``receiveRecover`` it will still only use the ``receiveRecover``
|
Note that when using ``become`` from ``receiveRecover`` it will still only use the ``receiveRecover``
|
||||||
behavior when replaying the events. When replay is completed it will use the new behavior.
|
behavior when replaying the events. When replay is completed it will use the new behavior.
|
||||||
|
|
||||||
|
.. _persistence-id-scala:
|
||||||
|
|
||||||
Identifiers
|
Identifiers
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
@ -440,6 +442,37 @@ mechanism when ``persist()`` is used. Notice the early stop behaviour that occur
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown-example-bad
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown-example-bad
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown-example-good
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#safe-shutdown-example-good
|
||||||
|
|
||||||
|
.. _replay-filter-scala:
|
||||||
|
|
||||||
|
Replay Filter
|
||||||
|
-------------
|
||||||
|
There could be cases where event streams are corrupted and multiple writers (i.e. multiple persistent actor instances)
|
||||||
|
journaled different messages with the same sequence number.
|
||||||
|
In such a case, you can configure how you filter replayed messages from multiple writers, upon recovery.
|
||||||
|
|
||||||
|
In your configuration, under the ``akka.persistence.journal.xxx.replay-filter`` section (where ``xxx`` is your journal plugin id),
|
||||||
|
you can select the replay filter ``mode`` from one of the following values:
|
||||||
|
|
||||||
|
* repair-by-discard-old
|
||||||
|
* fail
|
||||||
|
* warn
|
||||||
|
* off
|
||||||
|
|
||||||
|
For example, if you configure the replay filter for leveldb plugin, it looks like this::
|
||||||
|
|
||||||
|
# The replay filter can detect a corrupt event stream by inspecting
|
||||||
|
# sequence numbers and writerUuid when replaying events.
|
||||||
|
akka.persistence.journal.leveldb.replay-filter {
|
||||||
|
# What the filter should do when detecting invalid events.
|
||||||
|
# Supported values:
|
||||||
|
# `repair-by-discard-old` : discard events from old writers,
|
||||||
|
# warning is logged
|
||||||
|
# `fail` : fail the replay, error is logged
|
||||||
|
# `warn` : log warning but emit events untouched
|
||||||
|
# `off` : disable this feature completely
|
||||||
|
mode = repair-by-discard-old
|
||||||
|
}
|
||||||
|
|
||||||
.. _persistent-views:
|
.. _persistent-views:
|
||||||
|
|
||||||
Persistent Views
|
Persistent Views
|
||||||
|
|
|
||||||
|
|
@ -1222,6 +1222,16 @@ returned value downstream.
|
||||||
|
|
||||||
**completes** when any upstream completes
|
**completes** when any upstream completes
|
||||||
|
|
||||||
|
zipWithIndex
|
||||||
|
^^^^^^^
|
||||||
|
Zips elements of current flow with its indices.
|
||||||
|
|
||||||
|
**emits** upstream emits an element and is paired with their index
|
||||||
|
|
||||||
|
**backpressures** when downstream backpressures
|
||||||
|
|
||||||
|
**completes** when upstream completes
|
||||||
|
|
||||||
concat
|
concat
|
||||||
^^^^^^
|
^^^^^^
|
||||||
After completion of the original upstream the elements of the given source will be emitted.
|
After completion of the original upstream the elements of the given source will be emitted.
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,15 @@ protocol but Actors can model arbitrarily complex protocols when needed. The
|
||||||
protocol is bundled together with the behavior that implements it in a nicely
|
protocol is bundled together with the behavior that implements it in a nicely
|
||||||
wrapped scope—the ``HelloWorld`` object.
|
wrapped scope—the ``HelloWorld`` object.
|
||||||
|
|
||||||
Now we want to try out this Actor, so we must start an Actor system to host it:
|
Now we want to try out this Actor, so we must start an ActorSystem to host it:
|
||||||
|
|
||||||
.. includecode:: code/docs/akka/typed/IntroSpec.scala#hello-world
|
.. includecode:: code/docs/akka/typed/IntroSpec.scala#hello-world
|
||||||
|
|
||||||
After importing the Actor’s protocol definition we start an Actor system from
|
After importing the Actor’s protocol definition we start an Actor system from
|
||||||
the defined behavior, wrapping it in :class:`Props` like an actor on stage. The
|
the defined behavior, wrapping it in :class:`Props` like an actor on stage. The
|
||||||
props we are giving to this one are just the defaults, we could at this point
|
props we are giving to this one are just the defaults, we could at this point
|
||||||
also configure how and where the Actor should be deployed in a clustered
|
also configure which thread pool will be used to run it or its mailbox capacity
|
||||||
system.
|
for incoming messages.
|
||||||
|
|
||||||
As Carl Hewitt said, one Actor is no Actor—it would be quite lonely with
|
As Carl Hewitt said, one Actor is no Actor—it would be quite lonely with
|
||||||
nobody to talk to. In this sense the example is a little cruel because we only
|
nobody to talk to. In this sense the example is a little cruel because we only
|
||||||
|
|
@ -72,7 +72,7 @@ Note that the :class:`Future` that is returned by the “ask” operation is
|
||||||
properly typed already, no type checks or casts needed. This is possible due to
|
properly typed already, no type checks or casts needed. This is possible due to
|
||||||
the type information that is part of the message protocol: the ``?`` operator
|
the type information that is part of the message protocol: the ``?`` operator
|
||||||
takes as argument a function that accepts an :class:`ActorRef[U]` (which
|
takes as argument a function that accepts an :class:`ActorRef[U]` (which
|
||||||
explains the ``_`` hole in the expression on line 6 above) and the ``replyTo``
|
explains the ``_`` hole in the expression on line 7 above) and the ``replyTo``
|
||||||
parameter which we fill in like that is of type ``ActorRef[Greeted]``, which
|
parameter which we fill in like that is of type ``ActorRef[Greeted]``, which
|
||||||
means that the value that fulfills the :class:`Promise` can only be of type
|
means that the value that fulfills the :class:`Promise` can only be of type
|
||||||
:class:`Greeted`.
|
:class:`Greeted`.
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
|
|
||||||
package akka.http.javadsl.model;
|
package akka.http.javadsl.model;
|
||||||
|
|
||||||
import akka.japi.Pair;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import akka.japi.Pair;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple model for `application/x-www-form-urlencoded` form data.
|
* Simple model for `application/x-www-form-urlencoded` form data.
|
||||||
*/
|
*/
|
||||||
|
|
@ -51,4 +52,11 @@ public final class FormData {
|
||||||
public static FormData create(Map<String, String> params) {
|
public static FormData create(Map<String, String> params) {
|
||||||
return new FormData(Query.create(params));
|
return new FormData(Query.create(params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a FormData from the given parameters.
|
||||||
|
*/
|
||||||
|
public static FormData create(Iterable<Pair<String, String>> params) {
|
||||||
|
return new FormData(Query.create(params));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,10 +185,10 @@ private[http] trait Rendering {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the given string either directly (if it only contains token chars)
|
* Renders the given string either directly (if it only contains token chars)
|
||||||
* or in double quotes (if it contains at least one non-token char).
|
* or in double quotes (if it is empty or contains at least one non-token char).
|
||||||
*/
|
*/
|
||||||
def ~~#(s: String): this.type =
|
def ~~#(s: String): this.type =
|
||||||
if (CharacterClasses.tchar matchesAll s) this ~~ s else ~~#!(s)
|
if (s.nonEmpty && CharacterClasses.tchar.matchesAll(s)) this ~~ s else ~~#!(s)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the given string in double quotes.
|
* Renders the given string in double quotes.
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ object StatusCodes extends ObjectRegistry[Int, StatusCode] {
|
||||||
val Created = reg(s(201)("Created", "The request has been fulfilled and resulted in a new resource being created."))
|
val Created = reg(s(201)("Created", "The request has been fulfilled and resulted in a new resource being created."))
|
||||||
val Accepted = reg(s(202)("Accepted", "The request has been accepted for processing, but the processing has not been completed."))
|
val Accepted = reg(s(202)("Accepted", "The request has been accepted for processing, but the processing has not been completed."))
|
||||||
val NonAuthoritativeInformation = reg(s(203)("Non-Authoritative Information", "The server successfully processed the request, but is returning information that may be from another source."))
|
val NonAuthoritativeInformation = reg(s(203)("Non-Authoritative Information", "The server successfully processed the request, but is returning information that may be from another source."))
|
||||||
val NoContent = reg(s(204)("No Content", "", allowsEntity = false))
|
val NoContent = reg(s(204)("No Content", "The server successfully processed the request and is not returning any content.", allowsEntity = false))
|
||||||
val ResetContent = reg(s(205)("Reset Content", "The server successfully processed the request, but is not returning any content."))
|
val ResetContent = reg(s(205)("Reset Content", "The server successfully processed the request, but is not returning any content."))
|
||||||
val PartialContent = reg(s(206)("Partial Content", "The server is delivering only part of the resource due to a range header sent by the client."))
|
val PartialContent = reg(s(206)("Partial Content", "The server is delivering only part of the resource due to a range header sent by the client."))
|
||||||
val MultiStatus = reg(s(207)("Multi-Status", "The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made."))
|
val MultiStatus = reg(s(207)("Multi-Status", "The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made."))
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,8 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
|
||||||
"""Fancy yes="n:o",nonce=42""")
|
"""Fancy yes="n:o",nonce=42""")
|
||||||
"""Authorization: Fancy yes=no,nonce="4\\2"""" =!=
|
"""Authorization: Fancy yes=no,nonce="4\\2"""" =!=
|
||||||
Authorization(GenericHttpCredentials("Fancy", Map("yes" → "no", "nonce" → """4\2""")))
|
Authorization(GenericHttpCredentials("Fancy", Map("yes" → "no", "nonce" → """4\2""")))
|
||||||
|
"""Authorization: Other yes=no,empty=""""" =!=
|
||||||
|
Authorization(GenericHttpCredentials("Other", Map("yes" → "no", "empty" → "")))
|
||||||
"Authorization: Basic Qm9iOg==" =!=
|
"Authorization: Basic Qm9iOg==" =!=
|
||||||
Authorization(BasicHttpCredentials("Bob", ""))
|
Authorization(BasicHttpCredentials("Bob", ""))
|
||||||
"""Authorization: Digest name=Bob""" =!=
|
"""Authorization: Digest name=Bob""" =!=
|
||||||
|
|
@ -182,6 +184,8 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
|
||||||
"Content-Disposition: form-data" =!= `Content-Disposition`(ContentDispositionTypes.`form-data`)
|
"Content-Disposition: form-data" =!= `Content-Disposition`(ContentDispositionTypes.`form-data`)
|
||||||
"Content-Disposition: attachment; name=field1; filename=\"file/txt\"" =!=
|
"Content-Disposition: attachment; name=field1; filename=\"file/txt\"" =!=
|
||||||
`Content-Disposition`(ContentDispositionTypes.attachment, Map("name" → "field1", "filename" → "file/txt"))
|
`Content-Disposition`(ContentDispositionTypes.attachment, Map("name" → "field1", "filename" → "file/txt"))
|
||||||
|
"Content-Disposition: attachment; name=field1; other=\"\"" =!=
|
||||||
|
`Content-Disposition`(ContentDispositionTypes.attachment, Map("name" → "field1", "other" → ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
"Content-Encoding" in {
|
"Content-Encoding" in {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class RenderingSpec extends WordSpec with Matchers {
|
||||||
}
|
}
|
||||||
|
|
||||||
"correctly render escaped Strings" in {
|
"correctly render escaped Strings" in {
|
||||||
(new StringRendering ~~# "").get shouldEqual ""
|
(new StringRendering ~~# "").get shouldEqual "\"\""
|
||||||
(new StringRendering ~~# "hello").get shouldEqual "hello"
|
(new StringRendering ~~# "hello").get shouldEqual "hello"
|
||||||
(new StringRendering ~~# """hel"lo""").get shouldEqual """"hel\"lo""""
|
(new StringRendering ~~# """hel"lo""").get shouldEqual """"hel\"lo""""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
package akka.http.scaladsl.testkit
|
package akka.http.scaladsl.testkit
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{ ExecutionContext, Await }
|
import scala.concurrent.{ Await, ExecutionContext }
|
||||||
import akka.http.scaladsl.unmarshalling.{ Unmarshal, FromEntityUnmarshaller }
|
import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, Unmarshal }
|
||||||
import akka.http.scaladsl.marshalling._
|
import akka.http.scaladsl.marshalling._
|
||||||
import akka.http.scaladsl.model.HttpEntity
|
import akka.http.scaladsl.model.{ HttpEntity, HttpRequest, HttpResponse }
|
||||||
import akka.stream.Materializer
|
import akka.stream.Materializer
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
@ -17,6 +17,10 @@ trait MarshallingTestUtils {
|
||||||
def marshal[T: ToEntityMarshaller](value: T)(implicit ec: ExecutionContext, mat: Materializer): HttpEntity.Strict =
|
def marshal[T: ToEntityMarshaller](value: T)(implicit ec: ExecutionContext, mat: Materializer): HttpEntity.Strict =
|
||||||
Await.result(Marshal(value).to[HttpEntity].flatMap(_.toStrict(1.second)), 1.second)
|
Await.result(Marshal(value).to[HttpEntity].flatMap(_.toStrict(1.second)), 1.second)
|
||||||
|
|
||||||
|
def marshalToResponse[T: ToResponseMarshaller](value: T, request: HttpRequest = HttpRequest())(implicit ec: ExecutionContext, mat: Materializer): HttpResponse = {
|
||||||
|
Await.result(Marshal(value).toResponseFor(request), 1.second)
|
||||||
|
}
|
||||||
|
|
||||||
def unmarshalValue[T: FromEntityUnmarshaller](entity: HttpEntity)(implicit ec: ExecutionContext, mat: Materializer): T =
|
def unmarshalValue[T: FromEntityUnmarshaller](entity: HttpEntity)(implicit ec: ExecutionContext, mat: Materializer): T =
|
||||||
unmarshal(entity).get
|
unmarshal(entity).get
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
"The PredefinedToEntityMarshallers." - {
|
"The PredefinedToEntityMarshallers" - {
|
||||||
"StringMarshaller should marshal strings to `text/plain` content in UTF-8" in {
|
"StringMarshaller should marshal strings to `text/plain` content in UTF-8" in {
|
||||||
marshal("Ha“llo") shouldEqual HttpEntity("Ha“llo")
|
marshal("Ha“llo") shouldEqual HttpEntity("Ha“llo")
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,25 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"The GenericMarshallers." - {
|
"The PredefinedToResponseMarshallers" - {
|
||||||
|
"fromStatusCode should properly marshal entities that are not supposed to have a body" in {
|
||||||
|
marshalToResponse(StatusCodes.NoContent) shouldEqual HttpResponse(StatusCodes.NoContent, entity = HttpEntity.Empty)
|
||||||
|
}
|
||||||
|
"fromStatusCode should properly marshal entities that contain pre-defined content" in {
|
||||||
|
marshalToResponse(StatusCodes.EnhanceYourCalm) shouldEqual
|
||||||
|
HttpResponse(StatusCodes.EnhanceYourCalm, entity = HttpEntity(StatusCodes.EnhanceYourCalm.defaultMessage))
|
||||||
|
}
|
||||||
|
"fromStatusCodeAndHeadersAndValue should properly marshal entities that are not supposed to have a body" in {
|
||||||
|
marshalToResponse((StatusCodes.NoContent, "This Content was intentionally left blank.")) shouldEqual
|
||||||
|
HttpResponse(StatusCodes.NoContent, entity = HttpEntity.Empty)
|
||||||
|
}
|
||||||
|
"fromStatusCodeAndHeadersAndValue should properly marshal entities that contain pre-defined content" in {
|
||||||
|
marshalToResponse((StatusCodes.EnhanceYourCalm, "Patience, young padawan!")) shouldEqual
|
||||||
|
HttpResponse(StatusCodes.EnhanceYourCalm, entity = HttpEntity("Patience, young padawan!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"The GenericMarshallers" - {
|
||||||
"optionMarshaller should enable marshalling of Option[T]" in {
|
"optionMarshaller should enable marshalling of Option[T]" in {
|
||||||
|
|
||||||
marshal(Some("Ha“llo")) shouldEqual HttpEntity("Ha“llo")
|
marshal(Some("Ha“llo")) shouldEqual HttpEntity("Ha“llo")
|
||||||
|
|
@ -51,7 +69,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"The MultipartMarshallers." - {
|
"The MultipartMarshallers" - {
|
||||||
"multipartMarshaller should correctly marshal multipart content with" - {
|
"multipartMarshaller should correctly marshal multipart content with" - {
|
||||||
"one empty part" in {
|
"one empty part" in {
|
||||||
marshal(Multipart.General(`multipart/mixed`, Multipart.General.BodyPart.Strict(""))) shouldEqual HttpEntity(
|
marshal(Multipart.General(`multipart/mixed`, Multipart.General.BodyPart.Strict(""))) shouldEqual HttpEntity(
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,29 @@ class CodingDirectivesSpec extends RoutingSpec with Inside {
|
||||||
encodeResponseWith(Deflate) { nope }
|
encodeResponseWith(Deflate) { nope }
|
||||||
} ~> check { strictify(responseEntity) shouldEqual HttpEntity(ContentType(`text/plain`, `UTF-8`), nopeDeflated) }
|
} ~> check { strictify(responseEntity) shouldEqual HttpEntity(ContentType(`text/plain`, `UTF-8`), nopeDeflated) }
|
||||||
}
|
}
|
||||||
|
"not encode the response content with GZIP if the response is of status not allowing entity" in {
|
||||||
|
Post() ~> {
|
||||||
|
encodeResponseWith(Gzip) { complete { StatusCodes.NoContent } }
|
||||||
|
} ~> check {
|
||||||
|
response should haveNoContentEncoding
|
||||||
|
response shouldEqual HttpResponse(StatusCodes.NoContent, entity = HttpEntity.Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"not encode the response content with Deflate if the response is of status not allowing entity" in {
|
||||||
|
Post() ~> {
|
||||||
|
encodeResponseWith(Deflate) { complete((100, "Let's continue!")) }
|
||||||
|
} ~> check {
|
||||||
|
response should haveNoContentEncoding
|
||||||
|
response shouldEqual HttpResponse(StatusCodes.Continue, entity = HttpEntity.Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"encode the response content with GZIP if the response is of status allowing entity" in {
|
||||||
|
Post() ~> {
|
||||||
|
encodeResponseWith(Gzip) { nope }
|
||||||
|
} ~> check {
|
||||||
|
response should haveContentEncoding(gzip)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"the Gzip encoder" should {
|
"the Gzip encoder" should {
|
||||||
|
|
@ -478,6 +501,27 @@ class CodingDirectivesSpec extends RoutingSpec with Inside {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"the default marshaller" should {
|
||||||
|
"allow compressed responses with no body for informational messages" in {
|
||||||
|
Get() ~> `Accept-Encoding`(HttpEncodings.compress) ~> {
|
||||||
|
encodeResponse {
|
||||||
|
complete { StatusCodes.Continue }
|
||||||
|
}
|
||||||
|
} ~> check {
|
||||||
|
status shouldBe StatusCodes.Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"allow gzipped responses with no body for 204 messages" in {
|
||||||
|
Get() ~> `Accept-Encoding`(HttpEncodings.gzip) ~> {
|
||||||
|
encodeResponse {
|
||||||
|
complete { StatusCodes.NoContent }
|
||||||
|
}
|
||||||
|
} ~> check {
|
||||||
|
status shouldBe StatusCodes.NoContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def compress(input: String, encoder: Encoder): ByteString = {
|
def compress(input: String, encoder: Encoder): ByteString = {
|
||||||
val compressor = encoder.newCompressor
|
val compressor = encoder.newCompressor
|
||||||
compressor.compressAndFlush(ByteString(input)) ++ compressor.finish()
|
compressor.compressAndFlush(ByteString(input)) ++ compressor.finish()
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,10 @@ trait Encoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
object Encoder {
|
object Encoder {
|
||||||
val DefaultFilter: HttpMessage ⇒ Boolean = isCompressible _
|
val DefaultFilter: HttpMessage ⇒ Boolean = {
|
||||||
|
case req: HttpRequest ⇒ isCompressible(req)
|
||||||
|
case res @ HttpResponse(status, _, _, _) ⇒ isCompressible(res) && status.allowsEntity
|
||||||
|
}
|
||||||
private[coding] def isCompressible(msg: HttpMessage): Boolean =
|
private[coding] def isCompressible(msg: HttpMessage): Boolean =
|
||||||
msg.entity.contentType.mediaType.isCompressible
|
msg.entity.contentType.mediaType.isCompressible
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,15 @@
|
||||||
package akka.http.scaladsl.marshalling
|
package akka.http.scaladsl.marshalling
|
||||||
|
|
||||||
import akka.http.scaladsl.common.EntityStreamingSupport
|
import akka.http.scaladsl.common.EntityStreamingSupport
|
||||||
import akka.stream.impl.ConstantFun
|
|
||||||
|
|
||||||
import scala.collection.immutable
|
|
||||||
import akka.http.scaladsl.util.FastFuture._
|
|
||||||
import akka.http.scaladsl.model.MediaTypes._
|
import akka.http.scaladsl.model.MediaTypes._
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
import akka.http.scaladsl.server.ContentNegotiator
|
|
||||||
import akka.http.scaladsl.util.FastFuture
|
import akka.http.scaladsl.util.FastFuture
|
||||||
|
import akka.http.scaladsl.util.FastFuture._
|
||||||
|
import akka.stream.impl.ConstantFun
|
||||||
import akka.stream.scaladsl.Source
|
import akka.stream.scaladsl.Source
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
import scala.language.higherKinds
|
import scala.language.higherKinds
|
||||||
|
|
||||||
trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImplicits {
|
trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImplicits {
|
||||||
|
|
@ -33,7 +31,20 @@ trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImp
|
||||||
|
|
||||||
implicit val fromStatusCode: TRM[StatusCode] =
|
implicit val fromStatusCode: TRM[StatusCode] =
|
||||||
Marshaller.withOpenCharset(`text/plain`) { (status, charset) ⇒
|
Marshaller.withOpenCharset(`text/plain`) { (status, charset) ⇒
|
||||||
HttpResponse(status, entity = HttpEntity(ContentType(`text/plain`, charset), status.defaultMessage))
|
val responseEntity =
|
||||||
|
if (status.allowsEntity) HttpEntity(status.defaultMessage)
|
||||||
|
else HttpEntity.Empty
|
||||||
|
HttpResponse(status, entity = responseEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val fromStatusCodeAndHeaders: TRM[(StatusCode, immutable.Seq[HttpHeader])] =
|
||||||
|
Marshaller.withOpenCharset(`text/plain`) { (statusAndHeaders, charset) ⇒
|
||||||
|
val status = statusAndHeaders._1
|
||||||
|
val headers = statusAndHeaders._2
|
||||||
|
val responseEntity =
|
||||||
|
if (status.allowsEntity) HttpEntity(status.defaultMessage)
|
||||||
|
else HttpEntity.Empty
|
||||||
|
HttpResponse(status, headers, entity = responseEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit def fromStatusCodeAndValue[S, T](implicit sConv: S ⇒ StatusCode, mt: ToEntityMarshaller[T]): TRM[(S, T)] =
|
implicit def fromStatusCodeAndValue[S, T](implicit sConv: S ⇒ StatusCode, mt: ToEntityMarshaller[T]): TRM[(S, T)] =
|
||||||
|
|
@ -46,7 +57,8 @@ trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImp
|
||||||
|
|
||||||
implicit def fromStatusCodeAndHeadersAndValue[T](implicit mt: ToEntityMarshaller[T]): TRM[(StatusCode, immutable.Seq[HttpHeader], T)] =
|
implicit def fromStatusCodeAndHeadersAndValue[T](implicit mt: ToEntityMarshaller[T]): TRM[(StatusCode, immutable.Seq[HttpHeader], T)] =
|
||||||
Marshaller(implicit ec ⇒ {
|
Marshaller(implicit ec ⇒ {
|
||||||
case (status, headers, value) ⇒ mt(value).fast map (_ map (_ map (HttpResponse(status, headers, _))))
|
case (status, headers, value) if (status.allowsEntity) ⇒ mt(value).fast map (_ map (_ map (HttpResponse(status, headers, _))))
|
||||||
|
case (status, headers, _) ⇒ fromStatusCodeAndHeaders((status, headers))
|
||||||
})
|
})
|
||||||
|
|
||||||
implicit def fromEntityStreamingSupportAndByteStringMarshaller[T, M](implicit s: EntityStreamingSupport, m: ToByteStringMarshaller[T]): ToResponseMarshaller[Source[T, M]] = {
|
implicit def fromEntityStreamingSupportAndByteStringMarshaller[T, M](implicit s: EntityStreamingSupport, m: ToByteStringMarshaller[T]): ToResponseMarshaller[Source[T, M]] = {
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,9 @@ private[akka] class ReplayFilter(persistentActor: ActorRef, mode: ReplayFilter.M
|
||||||
if (r.persistent.writerUuid == writerUuid) {
|
if (r.persistent.writerUuid == writerUuid) {
|
||||||
// from same writer
|
// from same writer
|
||||||
if (r.persistent.sequenceNr < seqNo) {
|
if (r.persistent.sequenceNr < seqNo) {
|
||||||
val errMsg = s"Invalid replayed event [${r.persistent.sequenceNr}] in wrong order from " +
|
val errMsg = s"Invalid replayed event [sequenceNr=${r.persistent.sequenceNr}, writerUUID=${r.persistent.writerUuid}] as " +
|
||||||
s"writer [${r.persistent.writerUuid}] with persistenceId [${r.persistent.persistenceId}]"
|
s"the sequenceNr should be equal to or greater than already-processed event [sequenceNr=${seqNo}, writerUUID=${writerUuid}] from the same writer, for the same persistenceId [${r.persistent.persistenceId}]. " +
|
||||||
|
"Perhaps, events were journaled out of sequence, or duplicate persistentId for different entities?"
|
||||||
logIssue(errMsg)
|
logIssue(errMsg)
|
||||||
mode match {
|
mode match {
|
||||||
case RepairByDiscardOld ⇒ // discard
|
case RepairByDiscardOld ⇒ // discard
|
||||||
|
|
@ -92,8 +93,9 @@ private[akka] class ReplayFilter(persistentActor: ActorRef, mode: ReplayFilter.M
|
||||||
|
|
||||||
} else if (oldWriters.contains(r.persistent.writerUuid)) {
|
} else if (oldWriters.contains(r.persistent.writerUuid)) {
|
||||||
// from old writer
|
// from old writer
|
||||||
val errMsg = s"Invalid replayed event [${r.persistent.sequenceNr}] from old " +
|
val errMsg = s"Invalid replayed event [sequenceNr=${r.persistent.sequenceNr}, writerUUID=${r.persistent.writerUuid}]. " +
|
||||||
s"writer [${r.persistent.writerUuid}] with persistenceId [${r.persistent.persistenceId}]"
|
s"There was already a newer writer whose last replayed event was [sequenceNr=${seqNo}, writerUUID=${writerUuid}] for the same persistenceId [${r.persistent.persistenceId}]." +
|
||||||
|
"Perhaps, the old writer kept journaling messages after the new writer created, or duplicate persistentId for different entities?"
|
||||||
logIssue(errMsg)
|
logIssue(errMsg)
|
||||||
mode match {
|
mode match {
|
||||||
case RepairByDiscardOld ⇒ // discard
|
case RepairByDiscardOld ⇒ // discard
|
||||||
|
|
@ -112,13 +114,14 @@ private[akka] class ReplayFilter(persistentActor: ActorRef, mode: ReplayFilter.M
|
||||||
writerUuid = r.persistent.writerUuid
|
writerUuid = r.persistent.writerUuid
|
||||||
seqNo = r.persistent.sequenceNr
|
seqNo = r.persistent.sequenceNr
|
||||||
|
|
||||||
// clear the buffer from messages from other writers with higher seqNo
|
// clear the buffer for messages from old writers with higher seqNo
|
||||||
val iter = buffer.iterator()
|
val iter = buffer.iterator()
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
val msg = iter.next()
|
val msg = iter.next()
|
||||||
if (msg.persistent.sequenceNr >= seqNo) {
|
if (msg.persistent.sequenceNr >= seqNo) {
|
||||||
val errMsg = s"Invalid replayed event [${msg.persistent.sequenceNr}] in buffer from old " +
|
val errMsg = s"Invalid replayed event [sequenceNr=${r.persistent.sequenceNr}, writerUUID=${r.persistent.writerUuid}] from a new writer. " +
|
||||||
s"writer [${msg.persistent.writerUuid}] with persistenceId [${msg.persistent.persistenceId}]"
|
s"An older writer already sent an event [sequenceNr=${msg.persistent.sequenceNr}, writerUUID=${msg.persistent.writerUuid}] whose sequence number was equal or greater for the same persistenceId [${r.persistent.persistenceId}]. " +
|
||||||
|
"Perhaps, the new writer journaled the event out of sequence, or duplicate persistentId for different entities?"
|
||||||
logIssue(errMsg)
|
logIssue(errMsg)
|
||||||
mode match {
|
mode match {
|
||||||
case RepairByDiscardOld ⇒ iter.remove() // discard
|
case RepairByDiscardOld ⇒ iter.remove() // discard
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,8 @@ abstract class AtLeastOnceDeliverySpec(config: Config) extends PersistenceSpec(c
|
||||||
}
|
}
|
||||||
|
|
||||||
class LeveldbAtLeastOnceDeliverySpec extends AtLeastOnceDeliverySpec(
|
class LeveldbAtLeastOnceDeliverySpec extends AtLeastOnceDeliverySpec(
|
||||||
PersistenceSpec.config("leveldb", "AtLeastOnceDeliverySpec"))
|
PersistenceSpec.config("leveldb", "AtLeastOnceDeliverySpec")
|
||||||
|
.withFallback(ConfigFactory.parseString("akka.loglevel=debug")) // to iron out #20724
|
||||||
|
)
|
||||||
|
|
||||||
class InmemAtLeastOnceDeliverySpec extends AtLeastOnceDeliverySpec(PersistenceSpec.config("inmem", "AtLeastOnceDeliverySpec"))
|
class InmemAtLeastOnceDeliverySpec extends AtLeastOnceDeliverySpec(PersistenceSpec.config("inmem", "AtLeastOnceDeliverySpec"))
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ object PersistentActorRecoveryTimeoutSpec {
|
||||||
SteppingInmemJournal.config(PersistentActorRecoveryTimeoutSpec.journalId).withFallback(
|
SteppingInmemJournal.config(PersistentActorRecoveryTimeoutSpec.journalId).withFallback(
|
||||||
ConfigFactory.parseString(
|
ConfigFactory.parseString(
|
||||||
"""
|
"""
|
||||||
|akka.persistence.journal.stepping-inmem.recovery-event-timeout=100ms
|
|akka.persistence.journal.stepping-inmem.recovery-event-timeout=1s
|
||||||
""".stripMargin)).withFallback(PersistenceSpec.config("stepping-inmem", "PersistentActorRecoveryTimeoutSpec"))
|
""".stripMargin)).withFallback(PersistenceSpec.config("stepping-inmem", "PersistentActorRecoveryTimeoutSpec"))
|
||||||
|
|
||||||
class TestActor(probe: ActorRef) extends NamedPersistentActor("recovery-timeout-actor") {
|
class TestActor(probe: ActorRef) extends NamedPersistentActor("recovery-timeout-actor") {
|
||||||
|
|
|
||||||
8
akka-samples/README.md
Normal file
8
akka-samples/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
Use Lightbend Activator to run samples
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Use [Lightbend Activator](https://www.lightbend.com/activator/download) to run samples in this akka-samples directory.
|
||||||
|
Follow the instruction on the Activator download page, and the [Activator documentation](https://www.lightbend.com/activator/docs).
|
||||||
|
Once activator ui is up, you an find akka-sample-* projects by their names.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ class DslConsistencySpec extends WordSpec with Matchers {
|
||||||
("Flow" → List[Class[_]](sFlowClass, jFlowClass)) ::
|
("Flow" → List[Class[_]](sFlowClass, jFlowClass)) ::
|
||||||
("SubFlow" → List[Class[_]](sSubFlowClass, jSubFlowClass)) ::
|
("SubFlow" → List[Class[_]](sSubFlowClass, jSubFlowClass)) ::
|
||||||
("Sink" → List[Class[_]](sSinkClass, jSinkClass)) ::
|
("Sink" → List[Class[_]](sSinkClass, jSinkClass)) ::
|
||||||
("RunanbleFlow" → List[Class[_]](sRunnableGraphClass, jRunnableGraphClass)) ::
|
("RunnableFlow" → List[Class[_]](sRunnableGraphClass, jRunnableGraphClass)) ::
|
||||||
Nil foreach {
|
Nil foreach {
|
||||||
case (element, classes) ⇒
|
case (element, classes) ⇒
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
package akka.stream.impl.fusing
|
||||||
|
|
||||||
|
import akka.stream.scaladsl.{ Sink, Source }
|
||||||
|
import akka.stream._
|
||||||
|
import akka.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }
|
||||||
|
import akka.stream.testkit.Utils.TE
|
||||||
|
import akka.stream.testkit.{ TestPublisher, TestSubscriber }
|
||||||
|
import akka.testkit.AkkaSpec
|
||||||
|
|
||||||
|
class ChasingEventsSpec extends AkkaSpec {
|
||||||
|
|
||||||
|
implicit val materializer = ActorMaterializer(ActorMaterializerSettings(system).withFuzzing(false))
|
||||||
|
|
||||||
|
class CancelInChasedPull extends GraphStage[FlowShape[Int, Int]] {
|
||||||
|
val in = Inlet[Int]("Propagate.in")
|
||||||
|
val out = Outlet[Int]("Propagate.out")
|
||||||
|
override val shape: FlowShape[Int, Int] = FlowShape(in, out)
|
||||||
|
|
||||||
|
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with InHandler with OutHandler {
|
||||||
|
private var first = true
|
||||||
|
override def onPush(): Unit = push(out, grab(in))
|
||||||
|
override def onPull(): Unit = {
|
||||||
|
pull(in)
|
||||||
|
if (!first) cancel(in)
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
|
||||||
|
setHandlers(in, out, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompleteInChasedPush extends GraphStage[FlowShape[Int, Int]] {
|
||||||
|
val in = Inlet[Int]("Propagate.in")
|
||||||
|
val out = Outlet[Int]("Propagate.out")
|
||||||
|
override val shape: FlowShape[Int, Int] = FlowShape(in, out)
|
||||||
|
|
||||||
|
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with InHandler with OutHandler {
|
||||||
|
private var first = true
|
||||||
|
override def onPush(): Unit = {
|
||||||
|
push(out, grab(in))
|
||||||
|
complete(out)
|
||||||
|
}
|
||||||
|
override def onPull(): Unit = pull(in)
|
||||||
|
|
||||||
|
setHandlers(in, out, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FailureInChasedPush extends GraphStage[FlowShape[Int, Int]] {
|
||||||
|
val in = Inlet[Int]("Propagate.in")
|
||||||
|
val out = Outlet[Int]("Propagate.out")
|
||||||
|
override val shape: FlowShape[Int, Int] = FlowShape(in, out)
|
||||||
|
|
||||||
|
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with InHandler with OutHandler {
|
||||||
|
private var first = true
|
||||||
|
override def onPush(): Unit = {
|
||||||
|
push(out, grab(in))
|
||||||
|
fail(out, TE("test failure"))
|
||||||
|
}
|
||||||
|
override def onPull(): Unit = pull(in)
|
||||||
|
|
||||||
|
setHandlers(in, out, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChasableSink extends GraphStage[SinkShape[Int]] {
|
||||||
|
val in = Inlet[Int]("Chaseable.in")
|
||||||
|
override val shape: SinkShape[Int] = SinkShape(in)
|
||||||
|
|
||||||
|
@throws(classOf[Exception])
|
||||||
|
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with InHandler {
|
||||||
|
override def preStart(): Unit = pull(in)
|
||||||
|
override def onPush(): Unit = pull(in)
|
||||||
|
setHandler(in, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"Event chasing" must {
|
||||||
|
|
||||||
|
"propagate cancel if enqueued immediately after pull" in {
|
||||||
|
val upstream = TestPublisher.probe[Int]()
|
||||||
|
|
||||||
|
Source.fromPublisher(upstream).via(new CancelInChasedPull).runWith(Sink.ignore)
|
||||||
|
|
||||||
|
upstream.sendNext(0)
|
||||||
|
upstream.expectCancellation()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"propagate complete if enqueued immediately after push" in {
|
||||||
|
val downstream = TestSubscriber.probe[Int]()
|
||||||
|
|
||||||
|
Source(1 to 10).via(new CompleteInChasedPush).runWith(Sink.fromSubscriber(downstream))
|
||||||
|
|
||||||
|
downstream.requestNext(1)
|
||||||
|
downstream.expectComplete()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"propagate failure if enqueued immediately after push" in {
|
||||||
|
val downstream = TestSubscriber.probe[Int]()
|
||||||
|
|
||||||
|
Source(1 to 10).via(new FailureInChasedPush).runWith(Sink.fromSubscriber(downstream))
|
||||||
|
|
||||||
|
downstream.requestNext(1)
|
||||||
|
downstream.expectError()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,9 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
|
|
||||||
// FIXME test failure scenarios
|
// FIXME test failure scenarios
|
||||||
|
|
||||||
"properly transition on push and pull" in new PortTestSetup {
|
for (chasing ← List(false, true)) {
|
||||||
|
|
||||||
|
s"properly transition on push and pull (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
out.isAvailable should be(false)
|
out.isAvailable should be(false)
|
||||||
out.isClosed should be(false)
|
out.isClosed should be(false)
|
||||||
|
|
@ -81,7 +83,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
// Cycle completed
|
// Cycle completed
|
||||||
}
|
}
|
||||||
|
|
||||||
"drop ungrabbed element on pull" in new PortTestSetup {
|
s"drop ungrabbed element on pull (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
step()
|
step()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -95,7 +97,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate complete while downstream is active" in new PortTestSetup {
|
s"propagate complete while downstream is active (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
out.isAvailable should be(false)
|
out.isAvailable should be(false)
|
||||||
out.isClosed should be(false)
|
out.isClosed should be(false)
|
||||||
|
|
@ -152,7 +154,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate complete while upstream is active" in new PortTestSetup {
|
s"propagate complete while upstream is active (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
|
|
||||||
|
|
@ -213,7 +215,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate complete while pull is in flight" in new PortTestSetup {
|
s"propagate complete while pull is in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
|
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
|
|
@ -272,7 +274,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate complete while push is in flight" in new PortTestSetup {
|
s"propagate complete while push is in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -334,7 +336,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate complete while push is in flight and keep ungrabbed element" in new PortTestSetup {
|
s"propagate complete while push is in flight and keep ungrabbed element (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -355,7 +357,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
in.grab() should ===(0)
|
in.grab() should ===(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate complete while push is in flight and pulled after the push" in new PortTestSetup {
|
s"propagate complete while push is in flight and pulled after the push (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -381,7 +383,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore pull while completing" in new PortTestSetup {
|
s"ignore pull while completing (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
out.complete()
|
out.complete()
|
||||||
in.pull()
|
in.pull()
|
||||||
// While the pull event is not enqueued at this point, we should still report the state correctly
|
// While the pull event is not enqueued at this point, we should still report the state correctly
|
||||||
|
|
@ -400,7 +402,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate cancel while downstream is active" in new PortTestSetup {
|
s"propagate cancel while downstream is active (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
out.isAvailable should be(false)
|
out.isAvailable should be(false)
|
||||||
out.isClosed should be(false)
|
out.isClosed should be(false)
|
||||||
|
|
@ -458,7 +460,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate cancel while upstream is active" in new PortTestSetup {
|
s"propagate cancel while upstream is active (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
|
|
||||||
|
|
@ -520,7 +522,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate cancel while pull is in flight" in new PortTestSetup {
|
s"propagate cancel while pull is in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
|
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
|
|
@ -580,7 +582,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate cancel while push is in flight" in new PortTestSetup {
|
s"propagate cancel while push is in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -643,7 +645,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore push while cancelling" in new PortTestSetup {
|
s"ignore push while cancelling (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -666,7 +668,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"clear ungrabbed element even when cancelled" in new PortTestSetup {
|
s"clear ungrabbed element even when cancelled (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -698,7 +700,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore any completion if they are concurrent (cancel first)" in new PortTestSetup {
|
s"ignore any completion if they are concurrent (cancel first) (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.cancel()
|
in.cancel()
|
||||||
out.complete()
|
out.complete()
|
||||||
|
|
||||||
|
|
@ -715,7 +717,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore any completion if they are concurrent (complete first)" in new PortTestSetup {
|
s"ignore any completion if they are concurrent (complete first) (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
out.complete()
|
out.complete()
|
||||||
in.cancel()
|
in.cancel()
|
||||||
|
|
||||||
|
|
@ -732,7 +734,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore completion from a push-complete if cancelled while in flight" in new PortTestSetup {
|
s"ignore completion from a push-complete if cancelled while in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -754,7 +756,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore completion from a push-complete if cancelled after onPush" in new PortTestSetup {
|
s"ignore completion from a push-complete if cancelled after onPush (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -787,7 +789,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"not allow to grab element before it arrives" in new PortTestSetup {
|
s"not allow to grab element before it arrives (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
out.push(0)
|
out.push(0)
|
||||||
|
|
@ -795,7 +797,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"not allow to grab element if already cancelled" in new PortTestSetup {
|
s"not allow to grab element if already cancelled (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
|
|
||||||
|
|
@ -807,7 +809,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate failure while downstream is active" in new PortTestSetup {
|
s"propagate failure while downstream is active (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
out.isAvailable should be(false)
|
out.isAvailable should be(false)
|
||||||
out.isClosed should be(false)
|
out.isClosed should be(false)
|
||||||
|
|
@ -864,7 +866,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate failure while upstream is active" in new PortTestSetup {
|
s"propagate failure while upstream is active (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
|
|
||||||
|
|
@ -925,7 +927,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate failure while pull is in flight" in new PortTestSetup {
|
s"propagate failure while pull is in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
|
|
||||||
lastEvents() should be(Set.empty)
|
lastEvents() should be(Set.empty)
|
||||||
|
|
@ -984,7 +986,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate failure while push is in flight" in new PortTestSetup {
|
s"propagate failure while push is in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -1046,7 +1048,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"propagate failure while push is in flight and keep ungrabbed element" in new PortTestSetup {
|
s"propagate failure while push is in flight and keep ungrabbed element (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -1067,7 +1069,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
in.grab() should ===(0)
|
in.grab() should ===(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore pull while failing" in new PortTestSetup {
|
s"ignore pull while failing (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
out.fail(TE("test"))
|
out.fail(TE("test"))
|
||||||
in.pull()
|
in.pull()
|
||||||
in.hasBeenPulled should be(true)
|
in.hasBeenPulled should be(true)
|
||||||
|
|
@ -1085,7 +1087,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore any failure completion if they are concurrent (cancel first)" in new PortTestSetup {
|
s"ignore any failure completion if they are concurrent (cancel first) (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.cancel()
|
in.cancel()
|
||||||
out.fail(TE("test"))
|
out.fail(TE("test"))
|
||||||
|
|
||||||
|
|
@ -1102,7 +1104,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore any failure completion if they are concurrent (complete first)" in new PortTestSetup {
|
s"ignore any failure completion if they are concurrent (complete first) (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
out.fail(TE("test"))
|
out.fail(TE("test"))
|
||||||
in.cancel()
|
in.cancel()
|
||||||
|
|
||||||
|
|
@ -1119,7 +1121,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore failure from a push-then-fail if cancelled while in flight" in new PortTestSetup {
|
s"ignore failure from a push-then-fail if cancelled while in flight (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -1141,7 +1143,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore failure from a push-then-fail if cancelled after onPush" in new PortTestSetup {
|
s"ignore failure from a push-then-fail if cancelled after onPush (chasing = $chasing)" in new PortTestSetup(chasing) {
|
||||||
in.pull()
|
in.pull()
|
||||||
stepAll()
|
stepAll()
|
||||||
clearEvents()
|
clearEvents()
|
||||||
|
|
@ -1173,6 +1175,7 @@ class GraphInterpreterPortsSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
an[IllegalArgumentException] should be thrownBy { out.push(0) }
|
an[IllegalArgumentException] should be thrownBy { out.push(0) }
|
||||||
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
an[IllegalArgumentException] should be thrownBy { in.grab() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
package akka.stream.impl.fusing
|
package akka.stream.impl.fusing
|
||||||
|
|
||||||
import akka.event.Logging
|
import akka.event.Logging
|
||||||
|
import akka.stream.ActorAttributes.SupervisionStrategy
|
||||||
|
import akka.stream.Supervision.Decider
|
||||||
import akka.stream._
|
import akka.stream._
|
||||||
import akka.stream.impl.fusing.GraphInterpreter.{ DownstreamBoundaryStageLogic, Failed, GraphAssembly, UpstreamBoundaryStageLogic }
|
import akka.stream.impl.fusing.GraphInterpreter.{ DownstreamBoundaryStageLogic, Failed, GraphAssembly, UpstreamBoundaryStageLogic }
|
||||||
import akka.stream.stage.AbstractStage.PushPullGraphStage
|
import akka.stream.stage.AbstractStage.PushPullGraphStage
|
||||||
|
|
@ -176,10 +178,29 @@ trait GraphInterpreterSpecKit extends StreamSpec {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class PortTestSetup extends TestSetup {
|
abstract class PortTestSetup(chasing: Boolean = false) extends TestSetup {
|
||||||
val out = new UpstreamPortProbe[Int]
|
val out = new UpstreamPortProbe[Int]
|
||||||
val in = new DownstreamPortProbe[Int]
|
val in = new DownstreamPortProbe[Int]
|
||||||
|
|
||||||
|
class EventPropagateStage extends GraphStage[FlowShape[Int, Int]] {
|
||||||
|
val in = Inlet[Int]("Propagate.in")
|
||||||
|
val out = Outlet[Int]("Propagate.out")
|
||||||
|
override val shape: FlowShape[Int, Int] = FlowShape(in, out)
|
||||||
|
|
||||||
|
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with InHandler with OutHandler {
|
||||||
|
override def onPush(): Unit = push(out, grab(in))
|
||||||
|
override def onPull(): Unit = pull(in)
|
||||||
|
override def onUpstreamFinish(): Unit = complete(out)
|
||||||
|
override def onUpstreamFailure(ex: Throwable): Unit = fail(out, ex)
|
||||||
|
override def onDownstreamFinish(): Unit = cancel(in)
|
||||||
|
|
||||||
|
setHandlers(in, out, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step() means different depending whether we have a stage between the two probes or not
|
||||||
|
override def step(): Unit = interpreter.execute(eventLimit = if (!chasing) 1 else 2)
|
||||||
|
|
||||||
class UpstreamPortProbe[T] extends UpstreamProbe[T]("upstreamPort") {
|
class UpstreamPortProbe[T] extends UpstreamProbe[T]("upstreamPort") {
|
||||||
def isAvailable: Boolean = isAvailable(out)
|
def isAvailable: Boolean = isAvailable(out)
|
||||||
def isClosed: Boolean = isClosed(out)
|
def isClosed: Boolean = isClosed(out)
|
||||||
|
|
@ -215,16 +236,27 @@ trait GraphInterpreterSpecKit extends StreamSpec {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private val assembly = new GraphAssembly(
|
private val assembly = if (!chasing) {
|
||||||
|
new GraphAssembly(
|
||||||
stages = Array.empty,
|
stages = Array.empty,
|
||||||
originalAttributes = Array.empty,
|
originalAttributes = Array.empty,
|
||||||
ins = Array(null),
|
ins = Array(null),
|
||||||
inOwners = Array(-1),
|
inOwners = Array(-1),
|
||||||
outs = Array(null),
|
outs = Array(null),
|
||||||
outOwners = Array(-1))
|
outOwners = Array(-1))
|
||||||
|
} else {
|
||||||
|
val propagateStage = new EventPropagateStage
|
||||||
|
new GraphAssembly(
|
||||||
|
stages = Array(propagateStage),
|
||||||
|
originalAttributes = Array(Attributes.none),
|
||||||
|
ins = Array(propagateStage.in, null),
|
||||||
|
inOwners = Array(0, -1),
|
||||||
|
outs = Array(null, propagateStage.out),
|
||||||
|
outOwners = Array(-1, 0))
|
||||||
|
}
|
||||||
|
|
||||||
manualInit(assembly)
|
manualInit(assembly)
|
||||||
interpreter.attachDownstreamBoundary(interpreter.connections(0), in)
|
interpreter.attachDownstreamBoundary(interpreter.connections(if (chasing) 1 else 0), in)
|
||||||
interpreter.attachUpstreamBoundary(interpreter.connections(0), out)
|
interpreter.attachUpstreamBoundary(interpreter.connections(0), out)
|
||||||
interpreter.init(null)
|
interpreter.init(null)
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +339,7 @@ trait GraphInterpreterSpecKit extends StreamSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class OneBoundedSetup[T](_ops: GraphStageWithMaterializedValue[Shape, Any]*) extends Builder {
|
abstract class OneBoundedSetupWithDecider[T](decider: Decider, _ops: GraphStageWithMaterializedValue[Shape, Any]*) extends Builder {
|
||||||
val ops = _ops.toArray
|
val ops = _ops.toArray
|
||||||
|
|
||||||
val upstream = new UpstreamOneBoundedProbe[T]
|
val upstream = new UpstreamOneBoundedProbe[T]
|
||||||
|
|
@ -329,7 +361,7 @@ trait GraphInterpreterSpecKit extends StreamSpec {
|
||||||
import GraphInterpreter.Boundary
|
import GraphInterpreter.Boundary
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
val attributes = Array.fill[Attributes](ops.length)(Attributes.none)
|
val attributes = Array.fill[Attributes](ops.length)(ActorAttributes.supervisionStrategy(decider))
|
||||||
val ins = Array.ofDim[Inlet[_]](ops.length + 1)
|
val ins = Array.ofDim[Inlet[_]](ops.length + 1)
|
||||||
val inOwners = Array.ofDim[Int](ops.length + 1)
|
val inOwners = Array.ofDim[Int](ops.length + 1)
|
||||||
val outs = Array.ofDim[Outlet[_]](ops.length + 1)
|
val outs = Array.ofDim[Outlet[_]](ops.length + 1)
|
||||||
|
|
@ -429,4 +461,5 @@ trait GraphInterpreterSpecKit extends StreamSpec {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class OneBoundedSetup[T](_ops: GraphStageWithMaterializedValue[Shape, Any]*) extends OneBoundedSetupWithDecider[T](Supervision.stoppingDecider, _ops: _*)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,6 @@ class InterpreterSupervisionSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
override def toString = "TE"
|
override def toString = "TE"
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResumingMap[In, Out](_f: In ⇒ Out) extends Map(_f) {
|
|
||||||
|
|
||||||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
|
|
||||||
super.createLogic(inheritedAttributes.and(ActorAttributes.supervisionStrategy(resumingDecider)))
|
|
||||||
}
|
|
||||||
|
|
||||||
"Interpreter error handling" must {
|
"Interpreter error handling" must {
|
||||||
|
|
||||||
"handle external failure" in new OneBoundedSetup[Int](Map((x: Int) ⇒ x + 1)) {
|
"handle external failure" in new OneBoundedSetup[Int](Map((x: Int) ⇒ x + 1)) {
|
||||||
|
|
@ -62,8 +56,9 @@ class InterpreterSupervisionSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
lastEvents() should be(Set(Cancel, OnError(TE)))
|
lastEvents() should be(Set(Cancel, OnError(TE)))
|
||||||
}
|
}
|
||||||
|
|
||||||
"resume when Map throws" in new OneBoundedSetup[Int](
|
"resume when Map throws" in new OneBoundedSetupWithDecider[Int](
|
||||||
new ResumingMap((x: Int) ⇒ if (x == 0) throw TE else x)
|
Supervision.resumingDecider,
|
||||||
|
Map((x: Int) ⇒ if (x == 0) throw TE else x)
|
||||||
) {
|
) {
|
||||||
downstream.requestOne()
|
downstream.requestOne()
|
||||||
lastEvents() should be(Set(RequestOne))
|
lastEvents() should be(Set(RequestOne))
|
||||||
|
|
@ -88,10 +83,11 @@ class InterpreterSupervisionSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
lastEvents() should be(Set(OnNext(4)))
|
lastEvents() should be(Set(OnNext(4)))
|
||||||
}
|
}
|
||||||
|
|
||||||
"resume when Map throws in middle of the chain" in new OneBoundedSetup[Int](
|
"resume when Map throws in middle of the chain" in new OneBoundedSetupWithDecider[Int](
|
||||||
new ResumingMap((x: Int) ⇒ x + 1),
|
Supervision.resumingDecider,
|
||||||
new ResumingMap((x: Int) ⇒ if (x == 0) throw TE else x + 10),
|
Map((x: Int) ⇒ x + 1),
|
||||||
new ResumingMap((x: Int) ⇒ x + 100)
|
Map((x: Int) ⇒ if (x == 0) throw TE else x + 10),
|
||||||
|
Map((x: Int) ⇒ x + 100)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
downstream.requestOne()
|
downstream.requestOne()
|
||||||
|
|
@ -108,9 +104,10 @@ class InterpreterSupervisionSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
lastEvents() should be(Set(OnNext(114)))
|
lastEvents() should be(Set(OnNext(114)))
|
||||||
}
|
}
|
||||||
|
|
||||||
"resume when Map throws before Grouped" in new OneBoundedSetup[Int](
|
"resume when Map throws before Grouped" in new OneBoundedSetupWithDecider[Int](
|
||||||
new ResumingMap((x: Int) ⇒ x + 1),
|
Supervision.resumingDecider,
|
||||||
new ResumingMap((x: Int) ⇒ if (x <= 0) throw TE else x + 10),
|
Map((x: Int) ⇒ x + 1),
|
||||||
|
Map((x: Int) ⇒ if (x <= 0) throw TE else x + 10),
|
||||||
Grouped(3)) {
|
Grouped(3)) {
|
||||||
|
|
||||||
downstream.requestOne()
|
downstream.requestOne()
|
||||||
|
|
@ -128,9 +125,10 @@ class InterpreterSupervisionSpec extends StreamSpec with GraphInterpreterSpecKit
|
||||||
lastEvents() should be(Set(OnNext(Vector(13, 14, 15))))
|
lastEvents() should be(Set(OnNext(Vector(13, 14, 15))))
|
||||||
}
|
}
|
||||||
|
|
||||||
"complete after resume when Map throws before Grouped" in new OneBoundedSetup[Int](
|
"complete after resume when Map throws before Grouped" in new OneBoundedSetupWithDecider[Int](
|
||||||
new ResumingMap((x: Int) ⇒ x + 1),
|
Supervision.resumingDecider,
|
||||||
new ResumingMap((x: Int) ⇒ if (x <= 0) throw TE else x + 10),
|
Map((x: Int) ⇒ x + 1),
|
||||||
|
Map((x: Int) ⇒ if (x <= 0) throw TE else x + 10),
|
||||||
Grouped(1000)) {
|
Grouped(1000)) {
|
||||||
|
|
||||||
downstream.requestOne()
|
downstream.requestOne()
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ import akka.stream.{ ActorMaterializer, Attributes, BufferOverflowException, Del
|
||||||
import scala.concurrent.{ Await, Future }
|
import scala.concurrent.{ Await, Future }
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.control.NoStackTrace
|
import scala.util.control.NoStackTrace
|
||||||
|
import akka.stream.ThrottleMode
|
||||||
|
|
||||||
class FlowDelaySpec extends StreamSpec {
|
class FlowDelaySpec extends StreamSpec {
|
||||||
|
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
||||||
"A Delay" must {
|
"A Delay" must {
|
||||||
|
|
||||||
"deliver elements with some time shift" in {
|
"deliver elements with some time shift" in {
|
||||||
Await.result(
|
Await.result(
|
||||||
Source(1 to 10).delay(1.seconds).grouped(100).runWith(Sink.head),
|
Source(1 to 10).delay(1.seconds).grouped(100).runWith(Sink.head),
|
||||||
|
|
@ -156,5 +158,16 @@ class FlowDelaySpec extends StreamSpec {
|
||||||
expectMsg(Done)
|
expectMsg(Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"not overflow buffer when DelayOverflowStrategy.backpressure" in {
|
||||||
|
val probe = Source(1 to 6).delay(100.millis, DelayOverflowStrategy.backpressure)
|
||||||
|
.withAttributes(Attributes.inputBuffer(2, 2))
|
||||||
|
.throttle(1, 200.millis, 1, ThrottleMode.Shaping)
|
||||||
|
.runWith(TestSink.probe)
|
||||||
|
|
||||||
|
probe.request(10)
|
||||||
|
.expectNextN(1 to 6)
|
||||||
|
.expectComplete()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
package akka.stream.scaladsl
|
||||||
|
|
||||||
|
import akka.stream.testkit.Utils._
|
||||||
|
import akka.stream.{ ActorMaterializer, ActorMaterializerSettings }
|
||||||
|
import akka.stream.testkit.{ StreamSpec, TestSubscriber }
|
||||||
|
|
||||||
|
class FlowZipWithIndexSpec extends StreamSpec {
|
||||||
|
|
||||||
|
val settings = ActorMaterializerSettings(system)
|
||||||
|
.withInputBuffer(initialSize = 2, maxSize = 16)
|
||||||
|
|
||||||
|
implicit val materializer = ActorMaterializer(settings)
|
||||||
|
|
||||||
|
"A ZipWithIndex for Flow " must {
|
||||||
|
|
||||||
|
"work in the happy case" in assertAllStagesStopped {
|
||||||
|
val probe = TestSubscriber.manualProbe[(Int, Long)]()
|
||||||
|
Source(7 to 10).zipWithIndex.runWith(Sink.fromSubscriber(probe))
|
||||||
|
|
||||||
|
val subscription = probe.expectSubscription()
|
||||||
|
|
||||||
|
subscription.request(2)
|
||||||
|
probe.expectNext((7, 0L))
|
||||||
|
probe.expectNext((8, 1L))
|
||||||
|
|
||||||
|
subscription.request(1)
|
||||||
|
probe.expectNext((9, 2L))
|
||||||
|
subscription.request(1)
|
||||||
|
probe.expectNext((10, 3L))
|
||||||
|
|
||||||
|
probe.expectComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,17 +3,12 @@
|
||||||
*/
|
*/
|
||||||
package akka.stream.impl
|
package akka.stream.impl
|
||||||
|
|
||||||
import akka.event.LoggingAdapter
|
|
||||||
import akka.stream.ActorAttributes.SupervisionStrategy
|
import akka.stream.ActorAttributes.SupervisionStrategy
|
||||||
import akka.stream.Attributes._
|
import akka.stream.Attributes._
|
||||||
import akka.stream.Supervision.Decider
|
import akka.stream.Supervision.Decider
|
||||||
import akka.stream._
|
import akka.stream._
|
||||||
import akka.stream.impl.StreamLayout._
|
|
||||||
import akka.stream.stage.AbstractStage.PushPullGraphStage
|
import akka.stream.stage.AbstractStage.PushPullGraphStage
|
||||||
import akka.stream.stage.Stage
|
import akka.stream.stage.Stage
|
||||||
import org.reactivestreams.Processor
|
|
||||||
|
|
||||||
import scala.collection.immutable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
|
|
@ -81,6 +76,7 @@ object Stages {
|
||||||
val zip = name("zip")
|
val zip = name("zip")
|
||||||
val zipN = name("zipN")
|
val zipN = name("zipN")
|
||||||
val zipWithN = name("zipWithN")
|
val zipWithN = name("zipWithN")
|
||||||
|
val zipWithIndex = name("zipWithIndex")
|
||||||
val unzip = name("unzip")
|
val unzip = name("unzip")
|
||||||
val concat = name("concat")
|
val concat = name("concat")
|
||||||
val orElse = name("orElse")
|
val orElse = name("orElse")
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ object GraphInterpreter {
|
||||||
*
|
*
|
||||||
* From an external viewpoint, the GraphInterpreter takes an assembly of graph processing stages encoded as a
|
* From an external viewpoint, the GraphInterpreter takes an assembly of graph processing stages encoded as a
|
||||||
* [[GraphInterpreter#GraphAssembly]] object and provides facilities to execute and interact with this assembly.
|
* [[GraphInterpreter#GraphAssembly]] object and provides facilities to execute and interact with this assembly.
|
||||||
* The lifecylce of the Interpreter is roughly the following:
|
* The lifecycle of the Interpreter is roughly the following:
|
||||||
* - Boundary logics are attached via [[attachDownstreamBoundary()]] and [[attachUpstreamBoundary()]]
|
* - Boundary logics are attached via [[attachDownstreamBoundary()]] and [[attachUpstreamBoundary()]]
|
||||||
* - [[init()]] is called
|
* - [[init()]] is called
|
||||||
* - [[execute()]] is called whenever there is need for execution, providing an upper limit on the processed events
|
* - [[execute()]] is called whenever there is need for execution, providing an upper limit on the processed events
|
||||||
|
|
@ -842,6 +842,12 @@ final class GraphInterpreter(
|
||||||
connection.portState = currentState | (OutClosed | InFailed)
|
connection.portState = currentState | (OutClosed | InFailed)
|
||||||
connection.slot = Failed(ex, connection.slot)
|
connection.slot = Failed(ex, connection.slot)
|
||||||
if ((currentState & (Pulling | Pushing)) == 0) enqueue(connection)
|
if ((currentState & (Pulling | Pushing)) == 0) enqueue(connection)
|
||||||
|
else if (chasedPush eq connection) {
|
||||||
|
// Abort chasing so Failure is not lost (chasing does NOT decode the event but assumes it to be a PUSH
|
||||||
|
// but we just changed the event!)
|
||||||
|
chasedPush = NoEvent
|
||||||
|
enqueue(connection)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((currentState & OutClosed) == 0) completeConnection(connection.outOwnerId)
|
if ((currentState & OutClosed) == 0) completeConnection(connection.outOwnerId)
|
||||||
}
|
}
|
||||||
|
|
@ -853,6 +859,12 @@ final class GraphInterpreter(
|
||||||
if ((currentState & OutClosed) == 0) {
|
if ((currentState & OutClosed) == 0) {
|
||||||
connection.slot = Empty
|
connection.slot = Empty
|
||||||
if ((currentState & (Pulling | Pushing | InClosed)) == 0) enqueue(connection)
|
if ((currentState & (Pulling | Pushing | InClosed)) == 0) enqueue(connection)
|
||||||
|
else if (chasedPull eq connection) {
|
||||||
|
// Abort chasing so Cancel is not lost (chasing does NOT decode the event but assumes it to be a PULL
|
||||||
|
// but we just changed the event!)
|
||||||
|
chasedPull = NoEvent
|
||||||
|
enqueue(connection)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((currentState & InClosed) == 0) completeConnection(connection.inOwnerId)
|
if ((currentState & InClosed) == 0) completeConnection(connection.inOwnerId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
package akka.stream.impl.fusing
|
package akka.stream.impl.fusing
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit.NANOSECONDS
|
||||||
import akka.event.Logging.LogLevel
|
import akka.event.Logging.LogLevel
|
||||||
import akka.event.{ LogSource, Logging, LoggingAdapter }
|
import akka.event.{ LogSource, Logging, LoggingAdapter }
|
||||||
import akka.stream.Attributes.{ InputBuffer, LogLevels }
|
import akka.stream.Attributes.{ InputBuffer, LogLevels }
|
||||||
|
|
@ -25,8 +26,7 @@ import akka.stream.impl.Stages.DefaultAttributes
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
// FIXME: Not final because InterpreterSupervisionSpec. Some better option is needed here
|
final case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] {
|
||||||
case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] {
|
|
||||||
val in = Inlet[In]("Map.in")
|
val in = Inlet[In]("Map.in")
|
||||||
val out = Outlet[Out]("Map.out")
|
val out = Outlet[Out]("Map.out")
|
||||||
override val shape = FlowShape(in, out)
|
override val shape = FlowShape(in, out)
|
||||||
|
|
@ -1311,74 +1311,101 @@ final class Delay[T](val d: FiniteDuration, val strategy: DelayOverflowStrategy)
|
||||||
case None ⇒ throw new IllegalStateException(s"Couldn't find InputBuffer Attribute for $this")
|
case None ⇒ throw new IllegalStateException(s"Couldn't find InputBuffer Attribute for $this")
|
||||||
case Some(InputBuffer(min, max)) ⇒ max
|
case Some(InputBuffer(min, max)) ⇒ max
|
||||||
}
|
}
|
||||||
|
val delayMillis = d.toMillis
|
||||||
|
|
||||||
var buffer: BufferImpl[(Long, T)] = _ // buffer has pairs timestamp with upstream element
|
var buffer: BufferImpl[(Long, T)] = _ // buffer has pairs timestamp with upstream element
|
||||||
var willStop = false
|
|
||||||
|
|
||||||
override def preStart(): Unit = buffer = BufferImpl(size, materializer)
|
override def preStart(): Unit = buffer = BufferImpl(size, materializer)
|
||||||
|
|
||||||
//FIXME rewrite into distinct strategy functions to avoid matching on strategy for every input when full
|
val onPushWhenBufferFull: () ⇒ Unit = strategy match {
|
||||||
def onPush(): Unit = {
|
|
||||||
if (buffer.isFull) strategy match {
|
|
||||||
case EmitEarly ⇒
|
case EmitEarly ⇒
|
||||||
|
() ⇒ {
|
||||||
if (!isTimerActive(timerName))
|
if (!isTimerActive(timerName))
|
||||||
push(out, buffer.dequeue()._2)
|
push(out, buffer.dequeue()._2)
|
||||||
else {
|
else {
|
||||||
cancelTimer(timerName)
|
cancelTimer(timerName)
|
||||||
onTimer(timerName)
|
onTimer(timerName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case DropHead ⇒
|
case DropHead ⇒
|
||||||
|
() ⇒ {
|
||||||
buffer.dropHead()
|
buffer.dropHead()
|
||||||
grabAndPull(true)
|
grabAndPull()
|
||||||
|
}
|
||||||
case DropTail ⇒
|
case DropTail ⇒
|
||||||
|
() ⇒ {
|
||||||
buffer.dropTail()
|
buffer.dropTail()
|
||||||
grabAndPull(true)
|
grabAndPull()
|
||||||
|
}
|
||||||
case DropNew ⇒
|
case DropNew ⇒
|
||||||
|
() ⇒ {
|
||||||
grab(in)
|
grab(in)
|
||||||
if (!isTimerActive(timerName)) scheduleOnce(timerName, d)
|
if (!isTimerActive(timerName)) scheduleOnce(timerName, d)
|
||||||
case DropBuffer ⇒
|
|
||||||
buffer.clear()
|
|
||||||
grabAndPull(true)
|
|
||||||
case Fail ⇒
|
|
||||||
failStage(new BufferOverflowException(s"Buffer overflow for delay combinator (max capacity was: $size)!"))
|
|
||||||
case Backpressure ⇒ throw new IllegalStateException("Delay buffer must never overflow in Backpressure mode")
|
|
||||||
}
|
}
|
||||||
else {
|
case DropBuffer ⇒
|
||||||
grabAndPull(strategy != Backpressure || buffer.used < size - 1)
|
() ⇒ {
|
||||||
if (!isTimerActive(timerName)) scheduleOnce(timerName, d)
|
buffer.clear()
|
||||||
|
grabAndPull()
|
||||||
|
}
|
||||||
|
case Fail ⇒
|
||||||
|
() ⇒ {
|
||||||
|
failStage(new BufferOverflowException(s"Buffer overflow for delay combinator (max capacity was: $size)!"))
|
||||||
|
}
|
||||||
|
case Backpressure ⇒
|
||||||
|
() ⇒ {
|
||||||
|
throw new IllegalStateException("Delay buffer must never overflow in Backpressure mode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def grabAndPull(pullCondition: Boolean): Unit = {
|
def onPush(): Unit = {
|
||||||
|
if (buffer.isFull)
|
||||||
|
onPushWhenBufferFull()
|
||||||
|
else {
|
||||||
|
grabAndPull()
|
||||||
|
if (!isTimerActive(timerName)) {
|
||||||
|
scheduleOnce(timerName, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def pullCondition: Boolean =
|
||||||
|
strategy != Backpressure || buffer.used < size
|
||||||
|
|
||||||
|
def grabAndPull(): Unit = {
|
||||||
buffer.enqueue((System.nanoTime(), grab(in)))
|
buffer.enqueue((System.nanoTime(), grab(in)))
|
||||||
if (pullCondition) pull(in)
|
if (pullCondition) pull(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onUpstreamFinish(): Unit = {
|
override def onUpstreamFinish(): Unit =
|
||||||
if (isAvailable(out) && isTimerActive(timerName)) willStop = true
|
completeIfReady()
|
||||||
else completeStage()
|
|
||||||
}
|
|
||||||
|
|
||||||
def onPull(): Unit = {
|
def onPull(): Unit = {
|
||||||
if (!isTimerActive(timerName) && !buffer.isEmpty && nextElementWaitTime() < 0)
|
if (!isTimerActive(timerName) && !buffer.isEmpty && nextElementWaitTime() < 0)
|
||||||
push(out, buffer.dequeue()._2)
|
push(out, buffer.dequeue()._2)
|
||||||
|
|
||||||
if (!willStop && !hasBeenPulled(in)) pull(in)
|
if (!isClosed(in) && !hasBeenPulled(in) && pullCondition)
|
||||||
|
pull(in)
|
||||||
|
|
||||||
completeIfReady()
|
completeIfReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
setHandler(in, this)
|
setHandler(in, this)
|
||||||
setHandler(out, this)
|
setHandler(out, this)
|
||||||
|
|
||||||
def completeIfReady(): Unit = if (willStop && buffer.isEmpty) completeStage()
|
def completeIfReady(): Unit = if (isClosed(in) && buffer.isEmpty) completeStage()
|
||||||
|
|
||||||
def nextElementWaitTime(): Long = d.toMillis - (System.nanoTime() - buffer.peek()._1) * 1000 * 1000
|
def nextElementWaitTime(): Long = {
|
||||||
|
delayMillis - NANOSECONDS.toMillis(System.nanoTime() - buffer.peek()._1)
|
||||||
|
}
|
||||||
|
|
||||||
final override protected def onTimer(key: Any): Unit = {
|
final override protected def onTimer(key: Any): Unit = {
|
||||||
|
if (isAvailable(out))
|
||||||
push(out, buffer.dequeue()._2)
|
push(out, buffer.dequeue()._2)
|
||||||
|
|
||||||
if (!buffer.isEmpty) {
|
if (!buffer.isEmpty) {
|
||||||
val waitTime = nextElementWaitTime()
|
val waitTime = nextElementWaitTime()
|
||||||
if (waitTime > 10) scheduleOnce(timerName, waitTime.millis)
|
if (waitTime > 10)
|
||||||
|
scheduleOnce(timerName, waitTime.millis)
|
||||||
}
|
}
|
||||||
completeIfReady()
|
completeIfReady()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,20 @@
|
||||||
*/
|
*/
|
||||||
package akka.stream.javadsl
|
package akka.stream.javadsl
|
||||||
|
|
||||||
import akka.{ NotUsed, Done }
|
import akka.{ Done, NotUsed }
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
import akka.japi.{ function, Pair }
|
import akka.japi.{ Pair, function }
|
||||||
import akka.stream.impl.{ ConstantFun, StreamLayout }
|
import akka.stream.impl.{ ConstantFun, StreamLayout }
|
||||||
import akka.stream._
|
import akka.stream._
|
||||||
import akka.stream.stage.Stage
|
import akka.stream.stage.Stage
|
||||||
import org.reactivestreams.Processor
|
import org.reactivestreams.Processor
|
||||||
|
|
||||||
import scala.annotation.unchecked.uncheckedVariance
|
import scala.annotation.unchecked.uncheckedVariance
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
import akka.japi.Util
|
import akka.japi.Util
|
||||||
import java.util.Comparator
|
import java.util.Comparator
|
||||||
import java.util.concurrent.CompletionStage
|
import java.util.concurrent.CompletionStage
|
||||||
|
|
||||||
import scala.compat.java8.FutureConverters._
|
import scala.compat.java8.FutureConverters._
|
||||||
|
|
||||||
object Flow {
|
object Flow {
|
||||||
|
|
@ -1634,6 +1636,21 @@ final class Flow[-In, +Out, +Mat](delegate: scaladsl.Flow[In, Out, Mat]) extends
|
||||||
matF: function.Function2[Mat, M, M2]): javadsl.Flow[In, Out3, M2] =
|
matF: function.Function2[Mat, M, M2]): javadsl.Flow[In, Out3, M2] =
|
||||||
new Flow(delegate.zipWithMat[Out2, Out3, M, M2](that)(combinerToScala(combine))(combinerToScala(matF)))
|
new Flow(delegate.zipWithMat[Out2, Out3, M, M2](that)(combinerToScala(combine))(combinerToScala(matF)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the elements of current flow into a stream of tuples consisting
|
||||||
|
* of all elements paired with their index. Indices start at 0.
|
||||||
|
*
|
||||||
|
* '''Emits when''' upstream emits an element and is paired with their index
|
||||||
|
*
|
||||||
|
* '''Backpressures when''' downstream backpressures
|
||||||
|
*
|
||||||
|
* '''Completes when''' upstream completes
|
||||||
|
*
|
||||||
|
* '''Cancels when''' downstream cancels
|
||||||
|
*/
|
||||||
|
def zipWithIndex: Flow[In, Pair[Out @uncheckedVariance, Long], Mat] =
|
||||||
|
new Flow(delegate.zipWithIndex.map { case (elem, index) ⇒ Pair(elem, index) })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the first element has not passed through this stage before the provided timeout, the stream is failed
|
* If the first element has not passed through this stage before the provided timeout, the stream is failed
|
||||||
* with a [[java.util.concurrent.TimeoutException]].
|
* with a [[java.util.concurrent.TimeoutException]].
|
||||||
|
|
|
||||||
|
|
@ -156,9 +156,7 @@ object Source {
|
||||||
def range(start: Int, end: Int, step: Int): javadsl.Source[Integer, NotUsed] =
|
def range(start: Int, end: Int, step: Int): javadsl.Source[Integer, NotUsed] =
|
||||||
fromIterator[Integer](new function.Creator[util.Iterator[Integer]]() {
|
fromIterator[Integer](new function.Creator[util.Iterator[Integer]]() {
|
||||||
def create(): util.Iterator[Integer] =
|
def create(): util.Iterator[Integer] =
|
||||||
new Inclusive(start, end, step) {
|
Range.inclusive(start, end, step).iterator.asJava.asInstanceOf[util.Iterator[Integer]]
|
||||||
override def toString: String = s"Range($start to $end, step = $step)"
|
|
||||||
}.iterator.asJava.asInstanceOf[util.Iterator[Integer]]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -873,6 +871,21 @@ final class Source[+Out, +Mat](delegate: scaladsl.Source[Out, Mat]) extends Grap
|
||||||
matF: function.Function2[Mat, M, M2]): javadsl.Source[Out3, M2] =
|
matF: function.Function2[Mat, M, M2]): javadsl.Source[Out3, M2] =
|
||||||
new Source(delegate.zipWithMat[Out2, Out3, M, M2](that)(combinerToScala(combine))(combinerToScala(matF)))
|
new Source(delegate.zipWithMat[Out2, Out3, M, M2](that)(combinerToScala(combine))(combinerToScala(matF)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the elements of current [[Source]] into a stream of tuples consisting
|
||||||
|
* of all elements paired with their index. Indices start at 0.
|
||||||
|
*
|
||||||
|
* '''Emits when''' upstream emits an element and is paired with their index
|
||||||
|
*
|
||||||
|
* '''Backpressures when''' downstream backpressures
|
||||||
|
*
|
||||||
|
* '''Completes when''' upstream completes
|
||||||
|
*
|
||||||
|
* '''Cancels when''' downstream cancels
|
||||||
|
*/
|
||||||
|
def zipWithIndex: javadsl.Source[Pair[Out @uncheckedVariance, Long], Mat] =
|
||||||
|
new Source(delegate.zipWithIndex.map { case (elem, index) ⇒ Pair(elem, index) })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for running this `Source` with a foreach procedure. The given procedure is invoked
|
* Shortcut for running this `Source` with a foreach procedure. The given procedure is invoked
|
||||||
* for each received element.
|
* for each received element.
|
||||||
|
|
|
||||||
|
|
@ -1112,6 +1112,21 @@ class SubFlow[-In, +Out, +Mat](delegate: scaladsl.SubFlow[Out, Mat, scaladsl.Flo
|
||||||
combine: function.Function2[Out, Out2, Out3]): SubFlow[In, Out3, Mat] =
|
combine: function.Function2[Out, Out2, Out3]): SubFlow[In, Out3, Mat] =
|
||||||
new SubFlow(delegate.zipWith[Out2, Out3](that)(combinerToScala(combine)))
|
new SubFlow(delegate.zipWith[Out2, Out3](that)(combinerToScala(combine)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the elements of current [[Flow]] into a stream of tuples consisting
|
||||||
|
* of all elements paired with their index. Indices start at 0.
|
||||||
|
*
|
||||||
|
* '''Emits when''' upstream emits an element and is paired with their index
|
||||||
|
*
|
||||||
|
* '''Backpressures when''' downstream backpressures
|
||||||
|
*
|
||||||
|
* '''Completes when''' upstream completes
|
||||||
|
*
|
||||||
|
* '''Cancels when''' downstream cancels
|
||||||
|
*/
|
||||||
|
def zipWithIndex: SubFlow[In, akka.japi.Pair[Out @uncheckedVariance, Long], Mat] =
|
||||||
|
new SubFlow(delegate.zipWithIndex.map { case (elem, index) ⇒ akka.japi.Pair(elem, index) })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the first element has not passed through this stage before the provided timeout, the stream is failed
|
* If the first element has not passed through this stage before the provided timeout, the stream is failed
|
||||||
* with a [[java.util.concurrent.TimeoutException]].
|
* with a [[java.util.concurrent.TimeoutException]].
|
||||||
|
|
|
||||||
|
|
@ -1111,6 +1111,21 @@ class SubSource[+Out, +Mat](delegate: scaladsl.SubFlow[Out, Mat, scaladsl.Source
|
||||||
combine: function.Function2[Out, Out2, Out3]): SubSource[Out3, Mat] =
|
combine: function.Function2[Out, Out2, Out3]): SubSource[Out3, Mat] =
|
||||||
new SubSource(delegate.zipWith[Out2, Out3](that)(combinerToScala(combine)))
|
new SubSource(delegate.zipWith[Out2, Out3](that)(combinerToScala(combine)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the elements of current [[Source]] into a stream of tuples consisting
|
||||||
|
* of all elements paired with their index. Indices start at 0.
|
||||||
|
*
|
||||||
|
* '''Emits when''' upstream emits an element and is paired with their index
|
||||||
|
*
|
||||||
|
* '''Backpressures when''' downstream backpressures
|
||||||
|
*
|
||||||
|
* '''Completes when''' upstream completes
|
||||||
|
*
|
||||||
|
* '''Cancels when''' downstream cancels
|
||||||
|
*/
|
||||||
|
def zipWithIndex: javadsl.SubSource[akka.japi.Pair[Out @uncheckedVariance, Long], Mat] =
|
||||||
|
new SubSource(delegate.zipWithIndex.map { case (elem, index) ⇒ akka.japi.Pair(elem, index) })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the first element has not passed through this stage before the provided timeout, the stream is failed
|
* If the first element has not passed through this stage before the provided timeout, the stream is failed
|
||||||
* with a [[java.util.concurrent.TimeoutException]].
|
* with a [[java.util.concurrent.TimeoutException]].
|
||||||
|
|
|
||||||
|
|
@ -1655,6 +1655,29 @@ trait FlowOps[+Out, +Mat] {
|
||||||
FlowShape(zip.in0, zip.out)
|
FlowShape(zip.in0, zip.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine the elements of current flow into a stream of tuples consisting
|
||||||
|
* of all elements paired with their index. Indices start at 0.
|
||||||
|
*
|
||||||
|
* '''Emits when''' upstream emits an element and is paired with their index
|
||||||
|
*
|
||||||
|
* '''Backpressures when''' downstream backpressures
|
||||||
|
*
|
||||||
|
* '''Completes when''' upstream completes
|
||||||
|
*
|
||||||
|
* '''Cancels when''' downstream cancels
|
||||||
|
*/
|
||||||
|
def zipWithIndex: Repr[(Out, Long)] = {
|
||||||
|
statefulMapConcat[(Out, Long)] { () ⇒
|
||||||
|
var index: Long = 0L
|
||||||
|
elem ⇒ {
|
||||||
|
val zipped = (elem, index)
|
||||||
|
index += 1
|
||||||
|
immutable.Iterable[(Out, Long)](zipped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interleave is a deterministic merge of the given [[Source]] with elements of this [[Flow]].
|
* Interleave is a deterministic merge of the given [[Source]] with elements of this [[Flow]].
|
||||||
* It first emits `segmentSize` number of elements from this flow to downstream, then - same amount for `that`
|
* It first emits `segmentSize` number of elements from this flow to downstream, then - same amount for `that`
|
||||||
|
|
|
||||||
|
|
@ -586,7 +586,7 @@ private[akka] class BroadcastHub[T](bufferSize: Int) extends GraphStageWithMater
|
||||||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with OutHandler {
|
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with OutHandler {
|
||||||
private[this] var untilNextAdvanceSignal = DemandThreshold
|
private[this] var untilNextAdvanceSignal = DemandThreshold
|
||||||
private[this] val id = idCounter.getAndIncrement()
|
private[this] val id = idCounter.getAndIncrement()
|
||||||
private[this] var initialized = false
|
private[this] var offsetInitialized = false
|
||||||
private[this] var hubCallback: AsyncCallback[HubEvent] = _
|
private[this] var hubCallback: AsyncCallback[HubEvent] = _
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -604,6 +604,7 @@ private[akka] class BroadcastHub[T](bufferSize: Int) extends GraphStageWithMater
|
||||||
val onHubReady: Try[AsyncCallback[HubEvent]] ⇒ Unit = {
|
val onHubReady: Try[AsyncCallback[HubEvent]] ⇒ Unit = {
|
||||||
case Success(callback) ⇒
|
case Success(callback) ⇒
|
||||||
hubCallback = callback
|
hubCallback = callback
|
||||||
|
if (isAvailable(out) && offsetInitialized) onPull()
|
||||||
callback.invoke(RegistrationPending)
|
callback.invoke(RegistrationPending)
|
||||||
case Failure(ex) ⇒
|
case Failure(ex) ⇒
|
||||||
failStage(ex)
|
failStage(ex)
|
||||||
|
|
@ -621,12 +622,19 @@ private[akka] class BroadcastHub[T](bufferSize: Int) extends GraphStageWithMater
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that there is a potential race here. First we add ourselves to the pending registrations, then
|
||||||
|
* we send RegistrationPending. However, another downstream might have triggered our registration by its
|
||||||
|
* own RegistrationPending message, since we are in the list already.
|
||||||
|
* This means we might receive an onCommand(Initialize(offset)) *before* onHubReady fires so it is important
|
||||||
|
* to only serve elements after both offsetInitialized = true and hubCallback is not null.
|
||||||
|
*/
|
||||||
register()
|
register()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onPull(): Unit = {
|
override def onPull(): Unit = {
|
||||||
if (initialized) {
|
if (offsetInitialized && (hubCallback ne null)) {
|
||||||
val elem = logic.poll(offset)
|
val elem = logic.poll(offset)
|
||||||
|
|
||||||
elem match {
|
elem match {
|
||||||
|
|
@ -661,10 +669,10 @@ private[akka] class BroadcastHub[T](bufferSize: Int) extends GraphStageWithMater
|
||||||
case Wakeup ⇒
|
case Wakeup ⇒
|
||||||
if (isAvailable(out)) onPull()
|
if (isAvailable(out)) onPull()
|
||||||
case Initialize(initialOffset) ⇒
|
case Initialize(initialOffset) ⇒
|
||||||
initialized = true
|
offsetInitialized = true
|
||||||
previousPublishedOffset = initialOffset
|
previousPublishedOffset = initialOffset
|
||||||
offset = initialOffset
|
offset = initialOffset
|
||||||
if (isAvailable(out)) onPull()
|
if (isAvailable(out) && (hubCallback ne null)) onPull()
|
||||||
}
|
}
|
||||||
|
|
||||||
setHandler(out, this)
|
setHandler(out, this)
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,14 @@ AkkaBuild.experimentalSettings
|
||||||
Formatting.formatSettings
|
Formatting.formatSettings
|
||||||
|
|
||||||
disablePlugins(MimaPlugin)
|
disablePlugins(MimaPlugin)
|
||||||
|
|
||||||
|
initialCommands := """
|
||||||
|
import akka.typed._
|
||||||
|
import ScalaDSL._
|
||||||
|
import scala.concurrent._
|
||||||
|
import duration._
|
||||||
|
import akka.util.Timeout
|
||||||
|
implicit val timeout = Timeout(5.seconds)
|
||||||
|
"""
|
||||||
|
|
||||||
|
cancelable in Global := true
|
||||||
|
|
|
||||||
|
|
@ -69,17 +69,6 @@ trait ActorContext[T] {
|
||||||
*/
|
*/
|
||||||
def spawn[U](props: Props[U], name: String): ActorRef[U]
|
def spawn[U](props: Props[U], name: String): ActorRef[U]
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an untyped child Actor from the given [[akka.actor.Props]] under a randomly chosen name.
|
|
||||||
* It is good practice to name Actors wherever practical.
|
|
||||||
*/
|
|
||||||
def actorOf(props: untyped.Props): untyped.ActorRef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an untyped child Actor from the given [[akka.actor.Props]] and with the given name.
|
|
||||||
*/
|
|
||||||
def actorOf(props: untyped.Props, name: String): untyped.ActorRef
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force the child Actor under the given name to terminate after it finishes
|
* Force the child Actor under the given name to terminate after it finishes
|
||||||
* processing its current message. Nothing happens if the ActorRef does not
|
* processing its current message. Nothing happens if the ActorRef does not
|
||||||
|
|
@ -97,14 +86,6 @@ trait ActorContext[T] {
|
||||||
*/
|
*/
|
||||||
def watch[U](other: ActorRef[U]): ActorRef[U]
|
def watch[U](other: ActorRef[U]): ActorRef[U]
|
||||||
|
|
||||||
/**
|
|
||||||
* Register for [[Terminated]] notification once the Actor identified by the
|
|
||||||
* given [[akka.actor.ActorRef]] terminates. This notification is also generated when the
|
|
||||||
* [[ActorSystem]] to which the referenced Actor belongs is declared as
|
|
||||||
* failed (e.g. in reaction to being unreachable).
|
|
||||||
*/
|
|
||||||
def watch(other: akka.actor.ActorRef): akka.actor.ActorRef
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revoke the registration established by `watch`. A [[Terminated]]
|
* Revoke the registration established by `watch`. A [[Terminated]]
|
||||||
* notification will not subsequently be received for the referenced Actor.
|
* notification will not subsequently be received for the referenced Actor.
|
||||||
|
|
@ -112,18 +93,17 @@ trait ActorContext[T] {
|
||||||
def unwatch[U](other: ActorRef[U]): ActorRef[U]
|
def unwatch[U](other: ActorRef[U]): ActorRef[U]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revoke the registration established by `watch`. A [[Terminated]]
|
* Schedule the sending of a notification in case no other
|
||||||
* notification will not subsequently be received for the referenced Actor.
|
|
||||||
*/
|
|
||||||
def unwatch(other: akka.actor.ActorRef): akka.actor.ActorRef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedule the sending of a [[ReceiveTimeout]] notification in case no other
|
|
||||||
* message is received during the given period of time. The timeout starts anew
|
* message is received during the given period of time. The timeout starts anew
|
||||||
* with each received message. Provide `Duration.Undefined` to switch off this
|
* with each received message. Provide `Duration.Undefined` to switch off this
|
||||||
* mechanism.
|
* mechanism.
|
||||||
*/
|
*/
|
||||||
def setReceiveTimeout(d: Duration): Unit
|
def setReceiveTimeout(d: FiniteDuration, msg: T): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the sending of receive timeout notifications.
|
||||||
|
*/
|
||||||
|
def cancelReceiveTimeout(): Unit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule the sending of the given message to the given target Actor after
|
* Schedule the sending of the given message to the given target Actor after
|
||||||
|
|
@ -156,45 +136,37 @@ trait ActorContext[T] {
|
||||||
*/
|
*/
|
||||||
class StubbedActorContext[T](
|
class StubbedActorContext[T](
|
||||||
val name: String,
|
val name: String,
|
||||||
override val props: Props[T])(
|
override val props: Props[T],
|
||||||
override implicit val system: ActorSystem[Nothing]) extends ActorContext[T] {
|
override val system: ActorSystem[Nothing]) extends ActorContext[T] {
|
||||||
|
|
||||||
val inbox = Inbox.sync[T](name)
|
val inbox = Inbox[T](name)
|
||||||
override val self = inbox.ref
|
override val self = inbox.ref
|
||||||
|
|
||||||
private var _children = TreeMap.empty[String, Inbox.SyncInbox[_]]
|
private var _children = TreeMap.empty[String, Inbox[_]]
|
||||||
private val childName = Iterator from 1 map (Helpers.base64(_))
|
private val childName = Iterator from 1 map (Helpers.base64(_))
|
||||||
|
|
||||||
override def children: Iterable[ActorRef[Nothing]] = _children.values map (_.ref)
|
override def children: Iterable[ActorRef[Nothing]] = _children.values map (_.ref)
|
||||||
override def child(name: String): Option[ActorRef[Nothing]] = _children get name map (_.ref)
|
override def child(name: String): Option[ActorRef[Nothing]] = _children get name map (_.ref)
|
||||||
override def spawnAnonymous[U](props: Props[U]): ActorRef[U] = {
|
override def spawnAnonymous[U](props: Props[U]): ActorRef[U] = {
|
||||||
val i = Inbox.sync[U](childName.next())
|
val i = Inbox[U](childName.next())
|
||||||
_children += i.ref.untypedRef.path.name → i
|
_children += i.ref.path.name → i
|
||||||
i.ref
|
i.ref
|
||||||
}
|
}
|
||||||
override def spawn[U](props: Props[U], name: String): ActorRef[U] =
|
override def spawn[U](props: Props[U], name: String): ActorRef[U] =
|
||||||
_children get name match {
|
_children get name match {
|
||||||
case Some(_) ⇒ throw new untyped.InvalidActorNameException(s"actor name $name is already taken")
|
case Some(_) ⇒ throw new untyped.InvalidActorNameException(s"actor name $name is already taken")
|
||||||
case None ⇒
|
case None ⇒
|
||||||
val i = Inbox.sync[U](name)
|
// FIXME correct child path for the Inbox ref
|
||||||
|
val i = Inbox[U](name)
|
||||||
_children += name → i
|
_children += name → i
|
||||||
i.ref
|
i.ref
|
||||||
}
|
}
|
||||||
override def actorOf(props: untyped.Props): untyped.ActorRef = {
|
|
||||||
val i = Inbox.sync[Any](childName.next())
|
/**
|
||||||
_children += i.ref.untypedRef.path.name → i
|
* Do not actually stop the child inbox, only simulate the liveness check.
|
||||||
i.ref.untypedRef
|
* Removal is asynchronous, explicit removeInbox is needed from outside afterwards.
|
||||||
}
|
*/
|
||||||
override def actorOf(props: untyped.Props, name: String): untyped.ActorRef =
|
|
||||||
_children get name match {
|
|
||||||
case Some(_) ⇒ throw new untyped.InvalidActorNameException(s"actor name $name is already taken")
|
|
||||||
case None ⇒
|
|
||||||
val i = Inbox.sync[Any](name)
|
|
||||||
_children += name → i
|
|
||||||
i.ref.untypedRef
|
|
||||||
}
|
|
||||||
override def stop(child: ActorRef[Nothing]): Boolean = {
|
override def stop(child: ActorRef[Nothing]): Boolean = {
|
||||||
// removal is asynchronous, so don’t do it here; explicit removeInbox needed from outside
|
|
||||||
_children.get(child.path.name) match {
|
_children.get(child.path.name) match {
|
||||||
case None ⇒ false
|
case None ⇒ false
|
||||||
case Some(inbox) ⇒ inbox.ref == child
|
case Some(inbox) ⇒ inbox.ref == child
|
||||||
|
|
@ -204,36 +176,33 @@ class StubbedActorContext[T](
|
||||||
def watch(other: akka.actor.ActorRef): other.type = other
|
def watch(other: akka.actor.ActorRef): other.type = other
|
||||||
def unwatch[U](other: ActorRef[U]): ActorRef[U] = other
|
def unwatch[U](other: ActorRef[U]): ActorRef[U] = other
|
||||||
def unwatch(other: akka.actor.ActorRef): other.type = other
|
def unwatch(other: akka.actor.ActorRef): other.type = other
|
||||||
def setReceiveTimeout(d: Duration): Unit = ()
|
def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = ()
|
||||||
|
def cancelReceiveTimeout(): Unit = ()
|
||||||
|
|
||||||
def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): untyped.Cancellable = new untyped.Cancellable {
|
def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): untyped.Cancellable = new untyped.Cancellable {
|
||||||
def cancel() = false
|
def cancel() = false
|
||||||
def isCancelled = true
|
def isCancelled = true
|
||||||
}
|
}
|
||||||
implicit def executionContext: ExecutionContextExecutor = system.executionContext
|
|
||||||
def spawnAdapter[U](f: U ⇒ T): ActorRef[U] = ???
|
|
||||||
|
|
||||||
def getInbox[U](name: String): Inbox.SyncInbox[U] = _children(name).asInstanceOf[Inbox.SyncInbox[U]]
|
def executionContext: ExecutionContextExecutor = system.executionContext
|
||||||
def removeInbox(name: String): Unit = _children -= name
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
def spawnAdapter[U](f: U ⇒ T): ActorRef[U] = spawnAnonymous(Props.empty)
|
||||||
* TODO
|
|
||||||
*
|
/**
|
||||||
* Currently running a behavior requires that the context stays the same, since
|
* Retrieve the named inbox. The passed ActorRef must be one that was returned
|
||||||
* the behavior may well close over it and thus a change might not be effective
|
* by one of the spawn methods earlier.
|
||||||
* at all. Another issue is that there is genuine state within the context that
|
|
||||||
* is coupled to the behavior’s state: if child actors were created then
|
|
||||||
* migrating a behavior into a new context will not work.
|
|
||||||
*
|
|
||||||
* This note is about remembering the reasons behind this restriction and
|
|
||||||
* proposes an ActorContextProxy as a (broken) half-solution. Another avenue
|
|
||||||
* by which a solution may be explored is for Pure behaviors in that they
|
|
||||||
* may be forced to never remember anything that is immobile.
|
|
||||||
*/
|
*/
|
||||||
//class MobileActorContext[T](_name: String, _props: Props[T], _system: ActorSystem[Nothing])
|
def getInbox[U](child: ActorRef[U]): Inbox[U] = {
|
||||||
// extends EffectfulActorContext[T](_name, _props, _system) {
|
val inbox = _children(child.path.name).asInstanceOf[Inbox[U]]
|
||||||
//
|
if (inbox.ref != child) throw new IllegalArgumentException(s"$child is not a child of $this")
|
||||||
//}
|
inbox
|
||||||
//
|
}
|
||||||
//class ActorContextProxy[T](var d: ActorContext[T]) extends ActorContext[T]
|
|
||||||
|
/**
|
||||||
|
* Remove the given inbox from the list of children, for example after
|
||||||
|
* having simulated its termination.
|
||||||
|
*/
|
||||||
|
def removeInbox(child: ActorRef[Nothing]): Unit = _children -= child.path.name
|
||||||
|
|
||||||
|
override def toString: String = s"Inbox($self)"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
*/
|
*/
|
||||||
package akka.typed
|
package akka.typed
|
||||||
|
|
||||||
import akka.actor.ActorPath
|
import akka.{ actor ⇒ a }
|
||||||
import scala.annotation.unchecked.uncheckedVariance
|
import scala.annotation.unchecked.uncheckedVariance
|
||||||
import language.implicitConversions
|
import language.implicitConversions
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ActorRef is the identity or address of an Actor instance. It is valid
|
* An ActorRef is the identity or address of an Actor instance. It is valid
|
||||||
|
|
@ -16,20 +17,18 @@ import language.implicitConversions
|
||||||
* [[akka.event.EventStream]] on a best effort basis
|
* [[akka.event.EventStream]] on a best effort basis
|
||||||
* (i.e. this delivery is not reliable).
|
* (i.e. this delivery is not reliable).
|
||||||
*/
|
*/
|
||||||
abstract class ActorRef[-T] extends java.lang.Comparable[ActorRef[Any]] { this: ScalaActorRef[T] ⇒
|
abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[ActorRef[Nothing]] { this: internal.ActorRefImpl[T] ⇒
|
||||||
/**
|
/**
|
||||||
* INTERNAL API.
|
* Send a message to the Actor referenced by this ActorRef using *at-most-once*
|
||||||
*
|
* messaging semantics.
|
||||||
* Implementation detail. The underlying untyped [[akka.actor.ActorRef]]
|
|
||||||
* of this typed ActorRef.
|
|
||||||
*/
|
*/
|
||||||
private[akka] def untypedRef: akka.actor.ActorRef
|
def tell(msg: T): Unit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message to the Actor referenced by this ActorRef using *at-most-once*
|
* Send a message to the Actor referenced by this ActorRef using *at-most-once*
|
||||||
* messaging semantics.
|
* messaging semantics.
|
||||||
*/
|
*/
|
||||||
def tell(msg: T): Unit = untypedRef ! msg
|
def !(msg: T): Unit = tell(msg)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsafe utility method for widening the type accepted by this ActorRef;
|
* Unsafe utility method for widening the type accepted by this ActorRef;
|
||||||
|
|
@ -44,34 +43,44 @@ abstract class ActorRef[-T] extends java.lang.Comparable[ActorRef[Any]] { this:
|
||||||
* and more than one Actor instance can exist with the same path at different
|
* and more than one Actor instance can exist with the same path at different
|
||||||
* points in time, but not concurrently.
|
* points in time, but not concurrently.
|
||||||
*/
|
*/
|
||||||
def path: ActorPath = untypedRef.path
|
final val path: a.ActorPath = _path
|
||||||
|
|
||||||
override def toString = untypedRef.toString
|
/**
|
||||||
override def equals(other: Any) = other match {
|
* Comparison takes path and the unique id of the actor cell into account.
|
||||||
case a: ActorRef[_] ⇒ a.untypedRef == untypedRef
|
*/
|
||||||
|
final override def compareTo(other: ActorRef[Nothing]) = {
|
||||||
|
val x = this.path compareTo other.path
|
||||||
|
if (x == 0) if (this.path.uid < other.path.uid) -1 else if (this.path.uid == other.path.uid) 0 else 1
|
||||||
|
else x
|
||||||
|
}
|
||||||
|
|
||||||
|
final override def hashCode: Int = path.uid
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equals takes path and the unique id of the actor cell into account.
|
||||||
|
*/
|
||||||
|
final override def equals(that: Any): Boolean = that match {
|
||||||
|
case other: ActorRef[_] ⇒ path.uid == other.path.uid && path == other.path
|
||||||
case _ ⇒ false
|
case _ ⇒ false
|
||||||
}
|
}
|
||||||
override def hashCode = untypedRef.hashCode
|
|
||||||
override def compareTo(other: ActorRef[Any]) = untypedRef.compareTo(other.untypedRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
final override def toString: String = s"Actor[${path}#${path.uid}]"
|
||||||
* This trait is used to hide the `!` method from Java code.
|
|
||||||
*/
|
|
||||||
trait ScalaActorRef[-T] { this: ActorRef[T] ⇒
|
|
||||||
def !(msg: T): Unit = tell(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object ActorRef {
|
object ActorRef {
|
||||||
private class Combined[T](val untypedRef: akka.actor.ActorRef) extends ActorRef[T] with ScalaActorRef[T]
|
/**
|
||||||
|
* Create an ActorRef from a Future, buffering up to the given number of
|
||||||
implicit def toScalaActorRef[T](ref: ActorRef[T]): ScalaActorRef[T] = ref.asInstanceOf[ScalaActorRef[T]]
|
* messages in while the Future is not fulfilled.
|
||||||
|
*/
|
||||||
|
def apply[T](f: Future[ActorRef[T]], bufferSize: Int = 1000): ActorRef[T] = new internal.FutureRef(FuturePath, bufferSize, f)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a typed ActorRef from an untyped one and a protocol definition
|
* Create an ActorRef by providing a function that is invoked for sending
|
||||||
* (i.e. a recipient message type). This can be used to properly represent
|
* messages and a termination callback.
|
||||||
* untyped Actors within the typed world, given that they implement the assumed
|
|
||||||
* protocol.
|
|
||||||
*/
|
*/
|
||||||
def apply[T](ref: akka.actor.ActorRef): ActorRef[T] = new Combined[T](ref)
|
def apply[T](send: (T, internal.FunctionRef[T]) ⇒ Unit, terminate: internal.FunctionRef[T] ⇒ Unit): ActorRef[T] =
|
||||||
|
new internal.FunctionRef(FunctionPath, send, terminate)
|
||||||
|
|
||||||
|
private[typed] val FuturePath = a.RootActorPath(a.Address("akka.typed.internal", "future"))
|
||||||
|
private[typed] val FunctionPath = a.RootActorPath(a.Address("akka.typed.internal", "function"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,13 @@
|
||||||
*/
|
*/
|
||||||
package akka.typed
|
package akka.typed
|
||||||
|
|
||||||
import akka.event.EventStream
|
import akka.event.{ LoggingFilter, LoggingAdapter, EventStream }
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import akka.actor.ActorRefProvider
|
import akka.{ actor ⇒ a }
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
import akka.actor.DynamicAccess
|
import com.typesafe.config.{ Config, ConfigFactory }
|
||||||
import akka.actor.ActorSystemImpl
|
import scala.concurrent.{ ExecutionContextExecutor, Future }
|
||||||
import com.typesafe.config.Config
|
import akka.typed.adapter.{ PropsAdapter, ActorSystemAdapter }
|
||||||
import akka.actor.ExtendedActorSystem
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
import scala.concurrent.ExecutionContextExecutor
|
|
||||||
import scala.concurrent.Future
|
|
||||||
import akka.dispatch.Dispatchers
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ActorSystem is home to a hierarchy of Actors. It is created using
|
* An ActorSystem is home to a hierarchy of Actors. It is created using
|
||||||
|
|
@ -23,50 +18,41 @@ import akka.dispatch.Dispatchers
|
||||||
* A system also implements the [[ActorRef]] type, and sending a message to
|
* A system also implements the [[ActorRef]] type, and sending a message to
|
||||||
* the system directs that message to the root Actor.
|
* the system directs that message to the root Actor.
|
||||||
*/
|
*/
|
||||||
abstract class ActorSystem[-T](_name: String) extends ActorRef[T] { this: ScalaActorRef[T] ⇒
|
trait ActorSystem[-T] extends ActorRef[T] { this: internal.ActorRefImpl[T] ⇒
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API.
|
|
||||||
*
|
|
||||||
* Access to the underlying (untyped) ActorSystem.
|
|
||||||
*/
|
|
||||||
private[akka] val untyped: ExtendedActorSystem
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of this actor system, used to distinguish multiple ones within
|
* The name of this actor system, used to distinguish multiple ones within
|
||||||
* the same JVM & class loader.
|
* the same JVM & class loader.
|
||||||
*/
|
*/
|
||||||
def name: String = _name
|
def name: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The core settings extracted from the supplied configuration.
|
* The core settings extracted from the supplied configuration.
|
||||||
*/
|
*/
|
||||||
def settings: akka.actor.ActorSystem.Settings = untyped.settings
|
def settings: akka.actor.ActorSystem.Settings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log the configuration.
|
* Log the configuration.
|
||||||
*/
|
*/
|
||||||
def logConfiguration(): Unit = untyped.logConfiguration()
|
def logConfiguration(): Unit
|
||||||
|
|
||||||
|
def logFilter: LoggingFilter
|
||||||
|
def log: LoggingAdapter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start-up time in milliseconds since the epoch.
|
* Start-up time in milliseconds since the epoch.
|
||||||
*/
|
*/
|
||||||
def startTime: Long = untyped.startTime
|
def startTime: Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Up-time of this actor system in seconds.
|
* Up-time of this actor system in seconds.
|
||||||
*/
|
*/
|
||||||
def uptime: Long = untyped.uptime
|
def uptime: Long
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper object for looking up configured dispatchers.
|
|
||||||
*/
|
|
||||||
def dispatchers: Dispatchers = untyped.dispatchers
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ThreadFactory that can be used if the transport needs to create any Threads
|
* A ThreadFactory that can be used if the transport needs to create any Threads
|
||||||
*/
|
*/
|
||||||
def threadFactory: ThreadFactory = untyped.threadFactory
|
def threadFactory: ThreadFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ClassLoader wrapper which is used for reflective accesses internally. This is set
|
* ClassLoader wrapper which is used for reflective accesses internally. This is set
|
||||||
|
|
@ -75,65 +61,93 @@ abstract class ActorSystem[-T](_name: String) extends ActorRef[T] { this: ScalaA
|
||||||
* set on all threads created by the ActorSystem, if one was set during
|
* set on all threads created by the ActorSystem, if one was set during
|
||||||
* creation.
|
* creation.
|
||||||
*/
|
*/
|
||||||
def dynamicAccess: DynamicAccess = untyped.dynamicAccess
|
def dynamicAccess: a.DynamicAccess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ActorRefProvider is the only entity which creates all actor references within this actor system.
|
* A generic scheduler that can initiate the execution of tasks after some delay.
|
||||||
|
* It is recommended to use the ActorContext’s scheduling capabilities for sending
|
||||||
|
* messages to actors instead of registering a Runnable for execution using this facility.
|
||||||
*/
|
*/
|
||||||
def provider: ActorRefProvider = untyped.provider
|
def scheduler: a.Scheduler
|
||||||
|
|
||||||
/**
|
|
||||||
* The user guardian’s untyped [[akka.actor.ActorRef]].
|
|
||||||
*/
|
|
||||||
private[akka] override def untypedRef: akka.actor.ActorRef = untyped.provider.guardian
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main event bus of this actor system, used for example for logging.
|
* Main event bus of this actor system, used for example for logging.
|
||||||
*/
|
*/
|
||||||
def eventStream: EventStream = untyped.eventStream
|
def eventStream: EventStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facilities for lookup up thread-pools from configuration.
|
||||||
|
*/
|
||||||
|
def dispatchers: Dispatchers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default thread pool of this ActorSystem, configured with settings in `akka.actor.default-dispatcher`.
|
* The default thread pool of this ActorSystem, configured with settings in `akka.actor.default-dispatcher`.
|
||||||
*/
|
*/
|
||||||
implicit def executionContext: ExecutionContextExecutor = untyped.dispatcher
|
implicit def executionContext: ExecutionContextExecutor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminates this actor system. This will stop the guardian actor, which in turn
|
* Terminates this actor system. This will stop the guardian actor, which in turn
|
||||||
* will recursively stop all its child actors, then the system guardian
|
* will recursively stop all its child actors, then the system guardian
|
||||||
* (below which the logging actors reside).
|
* (below which the logging actors reside).
|
||||||
*/
|
*/
|
||||||
def terminate(): Future[Terminated] = untyped.terminate().map(t ⇒ Terminated(ActorRef(t.actor)))
|
def terminate(): Future[Terminated]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Future which will be completed after the ActorSystem has been terminated
|
* Returns a Future which will be completed after the ActorSystem has been terminated
|
||||||
* and termination hooks have been executed.
|
* and termination hooks have been executed.
|
||||||
*/
|
*/
|
||||||
def whenTerminated: Future[Terminated] = untyped.whenTerminated.map(t ⇒ Terminated(ActorRef(t.actor)))
|
def whenTerminated: Future[Terminated]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The deadLetter address is a destination that will accept (and discard)
|
* The deadLetter address is a destination that will accept (and discard)
|
||||||
* every message sent to it.
|
* every message sent to it.
|
||||||
*/
|
*/
|
||||||
def deadLetters[U]: ActorRef[U] = deadLetterRef
|
def deadLetters[U]: ActorRef[U]
|
||||||
lazy private val deadLetterRef = ActorRef[Any](untyped.deadLetters)
|
|
||||||
|
/**
|
||||||
|
* Create a string representation of the actor hierarchy within this system.
|
||||||
|
*
|
||||||
|
* The format of the string is subject to change, i.e. no stable “API”.
|
||||||
|
*/
|
||||||
|
def printTree: String
|
||||||
}
|
}
|
||||||
|
|
||||||
object ActorSystem {
|
object ActorSystem {
|
||||||
private class Impl[T](_name: String, _config: Config, _cl: ClassLoader, _ec: Option[ExecutionContext], _p: Props[T])
|
import internal._
|
||||||
extends ActorSystem[T](_name) with ScalaActorRef[T] {
|
|
||||||
override private[akka] val untyped: ExtendedActorSystem = new ActorSystemImpl(_name, _config, _cl, _ec, Some(Props.untyped(_p))).start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Wrapper(val untyped: ExtendedActorSystem) extends ActorSystem[Nothing](untyped.name) with ScalaActorRef[Nothing]
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ActorSystem implementation that is optimized for running
|
||||||
|
* Akka Typed [[Behavior]] hierarchies—this system cannot run untyped
|
||||||
|
* [[akka.actor.Actor]] instances.
|
||||||
|
*/
|
||||||
def apply[T](name: String, guardianProps: Props[T],
|
def apply[T](name: String, guardianProps: Props[T],
|
||||||
config: Option[Config] = None,
|
config: Option[Config] = None,
|
||||||
classLoader: Option[ClassLoader] = None,
|
classLoader: Option[ClassLoader] = None,
|
||||||
executionContext: Option[ExecutionContext] = None): ActorSystem[T] = {
|
executionContext: Option[ExecutionContext] = None): ActorSystem[T] = {
|
||||||
val cl = classLoader.getOrElse(akka.actor.ActorSystem.findClassLoader())
|
val cl = classLoader.getOrElse(akka.actor.ActorSystem.findClassLoader())
|
||||||
val appConfig = config.getOrElse(ConfigFactory.load(cl))
|
val appConfig = config.getOrElse(ConfigFactory.load(cl))
|
||||||
new Impl(name, appConfig, cl, executionContext, guardianProps)
|
new ActorSystemImpl(name, appConfig, cl, executionContext, guardianProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply(untyped: akka.actor.ActorSystem): ActorSystem[Nothing] = new Wrapper(untyped.asInstanceOf[ExtendedActorSystem])
|
/**
|
||||||
|
* Create an ActorSystem based on the untyped [[akka.actor.ActorSystem]]
|
||||||
|
* which runs Akka Typed [[Behavior]] on an emulation layer. In this
|
||||||
|
* system typed and untyped actors can coexist.
|
||||||
|
*/
|
||||||
|
def adapter[T](name: String, guardianProps: Props[T],
|
||||||
|
config: Option[Config] = None,
|
||||||
|
classLoader: Option[ClassLoader] = None,
|
||||||
|
executionContext: Option[ExecutionContext] = None): ActorSystem[T] = {
|
||||||
|
val cl = classLoader.getOrElse(akka.actor.ActorSystem.findClassLoader())
|
||||||
|
val appConfig = config.getOrElse(ConfigFactory.load(cl))
|
||||||
|
val untyped = new a.ActorSystemImpl(name, appConfig, cl, executionContext, Some(PropsAdapter(guardianProps)))
|
||||||
|
untyped.start()
|
||||||
|
new ActorSystemAdapter(untyped)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap an untyped [[akka.actor.ActorSystem]] such that it can be used from
|
||||||
|
* Akka Typed [[Behavior]].
|
||||||
|
*/
|
||||||
|
def wrap(untyped: a.ActorSystem): ActorSystem[Nothing] = new ActorSystemAdapter(untyped.asInstanceOf[a.ActorSystemImpl])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,17 @@
|
||||||
*/
|
*/
|
||||||
package akka.typed
|
package akka.typed
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.{ Future, Promise }
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import akka.actor.InternalActorRef
|
import akka.actor.InternalActorRef
|
||||||
import akka.pattern.AskTimeoutException
|
import akka.pattern.AskTimeoutException
|
||||||
import akka.pattern.PromiseActorRef
|
import akka.pattern.PromiseActorRef
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
|
import akka.actor.Scheduler
|
||||||
|
import akka.typed.internal.FunctionRef
|
||||||
|
import akka.actor.RootActorPath
|
||||||
|
import akka.actor.Address
|
||||||
|
import akka.util.LineNumbers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ask-pattern implements the initiator side of a request–reply protocol.
|
* The ask-pattern implements the initiator side of a request–reply protocol.
|
||||||
|
|
@ -31,39 +36,57 @@ import java.lang.IllegalArgumentException
|
||||||
*/
|
*/
|
||||||
object AskPattern {
|
object AskPattern {
|
||||||
implicit class Askable[T](val ref: ActorRef[T]) extends AnyVal {
|
implicit class Askable[T](val ref: ActorRef[T]) extends AnyVal {
|
||||||
def ?[U](f: ActorRef[U] ⇒ T)(implicit timeout: Timeout): Future[U] = ask(ref, timeout, f)
|
def ?[U](f: ActorRef[U] ⇒ T)(implicit timeout: Timeout, scheduler: Scheduler): Future[U] =
|
||||||
|
ref match {
|
||||||
|
case a: adapter.ActorRefAdapter[_] ⇒ askUntyped(ref, a.untyped, timeout, f)
|
||||||
|
case a: adapter.ActorSystemAdapter[_] ⇒ askUntyped(ref, a.untyped.guardian, timeout, f)
|
||||||
|
case _ ⇒ ask(ref, timeout, scheduler, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PromiseRef[U](actorRef: ActorRef[_], timeout: Timeout) {
|
private class PromiseRef[U](target: ActorRef[_], untyped: InternalActorRef, timeout: Timeout) {
|
||||||
val (ref: ActorRef[U], future: Future[U], promiseRef: PromiseActorRef) = actorRef.untypedRef match {
|
val (ref: ActorRef[U], future: Future[U], promiseRef: PromiseActorRef) =
|
||||||
case ref: InternalActorRef if ref.isTerminated ⇒
|
if (untyped.isTerminated)
|
||||||
(
|
(
|
||||||
ActorRef[U](ref.provider.deadLetters),
|
adapter.ActorRefAdapter[U](untyped.provider.deadLetters),
|
||||||
Future.failed[U](new AskTimeoutException(s"Recipient[$actorRef] had already been terminated.")))
|
Future.failed[U](new AskTimeoutException(s"Recipient[$target] had already been terminated.")), null)
|
||||||
case ref: InternalActorRef ⇒
|
else if (timeout.duration.length <= 0)
|
||||||
if (timeout.duration.length <= 0)
|
|
||||||
(
|
(
|
||||||
ActorRef[U](ref.provider.deadLetters),
|
adapter.ActorRefAdapter[U](untyped.provider.deadLetters),
|
||||||
Future.failed[U](new IllegalArgumentException(s"Timeout length must be positive, question not sent to [$actorRef]")))
|
Future.failed[U](new IllegalArgumentException(s"Timeout length must be positive, question not sent to [$target]")), null)
|
||||||
else {
|
else {
|
||||||
val a = PromiseActorRef(ref.provider, timeout, actorRef, "unknown")
|
val a = PromiseActorRef(untyped.provider, timeout, target, "unknown")
|
||||||
val b = ActorRef[U](a)
|
val b = adapter.ActorRefAdapter[U](a)
|
||||||
(b, a.result.future.asInstanceOf[Future[U]], a)
|
(b, a.result.future.asInstanceOf[Future[U]], a)
|
||||||
}
|
}
|
||||||
case _ ⇒ throw new IllegalArgumentException(s"cannot create PromiseRef for non-Akka ActorRef (${actorRef.getClass})")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private object PromiseRef {
|
private def askUntyped[T, U](target: ActorRef[T], untyped: InternalActorRef, timeout: Timeout, f: ActorRef[U] ⇒ T): Future[U] = {
|
||||||
def apply[U](actorRef: ActorRef[_])(implicit timeout: Timeout) = new PromiseRef[U](actorRef, timeout)
|
val p = new PromiseRef[U](target, untyped, timeout)
|
||||||
}
|
|
||||||
|
|
||||||
private[typed] def ask[T, U](actorRef: ActorRef[T], timeout: Timeout, f: ActorRef[U] ⇒ T): Future[U] = {
|
|
||||||
val p = PromiseRef[U](actorRef)(timeout)
|
|
||||||
val m = f(p.ref)
|
val m = f(p.ref)
|
||||||
p.promiseRef.messageClassName = m.getClass.getName
|
if (p.promiseRef ne null) p.promiseRef.messageClassName = m.getClass.getName
|
||||||
actorRef ! m
|
target ! m
|
||||||
p.future
|
p.future
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def ask[T, U](actorRef: ActorRef[T], timeout: Timeout, scheduler: Scheduler, f: ActorRef[U] ⇒ T): Future[U] = {
|
||||||
|
import akka.dispatch.ExecutionContexts.{ sameThreadExecutionContext ⇒ ec }
|
||||||
|
val p = Promise[U]
|
||||||
|
val ref = new FunctionRef[U](
|
||||||
|
AskPath,
|
||||||
|
(msg, self) ⇒ {
|
||||||
|
p.trySuccess(msg)
|
||||||
|
self.sendSystem(internal.Terminate())
|
||||||
|
},
|
||||||
|
(self) ⇒ if (!p.isCompleted) p.tryFailure(new NoSuchElementException("ask pattern terminated before value was received")))
|
||||||
|
actorRef ! f(ref)
|
||||||
|
val d = timeout.duration
|
||||||
|
val c = scheduler.scheduleOnce(d)(p.tryFailure(new AskTimeoutException(s"did not receive message within $d")))(ec)
|
||||||
|
val future = p.future
|
||||||
|
future.andThen {
|
||||||
|
case _ ⇒ c.cancel()
|
||||||
|
}(ec)
|
||||||
|
}
|
||||||
|
|
||||||
|
private[typed] val AskPath = RootActorPath(Address("akka.typed.internal", "ask"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,141 +63,6 @@ abstract class Behavior[T] {
|
||||||
* forked either.
|
* forked either.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* System signals are notifications that are generated by the system and
|
|
||||||
* delivered to the Actor behavior in a reliable fashion (i.e. they are
|
|
||||||
* guaranteed to arrive in contrast to the at-most-once semantics of normal
|
|
||||||
* Actor messages).
|
|
||||||
*/
|
|
||||||
sealed trait Signal
|
|
||||||
/**
|
|
||||||
* Lifecycle signal that is fired upon creation of the Actor. This will be the
|
|
||||||
* first message that the actor processes.
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case object PreStart extends Signal
|
|
||||||
/**
|
|
||||||
* Lifecycle signal that is fired upon restart of the Actor before replacing
|
|
||||||
* the behavior with the fresh one (i.e. this signal is received within the
|
|
||||||
* behavior that failed).
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case class PreRestart(failure: Throwable) extends Signal
|
|
||||||
/**
|
|
||||||
* Lifecycle signal that is fired upon restart of the Actor after replacing
|
|
||||||
* the behavior with the fresh one (i.e. this signal is received within the
|
|
||||||
* fresh replacement behavior).
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case class PostRestart(failure: Throwable) extends Signal
|
|
||||||
/**
|
|
||||||
* Lifecycle signal that is fired after this actor and all its child actors
|
|
||||||
* (transitively) have terminated. The [[Terminated]] signal is only sent to
|
|
||||||
* registered watchers after this signal has been processed.
|
|
||||||
*
|
|
||||||
* <b>IMPORTANT NOTE:</b> if the actor terminated by switching to the
|
|
||||||
* `Stopped` behavior then this signal will be ignored (i.e. the
|
|
||||||
* Stopped behavior will do nothing in reaction to it).
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case object PostStop extends Signal
|
|
||||||
/**
|
|
||||||
* Lifecycle signal that is fired when a direct child actor fails. The child
|
|
||||||
* actor will be suspended until its fate has been decided. The decision is
|
|
||||||
* communicated by calling the [[Failed#decide]] method. If this is not
|
|
||||||
* done then the default behavior is to escalate the failure, which amounts to
|
|
||||||
* failing this actor with the same exception that the child actor failed with.
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case class Failed(cause: Throwable, child: ActorRef[Nothing]) extends Signal {
|
|
||||||
import Failed._
|
|
||||||
|
|
||||||
private[this] var _decision: Decision = _
|
|
||||||
def decide(decision: Decision): Unit = _decision = decision
|
|
||||||
def getDecision: Decision = _decision match {
|
|
||||||
case null ⇒ NoFailureResponse
|
|
||||||
case x ⇒ x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The actor can register for a notification in case no message is received
|
|
||||||
* within a given time window, and the signal that is raised in this case is
|
|
||||||
* this one. See also [[ActorContext#setReceiveTimeout]].
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case object ReceiveTimeout extends Signal
|
|
||||||
/**
|
|
||||||
* Lifecycle signal that is fired when an Actor that was watched has terminated.
|
|
||||||
* Watching is performed by invoking the
|
|
||||||
* [[akka.typed.ActorContext]] `watch` method. The DeathWatch service is
|
|
||||||
* idempotent, meaning that registering twice has the same effect as registering
|
|
||||||
* once. Registration does not need to happen before the Actor terminates, a
|
|
||||||
* notification is guaranteed to arrive after both registration and termination
|
|
||||||
* have occurred. Termination of a remote Actor can also be effected by declaring
|
|
||||||
* the Actor’s home system as failed (e.g. as a result of being unreachable).
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
final case class Terminated(ref: ActorRef[Nothing]) extends Signal
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parent of an actor decides upon the fate of a failed child actor by
|
|
||||||
* encapsulating its next behavior in one of the four wrappers defined within
|
|
||||||
* this class.
|
|
||||||
*
|
|
||||||
* Failure responses have an associated precedence that ranks them, which is in
|
|
||||||
* descending importance:
|
|
||||||
*
|
|
||||||
* - Escalate
|
|
||||||
* - Stop
|
|
||||||
* - Restart
|
|
||||||
* - Resume
|
|
||||||
*/
|
|
||||||
object Failed {
|
|
||||||
|
|
||||||
sealed trait Decision
|
|
||||||
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
case object NoFailureResponse extends Decision
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resuming the child actor means that the result of processing the message
|
|
||||||
* on which it failed is just ignored, the previous state will be used to
|
|
||||||
* process the next message. The message that triggered the failure will not
|
|
||||||
* be processed again.
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
case object Resume extends Decision
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restarting the child actor means resetting its behavior to the initial
|
|
||||||
* one that was provided during its creation (i.e. the one which was passed
|
|
||||||
* into the [[Props]] constructor). The previously failed behavior will
|
|
||||||
* receive a [[PreRestart]] signal before this happens and the replacement
|
|
||||||
* behavior will receive a [[PostRestart]] signal afterwards.
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
case object Restart extends Decision
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stopping the child actor will free its resources and eventually
|
|
||||||
* (asynchronously) unregister its name from the parent. Completion of this
|
|
||||||
* process can be observed by watching the child actor and reacting to its
|
|
||||||
* [[Terminated]] signal.
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
case object Stop extends Decision
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default response to a failure in a child actor is to escalate the
|
|
||||||
* failure, entailing that the parent actor fails as well. This is equivalent
|
|
||||||
* to an exception unwinding the call stack, but it applies to the supervision
|
|
||||||
* hierarchy instead.
|
|
||||||
*/
|
|
||||||
@SerialVersionUID(1L)
|
|
||||||
case object Escalate extends Decision
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Behavior {
|
object Behavior {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -259,7 +124,7 @@ object Behavior {
|
||||||
* behavior) this method unwraps the behavior such that the innermost behavior
|
* behavior) this method unwraps the behavior such that the innermost behavior
|
||||||
* is returned, i.e. it removes the decorations.
|
* is returned, i.e. it removes the decorations.
|
||||||
*/
|
*/
|
||||||
def canonicalize[T](ctx: ActorContext[T], behavior: Behavior[T], current: Behavior[T]): Behavior[T] =
|
def canonicalize[T](behavior: Behavior[T], current: Behavior[T]): Behavior[T] =
|
||||||
behavior match {
|
behavior match {
|
||||||
case `sameBehavior` ⇒ current
|
case `sameBehavior` ⇒ current
|
||||||
case `unhandledBehavior` ⇒ current
|
case `unhandledBehavior` ⇒ current
|
||||||
|
|
@ -270,4 +135,3 @@ object Behavior {
|
||||||
|
|
||||||
def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq unhandledBehavior
|
def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq unhandledBehavior
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
akka-typed/src/main/scala/akka/typed/Dispatchers.scala
Normal file
13
akka-typed/src/main/scala/akka/typed/Dispatchers.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContextExecutor
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import akka.event.LoggingAdapter
|
||||||
|
|
||||||
|
trait Dispatchers {
|
||||||
|
def lookup(selector: DispatcherSelector): ExecutionContextExecutor
|
||||||
|
def shutdown(): Unit
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ object Effect {
|
||||||
@SerialVersionUID(1L) final case class Stopped(childName: String) extends Effect
|
@SerialVersionUID(1L) final case class Stopped(childName: String) extends Effect
|
||||||
@SerialVersionUID(1L) final case class Watched[T](other: ActorRef[T]) extends Effect
|
@SerialVersionUID(1L) final case class Watched[T](other: ActorRef[T]) extends Effect
|
||||||
@SerialVersionUID(1L) final case class Unwatched[T](other: ActorRef[T]) extends Effect
|
@SerialVersionUID(1L) final case class Unwatched[T](other: ActorRef[T]) extends Effect
|
||||||
@SerialVersionUID(1L) final case class ReceiveTimeoutSet(d: Duration) extends Effect
|
@SerialVersionUID(1L) final case class ReceiveTimeoutSet[T](d: Duration, msg: T) extends Effect
|
||||||
@SerialVersionUID(1L) final case class Messaged[U](other: ActorRef[U], msg: U) extends Effect
|
@SerialVersionUID(1L) final case class Messaged[U](other: ActorRef[U], msg: U) extends Effect
|
||||||
@SerialVersionUID(1L) final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
|
@SerialVersionUID(1L) final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
|
||||||
@SerialVersionUID(1L) case object EmptyEffect extends Effect
|
@SerialVersionUID(1L) case object EmptyEffect extends Effect
|
||||||
|
|
@ -31,7 +31,7 @@ object Effect {
|
||||||
* on it and otherwise stubs them out like a [[StubbedActorContext]].
|
* on it and otherwise stubs them out like a [[StubbedActorContext]].
|
||||||
*/
|
*/
|
||||||
class EffectfulActorContext[T](_name: String, _props: Props[T], _system: ActorSystem[Nothing])
|
class EffectfulActorContext[T](_name: String, _props: Props[T], _system: ActorSystem[Nothing])
|
||||||
extends StubbedActorContext[T](_name, _props)(_system) {
|
extends StubbedActorContext[T](_name, _props, _system) {
|
||||||
import akka.{ actor ⇒ a }
|
import akka.{ actor ⇒ a }
|
||||||
import Effect._
|
import Effect._
|
||||||
|
|
||||||
|
|
@ -54,27 +54,18 @@ class EffectfulActorContext[T](_name: String, _props: Props[T], _system: ActorSy
|
||||||
|
|
||||||
def currentBehavior: Behavior[T] = current
|
def currentBehavior: Behavior[T] = current
|
||||||
|
|
||||||
def run(msg: T): Unit = current = Behavior.canonicalize(this, current.message(this, msg), current)
|
def run(msg: T): Unit = current = Behavior.canonicalize(current.message(this, msg), current)
|
||||||
def signal(signal: Signal): Unit = current = Behavior.canonicalize(this, current.management(this, signal), current)
|
def signal(signal: Signal): Unit = current = Behavior.canonicalize(current.management(this, signal), current)
|
||||||
|
|
||||||
override def spawnAnonymous[U](props: Props[U]): ActorRef[U] = {
|
override def spawnAnonymous[U](props: Props[U]): ActorRef[U] = {
|
||||||
val ref = super.spawnAnonymous(props)
|
val ref = super.spawnAnonymous(props)
|
||||||
effectQueue.offer(Spawned(ref.untypedRef.path.name))
|
effectQueue.offer(Spawned(ref.path.name))
|
||||||
ref
|
ref
|
||||||
}
|
}
|
||||||
override def spawn[U](props: Props[U], name: String): ActorRef[U] = {
|
override def spawn[U](props: Props[U], name: String): ActorRef[U] = {
|
||||||
effectQueue.offer(Spawned(name))
|
effectQueue.offer(Spawned(name))
|
||||||
super.spawn(props, name)
|
super.spawn(props, name)
|
||||||
}
|
}
|
||||||
override def actorOf(props: a.Props): a.ActorRef = {
|
|
||||||
val ref = super.actorOf(props)
|
|
||||||
effectQueue.offer(Spawned(ref.path.name))
|
|
||||||
ref
|
|
||||||
}
|
|
||||||
override def actorOf(props: a.Props, name: String): a.ActorRef = {
|
|
||||||
effectQueue.offer(Spawned(name))
|
|
||||||
super.actorOf(props, name)
|
|
||||||
}
|
|
||||||
override def stop(child: ActorRef[Nothing]): Boolean = {
|
override def stop(child: ActorRef[Nothing]): Boolean = {
|
||||||
effectQueue.offer(Stopped(child.path.name))
|
effectQueue.offer(Stopped(child.path.name))
|
||||||
super.stop(child)
|
super.stop(child)
|
||||||
|
|
@ -87,17 +78,13 @@ class EffectfulActorContext[T](_name: String, _props: Props[T], _system: ActorSy
|
||||||
effectQueue.offer(Unwatched(other))
|
effectQueue.offer(Unwatched(other))
|
||||||
super.unwatch(other)
|
super.unwatch(other)
|
||||||
}
|
}
|
||||||
override def watch(other: akka.actor.ActorRef): other.type = {
|
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
|
||||||
effectQueue.offer(Watched(ActorRef[Any](other)))
|
effectQueue.offer(ReceiveTimeoutSet(d, msg))
|
||||||
super.watch(other)
|
super.setReceiveTimeout(d, msg)
|
||||||
}
|
}
|
||||||
override def unwatch(other: akka.actor.ActorRef): other.type = {
|
override def cancelReceiveTimeout(): Unit = {
|
||||||
effectQueue.offer(Unwatched(ActorRef[Any](other)))
|
effectQueue.offer(ReceiveTimeoutSet(Duration.Undefined, null))
|
||||||
super.unwatch(other)
|
super.cancelReceiveTimeout()
|
||||||
}
|
|
||||||
override def setReceiveTimeout(d: Duration): Unit = {
|
|
||||||
effectQueue.offer(ReceiveTimeoutSet(d))
|
|
||||||
super.setReceiveTimeout(d)
|
|
||||||
}
|
}
|
||||||
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
|
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
|
||||||
effectQueue.offer(Scheduled(delay, target, msg))
|
effectQueue.offer(Scheduled(delay, target, msg))
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2014-2016 Lightbend Inc. <http://www.lightbend.com>
|
|
||||||
*/
|
|
||||||
package akka.typed
|
|
||||||
|
|
||||||
import akka.{ actor ⇒ a }
|
|
||||||
import scala.concurrent.duration.Duration
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
|
||||||
import scala.concurrent.ExecutionContextExecutor
|
|
||||||
import akka.event.LoggingReceive
|
|
||||||
import akka.actor.DeathPactException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API. Mapping the execution of a [[Behavior]] onto a good old untyped
|
|
||||||
* [[akka.actor.Actor]].
|
|
||||||
*/
|
|
||||||
private[typed] class ActorAdapter[T](_initialBehavior: () ⇒ Behavior[T]) extends akka.actor.Actor {
|
|
||||||
import Behavior._
|
|
||||||
|
|
||||||
var behavior = _initialBehavior()
|
|
||||||
val ctx = new ActorContextAdapter[T](context)
|
|
||||||
|
|
||||||
def receive = LoggingReceive {
|
|
||||||
case akka.actor.Terminated(ref) ⇒
|
|
||||||
val msg = Terminated(ActorRef(ref))
|
|
||||||
next(behavior.management(ctx, msg), msg)
|
|
||||||
case akka.actor.ReceiveTimeout ⇒
|
|
||||||
next(behavior.management(ctx, ReceiveTimeout), ReceiveTimeout)
|
|
||||||
case msg ⇒
|
|
||||||
val m = msg.asInstanceOf[T]
|
|
||||||
next(behavior.message(ctx, m), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def next(b: Behavior[T], msg: Any): Unit = {
|
|
||||||
if (isUnhandled(b)) unhandled(msg)
|
|
||||||
behavior = canonicalize(ctx, b, behavior)
|
|
||||||
if (!isAlive(behavior)) {
|
|
||||||
context.stop(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def unhandled(msg: Any): Unit = msg match {
|
|
||||||
case Terminated(ref) ⇒ throw new DeathPactException(ref.untypedRef)
|
|
||||||
case other ⇒ super.unhandled(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val supervisorStrategy = a.OneForOneStrategy() {
|
|
||||||
case ex ⇒
|
|
||||||
import Failed._
|
|
||||||
import akka.actor.{ SupervisorStrategy ⇒ s }
|
|
||||||
val f = Failed(ex, ActorRef(sender()))
|
|
||||||
next(behavior.management(ctx, f), f)
|
|
||||||
f.getDecision match {
|
|
||||||
case Resume ⇒ s.Resume
|
|
||||||
case Restart ⇒ s.Restart
|
|
||||||
case Stop ⇒ s.Stop
|
|
||||||
case _ ⇒ s.Escalate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def preStart(): Unit =
|
|
||||||
next(behavior.management(ctx, PreStart), PreStart)
|
|
||||||
override def preRestart(reason: Throwable, message: Option[Any]): Unit =
|
|
||||||
next(behavior.management(ctx, PreRestart(reason)), PreRestart(reason))
|
|
||||||
override def postRestart(reason: Throwable): Unit =
|
|
||||||
next(behavior.management(ctx, PostRestart(reason)), PostRestart(reason))
|
|
||||||
override def postStop(): Unit =
|
|
||||||
next(behavior.management(ctx, PostStop), PostStop)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API. Wrapping an [[akka.actor.ActorContext]] as an [[ActorContext]].
|
|
||||||
*/
|
|
||||||
private[typed] class ActorContextAdapter[T](ctx: akka.actor.ActorContext) extends ActorContext[T] {
|
|
||||||
import Ops._
|
|
||||||
def self = ActorRef(ctx.self)
|
|
||||||
def props = Props(ctx.props)
|
|
||||||
val system = ActorSystem(ctx.system)
|
|
||||||
def children = ctx.children.map(ActorRef(_))
|
|
||||||
def child(name: String) = ctx.child(name).map(ActorRef(_))
|
|
||||||
def spawnAnonymous[U](props: Props[U]) = ctx.spawn(props)
|
|
||||||
def spawn[U](props: Props[U], name: String) = ctx.spawn(props, name)
|
|
||||||
def actorOf(props: a.Props) = ctx.actorOf(props)
|
|
||||||
def actorOf(props: a.Props, name: String) = ctx.actorOf(props, name)
|
|
||||||
def stop(child: ActorRef[Nothing]) =
|
|
||||||
child.untypedRef match {
|
|
||||||
case f: akka.actor.FunctionRef ⇒
|
|
||||||
val cell = ctx.asInstanceOf[akka.actor.ActorCell]
|
|
||||||
cell.removeFunctionRef(f)
|
|
||||||
case _ ⇒
|
|
||||||
ctx.child(child.path.name) match {
|
|
||||||
case Some(ref) if ref == child.untypedRef ⇒
|
|
||||||
ctx.stop(child.untypedRef)
|
|
||||||
true
|
|
||||||
case _ ⇒
|
|
||||||
false // none of our business
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def watch[U](other: ActorRef[U]) = { ctx.watch(other.untypedRef); other }
|
|
||||||
def watch(other: a.ActorRef) = { ctx.watch(other); other }
|
|
||||||
def unwatch[U](other: ActorRef[U]) = { ctx.unwatch(other.untypedRef); other }
|
|
||||||
def unwatch(other: a.ActorRef) = { ctx.unwatch(other); other }
|
|
||||||
def setReceiveTimeout(d: Duration) = ctx.setReceiveTimeout(d)
|
|
||||||
def executionContext: ExecutionContextExecutor = ctx.dispatcher
|
|
||||||
def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
|
|
||||||
import ctx.dispatcher
|
|
||||||
ctx.system.scheduler.scheduleOnce(delay, target.untypedRef, msg)
|
|
||||||
}
|
|
||||||
def spawnAdapter[U](f: U ⇒ T) = {
|
|
||||||
val cell = ctx.asInstanceOf[akka.actor.ActorCell]
|
|
||||||
val ref = cell.addFunctionRef((_, msg) ⇒ ctx.self ! f(msg.asInstanceOf[U]))
|
|
||||||
ActorRef[U](ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API. A small Actor that translates between message protocols.
|
|
||||||
*/
|
|
||||||
private[typed] class MessageWrapper(f: Any ⇒ Any) extends akka.actor.Actor {
|
|
||||||
def receive = {
|
|
||||||
case msg ⇒ context.parent ! f(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,24 +10,23 @@ import akka.actor.Address
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import akka.actor.ActorRefProvider
|
import akka.actor.ActorRefProvider
|
||||||
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
|
||||||
object Inbox {
|
class Inbox[T](name: String) {
|
||||||
|
|
||||||
def sync[T](name: String): SyncInbox[T] = new SyncInbox(name)
|
|
||||||
|
|
||||||
class SyncInbox[T](name: String) {
|
|
||||||
private val q = new ConcurrentLinkedQueue[T]
|
private val q = new ConcurrentLinkedQueue[T]
|
||||||
private val r = new akka.actor.MinimalActorRef {
|
|
||||||
override def provider: ActorRefProvider = ???
|
val ref: ActorRef[T] = {
|
||||||
override val path: ActorPath = RootActorPath(Address("akka", "SyncInbox")) / name
|
val uid = ThreadLocalRandom.current().nextInt()
|
||||||
override def !(msg: Any)(implicit sender: akka.actor.ActorRef) = q.offer(msg.asInstanceOf[T])
|
val path = RootActorPath(Address("akka.typed.inbox", "anonymous")).child(name).withUid(uid)
|
||||||
|
new internal.FunctionRef[T](path, (msg, self) ⇒ q.add(msg), (self) ⇒ ())
|
||||||
}
|
}
|
||||||
|
|
||||||
val ref: ActorRef[T] = ActorRef(r)
|
|
||||||
def receiveMsg(): T = q.poll() match {
|
def receiveMsg(): T = q.poll() match {
|
||||||
case null ⇒ throw new NoSuchElementException(s"polling on an empty inbox: $name")
|
case null ⇒ throw new NoSuchElementException(s"polling on an empty inbox: $name")
|
||||||
case x ⇒ x
|
case x ⇒ x
|
||||||
}
|
}
|
||||||
|
|
||||||
def receiveAll(): immutable.Seq[T] = {
|
def receiveAll(): immutable.Seq[T] = {
|
||||||
@tailrec def rec(acc: List[T]): List[T] = q.poll() match {
|
@tailrec def rec(acc: List[T]): List[T] = q.poll() match {
|
||||||
case null ⇒ acc.reverse
|
case null ⇒ acc.reverse
|
||||||
|
|
@ -35,6 +34,10 @@ object Inbox {
|
||||||
}
|
}
|
||||||
rec(Nil)
|
rec(Nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
def hasMessages: Boolean = q.peek() != null
|
def hasMessages: Boolean = q.peek() != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Inbox {
|
||||||
|
def apply[T](name: String): Inbox[T] = new Inbox(name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
141
akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala
Normal file
141
akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envelope that is published on the eventStream for every message that is
|
||||||
|
* dropped due to overfull queues.
|
||||||
|
*/
|
||||||
|
final case class Dropped(msg: Any, recipient: ActorRef[Nothing])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception that an actor fails with if it does not handle a Terminated message.
|
||||||
|
*/
|
||||||
|
final case class DeathPactException(ref: ActorRef[Nothing]) extends RuntimeException(s"death pact with $ref was triggered")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envelope for dead letters.
|
||||||
|
*/
|
||||||
|
final case class DeadLetter(msg: Any)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System signals are notifications that are generated by the system and
|
||||||
|
* delivered to the Actor behavior in a reliable fashion (i.e. they are
|
||||||
|
* guaranteed to arrive in contrast to the at-most-once semantics of normal
|
||||||
|
* Actor messages).
|
||||||
|
*/
|
||||||
|
sealed trait Signal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle signal that is fired upon creation of the Actor. This will be the
|
||||||
|
* first message that the actor processes.
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
final case object PreStart extends Signal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle signal that is fired upon restart of the Actor before replacing
|
||||||
|
* the behavior with the fresh one (i.e. this signal is received within the
|
||||||
|
* behavior that failed).
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
final case object PreRestart extends Signal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle signal that is fired upon restart of the Actor after replacing
|
||||||
|
* the behavior with the fresh one (i.e. this signal is received within the
|
||||||
|
* fresh replacement behavior).
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
final case object PostRestart extends Signal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle signal that is fired after this actor and all its child actors
|
||||||
|
* (transitively) have terminated. The [[Terminated]] signal is only sent to
|
||||||
|
* registered watchers after this signal has been processed.
|
||||||
|
*
|
||||||
|
* <b>IMPORTANT NOTE:</b> if the actor terminated by switching to the
|
||||||
|
* `Stopped` behavior then this signal will be ignored (i.e. the
|
||||||
|
* Stopped behavior will do nothing in reaction to it).
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
final case object PostStop extends Signal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle signal that is fired when an Actor that was watched has terminated.
|
||||||
|
* Watching is performed by invoking the
|
||||||
|
* [[akka.typed.ActorContext]] `watch` method. The DeathWatch service is
|
||||||
|
* idempotent, meaning that registering twice has the same effect as registering
|
||||||
|
* once. Registration does not need to happen before the Actor terminates, a
|
||||||
|
* notification is guaranteed to arrive after both registration and termination
|
||||||
|
* have occurred. Termination of a remote Actor can also be effected by declaring
|
||||||
|
* the Actor’s home system as failed (e.g. as a result of being unreachable).
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
final case class Terminated(ref: ActorRef[Nothing])(failed: Throwable) extends Signal {
|
||||||
|
def wasFailed: Boolean = failed ne null
|
||||||
|
def failure: Throwable = failed
|
||||||
|
def failureOption: Option[Throwable] = Option(failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME correct this documentation when the Restarter behavior has been implemented
|
||||||
|
*
|
||||||
|
* The parent of an actor decides upon the fate of a failed child actor by
|
||||||
|
* encapsulating its next behavior in one of the four wrappers defined within
|
||||||
|
* this class.
|
||||||
|
*
|
||||||
|
* Failure responses have an associated precedence that ranks them, which is in
|
||||||
|
* descending importance:
|
||||||
|
*
|
||||||
|
* - Escalate
|
||||||
|
* - Stop
|
||||||
|
* - Restart
|
||||||
|
* - Resume
|
||||||
|
*/
|
||||||
|
object Failed {
|
||||||
|
|
||||||
|
sealed trait Decision
|
||||||
|
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
case object NoFailureResponse extends Decision
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resuming the child actor means that the result of processing the message
|
||||||
|
* on which it failed is just ignored, the previous state will be used to
|
||||||
|
* process the next message. The message that triggered the failure will not
|
||||||
|
* be processed again.
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
case object Resume extends Decision
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restarting the child actor means resetting its behavior to the initial
|
||||||
|
* one that was provided during its creation (i.e. the one which was passed
|
||||||
|
* into the [[Props]] constructor). The previously failed behavior will
|
||||||
|
* receive a [[PreRestart]] signal before this happens and the replacement
|
||||||
|
* behavior will receive a [[PostRestart]] signal afterwards.
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
case object Restart extends Decision
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stopping the child actor will free its resources and eventually
|
||||||
|
* (asynchronously) unregister its name from the parent. Completion of this
|
||||||
|
* process can be observed by watching the child actor and reacting to its
|
||||||
|
* [[Terminated]] signal.
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
case object Stop extends Decision
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default response to a failure in a child actor is to escalate the
|
||||||
|
* failure, entailing that the parent actor fails as well. This is equivalent
|
||||||
|
* to an exception unwinding the call stack, but it applies to the supervision
|
||||||
|
* hierarchy instead.
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
case object Escalate extends Decision
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2014-2016 Lightbend Inc. <http://www.lightbend.com>
|
|
||||||
*/
|
|
||||||
package akka.typed
|
|
||||||
|
|
||||||
import language.implicitConversions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import the contents of this object to retrofit the typed APIs onto the
|
|
||||||
* untyped [[akka.actor.ActorSystem]], [[akka.actor.ActorContext]] and
|
|
||||||
* [[akka.actor.ActorRef]].
|
|
||||||
*/
|
|
||||||
object Ops {
|
|
||||||
|
|
||||||
implicit class ActorSystemOps(val sys: akka.actor.ActorSystem) extends AnyVal {
|
|
||||||
def spawn[T](props: Props[T]): ActorRef[T] =
|
|
||||||
ActorRef(sys.actorOf(Props.untyped(props)))
|
|
||||||
def spawn[T](props: Props[T], name: String): ActorRef[T] =
|
|
||||||
ActorRef(sys.actorOf(Props.untyped(props), name))
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit class ActorContextOps(val ctx: akka.actor.ActorContext) extends AnyVal {
|
|
||||||
def spawn[T](props: Props[T]): ActorRef[T] =
|
|
||||||
ActorRef(ctx.actorOf(Props.untyped(props)))
|
|
||||||
def spawn[T](props: Props[T], name: String): ActorRef[T] =
|
|
||||||
ActorRef(ctx.actorOf(Props.untyped(props), name))
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit def actorRefAdapter(ref: akka.actor.ActorRef): ActorRef[Any] = ActorRef(ref)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -3,18 +3,23 @@
|
||||||
*/
|
*/
|
||||||
package akka.typed
|
package akka.typed
|
||||||
|
|
||||||
import akka.actor.Deploy
|
import java.util.concurrent.Executor
|
||||||
import akka.routing.RouterConfig
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
|
sealed trait DispatcherSelector
|
||||||
|
case object DispatcherDefault extends DispatcherSelector
|
||||||
|
final case class DispatcherFromConfig(path: String) extends DispatcherSelector
|
||||||
|
final case class DispatcherFromExecutor(executor: Executor) extends DispatcherSelector
|
||||||
|
final case class DispatcherFromExecutionContext(ec: ExecutionContext) extends DispatcherSelector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props describe how to dress up a [[Behavior]] so that it can become an Actor.
|
* Props describe how to dress up a [[Behavior]] so that it can become an Actor.
|
||||||
*/
|
*/
|
||||||
@SerialVersionUID(1L)
|
final case class Props[T](creator: () ⇒ Behavior[T], dispatcher: DispatcherSelector, mailboxCapacity: Int) {
|
||||||
final case class Props[T](creator: () ⇒ Behavior[T], deploy: Deploy) {
|
def withDispatcher(configPath: String) = copy(dispatcher = DispatcherFromConfig(configPath))
|
||||||
def withDispatcher(d: String) = copy(deploy = deploy.copy(dispatcher = d))
|
def withDispatcher(executor: Executor) = copy(dispatcher = DispatcherFromExecutor(executor))
|
||||||
def withMailbox(m: String) = copy(deploy = deploy.copy(mailbox = m))
|
def withDispatcher(ec: ExecutionContext) = copy(dispatcher = DispatcherFromExecutionContext(ec))
|
||||||
def withRouter(r: RouterConfig) = copy(deploy = deploy.copy(routerConfig = r))
|
def withQueueSize(size: Int) = copy(mailboxCapacity = size)
|
||||||
def withDeploy(d: Deploy) = copy(deploy = d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,7 +32,7 @@ object Props {
|
||||||
* FIXME: investigate the pros and cons of making this take an explicit
|
* FIXME: investigate the pros and cons of making this take an explicit
|
||||||
* function instead of a by-name argument
|
* function instead of a by-name argument
|
||||||
*/
|
*/
|
||||||
def apply[T](block: ⇒ Behavior[T]): Props[T] = Props(() ⇒ block, akka.actor.Props.defaultDeploy)
|
def apply[T](block: ⇒ Behavior[T]): Props[T] = Props(() ⇒ block, DispatcherDefault, Int.MaxValue)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for a Behavior that just ignores all messages.
|
* Props for a Behavior that just ignores all messages.
|
||||||
|
|
@ -35,21 +40,4 @@ object Props {
|
||||||
def empty[T]: Props[T] = _empty.asInstanceOf[Props[T]]
|
def empty[T]: Props[T] = _empty.asInstanceOf[Props[T]]
|
||||||
private val _empty: Props[Any] = Props(ScalaDSL.Static[Any] { case _ ⇒ ScalaDSL.Unhandled })
|
private val _empty: Props[Any] = Props(ScalaDSL.Static[Any] { case _ ⇒ ScalaDSL.Unhandled })
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API.
|
|
||||||
*/
|
|
||||||
private[typed] def untyped[T](p: Props[T]): akka.actor.Props =
|
|
||||||
new akka.actor.Props(p.deploy, classOf[ActorAdapter[_]], p.creator :: Nil)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API.
|
|
||||||
*/
|
|
||||||
private[typed] def apply[T](p: akka.actor.Props): Props[T] = {
|
|
||||||
assert(p.clazz == classOf[ActorAdapter[_]], "typed.Actor must have typed.Props")
|
|
||||||
p.args match {
|
|
||||||
case (creator: Function0[_]) :: Nil ⇒
|
|
||||||
Props(creator.asInstanceOf[Function0[Behavior[T]]], p.deploy)
|
|
||||||
case _ ⇒ throw new AssertionError("typed.Actor args must be right")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ object ScalaDSL {
|
||||||
private def postProcess(ctx: ActorContext[U], behv: Behavior[T]): Behavior[U] =
|
private def postProcess(ctx: ActorContext[U], behv: Behavior[T]): Behavior[U] =
|
||||||
if (isUnhandled(behv)) Unhandled
|
if (isUnhandled(behv)) Unhandled
|
||||||
else if (isAlive(behv)) {
|
else if (isAlive(behv)) {
|
||||||
val next = canonicalize(ctx.asInstanceOf[ActorContext[T]], behv, behavior)
|
val next = canonicalize(behv, behavior)
|
||||||
if (next eq behavior) Same else Widened(next, matcher)
|
if (next eq behavior) Same else Widened(next, matcher)
|
||||||
} else Stopped
|
} else Stopped
|
||||||
|
|
||||||
|
|
@ -134,13 +134,13 @@ object ScalaDSL {
|
||||||
final case class Full[T](behavior: PartialFunction[MessageOrSignal[T], Behavior[T]]) extends Behavior[T] {
|
final case class Full[T](behavior: PartialFunction[MessageOrSignal[T], Behavior[T]]) extends Behavior[T] {
|
||||||
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
|
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
|
||||||
lazy val fallback: (MessageOrSignal[T]) ⇒ Behavior[T] = {
|
lazy val fallback: (MessageOrSignal[T]) ⇒ Behavior[T] = {
|
||||||
case Sig(context, PreRestart(_)) ⇒
|
case Sig(context, PreRestart) ⇒
|
||||||
context.children foreach { child ⇒
|
context.children foreach { child ⇒
|
||||||
context.unwatch(child.untypedRef)
|
context.unwatch[Nothing](child)
|
||||||
context.stop(child)
|
context.stop(child)
|
||||||
}
|
}
|
||||||
behavior.applyOrElse(Sig(context, PostStop), fallback)
|
behavior.applyOrElse(Sig(context, PostStop), fallback)
|
||||||
case Sig(context, PostRestart(_)) ⇒ behavior.applyOrElse(Sig(context, PreStart), fallback)
|
case Sig(context, PostRestart) ⇒ behavior.applyOrElse(Sig(context, PreStart), fallback)
|
||||||
case _ ⇒ Unhandled
|
case _ ⇒ Unhandled
|
||||||
}
|
}
|
||||||
behavior.applyOrElse(Sig(ctx, msg), fallback)
|
behavior.applyOrElse(Sig(ctx, msg), fallback)
|
||||||
|
|
@ -253,11 +253,11 @@ object ScalaDSL {
|
||||||
* sides of [[And]] and [[Or]] combinators.
|
* sides of [[And]] and [[Or]] combinators.
|
||||||
*/
|
*/
|
||||||
final case class SynchronousSelf[T](f: ActorRef[T] ⇒ Behavior[T]) extends Behavior[T] {
|
final case class SynchronousSelf[T](f: ActorRef[T] ⇒ Behavior[T]) extends Behavior[T] {
|
||||||
private val inbox = Inbox.sync[T]("syncbox")
|
private val inbox = Inbox[T]("synchronousSelf")
|
||||||
private var _behavior = f(inbox.ref)
|
private var _behavior = f(inbox.ref)
|
||||||
private def behavior = _behavior
|
private def behavior = _behavior
|
||||||
private def setBehavior(ctx: ActorContext[T], b: Behavior[T]): Unit =
|
private def setBehavior(ctx: ActorContext[T], b: Behavior[T]): Unit =
|
||||||
_behavior = canonicalize(ctx, b, _behavior)
|
_behavior = canonicalize(b, _behavior)
|
||||||
|
|
||||||
// FIXME should we protect against infinite loops?
|
// FIXME should we protect against infinite loops?
|
||||||
@tailrec private def run(ctx: ActorContext[T], next: Behavior[T]): Behavior[T] = {
|
@tailrec private def run(ctx: ActorContext[T], next: Behavior[T]): Behavior[T] = {
|
||||||
|
|
@ -290,8 +290,8 @@ object ScalaDSL {
|
||||||
val r = right.management(ctx, msg)
|
val r = right.management(ctx, msg)
|
||||||
if (isUnhandled(l) && isUnhandled(r)) Unhandled
|
if (isUnhandled(l) && isUnhandled(r)) Unhandled
|
||||||
else {
|
else {
|
||||||
val nextLeft = canonicalize(ctx, l, left)
|
val nextLeft = canonicalize(l, left)
|
||||||
val nextRight = canonicalize(ctx, r, right)
|
val nextRight = canonicalize(r, right)
|
||||||
val leftAlive = isAlive(nextLeft)
|
val leftAlive = isAlive(nextLeft)
|
||||||
val rightAlive = isAlive(nextRight)
|
val rightAlive = isAlive(nextRight)
|
||||||
|
|
||||||
|
|
@ -307,8 +307,8 @@ object ScalaDSL {
|
||||||
val r = right.message(ctx, msg)
|
val r = right.message(ctx, msg)
|
||||||
if (isUnhandled(l) && isUnhandled(r)) Unhandled
|
if (isUnhandled(l) && isUnhandled(r)) Unhandled
|
||||||
else {
|
else {
|
||||||
val nextLeft = canonicalize(ctx, l, left)
|
val nextLeft = canonicalize(l, left)
|
||||||
val nextRight = canonicalize(ctx, r, right)
|
val nextRight = canonicalize(r, right)
|
||||||
val leftAlive = isAlive(nextLeft)
|
val leftAlive = isAlive(nextLeft)
|
||||||
val rightAlive = isAlive(nextRight)
|
val rightAlive = isAlive(nextRight)
|
||||||
|
|
||||||
|
|
@ -337,11 +337,11 @@ object ScalaDSL {
|
||||||
val r = right.management(ctx, msg)
|
val r = right.management(ctx, msg)
|
||||||
if (isUnhandled(r)) Unhandled
|
if (isUnhandled(r)) Unhandled
|
||||||
else {
|
else {
|
||||||
val nr = canonicalize(ctx, r, right)
|
val nr = canonicalize(r, right)
|
||||||
if (isAlive(nr)) Or(left, nr) else left
|
if (isAlive(nr)) Or(left, nr) else left
|
||||||
}
|
}
|
||||||
case nl ⇒
|
case nl ⇒
|
||||||
val next = canonicalize(ctx, nl, left)
|
val next = canonicalize(nl, left)
|
||||||
if (isAlive(next)) Or(next, right) else right
|
if (isAlive(next)) Or(next, right) else right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,11 +351,11 @@ object ScalaDSL {
|
||||||
val r = right.message(ctx, msg)
|
val r = right.message(ctx, msg)
|
||||||
if (isUnhandled(r)) Unhandled
|
if (isUnhandled(r)) Unhandled
|
||||||
else {
|
else {
|
||||||
val nr = canonicalize(ctx, r, right)
|
val nr = canonicalize(r, right)
|
||||||
if (isAlive(nr)) Or(left, nr) else left
|
if (isAlive(nr)) Or(left, nr) else left
|
||||||
}
|
}
|
||||||
case nl ⇒
|
case nl ⇒
|
||||||
val next = canonicalize(ctx, nl, left)
|
val next = canonicalize(nl, left)
|
||||||
if (isAlive(next)) Or(next, right) else right
|
if (isAlive(next)) Or(next, right) else right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,10 +394,10 @@ object ScalaDSL {
|
||||||
FullTotal {
|
FullTotal {
|
||||||
case Sig(ctx, signal) ⇒
|
case Sig(ctx, signal) ⇒
|
||||||
val behv = behavior(ctx.self)
|
val behv = behavior(ctx.self)
|
||||||
canonicalize(ctx, behv.management(ctx, signal), behv)
|
canonicalize(behv.management(ctx, signal), behv)
|
||||||
case Msg(ctx, msg) ⇒
|
case Msg(ctx, msg) ⇒
|
||||||
val behv = behavior(ctx.self)
|
val behv = behavior(ctx.self)
|
||||||
canonicalize(ctx, behv.message(ctx, msg), behv)
|
canonicalize(behv.message(ctx, msg), behv)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -418,10 +418,10 @@ object ScalaDSL {
|
||||||
FullTotal {
|
FullTotal {
|
||||||
case Sig(ctx, signal) ⇒
|
case Sig(ctx, signal) ⇒
|
||||||
val behv = behavior(ctx)
|
val behv = behavior(ctx)
|
||||||
canonicalize(ctx, behv.management(ctx, signal), behv)
|
canonicalize(behv.management(ctx, signal), behv)
|
||||||
case Msg(ctx, msg) ⇒
|
case Msg(ctx, msg) ⇒
|
||||||
val behv = behavior(ctx)
|
val behv = behavior(ctx)
|
||||||
canonicalize(ctx, behv.message(ctx, msg), behv)
|
canonicalize(behv.message(ctx, msg), behv)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
|
||||||
|
private[typed] class ActorAdapter[T](_initialBehavior: () ⇒ Behavior[T]) extends a.Actor {
|
||||||
|
import Behavior._
|
||||||
|
|
||||||
|
var behavior: Behavior[T] = _
|
||||||
|
|
||||||
|
{
|
||||||
|
behavior = canonicalize(_initialBehavior(), behavior)
|
||||||
|
if (behavior == null) throw new IllegalStateException("initial behavior cannot be `same` or `unhandled`")
|
||||||
|
if (!isAlive(behavior)) context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ctx = new ActorContextAdapter[T](context)
|
||||||
|
|
||||||
|
var failures: Map[a.ActorRef, Throwable] = Map.empty
|
||||||
|
|
||||||
|
def receive = {
|
||||||
|
case a.Terminated(ref) ⇒
|
||||||
|
val msg =
|
||||||
|
if (failures contains ref) {
|
||||||
|
val ex = failures(ref)
|
||||||
|
failures -= ref
|
||||||
|
Terminated(ActorRefAdapter(ref))(ex)
|
||||||
|
} else Terminated(ActorRefAdapter(ref))(null)
|
||||||
|
next(behavior.management(ctx, msg), msg)
|
||||||
|
case a.ReceiveTimeout ⇒
|
||||||
|
next(behavior.message(ctx, ctx.receiveTimeoutMsg), ctx.receiveTimeoutMsg)
|
||||||
|
case msg: T @unchecked ⇒
|
||||||
|
next(behavior.message(ctx, msg), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def next(b: Behavior[T], msg: Any): Unit = {
|
||||||
|
if (isUnhandled(b)) unhandled(msg)
|
||||||
|
behavior = canonicalize(b, behavior)
|
||||||
|
if (!isAlive(behavior)) context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def unhandled(msg: Any): Unit = msg match {
|
||||||
|
case Terminated(ref) ⇒ throw new a.DeathPactException(toUntyped(ref))
|
||||||
|
case other ⇒ super.unhandled(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val supervisorStrategy = a.OneForOneStrategy() {
|
||||||
|
case ex ⇒
|
||||||
|
val ref = sender()
|
||||||
|
if (context.asInstanceOf[a.ActorCell].isWatching(ref)) failures = failures.updated(ref, ex)
|
||||||
|
a.SupervisorStrategy.Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
override def preStart(): Unit =
|
||||||
|
next(behavior.management(ctx, PreStart), PreStart)
|
||||||
|
override def preRestart(reason: Throwable, message: Option[Any]): Unit =
|
||||||
|
next(behavior.management(ctx, PreRestart), PreRestart)
|
||||||
|
override def postRestart(reason: Throwable): Unit =
|
||||||
|
next(behavior.management(ctx, PostRestart), PostRestart)
|
||||||
|
override def postStop(): Unit =
|
||||||
|
next(behavior.management(ctx, PostStop), PostStop)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.ExecutionContextExecutor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API. Wrapping an [[akka.actor.ActorContext]] as an [[ActorContext]].
|
||||||
|
*/
|
||||||
|
private[typed] class ActorContextAdapter[T](ctx: a.ActorContext) extends ActorContext[T] {
|
||||||
|
|
||||||
|
override def self = ActorRefAdapter(ctx.self)
|
||||||
|
override def props = PropsAdapter(ctx.props)
|
||||||
|
override val system = ActorSystemAdapter(ctx.system)
|
||||||
|
override def children = ctx.children.map(ActorRefAdapter(_))
|
||||||
|
override def child(name: String) = ctx.child(name).map(ActorRefAdapter(_))
|
||||||
|
override def spawnAnonymous[U](props: Props[U]) = ctx.spawnAnonymous(props)
|
||||||
|
override def spawn[U](props: Props[U], name: String) = ctx.spawn(props, name)
|
||||||
|
override def stop(child: ActorRef[Nothing]) =
|
||||||
|
toUntyped(child) match {
|
||||||
|
case f: akka.actor.FunctionRef ⇒
|
||||||
|
val cell = ctx.asInstanceOf[akka.actor.ActorCell]
|
||||||
|
cell.removeFunctionRef(f)
|
||||||
|
case untyped ⇒
|
||||||
|
ctx.child(child.path.name) match {
|
||||||
|
case Some(`untyped`) ⇒
|
||||||
|
ctx.stop(untyped)
|
||||||
|
true
|
||||||
|
case _ ⇒
|
||||||
|
false // none of our business
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override def watch[U](other: ActorRef[U]) = { ctx.watch(toUntyped(other)); other }
|
||||||
|
override def unwatch[U](other: ActorRef[U]) = { ctx.unwatch(toUntyped(other)); other }
|
||||||
|
var receiveTimeoutMsg: T = null.asInstanceOf[T]
|
||||||
|
override def setReceiveTimeout(d: FiniteDuration, msg: T) = {
|
||||||
|
receiveTimeoutMsg = msg
|
||||||
|
ctx.setReceiveTimeout(d)
|
||||||
|
}
|
||||||
|
override def cancelReceiveTimeout(): Unit = {
|
||||||
|
receiveTimeoutMsg = null.asInstanceOf[T]
|
||||||
|
ctx.setReceiveTimeout(Duration.Undefined)
|
||||||
|
}
|
||||||
|
override def executionContext: ExecutionContextExecutor = ctx.dispatcher
|
||||||
|
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
|
||||||
|
import ctx.dispatcher
|
||||||
|
ctx.system.scheduler.scheduleOnce(delay, toUntyped(target), msg)
|
||||||
|
}
|
||||||
|
override def spawnAdapter[U](f: U ⇒ T): ActorRef[U] = {
|
||||||
|
val cell = ctx.asInstanceOf[akka.actor.ActorCell]
|
||||||
|
val ref = cell.addFunctionRef((_, msg) ⇒ ctx.self ! f(msg.asInstanceOf[U]))
|
||||||
|
ActorRefAdapter[U](ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
|
||||||
|
private[typed] class ActorRefAdapter[-T](val untyped: a.InternalActorRef)
|
||||||
|
extends ActorRef[T](untyped.path) with internal.ActorRefImpl[T] {
|
||||||
|
|
||||||
|
override def tell(msg: T): Unit = untyped ! msg
|
||||||
|
override def isLocal: Boolean = true
|
||||||
|
override def sendSystem(signal: internal.SystemMessage): Unit = sendSystemMessage(untyped, signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private[typed] object ActorRefAdapter {
|
||||||
|
def apply[T](untyped: a.ActorRef): ActorRef[T] = new ActorRefAdapter(untyped.asInstanceOf[a.InternalActorRef])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
import akka.dispatch.sysmsg
|
||||||
|
import scala.concurrent.ExecutionContextExecutor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight wrapper for presenting an untyped ActorSystem to a Behavior (via the context).
|
||||||
|
* Therefore it does not have a lot of vals, only the whenTerminated Future is cached after
|
||||||
|
* its transformation because redoing that every time will add extra objects that persis for
|
||||||
|
* a longer time; in all other cases the wrapper will just be spawned for a single call in
|
||||||
|
* most circumstances.
|
||||||
|
*/
|
||||||
|
private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl)
|
||||||
|
extends ActorRef[T](a.RootActorPath(a.Address("akka", untyped.name)) / "user")
|
||||||
|
with ActorSystem[T] with internal.ActorRefImpl[T] {
|
||||||
|
|
||||||
|
// Members declared in akka.typed.ActorRef
|
||||||
|
override def tell(msg: T): Unit = untyped.guardian ! msg
|
||||||
|
override def isLocal: Boolean = true
|
||||||
|
override def sendSystem(signal: internal.SystemMessage): Unit = sendSystemMessage(untyped.guardian, signal)
|
||||||
|
|
||||||
|
// Members declared in akka.typed.ActorSystem
|
||||||
|
override def deadLetters[U]: ActorRef[U] = ActorRefAdapter(untyped.deadLetters)
|
||||||
|
override def dispatchers: Dispatchers = new Dispatchers {
|
||||||
|
override def lookup(selector: DispatcherSelector): ExecutionContextExecutor =
|
||||||
|
selector match {
|
||||||
|
case DispatcherDefault ⇒ untyped.dispatcher
|
||||||
|
case DispatcherFromConfig(str) ⇒ untyped.dispatchers.lookup(str)
|
||||||
|
case DispatcherFromExecutionContext(_) ⇒ throw new UnsupportedOperationException("cannot use DispatcherFromExecutionContext with ActorSystemAdapter")
|
||||||
|
case DispatcherFromExecutor(_) ⇒ throw new UnsupportedOperationException("cannot use DispatcherFromExecutor with ActorSystemAdapter")
|
||||||
|
}
|
||||||
|
override def shutdown(): Unit = () // there was no shutdown in untyped Akka
|
||||||
|
}
|
||||||
|
override def dynamicAccess: a.DynamicAccess = untyped.dynamicAccess
|
||||||
|
override def eventStream: akka.event.EventStream = untyped.eventStream
|
||||||
|
implicit override def executionContext: scala.concurrent.ExecutionContextExecutor = untyped.dispatcher
|
||||||
|
override def log: akka.event.LoggingAdapter = untyped.log
|
||||||
|
override def logConfiguration(): Unit = untyped.logConfiguration()
|
||||||
|
override def logFilter: akka.event.LoggingFilter = untyped.logFilter
|
||||||
|
override def name: String = untyped.name
|
||||||
|
override def scheduler: akka.actor.Scheduler = untyped.scheduler
|
||||||
|
override def settings: akka.actor.ActorSystem.Settings = untyped.settings
|
||||||
|
override def startTime: Long = untyped.startTime
|
||||||
|
override def threadFactory: java.util.concurrent.ThreadFactory = untyped.threadFactory
|
||||||
|
override def uptime: Long = untyped.uptime
|
||||||
|
override def printTree: String = untyped.printTree
|
||||||
|
|
||||||
|
import akka.dispatch.ExecutionContexts.sameThreadExecutionContext
|
||||||
|
|
||||||
|
override def terminate(): scala.concurrent.Future[akka.typed.Terminated] =
|
||||||
|
untyped.terminate().map(t ⇒ Terminated(ActorRefAdapter(t.actor))(null))(sameThreadExecutionContext)
|
||||||
|
override lazy val whenTerminated: scala.concurrent.Future[akka.typed.Terminated] =
|
||||||
|
untyped.whenTerminated.map(t ⇒ Terminated(ActorRefAdapter(t.actor))(null))(sameThreadExecutionContext)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private[typed] object ActorSystemAdapter {
|
||||||
|
def apply(untyped: a.ActorSystem): ActorSystem[Nothing] = new ActorSystemAdapter(untyped.asInstanceOf[a.ActorSystemImpl])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
|
||||||
|
private[typed] object PropsAdapter {
|
||||||
|
|
||||||
|
// FIXME dispatcher and queue size
|
||||||
|
def apply(p: Props[_]): a.Props = new a.Props(a.Deploy(), classOf[ActorAdapter[_]], (p.creator: AnyRef) :: Nil)
|
||||||
|
|
||||||
|
def apply[T](p: a.Props): Props[T] = {
|
||||||
|
assert(p.clazz == classOf[ActorAdapter[_]], "typed.Actor must have typed.Props")
|
||||||
|
p.args match {
|
||||||
|
case (creator: Function0[_]) :: Nil ⇒
|
||||||
|
// FIXME queue size
|
||||||
|
Props(creator.asInstanceOf[() ⇒ Behavior[T]], DispatcherFromConfig(p.deploy.dispatcher), Int.MaxValue)
|
||||||
|
case _ ⇒ throw new AssertionError("typed.Actor args must be right")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
akka-typed/src/main/scala/akka/typed/adapter/package.scala
Normal file
43
akka-typed/src/main/scala/akka/typed/adapter/package.scala
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
|
||||||
|
package object adapter {
|
||||||
|
|
||||||
|
import language.implicitConversions
|
||||||
|
import akka.dispatch.sysmsg
|
||||||
|
|
||||||
|
implicit class ActorSystemOps(val sys: akka.actor.ActorSystem) extends AnyVal {
|
||||||
|
def spawnAnonymous[T](props: Props[T]): ActorRef[T] =
|
||||||
|
ActorRefAdapter(sys.actorOf(PropsAdapter(props)))
|
||||||
|
def spawn[T](props: Props[T], name: String): ActorRef[T] =
|
||||||
|
ActorRefAdapter(sys.actorOf(PropsAdapter(props), name))
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit class ActorContextOps(val ctx: akka.actor.ActorContext) extends AnyVal {
|
||||||
|
def spawnAnonymous[T](props: Props[T]): ActorRef[T] =
|
||||||
|
ActorRefAdapter(ctx.actorOf(PropsAdapter(props)))
|
||||||
|
def spawn[T](props: Props[T], name: String): ActorRef[T] =
|
||||||
|
ActorRefAdapter(ctx.actorOf(PropsAdapter(props), name))
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def actorRefAdapter(ref: akka.actor.ActorRef): ActorRef[Any] = ActorRefAdapter(ref)
|
||||||
|
|
||||||
|
private[adapter] def toUntyped[U](ref: ActorRef[U]): akka.actor.InternalActorRef =
|
||||||
|
ref match {
|
||||||
|
case adapter: ActorRefAdapter[_] ⇒ adapter.untyped
|
||||||
|
case _ ⇒ throw new UnsupportedOperationException(s"only adapted untyped ActorRefs permissible ($ref of class ${ref.getClass})")
|
||||||
|
}
|
||||||
|
|
||||||
|
private[adapter] def sendSystemMessage(untyped: akka.actor.InternalActorRef, signal: internal.SystemMessage): Unit =
|
||||||
|
signal match {
|
||||||
|
case internal.Create() ⇒ throw new IllegalStateException("WAT? No, seriously.")
|
||||||
|
case internal.Terminate() ⇒ untyped.stop()
|
||||||
|
case internal.Watch(watchee, watcher) ⇒ untyped.sendSystemMessage(sysmsg.Watch(toUntyped(watchee), toUntyped(watcher)))
|
||||||
|
case internal.Unwatch(watchee, watcher) ⇒ untyped.sendSystemMessage(sysmsg.Unwatch(toUntyped(watchee), toUntyped(watcher)))
|
||||||
|
case internal.DeathWatchNotification(ref, cause) ⇒ untyped.sendSystemMessage(sysmsg.DeathWatchNotification(toUntyped(ref), true, false))
|
||||||
|
case internal.NoMessage ⇒ // just to suppress the warning
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
435
akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala
Normal file
435
akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala
Normal file
|
|
@ -0,0 +1,435 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import akka.actor.InvalidActorNameException
|
||||||
|
import akka.util.Helpers
|
||||||
|
import scala.concurrent.duration.{ Duration, FiniteDuration }
|
||||||
|
import akka.dispatch.ExecutionContexts
|
||||||
|
import scala.concurrent.ExecutionContextExecutor
|
||||||
|
import akka.actor.Cancellable
|
||||||
|
import akka.util.Unsafe.{ instance ⇒ unsafe }
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import java.util.Queue
|
||||||
|
import scala.annotation.{ tailrec, switch }
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import scala.util.control.Exception.Catcher
|
||||||
|
import akka.event.Logging.Error
|
||||||
|
import akka.event.Logging
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
object ActorCell {
|
||||||
|
/*
|
||||||
|
* Description of the _status field bit structure:
|
||||||
|
*
|
||||||
|
* bit 0-29: activation count (number of (system)messages)
|
||||||
|
* bit 30: terminating (or terminated)
|
||||||
|
* bit 31: terminated
|
||||||
|
*
|
||||||
|
* Activation count is a bit special:
|
||||||
|
* 0 means inactive
|
||||||
|
* 1 means active without normal messages (i.e. only system messages)
|
||||||
|
* N means active with N-1 normal messages (plus possibly system messages)
|
||||||
|
*/
|
||||||
|
final val terminatingShift = 30
|
||||||
|
|
||||||
|
final val activationMask = (1 << terminatingShift) - 1
|
||||||
|
// ensure that if all processors enqueue “the last message” concurrently, there is still no overflow
|
||||||
|
val maxActivations = activationMask - Runtime.getRuntime.availableProcessors - 1
|
||||||
|
|
||||||
|
final val terminatingBit = 1 << terminatingShift
|
||||||
|
final val terminatedBit = 1 << 31
|
||||||
|
|
||||||
|
def isTerminating(status: Int): Boolean = (status & terminatingBit) != 0
|
||||||
|
def isTerminated(status: Int): Boolean = status < 0
|
||||||
|
def isActive(status: Int): Boolean = (status & ~activationMask) == 0
|
||||||
|
|
||||||
|
def activations(status: Int): Int = status & activationMask
|
||||||
|
def messageCount(status: Int): Int = Math.max(0, activations(status) - 1)
|
||||||
|
|
||||||
|
val statusOffset = unsafe.objectFieldOffset(classOf[ActorCell[_]].getDeclaredField("_status"))
|
||||||
|
val systemQueueOffset = unsafe.objectFieldOffset(classOf[ActorCell[_]].getDeclaredField("_systemQueue"))
|
||||||
|
|
||||||
|
final val DefaultState = 0
|
||||||
|
final val SuspendedState = 1
|
||||||
|
final val SuspendedWaitForChildrenState = 2
|
||||||
|
|
||||||
|
final val Debug = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
private[typed] class ActorCell[T](
|
||||||
|
override val system: ActorSystem[Nothing],
|
||||||
|
override val props: Props[T],
|
||||||
|
val parent: ActorRefImpl[Nothing])
|
||||||
|
extends ActorContext[T] with Runnable with SupervisionMechanics[T] with DeathWatch[T] {
|
||||||
|
import ActorCell._
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of the ActorContext trait.
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected var childrenMap = Map.empty[String, ActorRefImpl[Nothing]]
|
||||||
|
protected var terminatingMap = Map.empty[String, ActorRefImpl[Nothing]]
|
||||||
|
override def children: Iterable[ActorRef[Nothing]] = childrenMap.values
|
||||||
|
override def child(name: String): Option[ActorRef[Nothing]] = childrenMap.get(name)
|
||||||
|
protected def removeChild(actor: ActorRefImpl[Nothing]): Unit = {
|
||||||
|
val n = actor.path.name
|
||||||
|
childrenMap.get(n) match {
|
||||||
|
case Some(`actor`) ⇒ childrenMap -= n
|
||||||
|
case _ ⇒
|
||||||
|
terminatingMap.get(n) match {
|
||||||
|
case Some(`actor`) ⇒ terminatingMap -= n
|
||||||
|
case _ ⇒
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private[typed] def terminating: Iterable[ActorRef[Nothing]] = terminatingMap.values
|
||||||
|
|
||||||
|
private var _self: ActorRefImpl[T] = _
|
||||||
|
private[typed] def setSelf(ref: ActorRefImpl[T]): Unit = _self = ref
|
||||||
|
override def self: ActorRefImpl[T] = _self
|
||||||
|
|
||||||
|
protected def ctx: ActorContext[T] = this
|
||||||
|
|
||||||
|
override def spawn[U](props: Props[U], name: String): ActorRef[U] = {
|
||||||
|
if (childrenMap contains name) throw new InvalidActorNameException(s"actor name [$name] is not unique")
|
||||||
|
if (terminatingMap contains name) throw new InvalidActorNameException(s"actor name [$name] is not yet free")
|
||||||
|
val cell = new ActorCell[U](system, props, self)
|
||||||
|
val ref = new LocalActorRef[U](self.path / name, cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
childrenMap = childrenMap.updated(name, ref)
|
||||||
|
ref.sendSystem(Create())
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
|
||||||
|
private var nextName = 0L
|
||||||
|
override def spawnAnonymous[U](props: Props[U]): ActorRef[U] = {
|
||||||
|
val name = Helpers.base64(nextName)
|
||||||
|
nextName += 1
|
||||||
|
spawn(props, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def stop(child: ActorRef[Nothing]): Boolean = {
|
||||||
|
val name = child.path.name
|
||||||
|
childrenMap.get(name) match {
|
||||||
|
case None ⇒ false
|
||||||
|
case Some(ref) if ref != child ⇒ false
|
||||||
|
case Some(ref) ⇒
|
||||||
|
ref.sendSystem(Terminate())
|
||||||
|
childrenMap -= name
|
||||||
|
terminatingMap = terminatingMap.updated(name, ref)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def stopAll(): Unit = {
|
||||||
|
childrenMap.valuesIterator.foreach { ref ⇒
|
||||||
|
ref.sendSystem(Terminate())
|
||||||
|
terminatingMap = terminatingMap.updated(ref.path.name, ref)
|
||||||
|
}
|
||||||
|
childrenMap = Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): Cancellable =
|
||||||
|
system.scheduler.scheduleOnce(delay)(target ! msg)(ExecutionContexts.sameThreadExecutionContext)
|
||||||
|
|
||||||
|
override val executionContext: ExecutionContextExecutor = system.dispatchers.lookup(props.dispatcher)
|
||||||
|
|
||||||
|
override def spawnAdapter[U](f: U ⇒ T): ActorRef[U] = {
|
||||||
|
val name = Helpers.base64(nextName, new java.lang.StringBuilder("$!"))
|
||||||
|
nextName += 1
|
||||||
|
val ref = new FunctionRef[U](
|
||||||
|
self.path / name,
|
||||||
|
(msg, _) ⇒ send(f(msg)),
|
||||||
|
(self) ⇒ sendSystem(DeathWatchNotification(self, null)))
|
||||||
|
childrenMap = childrenMap.updated(name, ref)
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
|
||||||
|
private[this] var receiveTimeout: (FiniteDuration, T) = null
|
||||||
|
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
|
||||||
|
if (Debug) println(s"$self setting receive timeout of $d, msg $msg")
|
||||||
|
receiveTimeout = (d, msg)
|
||||||
|
}
|
||||||
|
override def cancelReceiveTimeout(): Unit = {
|
||||||
|
if (Debug) println(s"$self canceling receive timeout")
|
||||||
|
receiveTimeout = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of the invocation mechanics.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// see comment in companion object for details
|
||||||
|
@volatile private[this] var _status: Int = 0
|
||||||
|
protected[typed] def getStatus: Int = _status
|
||||||
|
private[this] val queue: Queue[T] = new ConcurrentLinkedQueue[T]
|
||||||
|
private[typed] def peekMessage: T = queue.peek()
|
||||||
|
private[this] val maxQueue: Int = Math.min(props.mailboxCapacity, maxActivations)
|
||||||
|
@volatile private[this] var _systemQueue: LatestFirstSystemMessageList = SystemMessageList.LNil
|
||||||
|
|
||||||
|
protected def maySend: Boolean = !isTerminating
|
||||||
|
protected def isTerminating: Boolean = ActorCell.isTerminating(_status)
|
||||||
|
protected def setTerminating(): Unit = if (!ActorCell.isTerminating(_status)) unsafe.getAndAddInt(this, statusOffset, terminatingBit)
|
||||||
|
protected def setClosed(): Unit = if (!isTerminated(_status)) unsafe.getAndAddInt(this, statusOffset, terminatedBit)
|
||||||
|
|
||||||
|
private def handleException: Catcher[Unit] = {
|
||||||
|
case e: InterruptedException ⇒
|
||||||
|
publish(Error(e, self.path.toString, getClass, "interrupted during message send"))
|
||||||
|
Thread.currentThread.interrupt()
|
||||||
|
case NonFatal(e) ⇒
|
||||||
|
publish(Error(e, self.path.toString, getClass, "swallowing exception during message send"))
|
||||||
|
}
|
||||||
|
|
||||||
|
def send(msg: T): Unit =
|
||||||
|
try {
|
||||||
|
val old = unsafe.getAndAddInt(this, statusOffset, 1)
|
||||||
|
val oldActivations = activations(old)
|
||||||
|
// this is not an off-by-one: #msgs is activations-1 if >0
|
||||||
|
if (oldActivations > maxQueue) {
|
||||||
|
if (Debug) println(s"[$thread] $self NOT enqueueing $msg at status $old ($oldActivations > $maxQueue)")
|
||||||
|
// cannot enqueue, need to give back activation token
|
||||||
|
unsafe.getAndAddInt(this, statusOffset, -1)
|
||||||
|
system.eventStream.publish(Dropped(msg, self))
|
||||||
|
} else if (ActorCell.isTerminating(old)) {
|
||||||
|
if (Debug) println(s"[$thread] $self NOT enqueueing $msg at status $old (is terminating)")
|
||||||
|
unsafe.getAndAddInt(this, statusOffset, -1)
|
||||||
|
system.deadLetters ! msg
|
||||||
|
} else {
|
||||||
|
if (Debug) println(s"[$thread] $self enqueueing $msg at status $old")
|
||||||
|
// need to enqueue; if the actor sees the token but not the message, it will reschedule
|
||||||
|
queue.add(msg)
|
||||||
|
if (oldActivations == 0) {
|
||||||
|
if (Debug) println(s"[$thread] $self being woken up")
|
||||||
|
unsafe.getAndAddInt(this, statusOffset, 1) // the first 1 was just the “active” bit, now add 1msg
|
||||||
|
// if the actor was not yet running, set it in motion; spurious wakeups don’t hurt
|
||||||
|
executionContext.execute(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch handleException
|
||||||
|
|
||||||
|
def sendSystem(signal: SystemMessage): Unit = {
|
||||||
|
@tailrec def needToActivate(): Boolean = {
|
||||||
|
val currentList = _systemQueue
|
||||||
|
if (currentList.head == NoMessage) {
|
||||||
|
system.deadLetters.sorry.sendSystem(signal)
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
unsafe.compareAndSwapObject(this, systemQueueOffset, currentList.head, (signal :: currentList).head) || {
|
||||||
|
signal.unlink()
|
||||||
|
needToActivate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (needToActivate()) {
|
||||||
|
val old = unsafe.getAndAddInt(this, statusOffset, 1)
|
||||||
|
if (isTerminated(old)) {
|
||||||
|
// nothing to do
|
||||||
|
if (Debug) println(s"[$thread] $self NOT enqueueing $signal: terminating")
|
||||||
|
unsafe.getAndAddInt(this, statusOffset, -1)
|
||||||
|
} else if (activations(old) == 0) {
|
||||||
|
// all is good: we signaled the transition to active
|
||||||
|
if (Debug) println(s"[$thread] $self enqueueing $signal: activating")
|
||||||
|
executionContext.execute(this)
|
||||||
|
} else {
|
||||||
|
// take back that token: we didn’t actually enqueue a normal message and the actor was already active
|
||||||
|
if (Debug) println(s"[$thread] $self enqueueing $signal: already active")
|
||||||
|
unsafe.getAndAddInt(this, statusOffset, -1)
|
||||||
|
}
|
||||||
|
} else if (Debug) println(s"[$thread] $self NOT enqueueing $signal: terminated")
|
||||||
|
} catch handleException
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point into the actor: the ActorCell is a Runnable that is
|
||||||
|
* enqueued in its Executor whenever it needs to run. The _status field is
|
||||||
|
* used for coordination such that it is never enqueued more than once at
|
||||||
|
* any given time, because that would break the Actor Model.
|
||||||
|
*
|
||||||
|
* The idea here is to process at most as many messages as were in queued
|
||||||
|
* upon entry of this method, interleaving each normal message with the
|
||||||
|
* processing of all system messages that may have accumulated in the
|
||||||
|
* meantime. If at the end of the processing messages remain in the queue
|
||||||
|
* then this cell is rescheduled.
|
||||||
|
*
|
||||||
|
* All coordination occurs via a single Int field that is only updated in
|
||||||
|
* wait-free manner (LOCK XADD via unsafe.getAndAddInt), where conflicts are
|
||||||
|
* resolved by compensating actions. For a description of the bit usage see
|
||||||
|
* the companion object’s source code.
|
||||||
|
*/
|
||||||
|
override final def run(): Unit = {
|
||||||
|
if (Debug) println(s"[$thread] $self entering run(): interrupted=${Thread.currentThread.isInterrupted}")
|
||||||
|
val status = _status
|
||||||
|
val msgs = messageCount(status)
|
||||||
|
var processed = 0
|
||||||
|
try {
|
||||||
|
unscheduleReceiveTimeout()
|
||||||
|
if (!isTerminated(status)) {
|
||||||
|
while (processAllSystemMessages() && !queue.isEmpty() && processed < msgs) {
|
||||||
|
val msg = queue.poll()
|
||||||
|
processed += 1
|
||||||
|
processMessage(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scheduleReceiveTimeout()
|
||||||
|
} catch {
|
||||||
|
case NonFatal(ex) ⇒ fail(ex)
|
||||||
|
case ie: InterruptedException ⇒
|
||||||
|
fail(ie)
|
||||||
|
if (Debug) println(s"[$thread] $self interrupting due to catching InterruptedException")
|
||||||
|
Thread.currentThread.interrupt()
|
||||||
|
} finally {
|
||||||
|
// also remove the general activation token
|
||||||
|
processed += 1
|
||||||
|
val prev = unsafe.getAndAddInt(this, statusOffset, -processed)
|
||||||
|
val now = prev - processed
|
||||||
|
if (isTerminated(now)) {
|
||||||
|
// we’re finished
|
||||||
|
} else if (activations(now) > 0) {
|
||||||
|
// normal messages pending: reverse the deactivation
|
||||||
|
unsafe.getAndAddInt(this, statusOffset, 1)
|
||||||
|
// ... and reschedule
|
||||||
|
executionContext.execute(this)
|
||||||
|
} else if (_systemQueue.head != null) {
|
||||||
|
/*
|
||||||
|
* System message was enqueued after our last processing, we now need to
|
||||||
|
* race against the other party because the enqueue might have happened
|
||||||
|
* before the deactivation (above) and hence not scheduled.
|
||||||
|
*
|
||||||
|
* If we win, we reschedule; if we lose, we must remove the attempted
|
||||||
|
* activation token again.
|
||||||
|
*/
|
||||||
|
val again = unsafe.getAndAddInt(this, statusOffset, 1)
|
||||||
|
if (activations(again) == 0) executionContext.execute(this)
|
||||||
|
else unsafe.getAndAddInt(this, statusOffset, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Debug) println(s"[$thread] $self exiting run(): interrupted=${Thread.currentThread.isInterrupted}")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected[typed] var behavior: Behavior[T] = _
|
||||||
|
|
||||||
|
protected def next(b: Behavior[T], msg: Any): Unit = {
|
||||||
|
if (Behavior.isUnhandled(b)) unhandled(msg)
|
||||||
|
behavior = Behavior.canonicalize(b, behavior)
|
||||||
|
if (!Behavior.isAlive(behavior)) self.sendSystem(Terminate())
|
||||||
|
}
|
||||||
|
|
||||||
|
private def unhandled(msg: Any): Unit = msg match {
|
||||||
|
case Terminated(ref) ⇒ fail(DeathPactException(ref))
|
||||||
|
case _ ⇒ // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
private[this] var receiveTimeoutScheduled: Cancellable = null
|
||||||
|
private def unscheduleReceiveTimeout(): Unit =
|
||||||
|
if (receiveTimeoutScheduled ne null) {
|
||||||
|
receiveTimeoutScheduled.cancel()
|
||||||
|
receiveTimeoutScheduled = null
|
||||||
|
}
|
||||||
|
private def scheduleReceiveTimeout(): Unit =
|
||||||
|
receiveTimeout match {
|
||||||
|
case (d, msg) ⇒
|
||||||
|
receiveTimeoutScheduled = schedule(d, self, msg)
|
||||||
|
case other ⇒
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the messages in the mailbox
|
||||||
|
*/
|
||||||
|
private def processMessage(msg: T): Unit = {
|
||||||
|
if (Debug) println(s"[$thread] $self processing message $msg")
|
||||||
|
next(behavior.message(this, msg), msg)
|
||||||
|
if (Thread.interrupted())
|
||||||
|
throw new InterruptedException("Interrupted while processing actor messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
private def systemDrain(next: LatestFirstSystemMessageList): EarliestFirstSystemMessageList = {
|
||||||
|
val currentList = _systemQueue
|
||||||
|
if (currentList.head == NoMessage) SystemMessageList.ENil
|
||||||
|
else if (unsafe.compareAndSwapObject(this, systemQueueOffset, currentList.head, next.head)) currentList.reverse
|
||||||
|
else systemDrain(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will at least try to process all queued system messages: in case of
|
||||||
|
* failure simply drop and go on to the next, because there is nothing to
|
||||||
|
* restart here (failure is in ActorCell somewhere …). In case the mailbox
|
||||||
|
* becomes closed (because of processing a Terminate message), dump all
|
||||||
|
* already dequeued message to deadLetters.
|
||||||
|
*/
|
||||||
|
private def processAllSystemMessages(): Boolean = {
|
||||||
|
var interruption: Throwable = null
|
||||||
|
var messageList = systemDrain(SystemMessageList.LNil)
|
||||||
|
var continue = true
|
||||||
|
while (messageList.nonEmpty && continue) {
|
||||||
|
val msg = messageList.head
|
||||||
|
messageList = messageList.tail
|
||||||
|
msg.unlink()
|
||||||
|
continue =
|
||||||
|
try processSignal(msg)
|
||||||
|
catch {
|
||||||
|
case ie: InterruptedException ⇒
|
||||||
|
fail(ie)
|
||||||
|
if (Debug) println(s"[$thread] $self interrupting due to catching InterruptedException during system message processing")
|
||||||
|
Thread.currentThread.interrupt()
|
||||||
|
true
|
||||||
|
case ex @ (NonFatal(_) | _: AssertionError) ⇒
|
||||||
|
fail(ex)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* the second part of the condition is necessary to avoid logging an InterruptedException
|
||||||
|
* from the systemGuardian during shutdown
|
||||||
|
*/
|
||||||
|
if (Thread.interrupted() && system.whenTerminated.value.isEmpty)
|
||||||
|
interruption = new InterruptedException("Interrupted while processing system messages")
|
||||||
|
// don’t ever execute normal message when system message present!
|
||||||
|
if (messageList.isEmpty && continue) messageList = systemDrain(SystemMessageList.LNil)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* if we closed the mailbox, we must dump the remaining system messages
|
||||||
|
* to deadLetters (this is essential for DeathWatch)
|
||||||
|
*/
|
||||||
|
val dlm = system.deadLetters
|
||||||
|
if (isTerminated(_status) && messageList.isEmpty) messageList = systemDrain(new LatestFirstSystemMessageList(NoMessage))
|
||||||
|
while (messageList.nonEmpty) {
|
||||||
|
val msg = messageList.head
|
||||||
|
messageList = messageList.tail
|
||||||
|
if (Debug) println(s"[$thread] $self dropping dead system message $msg")
|
||||||
|
msg.unlink()
|
||||||
|
try dlm.sorry.sendSystem(msg)
|
||||||
|
catch {
|
||||||
|
case e: InterruptedException ⇒ interruption = e
|
||||||
|
case NonFatal(e) ⇒ system.eventStream.publish(
|
||||||
|
Error(e, self.path.toString, this.getClass, "error while enqueuing " + msg + " to deadLetters: " + e.getMessage))
|
||||||
|
}
|
||||||
|
if (isTerminated(_status) && messageList.isEmpty) messageList = systemDrain(new LatestFirstSystemMessageList(NoMessage))
|
||||||
|
}
|
||||||
|
// if we got an interrupted exception while handling system messages, then rethrow it
|
||||||
|
if (interruption ne null) {
|
||||||
|
if (Debug) println(s"[$thread] $self throwing interruption")
|
||||||
|
Thread.interrupted() // clear interrupted flag before throwing according to java convention
|
||||||
|
throw interruption
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// logging is not the main purpose, and if it fails there’s nothing we can do
|
||||||
|
protected final def publish(e: Logging.LogEvent): Unit = try system.eventStream.publish(e) catch { case NonFatal(_) ⇒ }
|
||||||
|
|
||||||
|
protected final def clazz(o: AnyRef): Class[_] = if (o eq null) this.getClass else o.getClass
|
||||||
|
|
||||||
|
private def thread: String = Thread.currentThread.getName
|
||||||
|
|
||||||
|
override def toString: String = f"ActorCell($self, status = ${_status}%08x, queue = $queue)"
|
||||||
|
}
|
||||||
194
akka-typed/src/main/scala/akka/typed/internal/ActorRefImpl.scala
Normal file
194
akka-typed/src/main/scala/akka/typed/internal/ActorRefImpl.scala
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
import akka.dispatch.sysmsg._
|
||||||
|
import akka.util.Unsafe.{ instance ⇒ unsafe }
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import java.util.ArrayList
|
||||||
|
import scala.util.{ Success, Failure }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every ActorRef is also an ActorRefImpl, but these two methods shall be
|
||||||
|
* completely hidden from client code. There is an implicit converter
|
||||||
|
* available in the package object, enabling `ref.toImpl` (or `ref.toImplN`
|
||||||
|
* for `ActorRef[Nothing]`—Scala refuses to infer `Nothing` as a type parameter).
|
||||||
|
*/
|
||||||
|
private[typed] trait ActorRefImpl[-T] extends ActorRef[T] {
|
||||||
|
def sendSystem(signal: SystemMessage): Unit
|
||||||
|
def isLocal: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local ActorRef that is backed by an asynchronous [[ActorCell]].
|
||||||
|
*/
|
||||||
|
private[typed] class LocalActorRef[-T](_path: a.ActorPath, cell: ActorCell[T])
|
||||||
|
extends ActorRef[T](_path) with ActorRefImpl[T] {
|
||||||
|
override def tell(msg: T): Unit = cell.send(msg)
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = cell.sendSystem(signal)
|
||||||
|
final override def isLocal: Boolean = true
|
||||||
|
private[typed] def getCell: ActorCell[_] = cell
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local ActorRef that just discards everything that is sent to it. This
|
||||||
|
* implies that it effectively has an infinite lifecycle, i.e. it never
|
||||||
|
* terminates (meaning: no Hawking radiation).
|
||||||
|
*/
|
||||||
|
private[typed] object BlackholeActorRef
|
||||||
|
extends ActorRef[Any](a.RootActorPath(a.Address("akka.typed.internal", "blackhole"))) with ActorRefImpl[Any] {
|
||||||
|
override def tell(msg: Any): Unit = ()
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = ()
|
||||||
|
final override def isLocal: Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local synchronous ActorRef that invokes the given function for every message send.
|
||||||
|
* This reference can be watched and will do the right thing when it receives a [[DeathWatchNotification]].
|
||||||
|
* This reference cannot watch other references.
|
||||||
|
*/
|
||||||
|
private[typed] final class FunctionRef[-T](
|
||||||
|
_path: a.ActorPath,
|
||||||
|
send: (T, FunctionRef[T]) ⇒ Unit,
|
||||||
|
_terminate: FunctionRef[T] ⇒ Unit)
|
||||||
|
extends WatchableRef[T](_path) {
|
||||||
|
|
||||||
|
override def tell(msg: T): Unit =
|
||||||
|
if (isAlive)
|
||||||
|
try send(msg, this) catch {
|
||||||
|
case NonFatal(ex) ⇒ // nothing we can do here
|
||||||
|
}
|
||||||
|
else () // we don’t have deadLetters available
|
||||||
|
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = signal match {
|
||||||
|
case Create() ⇒ // nothing to do
|
||||||
|
case DeathWatchNotification(ref, cause) ⇒ // we’re not watching, and we’re not a parent either
|
||||||
|
case Terminate() ⇒ doTerminate()
|
||||||
|
case Watch(watchee, watcher) ⇒ if (watchee == this && watcher != this) addWatcher(watcher.sorryForNothing)
|
||||||
|
case Unwatch(watchee, watcher) ⇒ if (watchee == this && watcher != this) remWatcher(watcher.sorryForNothing)
|
||||||
|
case NoMessage ⇒ // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isLocal = true
|
||||||
|
|
||||||
|
override def terminate(): Unit = _terminate(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mechanics for synthetic ActorRefs that have a lifecycle and support being watched.
|
||||||
|
*/
|
||||||
|
private[typed] abstract class WatchableRef[-T](_p: a.ActorPath) extends ActorRef[T](_p) with ActorRefImpl[T] {
|
||||||
|
import WatchableRef._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that is invoked when this ref has terminated. Even if doTerminate() is
|
||||||
|
* called multiple times, this callback is invoked only once.
|
||||||
|
*/
|
||||||
|
protected def terminate(): Unit
|
||||||
|
|
||||||
|
type S = Set[ActorRefImpl[Nothing]]
|
||||||
|
@volatile private[this] var _watchedBy: S = Set.empty
|
||||||
|
|
||||||
|
protected def isAlive: Boolean = _watchedBy != null
|
||||||
|
|
||||||
|
protected def doTerminate(): Unit = {
|
||||||
|
val watchedBy = unsafe.getAndSetObject(this, watchedByOffset, null).asInstanceOf[S]
|
||||||
|
if (watchedBy != null) {
|
||||||
|
try terminate() catch { case NonFatal(ex) ⇒ }
|
||||||
|
if (watchedBy.nonEmpty) watchedBy foreach sendTerminated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def sendTerminated(watcher: ActorRefImpl[Nothing]): Unit =
|
||||||
|
watcher.sendSystem(DeathWatchNotification(this, null))
|
||||||
|
|
||||||
|
@tailrec final protected def addWatcher(watcher: ActorRefImpl[Nothing]): Unit =
|
||||||
|
_watchedBy match {
|
||||||
|
case null ⇒ sendTerminated(watcher)
|
||||||
|
case watchedBy ⇒
|
||||||
|
if (!watchedBy.contains(watcher))
|
||||||
|
if (!unsafe.compareAndSwapObject(this, watchedByOffset, watchedBy, watchedBy + watcher))
|
||||||
|
addWatcher(watcher) // try again
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec final protected def remWatcher(watcher: ActorRefImpl[Nothing]): Unit = {
|
||||||
|
_watchedBy match {
|
||||||
|
case null ⇒ // do nothing...
|
||||||
|
case watchedBy ⇒
|
||||||
|
if (watchedBy.contains(watcher))
|
||||||
|
if (!unsafe.compareAndSwapObject(this, watchedByOffset, watchedBy, watchedBy - watcher))
|
||||||
|
remWatcher(watcher) // try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[typed] object WatchableRef {
|
||||||
|
val watchedByOffset = unsafe.objectFieldOffset(classOf[WatchableRef[_]].getDeclaredField("_watchedBy"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Future of an ActorRef can quite easily be wrapped as an ActorRef since no
|
||||||
|
* promises are made about delivery delays: as long as the Future is not ready
|
||||||
|
* messages will be queued, afterwards they get sent without waiting.
|
||||||
|
*/
|
||||||
|
private[typed] class FutureRef[-T](_p: a.ActorPath, bufferSize: Int, f: Future[ActorRef[T]]) extends WatchableRef[T](_p) {
|
||||||
|
import FutureRef._
|
||||||
|
|
||||||
|
@volatile private[this] var _target: Either[ArrayList[T], ActorRef[T]] = Left(new ArrayList[T])
|
||||||
|
|
||||||
|
f.onComplete {
|
||||||
|
case Success(ref) ⇒
|
||||||
|
_target match {
|
||||||
|
case l @ Left(list) ⇒
|
||||||
|
list.synchronized {
|
||||||
|
val it = list.iterator
|
||||||
|
while (it.hasNext) ref ! it.next()
|
||||||
|
if (unsafe.compareAndSwapObject(this, targetOffset, l, Right(ref)))
|
||||||
|
ref.sorry.sendSystem(Watch(ref, this))
|
||||||
|
// if this fails, concurrent termination has won and there is no point in watching
|
||||||
|
}
|
||||||
|
case _ ⇒ // already terminated
|
||||||
|
}
|
||||||
|
case Failure(ex) ⇒ doTerminate()
|
||||||
|
}(akka.dispatch.ExecutionContexts.sameThreadExecutionContext)
|
||||||
|
|
||||||
|
override def terminate(): Unit = {
|
||||||
|
val old = unsafe.getAndSetObject(this, targetOffset, Right(BlackholeActorRef))
|
||||||
|
old match {
|
||||||
|
case Right(target: ActorRef[_]) ⇒ target.sorry.sendSystem(Unwatch(target, this))
|
||||||
|
case _ ⇒ // nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def tell(msg: T): Unit =
|
||||||
|
_target match {
|
||||||
|
case Left(list) ⇒
|
||||||
|
list.synchronized {
|
||||||
|
if (_target.isRight) tell(msg)
|
||||||
|
else if (list.size < bufferSize) list.add(msg)
|
||||||
|
}
|
||||||
|
case Right(ref) ⇒ ref ! msg
|
||||||
|
}
|
||||||
|
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = signal match {
|
||||||
|
case Create() ⇒ // nothing to do
|
||||||
|
case DeathWatchNotification(ref, cause) ⇒
|
||||||
|
_target = Right(BlackholeActorRef) // avoid sending Unwatch() in this case
|
||||||
|
doTerminate() // this can only be the result of watching the target
|
||||||
|
case Terminate() ⇒ doTerminate()
|
||||||
|
case Watch(watchee, watcher) ⇒ if (watchee == this && watcher != this) addWatcher(watcher.sorryForNothing)
|
||||||
|
case Unwatch(watchee, watcher) ⇒ if (watchee == this && watcher != this) remWatcher(watcher.sorryForNothing)
|
||||||
|
case NoMessage ⇒ // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isLocal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private[typed] object FutureRef {
|
||||||
|
val targetOffset = unsafe.objectFieldOffset(classOf[FutureRef[_]].getDeclaredField("akka$typed$internal$FutureRef$$_target"))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
import scala.concurrent.{ ExecutionContextExecutor, Future }
|
||||||
|
import akka.{ actor ⇒ a, dispatch ⇒ d, event ⇒ e }
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import scala.util.control.ControlThrowable
|
||||||
|
import scala.collection.immutable
|
||||||
|
import akka.typed.Dispatchers
|
||||||
|
import scala.concurrent.Promise
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
import scala.util.Success
|
||||||
|
import akka.util.Timeout
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
object ActorSystemImpl {
|
||||||
|
import ScalaDSL._
|
||||||
|
|
||||||
|
sealed trait SystemCommand
|
||||||
|
case class CreateSystemActor[T](props: Props[T])(val replyTo: ActorRef[ActorRef[T]]) extends SystemCommand
|
||||||
|
|
||||||
|
val systemGuardianBehavior: Behavior[SystemCommand] =
|
||||||
|
ContextAware { ctx ⇒
|
||||||
|
Static {
|
||||||
|
case create: CreateSystemActor[t] ⇒
|
||||||
|
create.replyTo ! ctx.spawnAnonymous(create.props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Actor Ideas:
|
||||||
|
|
||||||
|
• remoting/clustering is just another set of actors/extensions
|
||||||
|
|
||||||
|
Receptionist:
|
||||||
|
|
||||||
|
• should be a new kind of Extension (where lookup yields ActorRef)
|
||||||
|
• obtaining a reference may either give a single remote one or a dynamic local proxy that routes to available instances—distinguished using a “stableDestination” flag (for read-your-writes semantics)
|
||||||
|
• perhaps fold sharding into this: how message routing is done should not matter
|
||||||
|
|
||||||
|
Streams:
|
||||||
|
|
||||||
|
• make new implementation of ActorMaterializer that leverages Envelope removal
|
||||||
|
• all internal actor creation must be asynchronous
|
||||||
|
• could offer ActorSystem extension for materializer
|
||||||
|
• remove downcasts to ActorMaterializer in akka-stream package—replace by proper function passing or Materializer APIs where needed (should make Gearpump happier as well)
|
||||||
|
• add new Sink/Source for ActorRef[]
|
||||||
|
|
||||||
|
Distributed Data:
|
||||||
|
|
||||||
|
• create new Behaviors around the logic
|
||||||
|
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
private[typed] class ActorSystemImpl[-T](
|
||||||
|
override val name: String,
|
||||||
|
_config: Config,
|
||||||
|
_cl: ClassLoader,
|
||||||
|
_ec: Option[ExecutionContext],
|
||||||
|
_userGuardianProps: Props[T])
|
||||||
|
extends ActorRef[T](a.RootActorPath(a.Address("akka", name)) / "user") with ActorSystem[T] with ActorRefImpl[T] {
|
||||||
|
|
||||||
|
import ActorSystemImpl._
|
||||||
|
|
||||||
|
if (!name.matches("""^[a-zA-Z0-9][a-zA-Z0-9-_]*$"""))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"invalid ActorSystem name [" + name +
|
||||||
|
"], must contain only word characters (i.e. [a-zA-Z0-9] plus non-leading '-' or '_')")
|
||||||
|
|
||||||
|
import a.ActorSystem.Settings
|
||||||
|
override val settings: Settings = new Settings(_cl, _config, name)
|
||||||
|
|
||||||
|
override def logConfiguration(): Unit = log.info(settings.toString)
|
||||||
|
|
||||||
|
protected def uncaughtExceptionHandler: Thread.UncaughtExceptionHandler =
|
||||||
|
new Thread.UncaughtExceptionHandler() {
|
||||||
|
def uncaughtException(thread: Thread, cause: Throwable): Unit = {
|
||||||
|
cause match {
|
||||||
|
case NonFatal(_) | _: InterruptedException | _: NotImplementedError | _: ControlThrowable ⇒ log.error(cause, "Uncaught error from thread [{}]", thread.getName)
|
||||||
|
case _ ⇒
|
||||||
|
if (settings.JvmExitOnFatalError) {
|
||||||
|
try {
|
||||||
|
log.error(cause, "Uncaught error from thread [{}] shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled", thread.getName)
|
||||||
|
import System.err
|
||||||
|
err.print("Uncaught error from thread [")
|
||||||
|
err.print(thread.getName)
|
||||||
|
err.print("] shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[")
|
||||||
|
err.print(name)
|
||||||
|
err.println("]")
|
||||||
|
cause.printStackTrace(System.err)
|
||||||
|
System.err.flush()
|
||||||
|
} finally {
|
||||||
|
System.exit(-1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error(cause, "Uncaught fatal error from thread [{}] shutting down ActorSystem [{}]", thread.getName, name)
|
||||||
|
terminate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val threadFactory: d.MonitorableThreadFactory =
|
||||||
|
d.MonitorableThreadFactory(name, settings.Daemonicity, Option(_cl), uncaughtExceptionHandler)
|
||||||
|
|
||||||
|
override val dynamicAccess: a.DynamicAccess = new a.ReflectiveDynamicAccess(_cl)
|
||||||
|
|
||||||
|
// this provides basic logging (to stdout) until .start() is called below
|
||||||
|
// FIXME!!!
|
||||||
|
private val untypedSystem = a.ActorSystem(name + "-untyped", _config)
|
||||||
|
override def eventStream = untypedSystem.eventStream
|
||||||
|
|
||||||
|
override val logFilter: e.LoggingFilter = {
|
||||||
|
val arguments = Vector(classOf[Settings] → settings, classOf[e.EventStream] → eventStream)
|
||||||
|
dynamicAccess.createInstanceFor[e.LoggingFilter](settings.LoggingFilter, arguments).get
|
||||||
|
}
|
||||||
|
|
||||||
|
override val log: e.LoggingAdapter = new e.BusLogging(eventStream, getClass.getName + "(" + name + ")", this.getClass, logFilter)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the scheduler service. This one needs one special behavior: if
|
||||||
|
* Closeable, it MUST execute all outstanding tasks upon .close() in order
|
||||||
|
* to properly shutdown all dispatchers.
|
||||||
|
*
|
||||||
|
* Furthermore, this timer service MUST throw IllegalStateException if it
|
||||||
|
* cannot schedule a task. Once scheduled, the task MUST be executed. If
|
||||||
|
* executed upon close(), the task may execute before its timeout.
|
||||||
|
*/
|
||||||
|
protected def createScheduler(): a.Scheduler =
|
||||||
|
dynamicAccess.createInstanceFor[a.Scheduler](settings.SchedulerClass, immutable.Seq(
|
||||||
|
classOf[Config] → settings.config,
|
||||||
|
classOf[e.LoggingAdapter] → log,
|
||||||
|
classOf[ThreadFactory] → threadFactory.withName(threadFactory.name + "-scheduler"))).get
|
||||||
|
|
||||||
|
override val scheduler: a.Scheduler = createScheduler()
|
||||||
|
private def closeScheduler(): Unit = scheduler match {
|
||||||
|
case x: Closeable ⇒ x.close()
|
||||||
|
case _ ⇒
|
||||||
|
}
|
||||||
|
|
||||||
|
override val dispatchers: Dispatchers = new DispatchersImpl(settings, log)
|
||||||
|
override val executionContext: ExecutionContextExecutor = dispatchers.lookup(DispatcherDefault)
|
||||||
|
|
||||||
|
override val startTime: Long = System.currentTimeMillis()
|
||||||
|
override def uptime: Long = (System.currentTimeMillis() - startTime) / 1000
|
||||||
|
|
||||||
|
private val terminationPromise: Promise[Terminated] = Promise()
|
||||||
|
|
||||||
|
private val rootPath: a.ActorPath = a.RootActorPath(a.Address("typed", name))
|
||||||
|
|
||||||
|
private val topLevelActors = new ConcurrentSkipListSet[ActorRefImpl[Nothing]]
|
||||||
|
private val terminateTriggered = new AtomicBoolean
|
||||||
|
private val theOneWhoWalksTheBubblesOfSpaceTime: ActorRefImpl[Nothing] =
|
||||||
|
new ActorRef[Nothing](rootPath) with ActorRefImpl[Nothing] {
|
||||||
|
override def tell(msg: Nothing): Unit = throw new UnsupportedOperationException("cannot send to theOneWhoWalksTheBubblesOfSpaceTime")
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = signal match {
|
||||||
|
case Terminate() ⇒
|
||||||
|
if (terminateTriggered.compareAndSet(false, true))
|
||||||
|
topLevelActors.asScala.foreach(ref ⇒ ref.sendSystem(Terminate()))
|
||||||
|
case DeathWatchNotification(ref, _) ⇒
|
||||||
|
topLevelActors.remove(ref)
|
||||||
|
if (topLevelActors.isEmpty) {
|
||||||
|
if (terminationPromise.tryComplete(Success(Terminated(this)(null)))) {
|
||||||
|
closeScheduler()
|
||||||
|
dispatchers.shutdown()
|
||||||
|
untypedSystem.terminate()
|
||||||
|
}
|
||||||
|
} else if (terminateTriggered.compareAndSet(false, true))
|
||||||
|
topLevelActors.asScala.foreach(ref ⇒ ref.sendSystem(Terminate()))
|
||||||
|
case _ ⇒ // ignore
|
||||||
|
}
|
||||||
|
override def isLocal: Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private def createTopLevel[U](props: Props[U], name: String): ActorRefImpl[U] = {
|
||||||
|
val cell = new ActorCell(this, props, theOneWhoWalksTheBubblesOfSpaceTime)
|
||||||
|
val ref = new LocalActorRef(rootPath / name, cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
topLevelActors.add(ref)
|
||||||
|
ref.sendSystem(Create())
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
|
||||||
|
private val systemGuardian: ActorRefImpl[SystemCommand] = createTopLevel(Props(systemGuardianBehavior), "system")
|
||||||
|
private val userGuardian: ActorRefImpl[T] = createTopLevel(_userGuardianProps, "user")
|
||||||
|
|
||||||
|
override def terminate(): Future[Terminated] = {
|
||||||
|
theOneWhoWalksTheBubblesOfSpaceTime.sendSystem(Terminate())
|
||||||
|
terminationPromise.future
|
||||||
|
}
|
||||||
|
override def whenTerminated: Future[Terminated] = terminationPromise.future
|
||||||
|
|
||||||
|
override def deadLetters[U]: ActorRefImpl[U] =
|
||||||
|
new ActorRef[U](rootPath) with ActorRefImpl[U] {
|
||||||
|
override def tell(msg: U): Unit = eventStream.publish(DeadLetter(msg))
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = {
|
||||||
|
signal match {
|
||||||
|
case Watch(watchee, watcher) ⇒ watcher.sorryForNothing.sendSystem(DeathWatchNotification(watchee, null))
|
||||||
|
case _ ⇒ // all good
|
||||||
|
}
|
||||||
|
eventStream.publish(DeadLetter(signal))
|
||||||
|
}
|
||||||
|
override def isLocal: Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override def tell(msg: T): Unit = userGuardian.tell(msg)
|
||||||
|
override def sendSystem(msg: SystemMessage): Unit = userGuardian.sendSystem(msg)
|
||||||
|
override def isLocal: Boolean = true
|
||||||
|
|
||||||
|
def systemActorOf[U](props: Props[U], name: String)(implicit timeout: Timeout): Future[ActorRef[U]] = {
|
||||||
|
import AskPattern._
|
||||||
|
implicit val sched = scheduler
|
||||||
|
systemGuardian ? CreateSystemActor(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
def printTree: String = {
|
||||||
|
def printNode(node: ActorRefImpl[Nothing], indent: String): String = {
|
||||||
|
node match {
|
||||||
|
case wc: LocalActorRef[_] ⇒
|
||||||
|
val cell = wc.getCell
|
||||||
|
(if (indent.isEmpty) "-> " else indent.dropRight(1) + "⌊-> ") +
|
||||||
|
node.path.name + " " + e.Logging.simpleName(node) + " " +
|
||||||
|
(if (cell.behavior ne null) cell.behavior.getClass else "null") +
|
||||||
|
" status=" + cell.getStatus +
|
||||||
|
" nextMsg=" + cell.peekMessage +
|
||||||
|
(if (cell.children.isEmpty && cell.terminating.isEmpty) "" else "\n") +
|
||||||
|
({
|
||||||
|
val terminating = cell.terminating.toSeq.sorted.map(r ⇒ printNode(r.sorryForNothing, indent + " T"))
|
||||||
|
val children = cell.children.toSeq.sorted
|
||||||
|
val bulk = children.dropRight(1) map (r ⇒ printNode(r.sorryForNothing, indent + " |"))
|
||||||
|
terminating ++ bulk ++ (children.lastOption map (r ⇒ printNode(r.sorryForNothing, indent + " ")))
|
||||||
|
} mkString ("\n"))
|
||||||
|
case _ ⇒
|
||||||
|
indent + node.path.name + " " + e.Logging.simpleName(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printNode(systemGuardian, "") + "\n" +
|
||||||
|
printNode(userGuardian, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
202
akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala
Normal file
202
akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import akka.event.Logging.{ Warning, Debug }
|
||||||
|
import akka.event.AddressTerminatedTopic
|
||||||
|
import akka.event.Logging
|
||||||
|
import akka.actor.Address
|
||||||
|
|
||||||
|
/*
|
||||||
|
* THOUGHTS
|
||||||
|
*
|
||||||
|
* - an ActorRef is a channel that allows sending messages — in particular it is NOT a sender!
|
||||||
|
* - a channel is scoped by the session it is part of
|
||||||
|
* - termination means that the session ends because sending further messages is pointless
|
||||||
|
* - this means that there is no ordering requirement between Terminated and any other received message
|
||||||
|
*/
|
||||||
|
private[typed] trait DeathWatch[T] {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* INTERFACE WITH ACTORCELL
|
||||||
|
*/
|
||||||
|
protected def system: ActorSystem[Nothing]
|
||||||
|
protected def self: ActorRefImpl[T]
|
||||||
|
protected def parent: ActorRefImpl[Nothing]
|
||||||
|
protected def behavior: Behavior[T]
|
||||||
|
protected def next(b: Behavior[T], msg: Any): Unit
|
||||||
|
protected def childrenMap: Map[String, ActorRefImpl[Nothing]]
|
||||||
|
protected def terminatingMap: Map[String, ActorRefImpl[Nothing]]
|
||||||
|
protected def isTerminating: Boolean
|
||||||
|
protected def ctx: ActorContext[T]
|
||||||
|
protected def maySend: Boolean
|
||||||
|
protected def publish(e: Logging.LogEvent): Unit
|
||||||
|
protected def clazz(obj: AnyRef): Class[_]
|
||||||
|
|
||||||
|
protected def removeChild(actor: ActorRefImpl[Nothing]): Unit
|
||||||
|
protected def finishTerminate(): Unit
|
||||||
|
|
||||||
|
type ARImpl = ActorRefImpl[Nothing]
|
||||||
|
|
||||||
|
private var watching = Set.empty[ARImpl]
|
||||||
|
private var watchedBy = Set.empty[ARImpl]
|
||||||
|
|
||||||
|
final def watch[U](_a: ActorRef[U]): ActorRef[U] = {
|
||||||
|
val a = _a.sorry
|
||||||
|
if (a != self && !watching.contains(a)) {
|
||||||
|
maintainAddressTerminatedSubscription(a) {
|
||||||
|
a.sendSystem(Watch(a, self))
|
||||||
|
watching += a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a
|
||||||
|
}
|
||||||
|
|
||||||
|
final def unwatch[U](_a: ActorRef[U]): ActorRef[U] = {
|
||||||
|
val a = _a.sorry
|
||||||
|
if (a != self && watching.contains(a)) {
|
||||||
|
a.sendSystem(Unwatch(a, self))
|
||||||
|
maintainAddressTerminatedSubscription(a) {
|
||||||
|
watching -= a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this actor is watching the subject of [[akka.actor.Terminated]] message
|
||||||
|
* it will be propagated to user's receive.
|
||||||
|
*/
|
||||||
|
protected def watchedActorTerminated(actor: ARImpl, failure: Throwable): Boolean = {
|
||||||
|
removeChild(actor)
|
||||||
|
if (watching.contains(actor)) {
|
||||||
|
maintainAddressTerminatedSubscription(actor) {
|
||||||
|
watching -= actor
|
||||||
|
}
|
||||||
|
if (maySend) {
|
||||||
|
val t = Terminated(actor)(failure)
|
||||||
|
next(behavior.management(ctx, t), t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isTerminating && terminatingMap.isEmpty) {
|
||||||
|
finishTerminate()
|
||||||
|
false
|
||||||
|
} else true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def tellWatchersWeDied(): Unit =
|
||||||
|
if (watchedBy.nonEmpty) {
|
||||||
|
try {
|
||||||
|
// Don't need to send to parent parent since it receives a DWN by default
|
||||||
|
def sendTerminated(ifLocal: Boolean)(watcher: ARImpl): Unit =
|
||||||
|
if (watcher.isLocal == ifLocal && watcher != parent) watcher.sendSystem(DeathWatchNotification(self, null))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* It is important to notify the remote watchers first, otherwise RemoteDaemon might shut down, causing
|
||||||
|
* the remoting to shut down as well. At this point Terminated messages to remote watchers are no longer
|
||||||
|
* deliverable.
|
||||||
|
*
|
||||||
|
* The problematic case is:
|
||||||
|
* 1. Terminated is sent to RemoteDaemon
|
||||||
|
* 1a. RemoteDaemon is fast enough to notify the terminator actor in RemoteActorRefProvider
|
||||||
|
* 1b. The terminator is fast enough to enqueue the shutdown command in the remoting
|
||||||
|
* 2. Only at this point is the Terminated (to be sent remotely) enqueued in the mailbox of remoting
|
||||||
|
*
|
||||||
|
* If the remote watchers are notified first, then the mailbox of the Remoting will guarantee the correct order.
|
||||||
|
*/
|
||||||
|
watchedBy foreach sendTerminated(ifLocal = false)
|
||||||
|
watchedBy foreach sendTerminated(ifLocal = true)
|
||||||
|
} finally {
|
||||||
|
maintainAddressTerminatedSubscription() {
|
||||||
|
watchedBy = Set.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def unwatchWatchedActors(): Unit =
|
||||||
|
if (watching.nonEmpty) {
|
||||||
|
maintainAddressTerminatedSubscription() {
|
||||||
|
try {
|
||||||
|
watching.foreach(watchee ⇒ watchee.sendSystem(Unwatch(watchee, self)))
|
||||||
|
} finally {
|
||||||
|
watching = Set.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def addWatcher(watchee: ARImpl, watcher: ARImpl): Unit = {
|
||||||
|
val watcheeSelf = watchee == self
|
||||||
|
val watcherSelf = watcher == self
|
||||||
|
|
||||||
|
if (watcheeSelf && !watcherSelf) {
|
||||||
|
if (!watchedBy.contains(watcher)) maintainAddressTerminatedSubscription(watcher) {
|
||||||
|
watchedBy += watcher
|
||||||
|
if (system.settings.DebugLifecycle) publish(Debug(self.path.toString, clazz(behavior), s"now watched by $watcher"))
|
||||||
|
}
|
||||||
|
} else if (!watcheeSelf && watcherSelf) {
|
||||||
|
watch[Nothing](watchee)
|
||||||
|
} else {
|
||||||
|
publish(Warning(self.path.toString, clazz(behavior), "BUG: illegal Watch(%s,%s) for %s".format(watchee, watcher, self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def remWatcher(watchee: ARImpl, watcher: ARImpl): Unit = {
|
||||||
|
val watcheeSelf = watchee == self
|
||||||
|
val watcherSelf = watcher == self
|
||||||
|
|
||||||
|
if (watcheeSelf && !watcherSelf) {
|
||||||
|
if (watchedBy.contains(watcher)) maintainAddressTerminatedSubscription(watcher) {
|
||||||
|
watchedBy -= watcher
|
||||||
|
if (system.settings.DebugLifecycle) publish(Debug(self.path.toString, clazz(behavior), s"no longer watched by $watcher"))
|
||||||
|
}
|
||||||
|
} else if (!watcheeSelf && watcherSelf) {
|
||||||
|
unwatch[Nothing](watchee)
|
||||||
|
} else {
|
||||||
|
publish(Warning(self.path.toString, clazz(behavior), "BUG: illegal Unwatch(%s,%s) for %s".format(watchee, watcher, self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def addressTerminated(address: Address): Unit = {
|
||||||
|
// cleanup watchedBy since we know they are dead
|
||||||
|
maintainAddressTerminatedSubscription() {
|
||||||
|
for (a ← watchedBy; if a.path.address == address) watchedBy -= a
|
||||||
|
}
|
||||||
|
|
||||||
|
for (a ← watching; if a.path.address == address) {
|
||||||
|
self.sendSystem(DeathWatchNotification(a, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts subscription to AddressTerminated if not already subscribing and the
|
||||||
|
* block adds a non-local ref to watching or watchedBy.
|
||||||
|
* Ends subscription to AddressTerminated if subscribing and the
|
||||||
|
* block removes the last non-local ref from watching and watchedBy.
|
||||||
|
*/
|
||||||
|
private def maintainAddressTerminatedSubscription[U](change: ARImpl = null)(block: ⇒ U): U = {
|
||||||
|
def isNonLocal(ref: ARImpl) = ref match {
|
||||||
|
case null ⇒ true
|
||||||
|
case a ⇒ !a.isLocal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNonLocal(change)) {
|
||||||
|
def hasNonLocalAddress: Boolean = ((watching exists isNonLocal) || (watchedBy exists isNonLocal))
|
||||||
|
val had = hasNonLocalAddress
|
||||||
|
val result = block
|
||||||
|
val has = hasNonLocalAddress
|
||||||
|
if (had && !has) unsubscribeAddressTerminated()
|
||||||
|
else if (!had && has) subscribeAddressTerminated()
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: these will need to be redone once remoting is integrated
|
||||||
|
private def unsubscribeAddressTerminated(): Unit = ???
|
||||||
|
private def subscribeAddressTerminated(): Unit = ???
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContextExecutor
|
||||||
|
import scala.concurrent.ExecutionContextExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import akka.event.LoggingAdapter
|
||||||
|
|
||||||
|
class DispatchersImpl(settings: akka.actor.ActorSystem.Settings, log: LoggingAdapter) extends Dispatchers {
|
||||||
|
private val ex: ExecutionContextExecutorService = new ExecutionContextExecutorService {
|
||||||
|
val es = Executors.newWorkStealingPool()
|
||||||
|
|
||||||
|
def reportFailure(cause: Throwable): Unit = log.error(cause, "exception caught by default executor")
|
||||||
|
def execute(command: Runnable): Unit = es.execute(command)
|
||||||
|
|
||||||
|
def awaitTermination(x$1: Long, x$2: java.util.concurrent.TimeUnit): Boolean = es.awaitTermination(x$1, x$2)
|
||||||
|
def invokeAll[T](x$1: java.util.Collection[_ <: java.util.concurrent.Callable[T]], x$2: Long, x$3: java.util.concurrent.TimeUnit): java.util.List[java.util.concurrent.Future[T]] = es.invokeAll(x$1, x$2, x$3)
|
||||||
|
def invokeAll[T](x$1: java.util.Collection[_ <: java.util.concurrent.Callable[T]]): java.util.List[java.util.concurrent.Future[T]] = es.invokeAll(x$1)
|
||||||
|
def invokeAny[T](x$1: java.util.Collection[_ <: java.util.concurrent.Callable[T]], x$2: Long, x$3: java.util.concurrent.TimeUnit): T = es.invokeAny(x$1, x$2, x$3)
|
||||||
|
def invokeAny[T](x$1: java.util.Collection[_ <: java.util.concurrent.Callable[T]]): T = es.invokeAny(x$1)
|
||||||
|
def isShutdown(): Boolean = es.isShutdown()
|
||||||
|
def isTerminated(): Boolean = es.isTerminated()
|
||||||
|
def shutdown(): Unit = es.shutdown()
|
||||||
|
def shutdownNow(): java.util.List[Runnable] = es.shutdownNow()
|
||||||
|
def submit(x$1: Runnable): java.util.concurrent.Future[_] = es.submit(x$1)
|
||||||
|
def submit[T](x$1: Runnable, x$2: T): java.util.concurrent.Future[T] = es.submit(x$1, x$2)
|
||||||
|
def submit[T](x$1: java.util.concurrent.Callable[T]): java.util.concurrent.Future[T] = es.submit(x$1)
|
||||||
|
}
|
||||||
|
def lookup(selector: DispatcherSelector): ExecutionContextExecutor = ex //FIXME respect selection
|
||||||
|
def shutdown(): Unit = {
|
||||||
|
ex.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import scala.annotation.{ tailrec, switch }
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import scala.util.control.Exception.Catcher
|
||||||
|
import akka.event.Logging
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
private[typed] trait SupervisionMechanics[T] {
|
||||||
|
import ActorCell._
|
||||||
|
|
||||||
|
/*
|
||||||
|
* INTERFACE WITH ACTOR CELL
|
||||||
|
*/
|
||||||
|
protected def system: ActorSystem[Nothing]
|
||||||
|
protected def props: Props[T]
|
||||||
|
protected def self: ActorRefImpl[T]
|
||||||
|
protected def parent: ActorRefImpl[Nothing]
|
||||||
|
protected def behavior: Behavior[T]
|
||||||
|
protected def behavior_=(b: Behavior[T]): Unit
|
||||||
|
protected def next(b: Behavior[T], msg: Any): Unit
|
||||||
|
protected def terminatingMap: Map[String, ActorRefImpl[Nothing]]
|
||||||
|
protected def stopAll(): Unit
|
||||||
|
protected def setTerminating(): Unit
|
||||||
|
protected def setClosed(): Unit
|
||||||
|
protected def maySend: Boolean
|
||||||
|
protected def ctx: ActorContext[T]
|
||||||
|
protected def publish(e: Logging.LogEvent): Unit
|
||||||
|
protected def clazz(obj: AnyRef): Class[_]
|
||||||
|
|
||||||
|
// INTERFACE WITH DEATHWATCH
|
||||||
|
protected def addWatcher(watchee: ActorRefImpl[Nothing], watcher: ActorRefImpl[Nothing]): Unit
|
||||||
|
protected def remWatcher(watchee: ActorRefImpl[Nothing], watcher: ActorRefImpl[Nothing]): Unit
|
||||||
|
protected def watchedActorTerminated(actor: ActorRefImpl[Nothing], failure: Throwable): Boolean
|
||||||
|
protected def tellWatchersWeDied(): Unit
|
||||||
|
protected def unwatchWatchedActors(): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process one system message and return whether further messages shall be processed.
|
||||||
|
*/
|
||||||
|
protected def processSignal(message: SystemMessage): Boolean = {
|
||||||
|
if (ActorCell.Debug) println(s"[${Thread.currentThread.getName}] $self processing system message $message")
|
||||||
|
message match {
|
||||||
|
case Watch(watchee, watcher) ⇒ { addWatcher(watchee.sorryForNothing, watcher.sorryForNothing); true }
|
||||||
|
case Unwatch(watchee, watcher) ⇒ { remWatcher(watchee.sorryForNothing, watcher.sorryForNothing); true }
|
||||||
|
case DeathWatchNotification(a, f) ⇒ watchedActorTerminated(a.sorryForNothing, f)
|
||||||
|
case Create() ⇒ create()
|
||||||
|
case Terminate() ⇒ terminate()
|
||||||
|
case NoMessage ⇒ false // only here to suppress warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[this] var _failed: Throwable = null
|
||||||
|
protected def failed: Throwable = _failed
|
||||||
|
|
||||||
|
protected def fail(thr: Throwable): Unit = {
|
||||||
|
if (_failed eq null) _failed = thr
|
||||||
|
publish(Logging.Error(thr, self.path.toString, getClass, thr.getMessage))
|
||||||
|
if (maySend) self.sendSystem(Terminate())
|
||||||
|
}
|
||||||
|
|
||||||
|
private def create(): Boolean = {
|
||||||
|
behavior = Behavior.canonicalize(props.creator(), behavior)
|
||||||
|
if (behavior == null) {
|
||||||
|
fail(new IllegalStateException("cannot start actor with “same” or “unhandled” behavior, terminating"))
|
||||||
|
} else {
|
||||||
|
if (system.settings.DebugLifecycle)
|
||||||
|
publish(Logging.Debug(self.path.toString, clazz(behavior), "started"))
|
||||||
|
if (Behavior.isAlive(behavior)) next(behavior.management(ctx, PreStart), PreStart)
|
||||||
|
else self.sendSystem(Terminate())
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
private def terminate(): Boolean = {
|
||||||
|
setTerminating()
|
||||||
|
unwatchWatchedActors()
|
||||||
|
stopAll()
|
||||||
|
if (terminatingMap.isEmpty) {
|
||||||
|
finishTerminate()
|
||||||
|
false
|
||||||
|
} else true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def finishTerminate(): Unit = {
|
||||||
|
val a = behavior
|
||||||
|
/*
|
||||||
|
* The following order is crucial for things to work properly. Only change this if you're very confident and lucky.
|
||||||
|
*/
|
||||||
|
try if (a ne null) a.management(ctx, PostStop)
|
||||||
|
catch { case NonFatal(ex) ⇒ publish(Logging.Error(ex, self.path.toString, clazz(a), "failure during PostStop")) }
|
||||||
|
finally try tellWatchersWeDied()
|
||||||
|
finally try parent.sendSystem(DeathWatchNotification(self, failed))
|
||||||
|
finally {
|
||||||
|
behavior = null
|
||||||
|
_failed = null
|
||||||
|
setClosed()
|
||||||
|
if (system.settings.DebugLifecycle)
|
||||||
|
publish(Logging.Debug(self.path.toString, clazz(a), "stopped"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*
|
||||||
|
* Helper companion object for [[LatestFirstSystemMessageList]] and
|
||||||
|
* [[EarliestFirstSystemMessageList]]
|
||||||
|
*/
|
||||||
|
private[typed] object SystemMessageList {
|
||||||
|
final val LNil: LatestFirstSystemMessageList = new LatestFirstSystemMessageList(null)
|
||||||
|
final val ENil: EarliestFirstSystemMessageList = new EarliestFirstSystemMessageList(null)
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
private[internal] def sizeInner(head: SystemMessage, acc: Int): Int = if (head eq null) acc else sizeInner(head.next, acc + 1)
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
private[internal] def reverseInner(head: SystemMessage, acc: SystemMessage): SystemMessage = {
|
||||||
|
if (head eq null) acc else {
|
||||||
|
val next = head.next
|
||||||
|
head.next = acc
|
||||||
|
reverseInner(next, head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* INTERNAL API
|
||||||
|
*
|
||||||
|
* Value class supporting list operations on system messages. The `next` field of [[SystemMessage]]
|
||||||
|
* is hidden, and can only accessed through the value classes [[LatestFirstSystemMessageList]] and
|
||||||
|
* [[EarliestFirstSystemMessageList]], abstracting over the fact that system messages are the
|
||||||
|
* list nodes themselves. If used properly, this stays a compile time construct without any allocation overhead.
|
||||||
|
*
|
||||||
|
* This list is mutable.
|
||||||
|
*
|
||||||
|
* The type of the list also encodes that the messages contained are in reverse order, i.e. the head of the list is the
|
||||||
|
* latest appended element.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private[typed] class LatestFirstSystemMessageList(val head: SystemMessage) extends AnyVal {
|
||||||
|
import SystemMessageList._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the list is empty or not. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def isEmpty: Boolean = head eq null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the list has at least one element or not. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def nonEmpty: Boolean = head ne null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the list is empty or not. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def size: Int = sizeInner(head, 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives back the list containing all the elements except the first. This operation has constant cost.
|
||||||
|
*
|
||||||
|
* *Warning:* as the underlying list nodes (the [[SystemMessage]] instances) are mutable, care
|
||||||
|
* should be taken when passing the tail to other methods. [[SystemMessage#unlink]] should be
|
||||||
|
* called on the head if one wants to detach the tail permanently.
|
||||||
|
*/
|
||||||
|
final def tail: LatestFirstSystemMessageList = new LatestFirstSystemMessageList(head.next)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses the list. This operation mutates the underlying list. The cost of the call to reverse is linear in the
|
||||||
|
* number of elements.
|
||||||
|
*
|
||||||
|
* The type of the returned list is of the opposite order: [[EarliestFirstSystemMessageList]]
|
||||||
|
*/
|
||||||
|
final def reverse: EarliestFirstSystemMessageList = new EarliestFirstSystemMessageList(reverseInner(head, null))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches a message to the current head of the list. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def ::(msg: SystemMessage): LatestFirstSystemMessageList = {
|
||||||
|
assert(msg ne null)
|
||||||
|
msg.next = head
|
||||||
|
new LatestFirstSystemMessageList(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* INTERNAL API
|
||||||
|
*
|
||||||
|
* Value class supporting list operations on system messages. The `next` field of [[SystemMessage]]
|
||||||
|
* is hidden, and can only accessed through the value classes [[LatestFirstSystemMessageList]] and
|
||||||
|
* [[EarliestFirstSystemMessageList]], abstracting over the fact that system messages are the
|
||||||
|
* list nodes themselves. If used properly, this stays a compile time construct without any allocation overhead.
|
||||||
|
*
|
||||||
|
* This list is mutable.
|
||||||
|
*
|
||||||
|
* This list type also encodes that the messages contained are in reverse order, i.e. the head of the list is the
|
||||||
|
* latest appended element.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private[typed] class EarliestFirstSystemMessageList(val head: SystemMessage) extends AnyVal {
|
||||||
|
import SystemMessageList._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the list is empty or not. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def isEmpty: Boolean = head eq null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the list has at least one element or not. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def nonEmpty: Boolean = head ne null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the list is empty or not. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def size: Int = sizeInner(head, 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives back the list containing all the elements except the first. This operation has constant cost.
|
||||||
|
*
|
||||||
|
* *Warning:* as the underlying list nodes (the [[SystemMessage]] instances) are mutable, care
|
||||||
|
* should be taken when passing the tail to other methods. [[SystemMessage#unlink]] should be
|
||||||
|
* called on the head if one wants to detach the tail permanently.
|
||||||
|
*/
|
||||||
|
final def tail: EarliestFirstSystemMessageList = new EarliestFirstSystemMessageList(head.next)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses the list. This operation mutates the underlying list. The cost of the call to reverse is linear in the
|
||||||
|
* number of elements.
|
||||||
|
*
|
||||||
|
* The type of the returned list is of the opposite order: [[LatestFirstSystemMessageList]]
|
||||||
|
*/
|
||||||
|
final def reverse: LatestFirstSystemMessageList = new LatestFirstSystemMessageList(reverseInner(head, null))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches a message to the current head of the list. This operation has constant cost.
|
||||||
|
*/
|
||||||
|
final def ::(msg: SystemMessage): EarliestFirstSystemMessageList = {
|
||||||
|
assert(msg ne null)
|
||||||
|
msg.next = head
|
||||||
|
new EarliestFirstSystemMessageList(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepends a list in a reversed order to the head of this list. The prepended list will be reversed during the process.
|
||||||
|
*
|
||||||
|
* Example: (3, 4, 5) reversePrepend (2, 1, 0) == (0, 1, 2, 3, 4, 5)
|
||||||
|
*
|
||||||
|
* The cost of this operation is linear in the size of the list that is to be prepended.
|
||||||
|
*/
|
||||||
|
final def reverse_:::(other: LatestFirstSystemMessageList): EarliestFirstSystemMessageList = {
|
||||||
|
var remaining = other
|
||||||
|
var result = this
|
||||||
|
while (remaining.nonEmpty) {
|
||||||
|
val msg = remaining.head
|
||||||
|
remaining = remaining.tail
|
||||||
|
result ::= msg
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System messages are handled specially: they form their own queue within
|
||||||
|
* each actor’s mailbox. This queue is encoded in the messages themselves to
|
||||||
|
* avoid extra allocations and overhead. The next pointer is a normal var, and
|
||||||
|
* it does not need to be volatile because in the enqueuing method its update
|
||||||
|
* is immediately succeeded by a volatile write and all reads happen after the
|
||||||
|
* volatile read in the dequeuing thread. Afterwards, the obtained list of
|
||||||
|
* system messages is handled in a single thread only and not ever passed around,
|
||||||
|
* hence no further synchronization is needed.
|
||||||
|
*
|
||||||
|
* INTERNAL API
|
||||||
|
*
|
||||||
|
* <b>NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS</b>
|
||||||
|
*/
|
||||||
|
private[typed] sealed trait SystemMessage extends Serializable {
|
||||||
|
// Next fields are only modifiable via the SystemMessageList value class
|
||||||
|
@transient
|
||||||
|
private[internal] var next: SystemMessage = _
|
||||||
|
|
||||||
|
def unlink(): Unit = next = null
|
||||||
|
|
||||||
|
def unlinked: Boolean = next eq null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
private[typed] final case class Create() extends SystemMessage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
private[typed] final case class Terminate() extends SystemMessage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
private[typed] final case class Watch(watchee: ActorRef[Nothing], watcher: ActorRef[Nothing]) extends SystemMessage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
private[typed] final case class Unwatch(watchee: ActorRef[Nothing], watcher: ActorRef[Nothing]) extends SystemMessage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
private[akka] final case class DeathWatchNotification(actor: ActorRef[Nothing], failureCause: Throwable) extends SystemMessage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@SerialVersionUID(1L)
|
||||||
|
private[akka] case object NoMessage extends SystemMessage // switched into the mailbox to signal termination
|
||||||
14
akka-typed/src/main/scala/akka/typed/internal/package.scala
Normal file
14
akka-typed/src/main/scala/akka/typed/internal/package.scala
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package akka.typed
|
||||||
|
|
||||||
|
package object internal {
|
||||||
|
/*
|
||||||
|
* These are safe due to the self-type of ActorRef
|
||||||
|
*/
|
||||||
|
implicit class ToImpl[U](val ref: ActorRef[U]) extends AnyVal {
|
||||||
|
def sorry: ActorRefImpl[U] = ref.asInstanceOf[ActorRefImpl[U]]
|
||||||
|
}
|
||||||
|
// This one is necessary because Scala refuses to infer Nothing
|
||||||
|
implicit class ToImplNothing(val ref: ActorRef[Nothing]) extends AnyVal {
|
||||||
|
def sorryForNothing: ActorRefImpl[Nothing] = ref.asInstanceOf[ActorRefImpl[Nothing]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,13 +11,16 @@ import scala.concurrent.duration.Deadline
|
||||||
import akka.typed.ActorContext
|
import akka.typed.ActorContext
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import akka.typed.ReceiveTimeout
|
|
||||||
import scala.collection.immutable.Queue
|
import scala.collection.immutable.Queue
|
||||||
|
|
||||||
|
// FIXME make this nice again once the Actor Algebra is implemented
|
||||||
object Receiver {
|
object Receiver {
|
||||||
import akka.typed.ScalaDSL._
|
import akka.typed.ScalaDSL._
|
||||||
|
|
||||||
sealed trait Command[T]
|
sealed trait InternalCommand[T]
|
||||||
|
case class ReceiveTimeout[T]() extends InternalCommand[T]
|
||||||
|
|
||||||
|
sealed trait Command[T] extends InternalCommand[T]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve one message from the Receiver, waiting at most for the given duration.
|
* Retrieve one message from the Receiver, waiting at most for the given duration.
|
||||||
|
|
@ -44,14 +47,14 @@ object Receiver {
|
||||||
ContextAware[Any] { ctx ⇒
|
ContextAware[Any] { ctx ⇒
|
||||||
SynchronousSelf { syncself ⇒
|
SynchronousSelf { syncself ⇒
|
||||||
Or(
|
Or(
|
||||||
empty(ctx).widen { case c: Command[t] ⇒ c.asInstanceOf[Command[T]] },
|
empty(ctx).widen { case c: InternalCommand[t] ⇒ c.asInstanceOf[InternalCommand[T]] },
|
||||||
Static[Any] {
|
Static[Any] {
|
||||||
case msg ⇒ syncself ! Enqueue(msg)
|
case msg ⇒ syncself ! Enqueue(msg)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}.narrow
|
}.narrow
|
||||||
|
|
||||||
private def empty[T](ctx: ActorContext[Any]): Behavior[Command[T]] =
|
private def empty[T](ctx: ActorContext[Any]): Behavior[InternalCommand[T]] =
|
||||||
Total {
|
Total {
|
||||||
case ExternalAddress(replyTo) ⇒ { replyTo ! ctx.self; Same }
|
case ExternalAddress(replyTo) ⇒ { replyTo ! ctx.self; Same }
|
||||||
case g @ GetOne(d) if d <= Duration.Zero ⇒ { g.replyTo ! GetOneResult(ctx.self, None); Same }
|
case g @ GetOne(d) if d <= Duration.Zero ⇒ { g.replyTo ! GetOneResult(ctx.self, None); Same }
|
||||||
|
|
@ -61,7 +64,7 @@ object Receiver {
|
||||||
case Enqueue(msg) ⇒ queued(ctx, msg)
|
case Enqueue(msg) ⇒ queued(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def queued[T](ctx: ActorContext[Any], t: T): Behavior[Command[T]] = {
|
private def queued[T](ctx: ActorContext[Any], t: T): Behavior[InternalCommand[T]] = {
|
||||||
val queue = new LinkedList[T]
|
val queue = new LinkedList[T]
|
||||||
queue.add(t)
|
queue.add(t)
|
||||||
Total {
|
Total {
|
||||||
|
|
@ -84,19 +87,17 @@ object Receiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private case class Asked[T](replyTo: ActorRef[GetOneResult[T]], deadline: Deadline)
|
private case class Asked[T](replyTo: ActorRef[GetOneResult[T]], deadline: Deadline)
|
||||||
private def asked[T](ctx: ActorContext[Any], queue: Queue[Asked[T]]): Behavior[Command[T]] = {
|
private def asked[T](ctx: ActorContext[Any], queue: Queue[Asked[T]]): Behavior[InternalCommand[T]] = {
|
||||||
ctx.setReceiveTimeout(queue.map(_.deadline).min.timeLeft)
|
ctx.setReceiveTimeout(queue.map(_.deadline).min.timeLeft, ReceiveTimeout())
|
||||||
|
|
||||||
Full {
|
Total {
|
||||||
case Sig(_, ReceiveTimeout) ⇒
|
case ReceiveTimeout() ⇒
|
||||||
val (overdue, remaining) = queue partition (_.deadline.isOverdue)
|
val (overdue, remaining) = queue partition (_.deadline.isOverdue)
|
||||||
overdue foreach (a ⇒ a.replyTo ! GetOneResult(ctx.self, None))
|
overdue foreach (a ⇒ a.replyTo ! GetOneResult(ctx.self, None))
|
||||||
if (remaining.isEmpty) {
|
if (remaining.isEmpty) {
|
||||||
ctx.setReceiveTimeout(Duration.Undefined)
|
ctx.cancelReceiveTimeout()
|
||||||
empty(ctx)
|
empty(ctx)
|
||||||
} else asked(ctx, remaining)
|
} else asked(ctx, remaining)
|
||||||
case Msg(_, msg) ⇒
|
|
||||||
msg match {
|
|
||||||
case ExternalAddress(replyTo) ⇒ { replyTo ! ctx.self; Same }
|
case ExternalAddress(replyTo) ⇒ { replyTo ! ctx.self; Same }
|
||||||
case g @ GetOne(d) if d <= Duration.Zero ⇒
|
case g @ GetOne(d) if d <= Duration.Zero ⇒
|
||||||
g.replyTo ! GetOneResult(ctx.self, None)
|
g.replyTo ! GetOneResult(ctx.self, None)
|
||||||
|
|
@ -113,10 +114,9 @@ object Receiver {
|
||||||
val (ask, q) = queue.dequeue
|
val (ask, q) = queue.dequeue
|
||||||
ask.replyTo ! GetOneResult(ctx.self, Some(msg))
|
ask.replyTo ! GetOneResult(ctx.self, Some(msg))
|
||||||
if (q.isEmpty) {
|
if (q.isEmpty) {
|
||||||
ctx.setReceiveTimeout(Duration.Undefined)
|
ctx.cancelReceiveTimeout()
|
||||||
empty(ctx)
|
empty(ctx)
|
||||||
} else asked(ctx, q)
|
} else asked(ctx, q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package patterns
|
||||||
|
|
||||||
|
import scala.reflect.ClassTag
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import akka.event.Logging
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple supervision strategy that restarts the underlying behavior for all
|
||||||
|
* failures of type Thr.
|
||||||
|
*
|
||||||
|
* FIXME add limited restarts and back-off (with limited buffering or vacation responder)
|
||||||
|
* FIXME write tests that ensure that all Behaviors are okay with getting PostRestart as first signal
|
||||||
|
*/
|
||||||
|
final case class Restarter[T, Thr <: Throwable: ClassTag](behavior: () ⇒ Behavior[T], resume: Boolean) extends Behavior[T] {
|
||||||
|
|
||||||
|
private[this] var current = behavior()
|
||||||
|
|
||||||
|
// FIXME remove allocation overhead once finalized
|
||||||
|
private def canonicalize(ctx: ActorContext[T], block: ⇒ Behavior[T]): Behavior[T] = {
|
||||||
|
val b =
|
||||||
|
try block
|
||||||
|
catch {
|
||||||
|
case ex: Thr ⇒
|
||||||
|
ctx.system.eventStream.publish(Logging.Error(ex, ctx.self.toString, current.getClass, ex.getMessage))
|
||||||
|
if (resume) current else restart(ctx)
|
||||||
|
}
|
||||||
|
current = Behavior.canonicalize(b, current)
|
||||||
|
if (Behavior.isAlive(current)) this else ScalaDSL.Stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
private def restart(ctx: ActorContext[T]): Behavior[T] = {
|
||||||
|
try current.management(ctx, PreRestart) catch { case NonFatal(_) ⇒ }
|
||||||
|
current = behavior()
|
||||||
|
current.management(ctx, PostRestart)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] =
|
||||||
|
canonicalize(ctx, current.management(ctx, signal))
|
||||||
|
|
||||||
|
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
|
||||||
|
canonicalize(ctx, current.message(ctx, msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
object Restarter {
|
||||||
|
class Apply[Thr <: Throwable](c: ClassTag[Thr], resume: Boolean) {
|
||||||
|
def wrap[T](p: Props[T]) = Props(() ⇒ Restarter(p.creator, resume)(c), p.dispatcher, p.mailboxCapacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply[Thr <: Throwable: ClassTag](resume: Boolean = false): Apply[Thr] = new Apply(implicitly, resume)
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
akka.loglevel = DEBUG
|
||||||
|
akka.actor.debug.lifecycle = off
|
||||||
|
|
||||||
dispatcher-1 {
|
dispatcher-1 {
|
||||||
fork-join-executor {
|
fork-join-executor {
|
||||||
parallelism-min=1
|
parallelism-min=1
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,19 @@ import scala.concurrent.duration._
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import akka.actor.DeadLetterSuppression
|
import akka.actor.DeadLetterSuppression
|
||||||
|
import akka.typed.ScalaDSL._
|
||||||
|
import akka.typed.patterns._
|
||||||
|
|
||||||
object ActorContextSpec {
|
object ActorContextSpec {
|
||||||
import ScalaDSL._
|
|
||||||
|
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
sealed trait Event
|
sealed trait Event
|
||||||
|
sealed trait Monitor extends Event
|
||||||
|
|
||||||
final case class GotSignal(signal: Signal) extends Event with DeadLetterSuppression
|
final case class GotSignal(signal: Signal) extends Monitor with DeadLetterSuppression
|
||||||
|
final case object GotReceiveTimeout extends Monitor
|
||||||
|
|
||||||
|
final case object ReceiveTimeout extends Command
|
||||||
|
|
||||||
final case class Ping(replyTo: ActorRef[Pong]) extends Command
|
final case class Ping(replyTo: ActorRef[Pong]) extends Command
|
||||||
sealed trait Pong extends Event
|
sealed trait Pong extends Event
|
||||||
|
|
@ -26,7 +31,7 @@ object ActorContextSpec {
|
||||||
|
|
||||||
final case class Throw(ex: Exception) extends Command
|
final case class Throw(ex: Exception) extends Command
|
||||||
|
|
||||||
final case class MkChild(name: Option[String], monitor: ActorRef[GotSignal], replyTo: ActorRef[Created]) extends Command
|
final case class MkChild(name: Option[String], monitor: ActorRef[Monitor], replyTo: ActorRef[Created]) extends Command
|
||||||
final case class Created(ref: ActorRef[Command]) extends Event
|
final case class Created(ref: ActorRef[Command]) extends Event
|
||||||
|
|
||||||
final case class SetTimeout(duration: FiniteDuration, replyTo: ActorRef[TimeoutSet.type]) extends Command
|
final case class SetTimeout(duration: FiniteDuration, replyTo: ActorRef[TimeoutSet.type]) extends Command
|
||||||
|
|
@ -68,16 +73,15 @@ object ActorContextSpec {
|
||||||
final case class GetAdapter(replyTo: ActorRef[Adapter]) extends Command
|
final case class GetAdapter(replyTo: ActorRef[Adapter]) extends Command
|
||||||
final case class Adapter(a: ActorRef[Command]) extends Event
|
final case class Adapter(a: ActorRef[Command]) extends Event
|
||||||
|
|
||||||
def subject(monitor: ActorRef[GotSignal]): Behavior[Command] =
|
def subject(monitor: ActorRef[Monitor]): Behavior[Command] =
|
||||||
FullTotal {
|
FullTotal {
|
||||||
case Sig(ctx, signal) ⇒
|
case Sig(ctx, signal) ⇒
|
||||||
monitor ! GotSignal(signal)
|
monitor ! GotSignal(signal)
|
||||||
signal match {
|
|
||||||
case f: Failed ⇒ f.decide(Failed.Restart)
|
|
||||||
case _ ⇒
|
|
||||||
}
|
|
||||||
Same
|
Same
|
||||||
case Msg(ctx, message) ⇒ message match {
|
case Msg(ctx, message) ⇒ message match {
|
||||||
|
case ReceiveTimeout ⇒
|
||||||
|
monitor ! GotReceiveTimeout
|
||||||
|
Same
|
||||||
case Ping(replyTo) ⇒
|
case Ping(replyTo) ⇒
|
||||||
replyTo ! Pong1
|
replyTo ! Pong1
|
||||||
Same
|
Same
|
||||||
|
|
@ -91,13 +95,16 @@ object ActorContextSpec {
|
||||||
throw ex
|
throw ex
|
||||||
case MkChild(name, mon, replyTo) ⇒
|
case MkChild(name, mon, replyTo) ⇒
|
||||||
val child = name match {
|
val child = name match {
|
||||||
case None ⇒ ctx.spawnAnonymous(Props(subject(mon)))
|
case None ⇒ ctx.spawnAnonymous(Restarter[Throwable]().wrap(Props(subject(mon))))
|
||||||
case Some(n) ⇒ ctx.spawn(Props(subject(mon)), n)
|
case Some(n) ⇒ ctx.spawn(Restarter[Throwable]().wrap(Props(subject(mon))), n)
|
||||||
}
|
}
|
||||||
replyTo ! Created(child)
|
replyTo ! Created(child)
|
||||||
Same
|
Same
|
||||||
case SetTimeout(d, replyTo) ⇒
|
case SetTimeout(d, replyTo) ⇒
|
||||||
ctx.setReceiveTimeout(d)
|
d match {
|
||||||
|
case f: FiniteDuration ⇒ ctx.setReceiveTimeout(f, ReceiveTimeout)
|
||||||
|
case _ ⇒ ctx.cancelReceiveTimeout()
|
||||||
|
}
|
||||||
replyTo ! TimeoutSet
|
replyTo ! TimeoutSet
|
||||||
Same
|
Same
|
||||||
case Schedule(delay, target, msg, replyTo) ⇒
|
case Schedule(delay, target, msg, replyTo) ⇒
|
||||||
|
|
@ -160,7 +167,6 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
| }
|
| }
|
||||||
|}""".stripMargin)) {
|
|}""".stripMargin)) {
|
||||||
import ActorContextSpec._
|
import ActorContextSpec._
|
||||||
import ScalaDSL._
|
|
||||||
|
|
||||||
val expectTimeout = 3.seconds
|
val expectTimeout = 3.seconds
|
||||||
|
|
||||||
|
|
@ -175,10 +181,18 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
*/
|
*/
|
||||||
def behavior(ctx: ActorContext[Event]): Behavior[Command]
|
def behavior(ctx: ActorContext[Event]): Behavior[Command]
|
||||||
|
|
||||||
def setup(name: String)(proc: (ActorContext[Event], StepWise.Steps[Event, ActorRef[Command]]) ⇒ StepWise.Steps[Event, _]): Future[TypedSpec.Status] =
|
implicit def system: ActorSystem[TypedSpec.Command]
|
||||||
runTest(s"$suite-$name")(StepWise[Event] { (ctx, startWith) ⇒
|
|
||||||
|
private def mySuite: String =
|
||||||
|
if (system eq nativeSystem) suite + "Native"
|
||||||
|
else suite + "Adapted"
|
||||||
|
|
||||||
|
def setup(name: String, wrapper: Option[Restarter.Apply[_]] = None)(
|
||||||
|
proc: (ActorContext[Event], StepWise.Steps[Event, ActorRef[Command]]) ⇒ StepWise.Steps[Event, _]): Future[TypedSpec.Status] =
|
||||||
|
runTest(s"$mySuite-$name")(StepWise[Event] { (ctx, startWith) ⇒
|
||||||
|
val props = wrapper.map(_.wrap(Props(behavior(ctx)))).getOrElse(Props(behavior(ctx)))
|
||||||
val steps =
|
val steps =
|
||||||
startWith.withKeepTraces(true)(ctx.spawn(Props(behavior(ctx)), "subject"))
|
startWith.withKeepTraces(true)(ctx.spawn(props, "subject"))
|
||||||
.expectMessage(expectTimeout) { (msg, ref) ⇒
|
.expectMessage(expectTimeout) { (msg, ref) ⇒
|
||||||
msg should ===(GotSignal(PreStart))
|
msg should ===(GotSignal(PreStart))
|
||||||
ref
|
ref
|
||||||
|
|
@ -246,25 +260,20 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def `01 must correctly wire the lifecycle hooks`(): Unit = sync(setup("ctx01") { (ctx, startWith) ⇒
|
def `01 must correctly wire the lifecycle hooks`(): Unit = sync(setup("ctx01", Some(Restarter[Throwable]())) { (ctx, startWith) ⇒
|
||||||
val self = ctx.self
|
val self = ctx.self
|
||||||
val ex = new Exception("KABOOM1")
|
val ex = new Exception("KABOOM1")
|
||||||
startWith { subj ⇒
|
startWith { subj ⇒
|
||||||
val log = muteExpectedException[Exception]("KABOOM1", occurrences = 1)
|
val log = muteExpectedException[Exception]("KABOOM1", occurrences = 1)
|
||||||
subj ! Throw(ex)
|
subj ! Throw(ex)
|
||||||
(subj, log)
|
(subj, log)
|
||||||
}.expectFailureKeep(expectTimeout) {
|
|
||||||
case (f, (subj, _)) ⇒
|
|
||||||
f.cause should ===(ex)
|
|
||||||
f.child should ===(subj)
|
|
||||||
Failed.Restart
|
|
||||||
}.expectMessage(expectTimeout) {
|
}.expectMessage(expectTimeout) {
|
||||||
case (msg, (subj, log)) ⇒
|
case (msg, (subj, log)) ⇒
|
||||||
msg should ===(GotSignal(PreRestart(ex)))
|
msg should ===(GotSignal(PreRestart))
|
||||||
log.assertDone(expectTimeout)
|
log.assertDone(expectTimeout)
|
||||||
subj
|
subj
|
||||||
}.expectMessage(expectTimeout) { (msg, subj) ⇒
|
}.expectMessage(expectTimeout) { (msg, subj) ⇒
|
||||||
msg should ===(GotSignal(PostRestart(ex)))
|
msg should ===(GotSignal(PostRestart))
|
||||||
ctx.stop(subj)
|
ctx.stop(subj)
|
||||||
}.expectMessage(expectTimeout) { (msg, _) ⇒
|
}.expectMessage(expectTimeout) { (msg, _) ⇒
|
||||||
msg should ===(GotSignal(PostStop))
|
msg should ===(GotSignal(PostStop))
|
||||||
|
|
@ -288,12 +297,11 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
val log = muteExpectedException[Exception]("KABOOM2", occurrences = 1)
|
val log = muteExpectedException[Exception]("KABOOM2", occurrences = 1)
|
||||||
child ! Throw(ex)
|
child ! Throw(ex)
|
||||||
(subj, child, log)
|
(subj, child, log)
|
||||||
}.expectMultipleMessages(expectTimeout, 3) {
|
}.expectMultipleMessages(expectTimeout, 2) {
|
||||||
case (msgs, (subj, child, log)) ⇒
|
case (msgs, (subj, child, log)) ⇒
|
||||||
msgs should ===(
|
msgs should ===(
|
||||||
GotSignal(Failed(`ex`, `child`)) ::
|
ChildEvent(GotSignal(PreRestart)) ::
|
||||||
ChildEvent(GotSignal(PreRestart(`ex`))) ::
|
ChildEvent(GotSignal(PostRestart)) :: Nil)
|
||||||
ChildEvent(GotSignal(PostRestart(`ex`))) :: Nil)
|
|
||||||
log.assertDone(expectTimeout)
|
log.assertDone(expectTimeout)
|
||||||
child ! BecomeInert(self) // necessary to avoid PostStop/Terminated interference
|
child ! BecomeInert(self) // necessary to avoid PostStop/Terminated interference
|
||||||
(subj, child)
|
(subj, child)
|
||||||
|
|
@ -327,7 +335,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def `05 must reset behavior upon Restart`(): Unit = sync(setup("ctx05") { (ctx, startWith) ⇒
|
def `05 must reset behavior upon Restart`(): Unit = sync(setup("ctx05", Some(Restarter[Exception]())) { (ctx, startWith) ⇒
|
||||||
val self = ctx.self
|
val self = ctx.self
|
||||||
val ex = new Exception("KABOOM05")
|
val ex = new Exception("KABOOM05")
|
||||||
startWith
|
startWith
|
||||||
|
|
@ -336,48 +344,36 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
val log = muteExpectedException[Exception]("KABOOM05")
|
val log = muteExpectedException[Exception]("KABOOM05")
|
||||||
subj ! Throw(ex)
|
subj ! Throw(ex)
|
||||||
(subj, log)
|
(subj, log)
|
||||||
}.expectFailureKeep(expectTimeout) {
|
|
||||||
case (f, (subj, log)) ⇒
|
|
||||||
f.child should ===(subj)
|
|
||||||
f.cause should ===(ex)
|
|
||||||
Failed.Restart
|
|
||||||
}.expectMessage(expectTimeout) {
|
}.expectMessage(expectTimeout) {
|
||||||
case (msg, (subj, log)) ⇒
|
case (msg, (subj, log)) ⇒
|
||||||
msg should ===(GotSignal(PostRestart(ex)))
|
msg should ===(GotSignal(PostRestart))
|
||||||
log.assertDone(expectTimeout)
|
log.assertDone(expectTimeout)
|
||||||
subj
|
subj
|
||||||
}.stimulate(_ ! Ping(self), _ ⇒ Pong1)
|
}
|
||||||
|
.stimulate(_ ! Ping(self), _ ⇒ Pong1)
|
||||||
})
|
})
|
||||||
|
|
||||||
def `06 must not reset behavior upon Resume`(): Unit = sync(setup("ctx06") { (ctx, startWith) ⇒
|
def `06 must not reset behavior upon Resume`(): Unit = sync(setup("ctx06", Some(Restarter[Exception](resume = true))) { (ctx, startWith) ⇒
|
||||||
val self = ctx.self
|
val self = ctx.self
|
||||||
val ex = new Exception("KABOOM05")
|
val ex = new Exception("KABOOM06")
|
||||||
startWith
|
startWith
|
||||||
.stimulate(_ ! BecomeInert(self), _ ⇒ BecameInert)
|
.stimulate(_ ! BecomeInert(self), _ ⇒ BecameInert)
|
||||||
.stimulate(_ ! Ping(self), _ ⇒ Pong2).keep { subj ⇒
|
.stimulate(_ ! Ping(self), _ ⇒ Pong2).keep { subj ⇒
|
||||||
|
muteExpectedException[Exception]("KABOOM06", occurrences = 1)
|
||||||
subj ! Throw(ex)
|
subj ! Throw(ex)
|
||||||
}.expectFailureKeep(expectTimeout) { (f, subj) ⇒
|
|
||||||
f.child should ===(subj)
|
|
||||||
f.cause should ===(ex)
|
|
||||||
Failed.Resume
|
|
||||||
}.stimulate(_ ! Ping(self), _ ⇒ Pong2)
|
}.stimulate(_ ! Ping(self), _ ⇒ Pong2)
|
||||||
})
|
})
|
||||||
|
|
||||||
def `07 must stop upon Stop`(): Unit = sync(setup("ctx07") { (ctx, startWith) ⇒
|
def `07 must stop upon Stop`(): Unit = sync(setup("ctx07") { (ctx, startWith) ⇒
|
||||||
val self = ctx.self
|
val self = ctx.self
|
||||||
val ex = new Exception("KABOOM05")
|
val ex = new Exception("KABOOM07")
|
||||||
startWith
|
startWith
|
||||||
.stimulate(_ ! Ping(self), _ ⇒ Pong1).keep { subj ⇒
|
.stimulate(_ ! Ping(self), _ ⇒ Pong1).keep { subj ⇒
|
||||||
|
muteExpectedException[Exception]("KABOOM07", occurrences = 1)
|
||||||
subj ! Throw(ex)
|
subj ! Throw(ex)
|
||||||
ctx.watch(subj)
|
ctx.watch(subj)
|
||||||
}.expectFailureKeep(expectTimeout) { (f, subj) ⇒
|
}.expectMulti(expectTimeout, 2) { (msgs, subj) ⇒
|
||||||
f.child should ===(subj)
|
msgs.toSet should ===(Set(Left(Terminated(subj)(null)), Right(GotSignal(PostStop))))
|
||||||
f.cause should ===(ex)
|
|
||||||
Failed.Stop
|
|
||||||
}.expectMessageKeep(expectTimeout) { (msg, _) ⇒
|
|
||||||
msg should ===(GotSignal(PostStop))
|
|
||||||
}.expectTermination(expectTimeout) { (t, subj) ⇒
|
|
||||||
t.ref should ===(subj)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -405,7 +401,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
msg should ===(Watched)
|
msg should ===(Watched)
|
||||||
child ! Stop
|
child ! Stop
|
||||||
}.expectMessage(expectTimeout) { (msg, child) ⇒
|
}.expectMessage(expectTimeout) { (msg, child) ⇒
|
||||||
msg should ===(GotSignal(Terminated(child)))
|
msg should ===(GotSignal(Terminated(child)(null)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -417,11 +413,11 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
child ! Stop
|
child ! Stop
|
||||||
}.expectTermination(expectTimeout) {
|
}.expectTermination(expectTimeout) {
|
||||||
case (t, (subj, child)) ⇒
|
case (t, (subj, child)) ⇒
|
||||||
t should ===(Terminated(child))
|
t should ===(Terminated(child)(null))
|
||||||
subj ! Watch(child, blackhole)
|
subj ! Watch(child, blackhole)
|
||||||
child
|
child
|
||||||
}.expectMessage(expectTimeout) { (msg, child) ⇒
|
}.expectMessage(expectTimeout) { (msg, child) ⇒
|
||||||
msg should ===(GotSignal(Terminated(child)))
|
msg should ===(GotSignal(Terminated(child)(null)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -441,7 +437,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
child ! Stop
|
child ! Stop
|
||||||
child
|
child
|
||||||
}.expectTermination(expectTimeout) { (t, child) ⇒
|
}.expectTermination(expectTimeout) { (t, child) ⇒
|
||||||
t should ===(Terminated(child))
|
t should ===(Terminated(child)(null))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -449,6 +445,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
val self = ctx.self
|
val self = ctx.self
|
||||||
startWith.mkChild(None, ctx.spawnAdapter(ChildEvent), self).keep {
|
startWith.mkChild(None, ctx.spawnAdapter(ChildEvent), self).keep {
|
||||||
case (subj, child) ⇒
|
case (subj, child) ⇒
|
||||||
|
muteExpectedException[DeathPactException]()
|
||||||
subj ! Watch(child, self)
|
subj ! Watch(child, self)
|
||||||
}.expectMessageKeep(expectTimeout) {
|
}.expectMessageKeep(expectTimeout) {
|
||||||
case (msg, (subj, child)) ⇒
|
case (msg, (subj, child)) ⇒
|
||||||
|
|
@ -458,10 +455,6 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
case (msg, (subj, child)) ⇒
|
case (msg, (subj, child)) ⇒
|
||||||
msg should ===(BecameCareless)
|
msg should ===(BecameCareless)
|
||||||
child ! Stop
|
child ! Stop
|
||||||
}.expectFailureKeep(expectTimeout) {
|
|
||||||
case (f, (subj, child)) ⇒
|
|
||||||
f.child should ===(subj)
|
|
||||||
Failed.Stop
|
|
||||||
}.expectMessage(expectTimeout) {
|
}.expectMessage(expectTimeout) {
|
||||||
case (msg, (subj, child)) ⇒
|
case (msg, (subj, child)) ⇒
|
||||||
msg should ===(GotSignal(PostStop))
|
msg should ===(GotSignal(PostStop))
|
||||||
|
|
@ -493,7 +486,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
startWith
|
startWith
|
||||||
.stimulate(_ ! SetTimeout(1.nano, self), _ ⇒ TimeoutSet)
|
.stimulate(_ ! SetTimeout(1.nano, self), _ ⇒ TimeoutSet)
|
||||||
.expectMessage(expectTimeout) { (msg, _) ⇒
|
.expectMessage(expectTimeout) { (msg, _) ⇒
|
||||||
msg should ===(GotSignal(ReceiveTimeout))
|
msg should ===(GotReceiveTimeout)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -525,54 +518,66 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
msg should ===(Pong1)
|
msg should ===(Pong1)
|
||||||
ctx.stop(subj)
|
ctx.stop(subj)
|
||||||
adapter
|
adapter
|
||||||
}.expectMessageKeep(expectTimeout) { (msg, _) ⇒
|
}.expectMulti(expectTimeout, 2) { (msgs, adapter) ⇒
|
||||||
msg should ===(GotSignal(PostStop))
|
msgs.toSet should ===(Set(Left(Terminated(adapter)(null)), Right(GotSignal(PostStop))))
|
||||||
}.expectTermination(expectTimeout) { (t, adapter) ⇒
|
|
||||||
t.ref should ===(adapter)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
object `An ActorContext` extends Tests {
|
trait Normal extends Tests {
|
||||||
override def suite = "basic"
|
override def suite = "basic"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = subject(ctx.self)
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = subject(ctx.self)
|
||||||
}
|
}
|
||||||
|
object `An ActorContext (native)` extends Normal with NativeSystem
|
||||||
|
object `An ActorContext (adapted)` extends Normal with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with widened Behavior` extends Tests {
|
trait Widened extends Tests {
|
||||||
override def suite = "widened"
|
override def suite = "widened"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = subject(ctx.self).widen { case x ⇒ x }
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = subject(ctx.self).widen { case x ⇒ x }
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with widened Behavior (native)` extends Widened with NativeSystem
|
||||||
|
object `An ActorContext with widened Behavior (adapted)` extends Widened with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with SynchronousSelf` extends Tests {
|
trait SynchronousSelf extends Tests {
|
||||||
override def suite = "synchronous"
|
override def suite = "synchronous"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = SynchronousSelf(self ⇒ subject(ctx.self))
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = SynchronousSelf(self ⇒ subject(ctx.self))
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with SynchronousSelf (native)` extends SynchronousSelf with NativeSystem
|
||||||
|
object `An ActorContext with SynchronousSelf (adapted)` extends SynchronousSelf with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with non-matching Tap` extends Tests {
|
trait NonMatchingTap extends Tests {
|
||||||
override def suite = "TapNonMatch"
|
override def suite = "TapNonMatch"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = Tap({ case null ⇒ }, subject(ctx.self))
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = Tap({ case null ⇒ }, subject(ctx.self))
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with non-matching Tap (native)` extends NonMatchingTap with NativeSystem
|
||||||
|
object `An ActorContext with non-matching Tap (adapted)` extends NonMatchingTap with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with matching Tap` extends Tests {
|
trait MatchingTap extends Tests {
|
||||||
override def suite = "TapMatch"
|
override def suite = "TapMatch"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = Tap({ case _ ⇒ }, subject(ctx.self))
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] = Tap({ case _ ⇒ }, subject(ctx.self))
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with matching Tap (native)` extends MatchingTap with NativeSystem
|
||||||
|
object `An ActorContext with matching Tap (adapted)` extends MatchingTap with AdaptedSystem
|
||||||
|
|
||||||
private val stoppingBehavior = Full[Command] { case Msg(_, Stop) ⇒ Stopped }
|
private val stoppingBehavior = Full[Command] { case Msg(_, Stop) ⇒ Stopped }
|
||||||
|
|
||||||
object `An ActorContext with And (left)` extends Tests {
|
trait AndLeft extends Tests {
|
||||||
override def suite = "and"
|
override def suite = "and"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
||||||
And(subject(ctx.self), stoppingBehavior)
|
And(subject(ctx.self), stoppingBehavior)
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with And (left, native)` extends AndLeft with NativeSystem
|
||||||
|
object `An ActorContext with And (left, adapted)` extends AndLeft with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with And (right)` extends Tests {
|
trait AndRight extends Tests {
|
||||||
override def suite = "and"
|
override def suite = "and"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
||||||
And(stoppingBehavior, subject(ctx.self))
|
And(stoppingBehavior, subject(ctx.self))
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with And (right, native)` extends AndRight with NativeSystem
|
||||||
|
object `An ActorContext with And (right, adapted)` extends AndRight with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with Or (left)` extends Tests {
|
trait OrLeft extends Tests {
|
||||||
override def suite = "basic"
|
override def suite = "basic"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
||||||
Or(subject(ctx.self), stoppingBehavior)
|
Or(subject(ctx.self), stoppingBehavior)
|
||||||
|
|
@ -581,8 +586,10 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
ref ! Stop
|
ref ! Stop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with Or (left, native)` extends OrLeft with NativeSystem
|
||||||
|
object `An ActorContext with Or (left, adapted)` extends OrLeft with AdaptedSystem
|
||||||
|
|
||||||
object `An ActorContext with Or (right)` extends Tests {
|
trait OrRight extends Tests {
|
||||||
override def suite = "basic"
|
override def suite = "basic"
|
||||||
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
override def behavior(ctx: ActorContext[Event]): Behavior[Command] =
|
||||||
Or(stoppingBehavior, subject(ctx.self))
|
Or(stoppingBehavior, subject(ctx.self))
|
||||||
|
|
@ -591,5 +598,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
||||||
ref ! Stop
|
ref ! Stop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `An ActorContext with Or (right, native)` extends OrRight with NativeSystem
|
||||||
|
object `An ActorContext with Or (right, adapted)` extends OrRight with AdaptedSystem
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
case class GetState(replyTo: ActorRef[State]) extends Command
|
case class GetState(replyTo: ActorRef[State]) extends Command
|
||||||
object GetState {
|
object GetState {
|
||||||
def apply()(implicit inbox: Inbox.SyncInbox[State]): GetState = GetState(inbox.ref)
|
def apply()(implicit inbox: Inbox[State]): GetState = GetState(inbox.ref)
|
||||||
}
|
}
|
||||||
case class AuxPing(id: Int) extends Command {
|
case class AuxPing(id: Int) extends Command {
|
||||||
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Pong :: Nil
|
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Pong :: Nil
|
||||||
|
|
@ -47,12 +47,13 @@ class BehaviorSpec extends TypedSpec {
|
||||||
val StateB: State = new State { override def toString = "StateB"; override def next = StateA }
|
val StateB: State = new State { override def toString = "StateB"; override def next = StateA }
|
||||||
|
|
||||||
trait Common {
|
trait Common {
|
||||||
|
def system: ActorSystem[TypedSpec.Command]
|
||||||
def behavior(monitor: ActorRef[Event]): Behavior[Command]
|
def behavior(monitor: ActorRef[Event]): Behavior[Command]
|
||||||
|
|
||||||
case class Setup(ctx: EffectfulActorContext[Command], inbox: Inbox.SyncInbox[Event])
|
case class Setup(ctx: EffectfulActorContext[Command], inbox: Inbox[Event])
|
||||||
|
|
||||||
protected def mkCtx(requirePreStart: Boolean = false, factory: (ActorRef[Event]) ⇒ Behavior[Command] = behavior) = {
|
protected def mkCtx(requirePreStart: Boolean = false, factory: (ActorRef[Event]) ⇒ Behavior[Command] = behavior) = {
|
||||||
val inbox = Inbox.sync[Event]("evt")
|
val inbox = Inbox[Event]("evt")
|
||||||
val ctx = new EffectfulActorContext("ctx", Props(factory(inbox.ref)), system)
|
val ctx = new EffectfulActorContext("ctx", Props(factory(inbox.ref)), system)
|
||||||
val msgs = inbox.receiveAll()
|
val msgs = inbox.receiveAll()
|
||||||
if (requirePreStart)
|
if (requirePreStart)
|
||||||
|
|
@ -71,7 +72,7 @@ class BehaviorSpec extends TypedSpec {
|
||||||
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
|
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
|
||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
def check[T](command: Command, aux: T*)(implicit inbox: Inbox.SyncInbox[T]): Setup = {
|
def check[T](command: Command, aux: T*)(implicit inbox: Inbox[T]): Setup = {
|
||||||
setup.ctx.run(command)
|
setup.ctx.run(command)
|
||||||
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
|
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
|
||||||
inbox.receiveAll() should ===(aux)
|
inbox.receiveAll() should ===(aux)
|
||||||
|
|
@ -83,7 +84,7 @@ class BehaviorSpec extends TypedSpec {
|
||||||
setup.inbox.receiveAll() should ===(expected ++ expected)
|
setup.inbox.receiveAll() should ===(expected ++ expected)
|
||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
def check2[T](command: Command, aux: T*)(implicit inbox: Inbox.SyncInbox[T]): Setup = {
|
def check2[T](command: Command, aux: T*)(implicit inbox: Inbox[T]): Setup = {
|
||||||
setup.ctx.run(command)
|
setup.ctx.run(command)
|
||||||
val expected = command.expectedResponse(setup.ctx)
|
val expected = command.expectedResponse(setup.ctx)
|
||||||
setup.inbox.receiveAll() should ===(expected ++ expected)
|
setup.inbox.receiveAll() should ===(expected ++ expected)
|
||||||
|
|
@ -109,65 +110,31 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to PreRestart`(): Unit = {
|
def `must react to PreRestart`(): Unit = {
|
||||||
mkCtx().check(PreRestart(ex))
|
mkCtx().check(PreRestart)
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to PreRestart after a message`(): Unit = {
|
def `must react to PreRestart after a message`(): Unit = {
|
||||||
mkCtx().check(GetSelf).check(PreRestart(ex))
|
mkCtx().check(GetSelf).check(PreRestart)
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to PostRestart`(): Unit = {
|
def `must react to PostRestart`(): Unit = {
|
||||||
mkCtx().check(PostRestart(ex))
|
mkCtx().check(PostRestart)
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to a message after PostRestart`(): Unit = {
|
def `must react to a message after PostRestart`(): Unit = {
|
||||||
mkCtx().check(PostRestart(ex)).check(GetSelf)
|
mkCtx().check(PostRestart).check(GetSelf)
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to Failed`(): Unit = {
|
|
||||||
val setup @ Setup(ctx, inbox) = mkCtx()
|
|
||||||
val f = Failed(ex, inbox.ref)
|
|
||||||
setup.check(f)
|
|
||||||
f.getDecision should ===(Failed.Restart)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to Failed after a message`(): Unit = {
|
|
||||||
val setup @ Setup(ctx, inbox) = mkCtx().check(GetSelf)
|
|
||||||
val f = Failed(ex, inbox.ref)
|
|
||||||
setup.check(f)
|
|
||||||
f.getDecision should ===(Failed.Restart)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to a message after Failed`(): Unit = {
|
|
||||||
val setup @ Setup(ctx, inbox) = mkCtx()
|
|
||||||
val f = Failed(ex, inbox.ref)
|
|
||||||
setup.check(f)
|
|
||||||
f.getDecision should ===(Failed.Restart)
|
|
||||||
setup.check(GetSelf)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to ReceiveTimeout`(): Unit = {
|
|
||||||
mkCtx().check(ReceiveTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to ReceiveTimeout after a message`(): Unit = {
|
|
||||||
mkCtx().check(GetSelf).check(ReceiveTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to a message after ReceiveTimeout`(): Unit = {
|
|
||||||
mkCtx().check(ReceiveTimeout).check(GetSelf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to Terminated`(): Unit = {
|
def `must react to Terminated`(): Unit = {
|
||||||
mkCtx().check(Terminated(Inbox.sync("x").ref))
|
mkCtx().check(Terminated(Inbox("x").ref)(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to Terminated after a message`(): Unit = {
|
def `must react to Terminated after a message`(): Unit = {
|
||||||
mkCtx().check(GetSelf).check(Terminated(Inbox.sync("x").ref))
|
mkCtx().check(GetSelf).check(Terminated(Inbox("x").ref)(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to a message after Terminated`(): Unit = {
|
def `must react to a message after Terminated`(): Unit = {
|
||||||
mkCtx().check(Terminated(Inbox.sync("x").ref)).check(GetSelf)
|
mkCtx().check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +169,7 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Become extends Common with Unhandled {
|
trait Become extends Common with Unhandled {
|
||||||
private implicit val inbox = Inbox.sync[State]("state")
|
private implicit val inbox = Inbox[State]("state")
|
||||||
|
|
||||||
def `must be in state A`(): Unit = {
|
def `must be in state A`(): Unit = {
|
||||||
mkCtx().check(GetState(), StateA)
|
mkCtx().check(GetState(), StateA)
|
||||||
|
|
@ -227,61 +194,27 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to PreRestart after swap`(): Unit = {
|
def `must react to PreRestart after swap`(): Unit = {
|
||||||
mkCtx().check(Swap).check(PreRestart(ex))
|
mkCtx().check(Swap).check(PreRestart)
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to PreRestart after a message after swap`(): Unit = {
|
def `must react to PreRestart after a message after swap`(): Unit = {
|
||||||
mkCtx().check(Swap).check(GetSelf).check(PreRestart(ex))
|
mkCtx().check(Swap).check(GetSelf).check(PreRestart)
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to a message after PostRestart after swap`(): Unit = {
|
def `must react to a message after PostRestart after swap`(): Unit = {
|
||||||
mkCtx().check(PostRestart(ex)).check(Swap).check(GetSelf)
|
mkCtx().check(PostRestart).check(Swap).check(GetSelf)
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to Failed after swap`(): Unit = {
|
|
||||||
val setup @ Setup(ctx, inbox) = mkCtx().check(Swap)
|
|
||||||
val f = Failed(ex, inbox.ref)
|
|
||||||
setup.check(f)
|
|
||||||
f.getDecision should ===(Failed.Restart)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to Failed after a message after swap`(): Unit = {
|
|
||||||
val setup @ Setup(ctx, inbox) = mkCtx().check(Swap).check(GetSelf)
|
|
||||||
val f = Failed(ex, inbox.ref)
|
|
||||||
setup.check(f)
|
|
||||||
f.getDecision should ===(Failed.Restart)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to a message after Failed after swap`(): Unit = {
|
|
||||||
val setup @ Setup(ctx, inbox) = mkCtx().check(Swap)
|
|
||||||
val f = Failed(ex, inbox.ref)
|
|
||||||
setup.check(f)
|
|
||||||
f.getDecision should ===(Failed.Restart)
|
|
||||||
setup.check(GetSelf)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to ReceiveTimeout after swap`(): Unit = {
|
|
||||||
mkCtx().check(Swap).check(ReceiveTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to ReceiveTimeout after a message after swap`(): Unit = {
|
|
||||||
mkCtx().check(Swap).check(GetSelf).check(ReceiveTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
def `must react to a message after ReceiveTimeout after swap`(): Unit = {
|
|
||||||
mkCtx().check(Swap).check(ReceiveTimeout).check(GetSelf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to Terminated after swap`(): Unit = {
|
def `must react to Terminated after swap`(): Unit = {
|
||||||
mkCtx().check(Swap).check(Terminated(Inbox.sync("x").ref))
|
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to Terminated after a message after swap`(): Unit = {
|
def `must react to Terminated after a message after swap`(): Unit = {
|
||||||
mkCtx().check(Swap).check(GetSelf).check(Terminated(Inbox.sync("x").ref))
|
mkCtx().check(Swap).check(GetSelf).check(Terminated(Inbox("x").ref)(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
def `must react to a message after Terminated after swap`(): Unit = {
|
def `must react to a message after Terminated after swap`(): Unit = {
|
||||||
mkCtx().check(Swap).check(Terminated(Inbox.sync("x").ref)).check(GetSelf)
|
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,10 +223,6 @@ class BehaviorSpec extends TypedSpec {
|
||||||
Full {
|
Full {
|
||||||
case Sig(ctx, signal) ⇒
|
case Sig(ctx, signal) ⇒
|
||||||
monitor ! GotSignal(signal)
|
monitor ! GotSignal(signal)
|
||||||
signal match {
|
|
||||||
case f: Failed ⇒ f.decide(Failed.Restart)
|
|
||||||
case _ ⇒
|
|
||||||
}
|
|
||||||
Same
|
Same
|
||||||
case Msg(ctx, GetSelf) ⇒
|
case Msg(ctx, GetSelf) ⇒
|
||||||
monitor ! Self(ctx.self)
|
monitor ! Self(ctx.self)
|
||||||
|
|
@ -317,21 +246,19 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object `A Full Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait FullBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = mkFull(monitor)
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = mkFull(monitor)
|
||||||
}
|
}
|
||||||
|
object `A Full Behavior (native)` extends FullBehavior with NativeSystem
|
||||||
|
object `A Full Behavior (adapted)` extends FullBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A FullTotal Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait FullTotalBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = behv(monitor, StateA)
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = behv(monitor, StateA)
|
||||||
private def behv(monitor: ActorRef[Event], state: State): Behavior[Command] = {
|
private def behv(monitor: ActorRef[Event], state: State): Behavior[Command] = {
|
||||||
import ScalaDSL.{ FullTotal, Msg, Sig, Same, Unhandled, Stopped }
|
import ScalaDSL.{ FullTotal, Msg, Sig, Same, Unhandled, Stopped }
|
||||||
FullTotal {
|
FullTotal {
|
||||||
case Sig(ctx, signal) ⇒
|
case Sig(ctx, signal) ⇒
|
||||||
monitor ! GotSignal(signal)
|
monitor ! GotSignal(signal)
|
||||||
signal match {
|
|
||||||
case f: Failed ⇒ f.decide(Failed.Restart)
|
|
||||||
case _ ⇒
|
|
||||||
}
|
|
||||||
Same
|
Same
|
||||||
case Msg(ctx, GetSelf) ⇒
|
case Msg(ctx, GetSelf) ⇒
|
||||||
monitor ! Self(ctx.self)
|
monitor ! Self(ctx.self)
|
||||||
|
|
@ -356,36 +283,48 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `A FullTotal Behavior (native)` extends FullTotalBehavior with NativeSystem
|
||||||
|
object `A FullTotal Behavior (adapted)` extends FullTotalBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A Widened Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait WidenedBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.Widened(mkFull(monitor), { case x ⇒ x })
|
ScalaDSL.Widened(mkFull(monitor), { case x ⇒ x })
|
||||||
}
|
}
|
||||||
|
object `A Widened Behavior (native)` extends WidenedBehavior with NativeSystem
|
||||||
|
object `A Widened Behavior (adapted)` extends WidenedBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A ContextAware Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait ContextAwareBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.ContextAware(ctx ⇒ mkFull(monitor))
|
ScalaDSL.ContextAware(ctx ⇒ mkFull(monitor))
|
||||||
}
|
}
|
||||||
|
object `A ContextAware Behavior (native)` extends ContextAwareBehavior with NativeSystem
|
||||||
|
object `A ContextAware Behavior (adapted)` extends ContextAwareBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A SelfAware Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait SelfAwareBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.SelfAware(self ⇒ mkFull(monitor))
|
ScalaDSL.SelfAware(self ⇒ mkFull(monitor))
|
||||||
}
|
}
|
||||||
|
object `A SelfAware Behavior (native)` extends SelfAwareBehavior with NativeSystem
|
||||||
|
object `A SelfAware Behavior (adapted)` extends SelfAwareBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A non-matching Tap Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait NonMatchingTapBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.Tap({ case null ⇒ }, mkFull(monitor))
|
ScalaDSL.Tap({ case null ⇒ }, mkFull(monitor))
|
||||||
}
|
}
|
||||||
|
object `A non-matching Tap Behavior (native)` extends NonMatchingTapBehavior with NativeSystem
|
||||||
|
object `A non-matching Tap Behavior (adapted)` extends NonMatchingTapBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A matching Tap Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait MatchingTapBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.Tap({ case _ ⇒ }, mkFull(monitor))
|
ScalaDSL.Tap({ case _ ⇒ }, mkFull(monitor))
|
||||||
}
|
}
|
||||||
|
object `A matching Tap Behavior (native)` extends MatchingTapBehavior with NativeSystem
|
||||||
|
object `A matching Tap Behavior (adapted)` extends MatchingTapBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A SynchronousSelf Behavior` extends Messages with BecomeWithLifecycle with Stoppable {
|
trait SynchronousSelfBehavior extends Messages with BecomeWithLifecycle with Stoppable {
|
||||||
import ScalaDSL._
|
import ScalaDSL._
|
||||||
|
|
||||||
implicit private val inbox = Inbox.sync[Command]("syncself")
|
implicit private val inbox = Inbox[Command]("syncself")
|
||||||
|
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
SynchronousSelf(self ⇒ mkFull(monitor))
|
SynchronousSelf(self ⇒ mkFull(monitor))
|
||||||
|
|
@ -413,9 +352,11 @@ class BehaviorSpec extends TypedSpec {
|
||||||
ctx.currentBehavior should ===(Stopped[Command])
|
ctx.currentBehavior should ===(Stopped[Command])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `A SynchronourSelf Behavior (native)` extends SynchronousSelfBehavior with NativeSystem
|
||||||
|
object `A SynchronousSelf Behavior (adapted)` extends SynchronousSelfBehavior with AdaptedSystem
|
||||||
|
|
||||||
trait And extends Common {
|
trait And extends Common {
|
||||||
private implicit val inbox = Inbox.sync[State]("and")
|
private implicit val inbox = Inbox[State]("and")
|
||||||
|
|
||||||
private def behavior2(monitor: ActorRef[Event]): Behavior[Command] =
|
private def behavior2(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.And(mkFull(monitor), mkFull(monitor))
|
ScalaDSL.And(mkFull(monitor), mkFull(monitor))
|
||||||
|
|
@ -431,15 +372,19 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object `A Behavior combined with And (left)` extends Messages with BecomeWithLifecycle with And {
|
trait BehaviorAndLeft extends Messages with BecomeWithLifecycle with And {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.And(mkFull(monitor), ScalaDSL.Empty)
|
ScalaDSL.And(mkFull(monitor), ScalaDSL.Empty)
|
||||||
}
|
}
|
||||||
|
object `A Behavior combined with And (left, native)` extends BehaviorAndLeft with NativeSystem
|
||||||
|
object `A Behavior combined with And (left, adapted)` extends BehaviorAndLeft with NativeSystem
|
||||||
|
|
||||||
object `A Behavior combined with And (right)` extends Messages with BecomeWithLifecycle with And {
|
trait BehaviorAndRight extends Messages with BecomeWithLifecycle with And {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.And(ScalaDSL.Empty, mkFull(monitor))
|
ScalaDSL.And(ScalaDSL.Empty, mkFull(monitor))
|
||||||
}
|
}
|
||||||
|
object `A Behavior combined with And (right, native)` extends BehaviorAndRight with NativeSystem
|
||||||
|
object `A Behavior combined with And (right, adapted)` extends BehaviorAndRight with NativeSystem
|
||||||
|
|
||||||
trait Or extends Common {
|
trait Or extends Common {
|
||||||
private def strange(monitor: ActorRef[Event]): Behavior[Command] =
|
private def strange(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
|
|
@ -470,17 +415,21 @@ class BehaviorSpec extends TypedSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object `A Behavior combined with Or (left)` extends Messages with BecomeWithLifecycle with Or {
|
trait BehaviorOrLeft extends Messages with BecomeWithLifecycle with Or {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.Or(mkFull(monitor), ScalaDSL.Empty)
|
ScalaDSL.Or(mkFull(monitor), ScalaDSL.Empty)
|
||||||
}
|
}
|
||||||
|
object `A Behavior combined with Or (left, native)` extends BehaviorOrLeft with NativeSystem
|
||||||
|
object `A Behavior combined with Or (left, adapted)` extends BehaviorOrLeft with NativeSystem
|
||||||
|
|
||||||
object `A Behavior combined with Or (right)` extends Messages with BecomeWithLifecycle with Or {
|
trait BehaviorOrRight extends Messages with BecomeWithLifecycle with Or {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.Or(ScalaDSL.Empty, mkFull(monitor))
|
ScalaDSL.Or(ScalaDSL.Empty, mkFull(monitor))
|
||||||
}
|
}
|
||||||
|
object `A Behavior combined with Or (right, native)` extends BehaviorOrRight with NativeSystem
|
||||||
|
object `A Behavior combined with Or (right, adapted)` extends BehaviorOrRight with NativeSystem
|
||||||
|
|
||||||
object `A Partial Behavior` extends Messages with Become with Stoppable {
|
trait PartialBehavior extends Messages with Become with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = behv(monitor, StateA)
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = behv(monitor, StateA)
|
||||||
def behv(monitor: ActorRef[Event], state: State): Behavior[Command] =
|
def behv(monitor: ActorRef[Event], state: State): Behavior[Command] =
|
||||||
ScalaDSL.Partial {
|
ScalaDSL.Partial {
|
||||||
|
|
@ -502,8 +451,10 @@ class BehaviorSpec extends TypedSpec {
|
||||||
case Stop ⇒ ScalaDSL.Stopped
|
case Stop ⇒ ScalaDSL.Stopped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `A Partial Behavior (native)` extends PartialBehavior with NativeSystem
|
||||||
|
object `A Partial Behavior (adapted)` extends PartialBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A Total Behavior` extends Messages with Become with Stoppable {
|
trait TotalBehavior extends Messages with Become with Stoppable {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = behv(monitor, StateA)
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] = behv(monitor, StateA)
|
||||||
def behv(monitor: ActorRef[Event], state: State): Behavior[Command] =
|
def behv(monitor: ActorRef[Event], state: State): Behavior[Command] =
|
||||||
ScalaDSL.Total {
|
ScalaDSL.Total {
|
||||||
|
|
@ -527,8 +478,10 @@ class BehaviorSpec extends TypedSpec {
|
||||||
case _: AuxPing ⇒ ScalaDSL.Unhandled
|
case _: AuxPing ⇒ ScalaDSL.Unhandled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `A Total Behavior (native)` extends TotalBehavior with NativeSystem
|
||||||
|
object `A Total Behavior (adapted)` extends TotalBehavior with AdaptedSystem
|
||||||
|
|
||||||
object `A Static Behavior` extends Messages {
|
trait StaticBehavior extends Messages {
|
||||||
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
override def behavior(monitor: ActorRef[Event]): Behavior[Command] =
|
||||||
ScalaDSL.Static {
|
ScalaDSL.Static {
|
||||||
case Ping ⇒ monitor ! Pong
|
case Ping ⇒ monitor ! Pong
|
||||||
|
|
@ -541,4 +494,6 @@ class BehaviorSpec extends TypedSpec {
|
||||||
case _: AuxPing ⇒
|
case _: AuxPing ⇒
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
object `A Static Behavior (native)` extends StaticBehavior with NativeSystem
|
||||||
|
object `A Static Behavior (adapted)` extends StaticBehavior with AdaptedSystem
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,20 @@ package akka.typed
|
||||||
import ScalaDSL._
|
import ScalaDSL._
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import akka.testkit.AkkaSpec
|
||||||
|
import akka.util.Timeout
|
||||||
|
|
||||||
|
@RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||||
class PerformanceSpec extends TypedSpec(
|
class PerformanceSpec extends TypedSpec(
|
||||||
ConfigFactory.parseString("""
|
ConfigFactory.parseString("""
|
||||||
# increase this if you do real benchmarking
|
# increase this if you do real benchmarking
|
||||||
akka.typed.PerformanceSpec.iterations=100000
|
akka.typed.PerformanceSpec.iterations=100000
|
||||||
""")) {
|
""")) {
|
||||||
|
|
||||||
object `A static behavior` {
|
override def setTimeout = Timeout(20.seconds)
|
||||||
|
|
||||||
object `must be fast` {
|
object `A static behavior` {
|
||||||
|
|
||||||
case class Ping(x: Int, pong: ActorRef[Pong], report: ActorRef[Pong])
|
case class Ping(x: Int, pong: ActorRef[Pong], report: ActorRef[Pong])
|
||||||
case class Pong(x: Int, ping: ActorRef[Ping], report: ActorRef[Pong])
|
case class Pong(x: Int, ping: ActorRef[Ping], report: ActorRef[Pong])
|
||||||
|
|
@ -46,7 +50,7 @@ class PerformanceSpec extends TypedSpec(
|
||||||
} ping ! Ping(count, pong, ctx.self)
|
} ping ! Ping(count, pong, ctx.self)
|
||||||
|
|
||||||
start
|
start
|
||||||
}.expectMultipleMessages(60.seconds, pairs * pings) { (msgs, start) ⇒
|
}.expectMultipleMessages(10.seconds, pairs * pings) { (msgs, start) ⇒
|
||||||
val stop = Deadline.now
|
val stop = Deadline.now
|
||||||
|
|
||||||
val rate = 2L * count * pairs * pings / (stop - start).toMillis
|
val rate = 2L * count * pairs * pings / (stop - start).toMillis
|
||||||
|
|
@ -54,7 +58,10 @@ class PerformanceSpec extends TypedSpec(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val iterations = system.settings.config.getInt("akka.typed.PerformanceSpec.iterations")
|
val iterations = nativeSystem.settings.config.getInt("akka.typed.PerformanceSpec.iterations")
|
||||||
|
|
||||||
|
trait CommonTests {
|
||||||
|
implicit def system: ActorSystem[TypedSpec.Command]
|
||||||
|
|
||||||
def `01 when warming up`(): Unit = sync(runTest("01")(behavior(1, 1, iterations, "dispatcher-1")))
|
def `01 when warming up`(): Unit = sync(runTest("01")(behavior(1, 1, iterations, "dispatcher-1")))
|
||||||
def `02 when using a single message on a single thread`(): Unit = sync(runTest("02")(behavior(1, 1, iterations, "dispatcher-1")))
|
def `02 when using a single message on a single thread`(): Unit = sync(runTest("02")(behavior(1, 1, iterations, "dispatcher-1")))
|
||||||
|
|
@ -65,8 +72,10 @@ class PerformanceSpec extends TypedSpec(
|
||||||
def `07 when using 4 pairs with 10 messages`(): Unit = sync(runTest("07")(behavior(4, 10, iterations, "dispatcher-8")))
|
def `07 when using 4 pairs with 10 messages`(): Unit = sync(runTest("07")(behavior(4, 10, iterations, "dispatcher-8")))
|
||||||
def `08 when using 8 pairs with a single message`(): Unit = sync(runTest("08")(behavior(8, 1, iterations, "dispatcher-8")))
|
def `08 when using 8 pairs with a single message`(): Unit = sync(runTest("08")(behavior(8, 1, iterations, "dispatcher-8")))
|
||||||
def `09 when using 8 pairs with 10 messages`(): Unit = sync(runTest("09")(behavior(8, 10, iterations, "dispatcher-8")))
|
def `09 when using 8 pairs with 10 messages`(): Unit = sync(runTest("09")(behavior(8, 10, iterations, "dispatcher-8")))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object `must be fast with native ActorSystem` extends CommonTests with NativeSystem
|
||||||
|
object `must be fast with ActorSystemAdapter` extends CommonTests with AdaptedSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,10 @@ object StepWise {
|
||||||
private final case class ThunkV(f: Any ⇒ Any) extends AST
|
private final case class ThunkV(f: Any ⇒ Any) extends AST
|
||||||
private final case class Message(timeout: FiniteDuration, f: (Any, Any) ⇒ Any, trace: Trace) extends AST
|
private final case class Message(timeout: FiniteDuration, f: (Any, Any) ⇒ Any, trace: Trace) extends AST
|
||||||
private final case class MultiMessage(timeout: FiniteDuration, count: Int, f: (Seq[Any], Any) ⇒ Any, trace: Trace) extends AST
|
private final case class MultiMessage(timeout: FiniteDuration, count: Int, f: (Seq[Any], Any) ⇒ Any, trace: Trace) extends AST
|
||||||
private final case class Failure(timeout: FiniteDuration, f: (Failed, Any) ⇒ (Failed.Decision, Any), trace: Trace) extends AST
|
|
||||||
private final case class Termination(timeout: FiniteDuration, f: (Terminated, Any) ⇒ Any, trace: Trace) extends AST
|
private final case class Termination(timeout: FiniteDuration, f: (Terminated, Any) ⇒ Any, trace: Trace) extends AST
|
||||||
|
private final case class Multi(timeout: FiniteDuration, count: Int, f: (Seq[Either[Signal, Any]], Any) ⇒ Any, trace: Trace) extends AST
|
||||||
|
|
||||||
|
private case object ReceiveTimeout
|
||||||
|
|
||||||
private sealed trait Trace {
|
private sealed trait Trace {
|
||||||
def getStackTrace: Array[StackTraceElement]
|
def getStackTrace: Array[StackTraceElement]
|
||||||
|
|
@ -78,24 +80,24 @@ object StepWise {
|
||||||
def expectMultipleMessages[V](timeout: FiniteDuration, count: Int)(f: (Seq[T], U) ⇒ V): Steps[T, V] =
|
def expectMultipleMessages[V](timeout: FiniteDuration, count: Int)(f: (Seq[T], U) ⇒ V): Steps[T, V] =
|
||||||
copy(ops = MultiMessage(timeout, count, f.asInstanceOf[(Seq[Any], Any) ⇒ Any], getTrace()) :: ops)
|
copy(ops = MultiMessage(timeout, count, f.asInstanceOf[(Seq[Any], Any) ⇒ Any], getTrace()) :: ops)
|
||||||
|
|
||||||
def expectFailure[V](timeout: FiniteDuration)(f: (Failed, U) ⇒ (Failed.Decision, V)): Steps[T, V] =
|
|
||||||
copy(ops = Failure(timeout, f.asInstanceOf[(Failed, Any) ⇒ (Failed.Decision, Any)], getTrace()) :: ops)
|
|
||||||
|
|
||||||
def expectTermination[V](timeout: FiniteDuration)(f: (Terminated, U) ⇒ V): Steps[T, V] =
|
def expectTermination[V](timeout: FiniteDuration)(f: (Terminated, U) ⇒ V): Steps[T, V] =
|
||||||
copy(ops = Termination(timeout, f.asInstanceOf[(Terminated, Any) ⇒ Any], getTrace()) :: ops)
|
copy(ops = Termination(timeout, f.asInstanceOf[(Terminated, Any) ⇒ Any], getTrace()) :: ops)
|
||||||
|
|
||||||
|
def expectMulti[V](timeout: FiniteDuration, count: Int)(f: (Seq[Either[Signal, T]], U) ⇒ V): Steps[T, V] =
|
||||||
|
copy(ops = Multi(timeout, count, f.asInstanceOf[(Seq[Either[Signal, Any]], Any) ⇒ Any], getTrace()) :: ops)
|
||||||
|
|
||||||
def expectMessageKeep(timeout: FiniteDuration)(f: (T, U) ⇒ Unit): Steps[T, U] =
|
def expectMessageKeep(timeout: FiniteDuration)(f: (T, U) ⇒ Unit): Steps[T, U] =
|
||||||
copy(ops = Message(timeout, (msg, value) ⇒ { f.asInstanceOf[(Any, Any) ⇒ Any](msg, value); value }, getTrace()) :: ops)
|
copy(ops = Message(timeout, (msg, value) ⇒ { f.asInstanceOf[(Any, Any) ⇒ Any](msg, value); value }, getTrace()) :: ops)
|
||||||
|
|
||||||
def expectMultipleMessagesKeep(timeout: FiniteDuration, count: Int)(f: (Seq[T], U) ⇒ Unit): Steps[T, U] =
|
def expectMultipleMessagesKeep(timeout: FiniteDuration, count: Int)(f: (Seq[T], U) ⇒ Unit): Steps[T, U] =
|
||||||
copy(ops = MultiMessage(timeout, count, (msgs, value) ⇒ { f.asInstanceOf[(Seq[Any], Any) ⇒ Any](msgs, value); value }, getTrace()) :: ops)
|
copy(ops = MultiMessage(timeout, count, (msgs, value) ⇒ { f.asInstanceOf[(Seq[Any], Any) ⇒ Any](msgs, value); value }, getTrace()) :: ops)
|
||||||
|
|
||||||
def expectFailureKeep(timeout: FiniteDuration)(f: (Failed, U) ⇒ Failed.Decision): Steps[T, U] =
|
|
||||||
copy(ops = Failure(timeout, (failed, value) ⇒ f.asInstanceOf[(Failed, Any) ⇒ Failed.Decision](failed, value) → value, getTrace()) :: ops)
|
|
||||||
|
|
||||||
def expectTerminationKeep(timeout: FiniteDuration)(f: (Terminated, U) ⇒ Unit): Steps[T, U] =
|
def expectTerminationKeep(timeout: FiniteDuration)(f: (Terminated, U) ⇒ Unit): Steps[T, U] =
|
||||||
copy(ops = Termination(timeout, (t, value) ⇒ { f.asInstanceOf[(Terminated, Any) ⇒ Any](t, value); value }, getTrace()) :: ops)
|
copy(ops = Termination(timeout, (t, value) ⇒ { f.asInstanceOf[(Terminated, Any) ⇒ Any](t, value); value }, getTrace()) :: ops)
|
||||||
|
|
||||||
|
def expectMultiKeep(timeout: FiniteDuration, count: Int)(f: (Seq[Either[Signal, T]], U) ⇒ Unit): Steps[T, U] =
|
||||||
|
copy(ops = Multi(timeout, count, (msgs, value) ⇒ { f.asInstanceOf[(Seq[Either[Signal, Any]], Any) ⇒ Any](msgs, value); value }, getTrace()) :: ops)
|
||||||
|
|
||||||
def withKeepTraces(b: Boolean): Steps[T, U] = copy(keepTraces = b)
|
def withKeepTraces(b: Boolean): Steps[T, U] = copy(keepTraces = b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,9 +107,9 @@ object StepWise {
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply[T](f: (ActorContext[T], StartWith[T]) ⇒ Steps[T, _]): Behavior[T] =
|
def apply[T](f: (ActorContext[T], StartWith[T]) ⇒ Steps[T, _]): Behavior[T] =
|
||||||
Full {
|
Full[Any] {
|
||||||
case Sig(ctx, PreStart) ⇒ run(ctx, f(ctx, new StartWith(keepTraces = false)).ops.reverse, ())
|
case Sig(ctx, PreStart) ⇒ run(ctx, f(ctx.asInstanceOf[ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ())
|
||||||
}
|
}.narrow
|
||||||
|
|
||||||
private def throwTimeout(trace: Trace, message: String): Nothing =
|
private def throwTimeout(trace: Trace, message: String): Nothing =
|
||||||
throw new TimeoutException(message) {
|
throw new TimeoutException(message) {
|
||||||
|
|
@ -125,47 +127,65 @@ object StepWise {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def run[T](ctx: ActorContext[T], ops: List[AST], value: Any): Behavior[T] =
|
private def run[T](ctx: ActorContext[Any], ops: List[AST], value: Any): Behavior[Any] =
|
||||||
ops match {
|
ops match {
|
||||||
case Thunk(f) :: tail ⇒ run(ctx, tail, f())
|
case Thunk(f) :: tail ⇒ run(ctx, tail, f())
|
||||||
case ThunkV(f) :: tail ⇒ run(ctx, tail, f(value))
|
case ThunkV(f) :: tail ⇒ run(ctx, tail, f(value))
|
||||||
case Message(t, f, trace) :: tail ⇒
|
case Message(t, f, trace) :: tail ⇒
|
||||||
ctx.setReceiveTimeout(t)
|
ctx.setReceiveTimeout(t, ReceiveTimeout)
|
||||||
Full {
|
Full {
|
||||||
case Sig(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for a message")
|
case Msg(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for a message")
|
||||||
case Msg(_, msg) ⇒ run(ctx, tail, f(msg, value))
|
case Msg(_, msg) ⇒
|
||||||
|
ctx.cancelReceiveTimeout()
|
||||||
|
run(ctx, tail, f(msg, value))
|
||||||
case Sig(_, other) ⇒ throwIllegalState(trace, s"unexpected $other while waiting for a message")
|
case Sig(_, other) ⇒ throwIllegalState(trace, s"unexpected $other while waiting for a message")
|
||||||
}
|
}
|
||||||
case MultiMessage(t, c, f, trace) :: tail ⇒
|
case MultiMessage(t, c, f, trace) :: tail ⇒
|
||||||
val deadline = Deadline.now + t
|
val deadline = Deadline.now + t
|
||||||
def behavior(count: Int, acc: List[Any]): Behavior[T] = {
|
def behavior(count: Int, acc: List[Any]): Behavior[Any] = {
|
||||||
ctx.setReceiveTimeout(deadline.timeLeft)
|
ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout)
|
||||||
Full {
|
Full {
|
||||||
case Sig(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
|
case Msg(_, ReceiveTimeout) ⇒
|
||||||
|
throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
|
||||||
case Msg(_, msg) ⇒
|
case Msg(_, msg) ⇒
|
||||||
val nextCount = count + 1
|
val nextCount = count + 1
|
||||||
if (nextCount == c) {
|
if (nextCount == c) {
|
||||||
|
ctx.cancelReceiveTimeout()
|
||||||
run(ctx, tail, f((msg :: acc).reverse, value))
|
run(ctx, tail, f((msg :: acc).reverse, value))
|
||||||
} else behavior(nextCount, msg :: acc)
|
} else behavior(nextCount, msg :: acc)
|
||||||
case Sig(_, other) ⇒ throwIllegalState(trace, s"unexpected $other while waiting for $c messages (got $count valid ones)")
|
case Sig(_, other) ⇒ throwIllegalState(trace, s"unexpected $other while waiting for $c messages (got $count valid ones)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
behavior(0, Nil)
|
behavior(0, Nil)
|
||||||
case Failure(t, f, trace) :: tail ⇒
|
case Multi(t, c, f, trace) :: tail ⇒
|
||||||
ctx.setReceiveTimeout(t)
|
val deadline = Deadline.now + t
|
||||||
|
def behavior(count: Int, acc: List[Either[Signal, Any]]): Behavior[Any] = {
|
||||||
|
ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout)
|
||||||
Full {
|
Full {
|
||||||
case Sig(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for a failure")
|
case Msg(_, ReceiveTimeout) ⇒
|
||||||
case Sig(_, failure: Failed) ⇒
|
throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
|
||||||
val (response, v) = f(failure, value)
|
case Msg(_, msg) ⇒
|
||||||
failure.decide(response)
|
val nextCount = count + 1
|
||||||
run(ctx, tail, v)
|
if (nextCount == c) {
|
||||||
case other ⇒ throwIllegalState(trace, s"unexpected $other while waiting for a message")
|
ctx.cancelReceiveTimeout()
|
||||||
|
run(ctx, tail, f((Right(msg) :: acc).reverse, value))
|
||||||
|
} else behavior(nextCount, Right(msg) :: acc)
|
||||||
|
case Sig(_, other) ⇒
|
||||||
|
val nextCount = count + 1
|
||||||
|
if (nextCount == c) {
|
||||||
|
ctx.cancelReceiveTimeout()
|
||||||
|
run(ctx, tail, f((Left(other) :: acc).reverse, value))
|
||||||
|
} else behavior(nextCount, Left(other) :: acc)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
behavior(0, Nil)
|
||||||
case Termination(t, f, trace) :: tail ⇒
|
case Termination(t, f, trace) :: tail ⇒
|
||||||
ctx.setReceiveTimeout(t)
|
ctx.setReceiveTimeout(t, ReceiveTimeout)
|
||||||
Full {
|
Full {
|
||||||
case Sig(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for termination")
|
case Msg(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for termination")
|
||||||
case Sig(_, t: Terminated) ⇒ run(ctx, tail, f(t, value))
|
case Sig(_, t: Terminated) ⇒
|
||||||
|
ctx.cancelReceiveTimeout()
|
||||||
|
run(ctx, tail, f(t, value))
|
||||||
case other ⇒ throwIllegalState(trace, s"unexpected $other while waiting for termination")
|
case other ⇒ throwIllegalState(trace, s"unexpected $other while waiting for termination")
|
||||||
}
|
}
|
||||||
case Nil ⇒ Stopped
|
case Nil ⇒ Stopped
|
||||||
|
|
|
||||||
|
|
@ -21,49 +21,79 @@ import akka.testkit.TestEvent.Mute
|
||||||
import org.scalatest.concurrent.ScalaFutures
|
import org.scalatest.concurrent.ScalaFutures
|
||||||
import org.scalactic.ConversionCheckedTripleEquals
|
import org.scalactic.ConversionCheckedTripleEquals
|
||||||
import org.scalactic.Constraint
|
import org.scalactic.Constraint
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import org.scalatest.exceptions.TestFailedException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for writing tests for typed Actors with ScalaTest.
|
* Helper class for writing tests for typed Actors with ScalaTest.
|
||||||
*/
|
*/
|
||||||
abstract class TypedSpec(config: Config) extends Spec with Matchers with BeforeAndAfterAll with ScalaFutures with ConversionCheckedTripleEquals {
|
@RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||||
|
class TypedSpecSetup extends Spec with Matchers with BeforeAndAfterAll with ScalaFutures with ConversionCheckedTripleEquals
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for writing tests against both ActorSystemImpl and ActorSystemAdapter.
|
||||||
|
*/
|
||||||
|
class TypedSpec(val config: Config) extends TypedSpecSetup {
|
||||||
import TypedSpec._
|
import TypedSpec._
|
||||||
import AskPattern._
|
import AskPattern._
|
||||||
|
|
||||||
def this() = this(ConfigFactory.empty)
|
def this() = this(ConfigFactory.empty)
|
||||||
|
|
||||||
implicit val system = ActorSystem(TypedSpec.getCallerName(classOf[TypedSpec]), Props(guardian()), Some(config withFallback AkkaSpec.testConf))
|
// extension point
|
||||||
|
def setTimeout: Timeout = Timeout(1.minute)
|
||||||
|
|
||||||
implicit val timeout = Timeout(1.minute)
|
val nativeSystem = ActorSystem(AkkaSpec.getCallerName(classOf[TypedSpec]), Props(guardian()), Some(config withFallback AkkaSpec.testConf))
|
||||||
|
val adaptedSystem = ActorSystem.adapter(AkkaSpec.getCallerName(classOf[TypedSpec]), Props(guardian()), Some(config withFallback AkkaSpec.testConf))
|
||||||
|
|
||||||
|
trait NativeSystem {
|
||||||
|
def system = nativeSystem
|
||||||
|
}
|
||||||
|
trait AdaptedSystem {
|
||||||
|
def system = adaptedSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val timeout = setTimeout
|
||||||
implicit val patience = PatienceConfig(3.seconds)
|
implicit val patience = PatienceConfig(3.seconds)
|
||||||
|
implicit val scheduler = nativeSystem.scheduler
|
||||||
|
|
||||||
override def afterAll(): Unit = {
|
override def afterAll(): Unit = {
|
||||||
Await.result(system ? (Terminate(_)), timeout.duration): Status
|
Await.result(nativeSystem ? (Terminate(_)), timeout.duration): Status
|
||||||
|
Await.result(adaptedSystem ? (Terminate(_)), timeout.duration): Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove after basing on ScalaTest 3 with async support
|
// TODO remove after basing on ScalaTest 3 with async support
|
||||||
import akka.testkit._
|
import akka.testkit._
|
||||||
def await[T](f: Future[T]): T = Await.result(f, 60.seconds.dilated(system.untyped))
|
def await[T](f: Future[T]): T = Await.result(f, timeout.duration * 1.1)
|
||||||
|
|
||||||
val blackhole = await(system ? Create(Props(ScalaDSL.Full[Any] { case _ ⇒ ScalaDSL.Same }), "blackhole"))
|
val blackhole = await(nativeSystem ? Create(Props(ScalaDSL.Full[Any] { case _ ⇒ ScalaDSL.Same }), "blackhole"))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run an Actor-based test. The test procedure is most conveniently
|
* Run an Actor-based test. The test procedure is most conveniently
|
||||||
* formulated using the [[StepWise$]] behavior type.
|
* formulated using the [[StepWise$]] behavior type.
|
||||||
*/
|
*/
|
||||||
def runTest[T: ClassTag](name: String)(behavior: Behavior[T]): Future[Status] =
|
def runTest[T: ClassTag](name: String)(behavior: Behavior[T])(implicit system: ActorSystem[Command]): Future[Status] =
|
||||||
system ? (RunTest(name, Props(behavior), _, timeout.duration))
|
system ? (RunTest(name, Props(behavior), _, timeout.duration))
|
||||||
|
|
||||||
// TODO remove after basing on ScalaTest 3 with async support
|
// TODO remove after basing on ScalaTest 3 with async support
|
||||||
def sync(f: Future[Status]): Unit = {
|
def sync(f: Future[Status])(implicit system: ActorSystem[Command]): Unit = {
|
||||||
def unwrap(ex: Throwable): Throwable = ex match {
|
def unwrap(ex: Throwable): Throwable = ex match {
|
||||||
case ActorInitializationException(_, _, ex) ⇒ ex
|
case ActorInitializationException(_, _, ex) ⇒ ex
|
||||||
case other ⇒ other
|
case other ⇒ other
|
||||||
}
|
}
|
||||||
|
|
||||||
await(f) match {
|
try await(f) match {
|
||||||
case Success ⇒ ()
|
case Success ⇒ ()
|
||||||
case Failed(ex) ⇒ throw unwrap(ex)
|
case Failed(ex) ⇒
|
||||||
case Timedout ⇒ fail("test timed out")
|
println(system.printTree)
|
||||||
|
throw unwrap(ex)
|
||||||
|
case Timedout ⇒
|
||||||
|
println(system.printTree)
|
||||||
|
fail("test timed out")
|
||||||
|
} catch {
|
||||||
|
case NonFatal(ex) ⇒
|
||||||
|
println(system.printTree)
|
||||||
|
throw ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +102,7 @@ abstract class TypedSpec(config: Config) extends Spec with Matchers with BeforeA
|
||||||
source: String = null,
|
source: String = null,
|
||||||
start: String = "",
|
start: String = "",
|
||||||
pattern: String = null,
|
pattern: String = null,
|
||||||
occurrences: Int = Int.MaxValue): EventFilter = {
|
occurrences: Int = Int.MaxValue)(implicit system: ActorSystem[Command]): EventFilter = {
|
||||||
val filter = EventFilter(message, source, start, pattern, occurrences)
|
val filter = EventFilter(message, source, start, pattern, occurrences)
|
||||||
system.eventStream.publish(Mute(filter))
|
system.eventStream.publish(Mute(filter))
|
||||||
filter
|
filter
|
||||||
|
|
@ -81,7 +111,7 @@ abstract class TypedSpec(config: Config) extends Spec with Matchers with BeforeA
|
||||||
/**
|
/**
|
||||||
* Group assertion that ensures that the given inboxes are empty.
|
* Group assertion that ensures that the given inboxes are empty.
|
||||||
*/
|
*/
|
||||||
def assertEmpty(inboxes: Inbox.SyncInbox[_]*): Unit = {
|
def assertEmpty(inboxes: Inbox[_]*): Unit = {
|
||||||
inboxes foreach (i ⇒ withClue(s"inbox $i had messages")(i.hasMessages should be(false)))
|
inboxes foreach (i ⇒ withClue(s"inbox $i had messages")(i.hasMessages should be(false)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,20 +146,11 @@ object TypedSpec {
|
||||||
|
|
||||||
def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] =
|
def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] =
|
||||||
FullTotal {
|
FullTotal {
|
||||||
case Sig(ctx, f @ t.Failed(ex, test)) ⇒
|
case Sig(ctx, t @ Terminated(test)) ⇒
|
||||||
outstanding get test match {
|
outstanding get test match {
|
||||||
case Some(reply) ⇒
|
case Some(reply) ⇒
|
||||||
reply ! Failed(ex)
|
if (t.failure eq null) reply ! Success
|
||||||
f.decide(t.Failed.Stop)
|
else reply ! Failed(t.failure)
|
||||||
guardian(outstanding - test)
|
|
||||||
case None ⇒
|
|
||||||
f.decide(t.Failed.Stop)
|
|
||||||
Same
|
|
||||||
}
|
|
||||||
case Sig(ctx, Terminated(test)) ⇒
|
|
||||||
outstanding get test match {
|
|
||||||
case Some(reply) ⇒
|
|
||||||
reply ! Success
|
|
||||||
guardian(outstanding - test)
|
guardian(outstanding - test)
|
||||||
case None ⇒ Same
|
case None ⇒ Same
|
||||||
}
|
}
|
||||||
|
|
@ -157,3 +178,25 @@ object TypedSpec {
|
||||||
reduced.head.replaceFirst(""".*\.""", "").replaceAll("[^a-zA-Z_0-9]", "_")
|
reduced.head.replaceFirst(""".*\.""", "").replaceAll("[^a-zA-Z_0-9]", "_")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TypedSpecSpec extends TypedSpec {
|
||||||
|
object `A TypedSpec` {
|
||||||
|
|
||||||
|
trait CommonTests {
|
||||||
|
implicit def system: ActorSystem[TypedSpec.Command]
|
||||||
|
|
||||||
|
def `must report failures`(): Unit = {
|
||||||
|
a[TestFailedException] must be thrownBy {
|
||||||
|
sync(runTest("failure")(StepWise[String]((ctx, startWith) ⇒
|
||||||
|
startWith {
|
||||||
|
fail("expected")
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object `when using the native implementation` extends CommonTests with NativeSystem
|
||||||
|
object `when using the adapted implementation` extends CommonTests with AdaptedSystem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,443 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import org.scalactic.ConversionCheckedTripleEquals
|
||||||
|
import org.scalatest.concurrent.ScalaFutures
|
||||||
|
import org.scalatest.exceptions.TestFailedException
|
||||||
|
import org.scalatest._
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||||
|
class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with ScalaFutures with ConversionCheckedTripleEquals {
|
||||||
|
|
||||||
|
import ScalaDSL._
|
||||||
|
|
||||||
|
val sys = new ActorSystemStub("ActorCellSpec")
|
||||||
|
def ec = sys.controlledExecutor
|
||||||
|
|
||||||
|
object `An ActorCell` {
|
||||||
|
|
||||||
|
def `must be creatable`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "creatable", true)
|
||||||
|
val cell = new ActorCell(sys, Props({ parent ! "created"; Static[String] { s ⇒ parent ! s } }), parent)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
cell.send("hello")
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("hello") :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must be creatable with ???`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "creatable???", true)
|
||||||
|
val self = new DebugRef[String](sys.path / "creatableSelf", true)
|
||||||
|
val ??? = new NotImplementedError
|
||||||
|
val cell = new ActorCell(sys, Props[String]({ parent ! "created"; throw ??? }), parent)
|
||||||
|
cell.setSelf(self)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
// explicitly verify termination via self-signal
|
||||||
|
self.receiveAll() should ===(Left(Terminate()) :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(self, ???)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must be able to terminate after construction`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "terminate", true)
|
||||||
|
val self = new DebugRef[String](sys.path / "terminateSelf", true)
|
||||||
|
val cell = new ActorCell(sys, Props({ parent ! "created"; Stopped[String] }), parent)
|
||||||
|
cell.setSelf(self)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
// explicitly verify termination via self-signal
|
||||||
|
self.receiveAll() should ===(Left(Terminate()) :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(self, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must be able to terminate after PreStart`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "terminate", true)
|
||||||
|
val self = new DebugRef[String](sys.path / "terminateSelf", true)
|
||||||
|
val cell = new ActorCell(sys, Props({ parent ! "created"; Full[String] { case Sig(ctx, PreStart) ⇒ Stopped } }), parent)
|
||||||
|
cell.setSelf(self)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
// explicitly verify termination via self-signal
|
||||||
|
self.receiveAll() should ===(Left(Terminate()) :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(self, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must terminate upon failure during processing`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "terminate", true)
|
||||||
|
val self = new DebugRef[String](sys.path / "terminateSelf", true)
|
||||||
|
val ex = new AssertionError
|
||||||
|
val cell = new ActorCell(sys, Props({ parent ! "created"; Static[String](s ⇒ throw ex) }), parent)
|
||||||
|
cell.setSelf(self)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
cell.send("")
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
// explicitly verify termination via self-signal
|
||||||
|
self.receiveAll() should ===(Left(Terminate()) :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(self, ex)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must signal failure when starting behavior is "same"`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "startSame", true)
|
||||||
|
val self = new DebugRef[String](sys.path / "startSameSelf", true)
|
||||||
|
val cell = new ActorCell(sys, Props({ parent ! "created"; Same[String] }), parent)
|
||||||
|
cell.setSelf(self)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
// explicitly verify termination via self-signal
|
||||||
|
self.receiveAll() should ===(Left(Terminate()) :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() match {
|
||||||
|
case Left(DeathWatchNotification(`self`, exc)) :: Nil ⇒
|
||||||
|
exc should not be null
|
||||||
|
exc shouldBe an[IllegalStateException]
|
||||||
|
exc.getMessage should include("same")
|
||||||
|
case other ⇒ fail(s"$other was not a DeathWatchNotification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must signal failure when starting behavior is "unhandled"`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "startSame", true)
|
||||||
|
val self = new DebugRef[String](sys.path / "startSameSelf", true)
|
||||||
|
val cell = new ActorCell(sys, Props({ parent ! "created"; Unhandled[String] }), parent)
|
||||||
|
cell.setSelf(self)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Right("created") :: Nil)
|
||||||
|
// explicitly verify termination via self-signal
|
||||||
|
self.receiveAll() should ===(Left(Terminate()) :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() match {
|
||||||
|
case Left(DeathWatchNotification(`self`, exc)) :: Nil ⇒
|
||||||
|
exc should not be null
|
||||||
|
exc shouldBe an[IllegalStateException]
|
||||||
|
exc.getMessage should include("same")
|
||||||
|
case other ⇒ fail(s"$other was not a DeathWatchNotification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* also tests:
|
||||||
|
* - must reschedule for self-message
|
||||||
|
* - must not reschedule for message when already activated
|
||||||
|
* - must not reschedule for signal when already activated
|
||||||
|
*/
|
||||||
|
def `must not execute more messages than were batched naturally`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "batching", true)
|
||||||
|
val cell = new ActorCell(sys, Props(SelfAware[String] { self ⇒ Static { s ⇒ self ! s; parent ! s } }), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Nil)
|
||||||
|
cell.send("one")
|
||||||
|
cell.send("two")
|
||||||
|
ec.queueSize should ===(1)
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(1)
|
||||||
|
parent.receiveAll() should ===(Right("one") :: Right("two") :: Nil)
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(1)
|
||||||
|
parent.receiveAll() should ===(Right("one") :: Right("two") :: Nil)
|
||||||
|
cell.send("three")
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(1)
|
||||||
|
parent.receiveAll() should ===(Right("one") :: Right("two") :: Right("three") :: Nil)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.queueSize should ===(1)
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must signal DeathWatch when terminating normally`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "watchNormal", true)
|
||||||
|
val client = new DebugRef[String](parent.path / "client", true)
|
||||||
|
val cell = new ActorCell(sys, Props(Empty[String]), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Watch(ref, client))
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
client.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* also tests:
|
||||||
|
* - must turn a DeathWatchNotification into a Terminated signal while watching
|
||||||
|
* - must terminate with DeathPactException when not handling a Terminated signal
|
||||||
|
* - must send a Watch message when watching another actor
|
||||||
|
*/
|
||||||
|
def `must signal DeathWatch when terminating abnormally`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "watchAbnormal", true)
|
||||||
|
val client = new DebugRef[String](parent.path / "client", true)
|
||||||
|
val other = new DebugRef[String](parent.path / "other", true)
|
||||||
|
val cell = new ActorCell(sys, Props(ContextAware[String] { ctx ⇒ ctx.watch(parent); Empty }), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(Watch(parent, ref)) :: Nil)
|
||||||
|
// test that unwatched termination is ignored
|
||||||
|
cell.sendSystem(DeathWatchNotification(other, null))
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Nil)
|
||||||
|
// now trigger failure by death pact
|
||||||
|
cell.sendSystem(Watch(ref, client))
|
||||||
|
cell.sendSystem(DeathWatchNotification(parent, null))
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() match {
|
||||||
|
case Left(DeathWatchNotification(ref, exc)) :: Nil ⇒
|
||||||
|
exc should not be null
|
||||||
|
exc shouldBe a[DeathPactException]
|
||||||
|
case other ⇒ fail(s"$other was not a DeathWatchNotification")
|
||||||
|
}
|
||||||
|
client.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must signal DeathWatch when watching after termination`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "watchLate", true)
|
||||||
|
val client = new DebugRef[String](parent.path / "client", true)
|
||||||
|
val cell = new ActorCell(sys, Props(Stopped[String]), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
cell.sendSystem(Watch(ref, client))
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
sys.deadLettersInbox.receiveAll() should ===(Left(Watch(ref, client)) :: Nil)
|
||||||
|
// correct behavior of deadLetters is verified in ActorSystemSpec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must signal DeathWatch when watching after termination but before deactivation`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "watchSomewhatLate", true)
|
||||||
|
val client = new DebugRef[String](parent.path / "client", true)
|
||||||
|
val cell = new ActorCell(sys, Props(Empty[String]), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
cell.sendSystem(Watch(ref, client))
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
sys.deadLettersInbox.receiveAll() should ===(Left(Watch(ref, client)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must not signal DeathWatch after Unwatch has been processed`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "watchUnwatch", true)
|
||||||
|
val client = new DebugRef[String](parent.path / "client", true)
|
||||||
|
val cell = new ActorCell(sys, Props(Empty[String]), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Watch(ref, client))
|
||||||
|
cell.sendSystem(Unwatch(ref, client))
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
client.receiveAll() should ===(Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must send messages to deadLetters after being terminated`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "sendDeadLetters", true)
|
||||||
|
val cell = new ActorCell(sys, Props(Stopped[String]), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
cell.send("42")
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
sys.deadLettersInbox.receiveAll() should ===(Right("42") :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* also tests:
|
||||||
|
* - child creation
|
||||||
|
*/
|
||||||
|
def `must not terminate before children have terminated`(): Unit = {
|
||||||
|
val parent = new DebugRef[ActorRef[Nothing]](sys.path / "waitForChild", true)
|
||||||
|
val cell = new ActorCell(sys, Props(ContextAware[String] { ctx ⇒
|
||||||
|
ctx.spawn(Props(SelfAware[String] { self ⇒ parent ! self; Empty }), "child")
|
||||||
|
Empty
|
||||||
|
}), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne() // creating subject
|
||||||
|
parent.hasSomething should ===(false)
|
||||||
|
ec.runOne() // creating child
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
val child = parent.receiveAll() match {
|
||||||
|
case Right(child) :: Nil ⇒
|
||||||
|
child.sorryForNothing.sendSystem(Watch(child, parent))
|
||||||
|
child
|
||||||
|
case other ⇒ fail(s"$other was not List(Right(<child>))")
|
||||||
|
}
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne() // begin subject termination, will initiate child termination
|
||||||
|
parent.hasSomething should ===(false)
|
||||||
|
ec.runOne() // terminate child
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(child, null)) :: Nil)
|
||||||
|
ec.runOne() // terminate subject
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must properly terminate if failing while handling Terminated for child actor`(): Unit = {
|
||||||
|
val parent = new DebugRef[ActorRef[Nothing]](sys.path / "terminateWhenDeathPact", true)
|
||||||
|
val cell = new ActorCell(sys, Props(ContextAware[String] { ctx ⇒
|
||||||
|
ctx.watch(ctx.spawn(Props(SelfAware[String] { self ⇒ parent ! self; Empty }), "child"))
|
||||||
|
Empty
|
||||||
|
}), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne() // creating subject
|
||||||
|
parent.hasSomething should ===(false)
|
||||||
|
ec.runOne() // creating child
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
val child = parent.receiveAll() match {
|
||||||
|
case Right(child: ActorRefImpl[Nothing]) :: Nil ⇒
|
||||||
|
child.sendSystem(Watch(child, parent))
|
||||||
|
child
|
||||||
|
case other ⇒ fail(s"$other was not List(Right(<child>))")
|
||||||
|
}
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
child.sendSystem(Terminate())
|
||||||
|
ec.runOne() // child terminates and enqueues DeathWatchNotification
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(child, null)) :: Nil)
|
||||||
|
ec.runOne() // cell fails during Terminated and terminates with DeathPactException
|
||||||
|
parent.receiveAll() match {
|
||||||
|
case Left(DeathWatchNotification(`ref`, ex: DeathPactException)) :: Nil ⇒
|
||||||
|
ex.getMessage should include("death pact")
|
||||||
|
case other ⇒ fail(s"$other was not Left(DeathWatchNotification($ref, DeathPactException))")
|
||||||
|
}
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must not terminate twice if failing in PostStop`(): Unit = {
|
||||||
|
val parent = new DebugRef[String](sys.path / "terminateProperlyPostStop", true)
|
||||||
|
val cell = new ActorCell(sys, Props(Full[String] { case Sig(_, PostStop) ⇒ ??? }), parent)
|
||||||
|
val ref = new LocalActorRef(parent.path / "child", cell)
|
||||||
|
cell.setSelf(ref)
|
||||||
|
debugCell(cell) {
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Create())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
cell.sendSystem(Terminate())
|
||||||
|
ec.runOne()
|
||||||
|
ec.queueSize should ===(0)
|
||||||
|
parent.receiveAll() should ===(Left(DeathWatchNotification(ref, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def debugCell[T, U](cell: ActorCell[T])(block: ⇒ U): U =
|
||||||
|
try block
|
||||||
|
catch {
|
||||||
|
case ex: TestFailedException ⇒
|
||||||
|
println(cell)
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.scalactic.ConversionCheckedTripleEquals
|
||||||
|
import org.scalatest._
|
||||||
|
import org.scalatest.exceptions.TestFailedException
|
||||||
|
import org.scalatest.concurrent.ScalaFutures
|
||||||
|
import org.scalatest.concurrent.Eventually
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.{ Future, Promise }
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
|
@RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||||
|
class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with ScalaFutures with Eventually with ConversionCheckedTripleEquals {
|
||||||
|
import ScalaDSL._
|
||||||
|
|
||||||
|
override implicit val patienceConfig = PatienceConfig(1.second)
|
||||||
|
|
||||||
|
case class Probe(msg: String, replyTo: ActorRef[String])
|
||||||
|
|
||||||
|
trait CommonTests {
|
||||||
|
def system[T](name: String, props: Props[T]): ActorSystem[T]
|
||||||
|
def suite: String
|
||||||
|
|
||||||
|
def withSystem[T](name: String, props: Props[T], doTerminate: Boolean = true)(block: ActorSystem[T] ⇒ Unit): Terminated = {
|
||||||
|
val sys = system(s"$suite-$name", props)
|
||||||
|
try {
|
||||||
|
block(sys)
|
||||||
|
if (doTerminate) sys.terminate().futureValue else sys.whenTerminated.futureValue
|
||||||
|
} catch {
|
||||||
|
case NonFatal(ex) ⇒
|
||||||
|
sys.terminate()
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must start the guardian actor and terminate when it terminates`(): Unit = {
|
||||||
|
val t = withSystem("a", Props(Total[Probe] { p ⇒ p.replyTo ! p.msg; Stopped }), doTerminate = false) { sys ⇒
|
||||||
|
val inbox = Inbox[String]("a")
|
||||||
|
sys ! Probe("hello", inbox.ref)
|
||||||
|
eventually { inbox.hasMessages should ===(true) }
|
||||||
|
inbox.receiveAll() should ===("hello" :: Nil)
|
||||||
|
}
|
||||||
|
val p = t.ref.path
|
||||||
|
p.name should ===("/")
|
||||||
|
p.address.system should ===(suite + "-a")
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must terminate the guardian actor`(): Unit = {
|
||||||
|
val inbox = Inbox[String]("terminate")
|
||||||
|
val sys = system("terminate", Props(Full[Probe] {
|
||||||
|
case Sig(ctx, PostStop) ⇒
|
||||||
|
inbox.ref ! "done"
|
||||||
|
Same
|
||||||
|
}))
|
||||||
|
sys.terminate().futureValue
|
||||||
|
inbox.receiveAll() should ===("done" :: Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must log to the event stream`(): Unit = pending
|
||||||
|
|
||||||
|
def `must have a name`(): Unit =
|
||||||
|
withSystem("name", Props(Empty[String])) { sys ⇒
|
||||||
|
sys.name should ===(suite + "-name")
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must report its uptime`(): Unit =
|
||||||
|
withSystem("uptime", Props(Empty[String])) { sys ⇒
|
||||||
|
sys.uptime should be < 1L
|
||||||
|
Thread.sleep(1000)
|
||||||
|
sys.uptime should be >= 1L
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must have a working thread factory`(): Unit =
|
||||||
|
withSystem("thread", Props(Empty[String])) { sys ⇒
|
||||||
|
val p = Promise[Int]
|
||||||
|
sys.threadFactory.newThread(new Runnable {
|
||||||
|
def run(): Unit = p.success(42)
|
||||||
|
}).start()
|
||||||
|
p.future.futureValue should ===(42)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must be able to run Futures`(): Unit =
|
||||||
|
withSystem("futures", Props(Empty[String])) { sys ⇒
|
||||||
|
val f = Future(42)(sys.executionContext)
|
||||||
|
f.futureValue should ===(42)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object `An ActorSystemImpl` extends CommonTests {
|
||||||
|
def system[T](name: String, props: Props[T]): ActorSystem[T] = ActorSystem(name, props)
|
||||||
|
def suite = "native"
|
||||||
|
|
||||||
|
// this is essential to complete ActorCellSpec, see there
|
||||||
|
def `must correctly treat Watch dead letters`(): Unit =
|
||||||
|
withSystem("deadletters", Props(Empty[String])) { sys ⇒
|
||||||
|
val client = new DebugRef[Int](sys.path / "debug", true)
|
||||||
|
sys.deadLetters.sorry.sendSystem(Watch(sys, client))
|
||||||
|
client.receiveAll() should ===(Left(DeathWatchNotification(sys, null)) :: Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object `An ActorSystemAdapter` extends CommonTests {
|
||||||
|
def system[T](name: String, props: Props[T]): ActorSystem[T] = ActorSystem.adapter(name, props)
|
||||||
|
def suite = "adapter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a, event ⇒ e }
|
||||||
|
import scala.concurrent._
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
|
||||||
|
private[typed] class ActorSystemStub(val name: String)
|
||||||
|
extends ActorRef[Nothing](a.RootActorPath(a.Address("akka", name)) / "user")
|
||||||
|
with ActorSystem[Nothing] with ActorRefImpl[Nothing] {
|
||||||
|
|
||||||
|
override val settings: a.ActorSystem.Settings = new a.ActorSystem.Settings(getClass.getClassLoader, ConfigFactory.empty, name)
|
||||||
|
|
||||||
|
override def tell(msg: Nothing): Unit = throw new RuntimeException("must not send message to ActorSystemStub")
|
||||||
|
|
||||||
|
override def isLocal: Boolean = true
|
||||||
|
override def sendSystem(signal: akka.typed.internal.SystemMessage): Unit =
|
||||||
|
throw new RuntimeException("must not send SYSTEM message to ActorSystemStub")
|
||||||
|
|
||||||
|
val deadLettersInbox = new DebugRef[Any](path.parent / "deadLetters", true)
|
||||||
|
override def deadLetters[U]: akka.typed.ActorRef[U] = deadLettersInbox
|
||||||
|
|
||||||
|
val controlledExecutor = new ControlledExecutor
|
||||||
|
implicit override def executionContext: scala.concurrent.ExecutionContextExecutor = controlledExecutor
|
||||||
|
override def dispatchers: akka.typed.Dispatchers = new Dispatchers {
|
||||||
|
def lookup(selector: DispatcherSelector): ExecutionContextExecutor = controlledExecutor
|
||||||
|
def shutdown(): Unit = ()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def dynamicAccess: a.DynamicAccess = new a.ReflectiveDynamicAccess(getClass.getClassLoader)
|
||||||
|
override def eventStream: e.EventStream = new e.EventStream
|
||||||
|
override def logFilter: e.LoggingFilter = throw new UnsupportedOperationException("no log filter")
|
||||||
|
override def log: e.LoggingAdapter = new e.BusLogging(eventStream, path.parent.toString, getClass)
|
||||||
|
override def logConfiguration(): Unit = log.info(settings.toString)
|
||||||
|
|
||||||
|
override def scheduler: a.Scheduler = throw new UnsupportedOperationException("no scheduler")
|
||||||
|
|
||||||
|
private val terminationPromise = Promise[Terminated]
|
||||||
|
override def terminate(): Future[akka.typed.Terminated] = {
|
||||||
|
terminationPromise.trySuccess(Terminated(this)(null))
|
||||||
|
terminationPromise.future
|
||||||
|
}
|
||||||
|
override def whenTerminated: Future[akka.typed.Terminated] = terminationPromise.future
|
||||||
|
override val startTime: Long = System.currentTimeMillis()
|
||||||
|
override def uptime: Long = System.currentTimeMillis() - startTime
|
||||||
|
override def threadFactory: java.util.concurrent.ThreadFactory = new ThreadFactory {
|
||||||
|
override def newThread(r: Runnable): Thread = new Thread(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def printTree: String = "no tree for ActorSystemStub"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed.internal
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContextExecutor
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
class ControlledExecutor extends ExecutionContextExecutor {
|
||||||
|
private val tasks = new LinkedList[Runnable]
|
||||||
|
|
||||||
|
def queueSize: Int = tasks.size()
|
||||||
|
|
||||||
|
def runOne(): Unit = tasks.pop().run()
|
||||||
|
|
||||||
|
def runAll(): Unit = while (!tasks.isEmpty()) runOne()
|
||||||
|
|
||||||
|
def execute(task: Runnable): Unit = {
|
||||||
|
tasks.add(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
def reportFailure(cause: Throwable): Unit = {
|
||||||
|
cause.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
53
akka-typed/src/test/scala/akka/typed/internal/DebugRef.scala
Normal file
53
akka-typed/src/test/scala/akka/typed/internal/DebugRef.scala
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import akka.{ actor ⇒ a }
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
private[typed] class DebugRef[T](_path: a.ActorPath, override val isLocal: Boolean)
|
||||||
|
extends ActorRef[T](_path) with ActorRefImpl[T] {
|
||||||
|
|
||||||
|
private val q = new ConcurrentLinkedQueue[Either[SystemMessage, T]]
|
||||||
|
|
||||||
|
override def tell(msg: T): Unit = q.add(Right(msg))
|
||||||
|
override def sendSystem(signal: SystemMessage): Unit = q.add(Left(signal))
|
||||||
|
|
||||||
|
def hasMessage: Boolean = q.peek match {
|
||||||
|
case null ⇒ false
|
||||||
|
case Left(_) ⇒ false
|
||||||
|
case Right(_) ⇒ true
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasSignal: Boolean = q.peek match {
|
||||||
|
case null ⇒ false
|
||||||
|
case Left(_) ⇒ true
|
||||||
|
case Right(_) ⇒ false
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasSomething: Boolean = q.peek != null
|
||||||
|
|
||||||
|
def receiveMessage(): T = q.poll match {
|
||||||
|
case null ⇒ throw new NoSuchElementException("empty DebugRef")
|
||||||
|
case Left(signal) ⇒ throw new IllegalStateException(s"expected message but found signal $signal")
|
||||||
|
case Right(msg) ⇒ msg
|
||||||
|
}
|
||||||
|
|
||||||
|
def receiveSignal(): SystemMessage = q.poll match {
|
||||||
|
case null ⇒ throw new NoSuchElementException("empty DebugRef")
|
||||||
|
case Left(signal) ⇒ signal
|
||||||
|
case Right(msg) ⇒ throw new IllegalStateException(s"expected signal but found message $msg")
|
||||||
|
}
|
||||||
|
|
||||||
|
def receiveAll(): List[Either[SystemMessage, T]] = {
|
||||||
|
@tailrec def rec(acc: List[Either[SystemMessage, T]]): List[Either[SystemMessage, T]] =
|
||||||
|
q.poll match {
|
||||||
|
case null ⇒ acc.reverse
|
||||||
|
case other ⇒ rec(other :: acc)
|
||||||
|
}
|
||||||
|
rec(Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com/>
|
||||||
|
*/
|
||||||
|
package akka.typed
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import scala.concurrent.{ Promise, Future }
|
||||||
|
|
||||||
|
class FunctionRefSpec extends TypedSpecSetup {
|
||||||
|
|
||||||
|
object `A FunctionRef` {
|
||||||
|
|
||||||
|
def `must forward messages that are received after getting the ActorRef (completed later)`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
val target = new DebugRef[String](ref.path / "target", true)
|
||||||
|
p.success(target)
|
||||||
|
ref ! "42"
|
||||||
|
ref ! "43"
|
||||||
|
target.receiveAll() should ===(Left(Watch(target, ref)) :: Right("42") :: Right("43") :: Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must forward messages that are received after getting the ActorRef (already completed)`(): Unit = {
|
||||||
|
val target = new DebugRef[String](ActorRef.FuturePath / "target", true)
|
||||||
|
val f = Future.successful(target)
|
||||||
|
val ref = ActorRef(f)
|
||||||
|
ref ! "42"
|
||||||
|
ref ! "43"
|
||||||
|
target.receiveAll() should ===(Left(Watch(target, ref)) :: Right("42") :: Right("43") :: Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must forward messages that are received before getting the ActorRef`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
ref ! "42"
|
||||||
|
ref ! "43"
|
||||||
|
val target = new DebugRef[String](ref.path / "target", true)
|
||||||
|
p.success(target)
|
||||||
|
target.receiveAll() should ===(Right("42") :: Right("43") :: Left(Watch(target, ref)) :: Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must notify watchers when the future fails`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
val client1 = new DebugRef(ref.path / "c1", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client1))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
p.failure(new Exception)
|
||||||
|
client1.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val client2 = new DebugRef(ref.path / "c2", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client2))
|
||||||
|
client2.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client2.hasSomething should ===(false)
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must notify watchers when terminated`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
val client1 = new DebugRef(ref.path / "c1", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client1))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Terminate())
|
||||||
|
client1.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val client2 = new DebugRef(ref.path / "c2", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client2))
|
||||||
|
client2.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client2.hasSomething should ===(false)
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must notify watchers when terminated after receiving the target`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
val client1 = new DebugRef(ref.path / "c1", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client1))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val target = new DebugRef[String](ref.path / "target", true)
|
||||||
|
p.success(target)
|
||||||
|
ref ! "42"
|
||||||
|
ref ! "43"
|
||||||
|
target.receiveAll() should ===(Left(Watch(target, ref)) :: Right("42") :: Right("43") :: Nil)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Terminate())
|
||||||
|
client1.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
target.receiveAll() should ===(Left(Unwatch(target, ref)) :: Nil)
|
||||||
|
|
||||||
|
val client2 = new DebugRef(ref.path / "c2", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client2))
|
||||||
|
client2.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client2.hasSomething should ===(false)
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must notify watchers when receiving the target after terminating`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
val client1 = new DebugRef(ref.path / "c1", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client1))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Terminate())
|
||||||
|
client1.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val target = new DebugRef[String](ref.path / "target", true)
|
||||||
|
p.success(target)
|
||||||
|
ref ! "42"
|
||||||
|
ref ! "43"
|
||||||
|
target.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val client2 = new DebugRef(ref.path / "c2", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client2))
|
||||||
|
client2.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client2.hasSomething should ===(false)
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
def `must notify watchers when the target ActorRef terminates`(): Unit = {
|
||||||
|
val p = Promise[ActorRef[String]]
|
||||||
|
val ref = ActorRef(p.future)
|
||||||
|
val client1 = new DebugRef(ref.path / "c1", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client1))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val target = new DebugRef[String](ref.path / "target", true)
|
||||||
|
p.success(target)
|
||||||
|
ref ! "42"
|
||||||
|
ref ! "43"
|
||||||
|
target.receiveAll() should ===(Left(Watch(target, ref)) :: Right("42") :: Right("43") :: Nil)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(DeathWatchNotification(target, null))
|
||||||
|
client1.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
target.hasSomething should ===(false)
|
||||||
|
|
||||||
|
val client2 = new DebugRef(ref.path / "c2", true)
|
||||||
|
|
||||||
|
ref.sorry.sendSystem(Watch(ref, client2))
|
||||||
|
client2.receiveSignal() should ===(DeathWatchNotification(ref, null))
|
||||||
|
client2.hasSomething should ===(false)
|
||||||
|
client1.hasSomething should ===(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ object ReceiverSpec {
|
||||||
class ReceiverSpec extends TypedSpec {
|
class ReceiverSpec extends TypedSpec {
|
||||||
import ReceiverSpec._
|
import ReceiverSpec._
|
||||||
|
|
||||||
private val dummyInbox = Inbox.sync[Replies[Msg]]("dummy")
|
private val dummyInbox = Inbox[Replies[Msg]]("dummy")
|
||||||
|
|
||||||
private val startingPoints: Seq[Setup] = Seq(
|
private val startingPoints: Seq[Setup] = Seq(
|
||||||
Setup("initial", ctx ⇒ behavior[Msg], 0, 0),
|
Setup("initial", ctx ⇒ behavior[Msg], 0, 0),
|
||||||
|
|
@ -36,7 +36,7 @@ class ReceiverSpec extends TypedSpec {
|
||||||
private def afterGetOneTimeout(ctx: ActorContext[Command[Msg]]): Behavior[Command[Msg]] =
|
private def afterGetOneTimeout(ctx: ActorContext[Command[Msg]]): Behavior[Command[Msg]] =
|
||||||
behavior[Msg]
|
behavior[Msg]
|
||||||
.message(ctx, GetOne(1.nano)(dummyInbox.ref))
|
.message(ctx, GetOne(1.nano)(dummyInbox.ref))
|
||||||
.management(ctx, ReceiveTimeout)
|
.asInstanceOf[Behavior[InternalCommand[Msg]]].message(ctx.asInstanceOf[ActorContext[InternalCommand[Msg]]], ReceiveTimeout()).asInstanceOf[Behavior[Command[Msg]]]
|
||||||
|
|
||||||
private def afterGetAll(ctx: ActorContext[Command[Msg]]): Behavior[Command[Msg]] =
|
private def afterGetAll(ctx: ActorContext[Command[Msg]]): Behavior[Command[Msg]] =
|
||||||
behavior[Msg]
|
behavior[Msg]
|
||||||
|
|
@ -50,13 +50,13 @@ class ReceiverSpec extends TypedSpec {
|
||||||
.message(ctx, GetAll(Duration.Zero)(dummyInbox.ref))
|
.message(ctx, GetAll(Duration.Zero)(dummyInbox.ref))
|
||||||
|
|
||||||
private def setup(name: String, behv: Behavior[Command[Msg]] = behavior[Msg])(
|
private def setup(name: String, behv: Behavior[Command[Msg]] = behavior[Msg])(
|
||||||
proc: (EffectfulActorContext[Command[Msg]], EffectfulActorContext[Msg], Inbox.SyncInbox[Replies[Msg]]) ⇒ Unit): Unit =
|
proc: (EffectfulActorContext[Command[Msg]], EffectfulActorContext[Msg], Inbox[Replies[Msg]]) ⇒ Unit): Unit =
|
||||||
for (Setup(description, behv, messages, effects) ← startingPoints) {
|
for (Setup(description, behv, messages, effects) ← startingPoints) {
|
||||||
val ctx = new EffectfulActorContext("ctx", Props(ScalaDSL.ContextAware(behv)), system)
|
val ctx = new EffectfulActorContext("ctx", Props(ScalaDSL.ContextAware(behv)), nativeSystem)
|
||||||
withClue(s"[running for starting point '$description' (${ctx.currentBehavior})]: ") {
|
withClue(s"[running for starting point '$description' (${ctx.currentBehavior})]: ") {
|
||||||
dummyInbox.receiveAll() should have size messages
|
dummyInbox.receiveAll() should have size messages
|
||||||
ctx.getAllEffects() should have size effects
|
ctx.getAllEffects() should have size effects
|
||||||
proc(ctx, ctx.asInstanceOf[EffectfulActorContext[Msg]], Inbox.sync[Replies[Msg]](name))
|
proc(ctx, ctx.asInstanceOf[EffectfulActorContext[Msg]], Inbox[Replies[Msg]](name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ class ReceiverSpec extends TypedSpec {
|
||||||
*/
|
*/
|
||||||
def `must return "self" as external address`(): Unit =
|
def `must return "self" as external address`(): Unit =
|
||||||
setup("") { (int, ext, _) ⇒
|
setup("") { (int, ext, _) ⇒
|
||||||
val inbox = Inbox.sync[ActorRef[Msg]]("extAddr")
|
val inbox = Inbox[ActorRef[Msg]]("extAddr")
|
||||||
int.run(ExternalAddress(inbox.ref))
|
int.run(ExternalAddress(inbox.ref))
|
||||||
int.hasEffects should be(false)
|
int.hasEffects should be(false)
|
||||||
inbox.receiveAll() should be(List(int.self))
|
inbox.receiveAll() should be(List(int.self))
|
||||||
|
|
@ -97,13 +97,14 @@ class ReceiverSpec extends TypedSpec {
|
||||||
setup("getOneLater") { (int, ext, inbox) ⇒
|
setup("getOneLater") { (int, ext, inbox) ⇒
|
||||||
int.run(GetOne(1.second)(inbox.ref))
|
int.run(GetOne(1.second)(inbox.ref))
|
||||||
int.getAllEffects() match {
|
int.getAllEffects() match {
|
||||||
case ReceiveTimeoutSet(d) :: Nil ⇒ d > Duration.Zero should be(true)
|
case ReceiveTimeoutSet(d, _) :: Nil ⇒ d > Duration.Zero should be(true)
|
||||||
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
||||||
}
|
}
|
||||||
inbox.hasMessages should be(false)
|
inbox.hasMessages should be(false)
|
||||||
ext.run(Msg(1))
|
ext.run(Msg(1))
|
||||||
int.getAllEffects() match {
|
int.getAllEffects() match {
|
||||||
case ReceiveTimeoutSet(d) :: Nil ⇒ d should be theSameInstanceAs (Duration.Undefined)
|
case ReceiveTimeoutSet(d, _) :: Nil ⇒ d should be theSameInstanceAs (Duration.Undefined)
|
||||||
|
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
||||||
}
|
}
|
||||||
inbox.receiveAll() should be(GetOneResult(int.self, Some(Msg(1))) :: Nil)
|
inbox.receiveAll() should be(GetOneResult(int.self, Some(Msg(1))) :: Nil)
|
||||||
}
|
}
|
||||||
|
|
@ -122,15 +123,15 @@ class ReceiverSpec extends TypedSpec {
|
||||||
setup("getNoneTimeout") { (int, ext, inbox) ⇒
|
setup("getNoneTimeout") { (int, ext, inbox) ⇒
|
||||||
int.run(GetOne(1.nano)(inbox.ref))
|
int.run(GetOne(1.nano)(inbox.ref))
|
||||||
int.getAllEffects() match {
|
int.getAllEffects() match {
|
||||||
case ReceiveTimeoutSet(d) :: Nil ⇒ // okay
|
case ReceiveTimeoutSet(d, _) :: Nil ⇒ // okay
|
||||||
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
||||||
}
|
}
|
||||||
inbox.hasMessages should be(false)
|
inbox.hasMessages should be(false)
|
||||||
// currently this all takes >1ns, but who knows what the future brings
|
// currently this all takes >1ns, but who knows what the future brings
|
||||||
Thread.sleep(1)
|
Thread.sleep(1)
|
||||||
int.signal(ReceiveTimeout)
|
int.asInstanceOf[EffectfulActorContext[InternalCommand[Msg]]].run(ReceiveTimeout())
|
||||||
int.getAllEffects() match {
|
int.getAllEffects() match {
|
||||||
case ReceiveTimeoutSet(d) :: Nil ⇒ d should be theSameInstanceAs (Duration.Undefined)
|
case ReceiveTimeoutSet(d, _) :: Nil ⇒ d should be theSameInstanceAs (Duration.Undefined)
|
||||||
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
||||||
}
|
}
|
||||||
inbox.receiveAll() should be(GetOneResult(int.self, None) :: Nil)
|
inbox.receiveAll() should be(GetOneResult(int.self, None) :: Nil)
|
||||||
|
|
@ -218,7 +219,7 @@ class ReceiverSpec extends TypedSpec {
|
||||||
setup("getAllWhileGetOne") { (int, ext, inbox) ⇒
|
setup("getAllWhileGetOne") { (int, ext, inbox) ⇒
|
||||||
int.run(GetOne(1.second)(inbox.ref))
|
int.run(GetOne(1.second)(inbox.ref))
|
||||||
int.getAllEffects() match {
|
int.getAllEffects() match {
|
||||||
case ReceiveTimeoutSet(d) :: Nil ⇒ // okay
|
case ReceiveTimeoutSet(d, _) :: Nil ⇒ // okay
|
||||||
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
||||||
}
|
}
|
||||||
inbox.hasMessages should be(false)
|
inbox.hasMessages should be(false)
|
||||||
|
|
@ -231,13 +232,13 @@ class ReceiverSpec extends TypedSpec {
|
||||||
setup("getAllWhileGetOne") { (int, ext, inbox) ⇒
|
setup("getAllWhileGetOne") { (int, ext, inbox) ⇒
|
||||||
int.run(GetOne(1.second)(inbox.ref))
|
int.run(GetOne(1.second)(inbox.ref))
|
||||||
int.getAllEffects() match {
|
int.getAllEffects() match {
|
||||||
case ReceiveTimeoutSet(d) :: Nil ⇒ // okay
|
case ReceiveTimeoutSet(d, _) :: Nil ⇒ // okay
|
||||||
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
case other ⇒ fail(s"$other was not List(ReceiveTimeoutSet(_))")
|
||||||
}
|
}
|
||||||
inbox.hasMessages should be(false)
|
inbox.hasMessages should be(false)
|
||||||
int.run(GetAll(1.nano)(inbox.ref))
|
int.run(GetAll(1.nano)(inbox.ref))
|
||||||
val msg = int.getAllEffects() match {
|
val msg = int.getAllEffects() match {
|
||||||
case (s: Scheduled[_]) :: ReceiveTimeoutSet(_) :: Nil ⇒ assertScheduled(s, int.self)
|
case (s: Scheduled[_]) :: ReceiveTimeoutSet(_, _) :: Nil ⇒ assertScheduled(s, int.self)
|
||||||
}
|
}
|
||||||
inbox.receiveAll() should be(Nil)
|
inbox.receiveAll() should be(Nil)
|
||||||
int.run(msg)
|
int.run(msg)
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,17 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
case object ServiceKeyB extends ServiceKey[ServiceB]
|
case object ServiceKeyB extends ServiceKey[ServiceB]
|
||||||
val propsB = Props(Static[ServiceB](msg ⇒ ()))
|
val propsB = Props(Static[ServiceB](msg ⇒ ()))
|
||||||
|
|
||||||
object `A Receptionist` {
|
trait CommonTests {
|
||||||
|
implicit def system: ActorSystem[TypedSpec.Command]
|
||||||
|
|
||||||
def `must register a service`(): Unit = {
|
def `must register a service`(): Unit = {
|
||||||
val ctx = new EffectfulActorContext("register", Props(behavior), system)
|
val ctx = new EffectfulActorContext("register", Props(behavior), system)
|
||||||
val a = Inbox.sync[ServiceA]("a")
|
val a = Inbox[ServiceA]("a")
|
||||||
val r = Inbox.sync[Registered[_]]("r")
|
val r = Inbox[Registered[_]]("r")
|
||||||
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
||||||
ctx.getAllEffects() should be(Effect.Watched(a.ref) :: Nil)
|
ctx.getAllEffects() should be(Effect.Watched(a.ref) :: Nil)
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
||||||
val q = Inbox.sync[Listing[ServiceA]]("q")
|
val q = Inbox[Listing[ServiceA]]("q")
|
||||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||||
ctx.getAllEffects() should be(Nil)
|
ctx.getAllEffects() should be(Nil)
|
||||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
||||||
|
|
@ -37,14 +38,14 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
|
|
||||||
def `must register two services`(): Unit = {
|
def `must register two services`(): Unit = {
|
||||||
val ctx = new EffectfulActorContext("registertwo", Props(behavior), system)
|
val ctx = new EffectfulActorContext("registertwo", Props(behavior), system)
|
||||||
val a = Inbox.sync[ServiceA]("a")
|
val a = Inbox[ServiceA]("a")
|
||||||
val r = Inbox.sync[Registered[_]]("r")
|
val r = Inbox[Registered[_]]("r")
|
||||||
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
||||||
val b = Inbox.sync[ServiceB]("b")
|
val b = Inbox[ServiceB]("b")
|
||||||
ctx.run(Register(ServiceKeyB, b.ref)(r.ref))
|
ctx.run(Register(ServiceKeyB, b.ref)(r.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyB, b.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyB, b.ref))
|
||||||
val q = Inbox.sync[Listing[_]]("q")
|
val q = Inbox[Listing[_]]("q")
|
||||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
||||||
ctx.run(Find(ServiceKeyB)(q.ref))
|
ctx.run(Find(ServiceKeyB)(q.ref))
|
||||||
|
|
@ -54,14 +55,14 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
|
|
||||||
def `must register two services with the same key`(): Unit = {
|
def `must register two services with the same key`(): Unit = {
|
||||||
val ctx = new EffectfulActorContext("registertwosame", Props(behavior), system)
|
val ctx = new EffectfulActorContext("registertwosame", Props(behavior), system)
|
||||||
val a1 = Inbox.sync[ServiceA]("a1")
|
val a1 = Inbox[ServiceA]("a1")
|
||||||
val r = Inbox.sync[Registered[_]]("r")
|
val r = Inbox[Registered[_]]("r")
|
||||||
ctx.run(Register(ServiceKeyA, a1.ref)(r.ref))
|
ctx.run(Register(ServiceKeyA, a1.ref)(r.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyA, a1.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyA, a1.ref))
|
||||||
val a2 = Inbox.sync[ServiceA]("a2")
|
val a2 = Inbox[ServiceA]("a2")
|
||||||
ctx.run(Register(ServiceKeyA, a2.ref)(r.ref))
|
ctx.run(Register(ServiceKeyA, a2.ref)(r.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyA, a2.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyA, a2.ref))
|
||||||
val q = Inbox.sync[Listing[_]]("q")
|
val q = Inbox[Listing[_]]("q")
|
||||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a1.ref, a2.ref)))
|
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a1.ref, a2.ref)))
|
||||||
ctx.run(Find(ServiceKeyB)(q.ref))
|
ctx.run(Find(ServiceKeyB)(q.ref))
|
||||||
|
|
@ -71,31 +72,31 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
|
|
||||||
def `must unregister services when they terminate`(): Unit = {
|
def `must unregister services when they terminate`(): Unit = {
|
||||||
val ctx = new EffectfulActorContext("registertwosame", Props(behavior), system)
|
val ctx = new EffectfulActorContext("registertwosame", Props(behavior), system)
|
||||||
val r = Inbox.sync[Registered[_]]("r")
|
val r = Inbox[Registered[_]]("r")
|
||||||
val a = Inbox.sync[ServiceA]("a")
|
val a = Inbox[ServiceA]("a")
|
||||||
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
||||||
ctx.getEffect() should be(Effect.Watched(a.ref))
|
ctx.getEffect() should be(Effect.Watched(a.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
||||||
|
|
||||||
val b = Inbox.sync[ServiceB]("b")
|
val b = Inbox[ServiceB]("b")
|
||||||
ctx.run(Register(ServiceKeyB, b.ref)(r.ref))
|
ctx.run(Register(ServiceKeyB, b.ref)(r.ref))
|
||||||
ctx.getEffect() should be(Effect.Watched(b.ref))
|
ctx.getEffect() should be(Effect.Watched(b.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyB, b.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyB, b.ref))
|
||||||
|
|
||||||
val c = Inbox.sync[Any]("c")
|
val c = Inbox[Any]("c")
|
||||||
ctx.run(Register(ServiceKeyA, c.ref)(r.ref))
|
ctx.run(Register(ServiceKeyA, c.ref)(r.ref))
|
||||||
ctx.run(Register(ServiceKeyB, c.ref)(r.ref))
|
ctx.run(Register(ServiceKeyB, c.ref)(r.ref))
|
||||||
ctx.getAllEffects() should be(Seq(Effect.Watched(c.ref), Effect.Watched(c.ref)))
|
ctx.getAllEffects() should be(Seq(Effect.Watched(c.ref), Effect.Watched(c.ref)))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyA, c.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyA, c.ref))
|
||||||
r.receiveMsg() should be(Registered(ServiceKeyB, c.ref))
|
r.receiveMsg() should be(Registered(ServiceKeyB, c.ref))
|
||||||
|
|
||||||
val q = Inbox.sync[Listing[_]]("q")
|
val q = Inbox[Listing[_]]("q")
|
||||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref, c.ref)))
|
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref, c.ref)))
|
||||||
ctx.run(Find(ServiceKeyB)(q.ref))
|
ctx.run(Find(ServiceKeyB)(q.ref))
|
||||||
q.receiveMsg() should be(Listing(ServiceKeyB, Set(b.ref, c.ref)))
|
q.receiveMsg() should be(Listing(ServiceKeyB, Set(b.ref, c.ref)))
|
||||||
|
|
||||||
ctx.signal(Terminated(c.ref))
|
ctx.signal(Terminated(c.ref)(null))
|
||||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
||||||
ctx.run(Find(ServiceKeyB)(q.ref))
|
ctx.run(Find(ServiceKeyB)(q.ref))
|
||||||
|
|
@ -106,7 +107,6 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
def `must work with ask`(): Unit = sync(runTest("Receptionist") {
|
def `must work with ask`(): Unit = sync(runTest("Receptionist") {
|
||||||
StepWise[Registered[ServiceA]] { (ctx, startWith) ⇒
|
StepWise[Registered[ServiceA]] { (ctx, startWith) ⇒
|
||||||
val self = ctx.self
|
val self = ctx.self
|
||||||
import system.executionContext
|
|
||||||
startWith.withKeepTraces(true) {
|
startWith.withKeepTraces(true) {
|
||||||
val r = ctx.spawnAnonymous(Props(behavior))
|
val r = ctx.spawnAnonymous(Props(behavior))
|
||||||
val s = ctx.spawnAnonymous(propsA)
|
val s = ctx.spawnAnonymous(propsA)
|
||||||
|
|
@ -116,7 +116,7 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
}.expectMessage(1.second) {
|
}.expectMessage(1.second) {
|
||||||
case (msg, (f, s)) ⇒
|
case (msg, (f, s)) ⇒
|
||||||
msg should be(Registered(ServiceKeyA, s))
|
msg should be(Registered(ServiceKeyA, s))
|
||||||
f foreach (self ! _)
|
f.foreach(self ! _)(system.executionContext)
|
||||||
s
|
s
|
||||||
}.expectMessage(1.second) {
|
}.expectMessage(1.second) {
|
||||||
case (msg, s) ⇒
|
case (msg, s) ⇒
|
||||||
|
|
@ -127,4 +127,7 @@ class ReceptionistSpec extends TypedSpec {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object `A Receptionist (native)` extends CommonTests with NativeSystem
|
||||||
|
object `A Receptionist (adapted)` extends CommonTests with AdaptedSystem
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -993,7 +993,6 @@ object MiMa extends AutoPlugin {
|
||||||
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.remote.RemoteWatcher.receiveHeartbeatRsp"),
|
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.remote.RemoteWatcher.receiveHeartbeatRsp"),
|
||||||
ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.remote.RemoteWatcher.selfHeartbeatRspMsg")
|
ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.remote.RemoteWatcher.selfHeartbeatRspMsg")
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue