Merge branch 'master' into wip-merge-to-master-patriknw

This commit is contained in:
Patrik Nordwall 2016-09-29 13:56:37 +02:00
commit 54f5b836fc
91 changed files with 5343 additions and 2232 deletions

View file

@ -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 {

View file

@ -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()

View file

@ -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(""))

View file

@ -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)) {

View file

@ -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
} }

View file

@ -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 = {

View file

@ -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 {

View file

@ -7,5 +7,4 @@ Additional Information
../common/binary-compatibility-rules ../common/binary-compatibility-rules
faq faq
books books
language-bindings
osgi osgi

View file

@ -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/>`_.

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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 Actors protocol definition we start an Actor system from After importing the Actors 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`.

View file

@ -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));
}
} }

View file

@ -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.

View file

@ -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."))

View file

@ -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 {

View file

@ -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""""
} }

View file

@ -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

View file

@ -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(

View file

@ -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()

View file

@ -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

View file

@ -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]] = {

View file

@ -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

View file

@ -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"))

View file

@ -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
View 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.

View file

@ -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)

View file

@ -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()
}
}
}

View file

@ -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) {
stages = Array.empty, new GraphAssembly(
originalAttributes = Array.empty, stages = Array.empty,
ins = Array(null), originalAttributes = Array.empty,
inOwners = Array(-1), ins = Array(null),
outs = Array(null), inOwners = Array(-1),
outOwners = Array(-1)) outs = Array(null),
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: _*)
} }

View file

@ -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()

View file

@ -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()
}
} }
} }

View file

@ -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()
}
}
}

View file

@ -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")

View file

@ -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)
} }

View file

@ -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 = { case EmitEarly
if (buffer.isFull) strategy match { () {
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 }
case DropBuffer
() {
buffer.clear() buffer.clear()
grabAndPull(true) grabAndPull()
case Fail }
case Fail
() {
failStage(new BufferOverflowException(s"Buffer overflow for delay combinator (max capacity was: $size)!")) 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") }
} case Backpressure
() {
throw new IllegalStateException("Delay buffer must never overflow in Backpressure mode")
}
}
def onPush(): Unit = {
if (buffer.isFull)
onPushWhenBufferFull()
else { else {
grabAndPull(strategy != Backpressure || buffer.used < size - 1) grabAndPull()
if (!isTimerActive(timerName)) scheduleOnce(timerName, d) if (!isTimerActive(timerName)) {
scheduleOnce(timerName, d)
}
} }
} }
def grabAndPull(pullCondition: Boolean): Unit = { 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 = {
push(out, buffer.dequeue()._2) if (isAvailable(out))
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()
} }

View file

@ -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]].

View file

@ -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.

View file

@ -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]].

View file

@ -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]].

View file

@ -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`

View file

@ -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)

View file

@ -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

View file

@ -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
@ -155,46 +135,38 @@ trait ActorContext[T] {
* See [[EffectfulActorContext]] for more advanced uses. * See [[EffectfulActorContext]] for more advanced uses.
*/ */
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 dont 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)
/**
* Retrieve the named inbox. The passed ActorRef must be one that was returned
* by one of the spawn methods earlier.
*/
def getInbox[U](child: ActorRef[U]): Inbox[U] = {
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
}
/**
* 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)"
} }
/*
* TODO
*
* Currently running a behavior requires that the context stays the same, since
* the behavior may well close over it and thus a change might not be effective
* at all. Another issue is that there is genuine state within the context that
* is coupled to the behaviors 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])
// extends EffectfulActorContext[T](_name, _props, _system) {
//
//}
//
//class ActorContextProxy[T](var d: ActorContext[T]) extends ActorContext[T]

View file

@ -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 */
case _ false 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
} }
override def hashCode = untypedRef.hashCode
override def compareTo(other: ActorRef[Any]) = untypedRef.compareTo(other.untypedRef)
}
/** final override def hashCode: Int = path.uid
* This trait is used to hide the `!` method from Java code.
*/ /**
trait ScalaActorRef[-T] { this: ActorRef[T] * Equals takes path and the unique id of the actor cell into account.
def !(msg: T): Unit = tell(msg) */
final override def equals(that: Any): Boolean = that match {
case other: ActorRef[_] path.uid == other.path.uid && path == other.path
case _ false
}
final override def toString: String = s"Actor[${path}#${path.uid}]"
} }
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"))
} }

View file

@ -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 ActorContexts 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 guardians 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]] hierarchiesthis 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])
} }

View file

@ -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 requestreply protocol. * The ask-pattern implements the initiator side of a requestreply 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) (
( adapter.ActorRefAdapter[U](untyped.provider.deadLetters),
ActorRef[U](ref.provider.deadLetters), Future.failed[U](new IllegalArgumentException(s"Timeout length must be positive, question not sent to [$target]")), null)
Future.failed[U](new IllegalArgumentException(s"Timeout length must be positive, question not sent to [$actorRef]"))) else {
else { val a = PromiseActorRef(untyped.provider, timeout, target, "unknown")
val a = PromiseActorRef(ref.provider, timeout, actorRef, "unknown") val b = adapter.ActorRefAdapter[U](a)
val b = ActorRef[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"))
} }

View file

@ -57,147 +57,12 @@ abstract class Behavior[T] {
/* /*
* FIXME * FIXME
* *
* Closing over ActorContext makes a Behavior immobile: it cannot be moved to * Closing over ActorContext makes a Behavior immobile: it cannot be moved to
* another context and executed there, and therefore it cannot be replicated or * another context and executed there, and therefore it cannot be replicated or
* 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 Actors 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
} }

View 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
}

View file

@ -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))

View file

@ -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)
}
}

View file

@ -10,31 +10,34 @@ 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
class Inbox[T](name: String) {
private val q = new ConcurrentLinkedQueue[T]
val ref: ActorRef[T] = {
val uid = ThreadLocalRandom.current().nextInt()
val path = RootActorPath(Address("akka.typed.inbox", "anonymous")).child(name).withUid(uid)
new internal.FunctionRef[T](path, (msg, self) q.add(msg), (self) ())
}
def receiveMsg(): T = q.poll() match {
case null throw new NoSuchElementException(s"polling on an empty inbox: $name")
case x x
}
def receiveAll(): immutable.Seq[T] = {
@tailrec def rec(acc: List[T]): List[T] = q.poll() match {
case null acc.reverse
case x rec(x :: acc)
}
rec(Nil)
}
def hasMessages: Boolean = q.peek() != null
}
object Inbox { object Inbox {
def apply[T](name: String): Inbox[T] = new Inbox(name)
def sync[T](name: String): SyncInbox[T] = new SyncInbox(name)
class SyncInbox[T](name: String) {
private val q = new ConcurrentLinkedQueue[T]
private val r = new akka.actor.MinimalActorRef {
override def provider: ActorRefProvider = ???
override val path: ActorPath = RootActorPath(Address("akka", "SyncInbox")) / name
override def !(msg: Any)(implicit sender: akka.actor.ActorRef) = q.offer(msg.asInstanceOf[T])
}
val ref: ActorRef[T] = ActorRef(r)
def receiveMsg(): T = q.poll() match {
case null throw new NoSuchElementException(s"polling on an empty inbox: $name")
case x x
}
def receiveAll(): immutable.Seq[T] = {
@tailrec def rec(acc: List[T]): List[T] = q.poll() match {
case null acc.reverse
case x rec(x :: acc)
}
rec(Nil)
}
def hasMessages: Boolean = q.peek() != null
}
} }

View 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 Actors 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
}

View file

@ -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)
}

View file

@ -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")
}
}
} }

View file

@ -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,14 +134,14 @@ 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)
} }
/** /**

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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])
}

View file

@ -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])
}

View file

@ -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")
}
}
}

View 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
}
}

View 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 dont 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 didnt 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 objects 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)) {
// were 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")
// dont 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 theres 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)"
}

View 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 dont have deadLetters available
override def sendSystem(signal: SystemMessage): Unit = signal match {
case Create() // nothing to do
case DeathWatchNotification(ref, cause) // were not watching, and were 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"))
}

View file

@ -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 instancesdistinguished 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 packagereplace 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, "")
}
}

View 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 = ???
}

View file

@ -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()
}
}

View file

@ -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"))
}
}
}

View file

@ -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 actors 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

View 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]]
}
}

View file

@ -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,39 +87,36 @@ 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) case ExternalAddress(replyTo) { replyTo ! ctx.self; Same }
msg match { case g @ GetOne(d) if d <= Duration.Zero
case ExternalAddress(replyTo) { replyTo ! ctx.self; Same } g.replyTo ! GetOneResult(ctx.self, None)
case g @ GetOne(d) if d <= Duration.Zero asked(ctx, queue)
g.replyTo ! GetOneResult(ctx.self, None) case g @ GetOne(d)
asked(ctx, queue) asked(ctx, queue enqueue Asked(g.replyTo, Deadline.now + d))
case g @ GetOne(d) case g @ GetAll(d) if d <= Duration.Zero
asked(ctx, queue enqueue Asked(g.replyTo, Deadline.now + d)) g.replyTo ! GetAllResult(ctx.self, Nil)
case g @ GetAll(d) if d <= Duration.Zero asked(ctx, queue)
g.replyTo ! GetAllResult(ctx.self, Nil) case g @ GetAll(d)
asked(ctx, queue) ctx.schedule(d, ctx.self, GetAll(Duration.Zero)(g.replyTo))
case g @ GetAll(d) asked(ctx, queue)
ctx.schedule(d, ctx.self, GetAll(Duration.Zero)(g.replyTo)) case Enqueue(msg)
asked(ctx, queue) val (ask, q) = queue.dequeue
case Enqueue(msg) ask.replyTo ! GetOneResult(ctx.self, Some(msg))
val (ask, q) = queue.dequeue if (q.isEmpty) {
ask.replyTo ! GetOneResult(ctx.self, Some(msg)) ctx.cancelReceiveTimeout()
if (q.isEmpty) { empty(ctx)
ctx.setReceiveTimeout(Duration.Undefined) } else asked(ctx, q)
empty(ctx)
} else asked(ctx, q)
}
} }
} }
} }

View file

@ -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)
}

View file

@ -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

View file

@ -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
} }

View file

@ -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
} }

View file

@ -6,55 +6,62 @@ 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
""")) { """)) {
override def setTimeout = Timeout(20.seconds)
object `A static behavior` { object `A static behavior` {
object `must be fast` { case class Ping(x: Int, pong: ActorRef[Pong], report: ActorRef[Pong])
case class Pong(x: Int, ping: ActorRef[Ping], report: ActorRef[Pong])
case class Ping(x: Int, pong: ActorRef[Pong], report: ActorRef[Pong]) def behavior(pairs: Int, pings: Int, count: Int, executor: String) =
case class Pong(x: Int, ping: ActorRef[Ping], report: ActorRef[Pong]) StepWise[Pong] { (ctx, startWith)
startWith {
def behavior(pairs: Int, pings: Int, count: Int, executor: String) = val pinger = Props(SelfAware[Ping](self Static { msg
StepWise[Pong] { (ctx, startWith) if (msg.x == 0) {
startWith { msg.report ! Pong(0, self, msg.report)
} else msg.pong ! Pong(msg.x - 1, self, msg.report)
})).withDispatcher(executor)
val pinger = Props(SelfAware[Ping](self Static { msg val ponger = Props(SelfAware[Pong](self Static { msg
if (msg.x == 0) { msg.ping ! Ping(msg.x, self, msg.report)
msg.report ! Pong(0, self, msg.report) })).withDispatcher(executor)
} else msg.pong ! Pong(msg.x - 1, self, msg.report)
})).withDispatcher(executor)
val ponger = Props(SelfAware[Pong](self Static { msg val actors =
msg.ping ! Ping(msg.x, self, msg.report) for (i 1 to pairs)
})).withDispatcher(executor) yield (ctx.spawn(pinger, s"pinger-$i"), ctx.spawn(ponger, s"ponger-$i"))
val actors = val start = Deadline.now
for (i 1 to pairs)
yield (ctx.spawn(pinger, s"pinger-$i"), ctx.spawn(ponger, s"ponger-$i"))
val start = Deadline.now for {
(ping, pong) actors
_ 1 to pings
} ping ! Ping(count, pong, ctx.self)
for { start
(ping, pong) actors }.expectMultipleMessages(10.seconds, pairs * pings) { (msgs, start)
_ 1 to pings val stop = Deadline.now
} ping ! Ping(count, pong, ctx.self)
start val rate = 2L * count * pairs * pings / (stop - start).toMillis
}.expectMultipleMessages(60.seconds, pairs * pings) { (msgs, start) info(s"messaging rate was $rate/ms")
val stop = Deadline.now
val rate = 2L * count * pairs * pings / (stop - start).toMillis
info(s"messaging rate was $rate/ms")
}
} }
}
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
} }
} }

View file

@ -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,48 +127,66 @@ 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)
case Sig(_, other) throwIllegalState(trace, s"unexpected $other while waiting for a message") ctx.cancelReceiveTimeout()
run(ctx, tail, f(msg, value))
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
Full { def behavior(count: Int, acc: List[Either[Signal, Any]]): Behavior[Any] = {
case Sig(_, ReceiveTimeout) throwTimeout(trace, s"timeout of $t expired while waiting for a failure") ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout)
case Sig(_, failure: Failed) Full {
val (response, v) = f(failure, value) case Msg(_, ReceiveTimeout)
failure.decide(response) throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
run(ctx, tail, v) case Msg(_, msg)
case other throwIllegalState(trace, s"unexpected $other while waiting for a message") val nextCount = count + 1
if (nextCount == c) {
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)
case other throwIllegalState(trace, s"unexpected $other while waiting for termination") ctx.cancelReceiveTimeout()
run(ctx, tail, f(t, value))
case other throwIllegalState(trace, s"unexpected $other while waiting for termination")
} }
case Nil Stopped case Nil Stopped
} }

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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"
}
}

View file

@ -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"
}

View file

@ -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()
}
}

View 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)
}
}

View file

@ -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)
}
}
}

View file

@ -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,16 +123,16 @@ 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,8 +219,8 @@ 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(Duration.Zero)(inbox.ref)) int.run(GetAll(Duration.Zero)(inbox.ref))
@ -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)

View file

@ -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
} }

View file

@ -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")
) )
) )
} }