Merge pull request #22999 from ktoso/wip-typed-to-master

Merge Akka Typed to master branch
This commit is contained in:
Patrik Nordwall 2017-05-23 10:20:06 +02:00 committed by GitHub
commit 70d69c5fb2
106 changed files with 8303 additions and 3638 deletions

View file

@ -152,6 +152,14 @@ trait ActorContext extends ActorRefFactory {
*/
def watch(subject: ActorRef): ActorRef
/**
* Registers this actor as a Monitor for the provided ActorRef.
* This actor will receive the specified message when watched
* actor is terminated.
* @return the provided ActorRef
*/
def watchWith(subject: ActorRef, msg: Any): ActorRef
/**
* Unregisters this actor as Monitor for the provided ActorRef.
* @return the provided ActorRef

View file

@ -706,7 +706,8 @@ private[akka] class ActorSystemImpl(
def actorOf(props: Props, name: String): ActorRef =
if (guardianProps.isEmpty) guardian.underlying.attachChild(props, name, systemService = false)
else throw new UnsupportedOperationException("cannot create top-level actor from the outside on ActorSystem with custom user guardian")
else throw new UnsupportedOperationException(
s"cannot create top-level actor [$name] from the outside on ActorSystem with custom user guardian")
def actorOf(props: Props): ActorRef =
if (guardianProps.isEmpty) guardian.underlying.attachChild(props, systemService = false)

View file

@ -11,7 +11,11 @@ import akka.event.AddressTerminatedTopic
private[akka] trait DeathWatch { this: ActorCell
private var watching: Set[ActorRef] = ActorCell.emptyActorRefSet
/**
* This map holds a [[None]] for actors for which we send a [[Terminated]] notification on termination,
* ``Some(message)`` for actors for which we send a custom termination message.
*/
private var watching: Map[ActorRef, Option[Any]] = Map.empty
private var watchedBy: Set[ActorRef] = ActorCell.emptyActorRefSet
private var terminatedQueued: Set[ActorRef] = ActorCell.emptyActorRefSet
@ -22,7 +26,18 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
if (a != self && !watchingContains(a)) {
maintainAddressTerminatedSubscription(a) {
a.sendSystemMessage(Watch(a, self)) // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
watching += a
watching = watching.updated(a, None)
}
}
a
}
override final def watchWith(subject: ActorRef, msg: Any): ActorRef = subject match {
case a: InternalActorRef
if (a != self && !watchingContains(a)) {
maintainAddressTerminatedSubscription(a) {
a.sendSystemMessage(Watch(a, self)) // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
watching = watching.updated(a, Some(msg))
}
}
a
@ -33,7 +48,7 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
if (a != self && watchingContains(a)) {
a.sendSystemMessage(Unwatch(a, self)) // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
maintainAddressTerminatedSubscription(a) {
watching = removeFromSet(a, watching)
watching = removeFromMap(a, watching)
}
}
terminatedQueued = removeFromSet(a, terminatedQueued)
@ -51,14 +66,16 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
* it will be propagated to user's receive.
*/
protected def watchedActorTerminated(actor: ActorRef, existenceConfirmed: Boolean, addressTerminated: Boolean): Unit = {
if (watchingContains(actor)) {
maintainAddressTerminatedSubscription(actor) {
watching = removeFromSet(actor, watching)
}
if (!isTerminating) {
self.tell(Terminated(actor)(existenceConfirmed, addressTerminated), actor)
terminatedQueuedFor(actor)
}
watchingGet(actor) match {
case None // We're apparently no longer watching this actor.
case Some(optionalMessage)
maintainAddressTerminatedSubscription(actor) {
watching = removeFromMap(actor, watching)
}
if (!isTerminating) {
self.tell(optionalMessage.getOrElse(Terminated(actor)(existenceConfirmed, addressTerminated)), actor)
terminatedQueuedFor(actor)
}
}
if (childrenRefs.getByRef(actor).isDefined) handleChildTerminated(actor)
}
@ -72,12 +89,28 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
watching.contains(subject) || (subject.path.uid != ActorCell.undefinedUid &&
watching.contains(new UndefinedUidActorRef(subject)))
// TODO this should be removed and be replaced with `watching.get(subject)`
// when all actor references have uid, i.e. actorFor is removed
// Returns None when the subject is not being watched.
// If the subject is being matched, the inner option is the optional custom termination
// message that should be sent instead of the default Terminated.
private def watchingGet(subject: ActorRef): Option[Option[Any]] =
watching.get(subject).orElse(
if (subject.path.uid == ActorCell.undefinedUid) None
else watching.get(new UndefinedUidActorRef(subject)))
// TODO this should be removed and be replaced with `set - subject`
// when all actor references have uid, i.e. actorFor is removed
private def removeFromSet(subject: ActorRef, set: Set[ActorRef]): Set[ActorRef] =
if (subject.path.uid != ActorCell.undefinedUid) (set - subject) - new UndefinedUidActorRef(subject)
else set filterNot (_.path == subject.path)
// TODO this should be removed and be replaced with `set - subject`
// when all actor references have uid, i.e. actorFor is removed
private def removeFromMap[T](subject: ActorRef, map: Map[ActorRef, T]): Map[ActorRef, T] =
if (subject.path.uid != ActorCell.undefinedUid) (map - subject) - new UndefinedUidActorRef(subject)
else map filterKeys (_.path != subject.path)
protected def tellWatchersWeDied(): Unit =
if (!watchedBy.isEmpty) {
try {
@ -113,10 +146,13 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
maintainAddressTerminatedSubscription() {
try {
watching foreach { // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
case watchee: InternalActorRef watchee.sendSystemMessage(Unwatch(watchee, self))
case (watchee: InternalActorRef, _) watchee.sendSystemMessage(Unwatch(watchee, self))
case (watchee, _)
// should never happen, suppress "match may not be exhaustive" compiler warning
throw new IllegalStateException(s"Expected InternalActorRef, but got [${watchee.getClass.getName}]")
}
} finally {
watching = ActorCell.emptyActorRefSet
watching = Map.empty
terminatedQueued = ActorCell.emptyActorRefSet
}
}
@ -166,7 +202,7 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
// When a parent is watching a child and it terminates due to AddressTerminated
// it is removed by sending DeathWatchNotification with existenceConfirmed = true to support
// immediate creation of child with same name.
for (a watching; if a.path.address == address) {
for ((a, _) watching; if a.path.address == address) {
self.sendSystemMessage(DeathWatchNotification(a, existenceConfirmed = childrenRefs.getByRef(a).isDefined, addressTerminated = true))
}
}
@ -185,7 +221,7 @@ private[akka] trait DeathWatch { this: ActorCell ⇒
}
if (isNonLocal(change)) {
def hasNonLocalAddress: Boolean = ((watching exists isNonLocal) || (watchedBy exists isNonLocal))
def hasNonLocalAddress: Boolean = ((watching.keysIterator exists isNonLocal) || (watchedBy exists isNonLocal))
val had = hasNonLocalAddress
val result = block
val has = hasNonLocalAddress

View file

@ -5,6 +5,7 @@
@@@ index
* [actors](actors.md)
* [typed](typed.md)
* [fault-tolerance](fault-tolerance.md)
* [dispatchers](dispatchers.md)
* [mailboxes](mailboxes.md)
@ -17,4 +18,4 @@
* [testing](testing.md)
* [typed-actors](typed-actors.md)
@@@
@@@

View file

@ -0,0 +1,273 @@
# Akka Typed
@@@ warning
This module is currently marked as @ref:[may change](common/may-change.md) in the sense
of being the subject of active research. This means that API or semantics can
change without warning or deprecation period and it is not recommended to use
this module in production just yet—you have been warned.
@@@
As discussed in `actor-systems` (and following chapters) Actors are about
sending messages between independent units of computation, but how does that
look like? In all of the following these imports are assumed:
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #imports }
With these in place we can define our first Actor, and of course it will say
hello!
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world-actor }
This small piece of code defines two message types, one for commanding the
Actor to greet someone and one that the Actor will use to confirm that it has
done so. The `Greet` type contains not only the information of whom to
greet, it also holds an `ActorRef` that the sender of the message
supplies so that the `HelloWorld` Actor can send back the confirmation
message.
The behavior of the Actor is defined as the `greeter` value with the help
of the `immutable` behavior constructor. This constructor is called
immutable because the behavior instance doesn't have or close over any mutable
state. Processing the next message may result in a new behavior that can
potentially be different from this one. State is updated by returning a new
behavior that holds the new immutable state. In this case we don't need to
update any state, so we return `Same`.
The type of the messages handled by this behavior is declared to be of class
`Greet`, which implies that the supplied functions `msg` argument is
also typed as such. This is why we can access the `whom` and `replyTo`
members without needing to use a pattern match.
On the last line we see the `HelloWorld` Actor send a message to another
Actor, which is done using the `!` operator (pronounced “tell”). Since the
`replyTo` address is declared to be of type `ActorRef<Greeted>` the
compiler will only permit us to send messages of this type, other usage will
not be accepted.
The accepted message types of an Actor together with all reply types defines
the protocol spoken by this Actor; in this case it is a simple requestreply
protocol but Actors can model arbitrarily complex protocols when needed. The
protocol is bundled together with the behavior that implements it in a nicely
wrapped scope—the `HelloWorld` class.
Now we want to try out this Actor, so we must start an ActorSystem to host it:
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world }
We start an Actor system from the defined `greeter` behavior.
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
give the `HelloWorld` Actor a fake person to talk to—the “ask” pattern
can be used to send a message such that the reply fulfills a `CompletionStage`.
Note that the `CompletionStage` that is returned by the “ask” operation is
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 `ask` operator
takes as argument a function that pass an `ActorRef<U>`, which is the
`replyTo` parameter of the `Greet` message, which means that when sending
the reply message to that `ActorRef` the message that fulfills the
`CompletionStage` can only be of type `Greeted`.
We use this here to send the `Greet` command to the Actor and when the
reply comes back we will print it out and tell the actor system to shut down and
the program ends.
This shows that there are aspects of Actor messaging that can be type-checked
by the compiler, but this ability is not unlimited, there are bounds to what we
can statically express. Before we go on with a more complex (and realistic)
example we make a small detour to highlight some of the theory behind this.
## A Little Bit of Theory
The [Actor Model](http://en.wikipedia.org/wiki/Actor_model) as defined by
Hewitt, Bishop and Steiger in 1973 is a computational model that expresses
exactly what it means for computation to be distributed. The processing
units—Actors—can only communicate by exchanging messages and upon reception of a
message an Actor can do the following three fundamental actions:
1. send a finite number of messages to Actors it knows
2. create a finite number of new Actors
3. designate the behavior to be applied to the next message
The Akka Typed project expresses these actions using behaviors and addresses.
Messages can be sent to an address and behind this façade there is a behavior
that receives the message and acts upon it. The binding between address and
behavior can change over time as per the third point above, but that is not
visible on the outside.
With this preamble we can get to the unique property of this project, namely
that it introduces static type checking to Actor interactions: addresses are
parameterized and only messages that are of the specified type can be sent to
them. The association between an address and its type parameter must be made
when the address (and its Actor) is created. For this purpose each behavior is
also parameterized with the type of messages it is able to process. Since the
behavior can change behind the address façade, designating the next behavior is
a constrained operation: the successor must handle the same type of messages as
its predecessor. This is necessary in order to not invalidate the addresses
that refer to this Actor.
What this enables is that whenever a message is sent to an Actor we can
statically ensure that the type of the message is one that the Actor declares
to handle—we can avoid the mistake of sending completely pointless messages.
What we cannot statically ensure, though, is that the behavior behind the
address will be in a given state when our message is received. The fundamental
reason is that the association between address and behavior is a dynamic
runtime property, the compiler cannot know it while it translates the source
code.
This is the same as for normal Java objects with internal variables: when
compiling the program we cannot know what their value will be, and if the
result of a method call depends on those variables then the outcome is
uncertain to a degree—we can only be certain that the returned value is of a
given type.
We have seen above that the return type of an Actor command is described by the
type of reply-to address that is contained within the message. This allows a
conversation to be described in terms of its types: the reply will be of type
A, but it might also contain an address of type B, which then allows the other
Actor to continue the conversation by sending a message of type B to this new
address. While we cannot statically express the “current” state of an Actor, we
can express the current state of a protocol between two Actors, since that is
just given by the last message type that was received or sent.
In the next section we demonstrate this on a more realistic example.
## A More Complex Example
Consider an Actor that runs a chat room: client Actors may connect by sending
a message that contains their screen name and then they can post messages. The
chat room Actor will disseminate all posted messages to all currently connected
client Actors. The protocol definition could look like the following:
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #chatroom-protocol }
Initially the client Actors only get access to an `ActorRef<GetSession>`
which allows them to make the first step. Once a clients session has been
established it gets a `SessionGranted` message that contains a `handle` to
unlock the next protocol step, posting messages. The `PostMessage`
command will need to be sent to this particular address that represents the
session that has been added to the chat room. The other aspect of a session is
that the client has revealed its own address, via the `replyTo` argument, so that subsequent
`MessagePosted` events can be sent to it.
This illustrates how Actors can express more than just the equivalent of method
calls on Java objects. The declared message types and their contents describe a
full protocol that can involve multiple Actors and that can evolve over
multiple steps. The implementation of the chat room protocol would be as simple
as the following:
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #chatroom-behavior }
The core of this behavior is stateful, the chat room itself does not change
into something else when sessions are established, but we introduce a variable
that tracks the opened sessions. Note that by using a method parameter a `var`
is not needed. When a new `GetSession` command comes in we add that client to the
list that is in the returned behavior. Then we also need to create the sessions
`ActorRef` that will be used to post messages. In this case we want to
create a very simple Actor that just repackages the `PostMessage`
command into a `PostSessionMessage` command which also includes the
screen name. Such a wrapper Actor can be created by using the
`spawnAdapter` method on the `ActorContext`, so that we can then
go on to reply to the client with the `SessionGranted` result.
The behavior that we declare here can handle both subtypes of `Command`.
`GetSession` has been explained already and the
`PostSessionMessage` commands coming from the wrapper Actors will
trigger the dissemination of the contained chat room message to all connected
clients. But we do not want to give the ability to send
`PostSessionMessage` commands to arbitrary clients, we reserve that
right to the wrappers we create—otherwise clients could pose as completely
different screen names (imagine the `GetSession` protocol to include
authentication information to further secure this). Therefore `PostSessionMessage`
has `private` visibility and can't be created outside the actor.
If we did not care about securing the correspondence between a session and a
screen name then we could change the protocol such that `PostMessage` is
removed and all clients just get an `ActorRef<PostSessionMessage>` to
send to. In this case no wrapper would be needed and we could just use
`ctx.getSelf()`. The type-checks work out in that case because
`ActorRef<T>` is contravariant in its type parameter, meaning that we
can use a `ActorRef<Command>` wherever an
`ActorRef<PostSessionMessage>` is needed—this makes sense because the
former simply speaks more languages than the latter. The opposite would be
problematic, so passing an `ActorRef<PostSessionMessage>` where
`ActorRef<Command>` is required will lead to a type error.
### Trying it out
In order to see this chat room in action we need to write a client Actor that can use it:
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #chatroom-gabbler }
From this behavior we can create an Actor that will accept a chat room session,
post a message, wait to see it published, and then terminate. The last step
requires the ability to change behavior, we need to transition from the normal
running behavior into the terminated state. This is why here we do not return
`same`, as above, but another special value `stopped`.
Now to try things out we must start both a chat room and a gabbler and of
course we do this inside an Actor system. Since there can be only one guardian
supervisor we could either start the chat room from the gabbler (which we dont
want—it complicates its logic) or the gabbler from the chat room (which is
nonsensical) or we start both of them from a third Actor—our only sensible
choice:
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #chatroom-main }
In good tradition we call the `main` Actor what it is, it directly
corresponds to the `main` method in a traditional Java application. This
Actor will perform its job on its own accord, we do not need to send messages
from the outside, so we declare it to be of type `Void`. Actors receive not
only external messages, they also are notified of certain system events,
so-called Signals. In order to get access to those we choose to implement this
particular one using the `immutable` behavior decorator. The
provided `onSignal` function will be invoked for signals (subclasses of `Signal`)
or the `onMessage` function for user messages.
This particular `main` Actor is created using `Actor.deferred`, which is like a factory for a behavior.
Creation of the behavior instance is deferred until the actor is started, as opposed to `Actor.immutable`
that creates the behavior instance immediately before the actor is running. The factory function in
`deferred` pass the `ActorContext` as parameter and that can for example be used for spawning child actors.
This `main` Actor creates the chat room and the gabbler and the session between them is initiated, and when the
gabbler is finished we will receive the `Terminated` event due to having
called `ctx.watch` for it. This allows us to shut down the Actor system: when
the main Actor terminates there is nothing more to do.
## Status of this Project and Relation to Akka Actors
Akka Typed is the result of many years of research and previous attempts
(including Typed Channels in the 2.2.x series) and it is on its way to
stabilization, but maturing such a profound change to the core concept of Akka
will take a long time. We expect that this module will stay marked
@ref:[may change](common/may-change.md) for multiple major releases of Akka and the
plain `akka.actor.Actor` will not be deprecated or go away anytime soon.
Being a research project also entails that the reference documentation is not
as detailed as it will be for a final version, please refer to the API
documentation for greater depth and finer detail.
### Main Differences
The most prominent difference is the removal of the `sender()` functionality.
This turned out to be the Achilles heel of the Typed Channels project, it is
the feature that makes its type signatures and macros too complex to be viable.
The solution chosen in Akka Typed is to explicitly include the properly typed
reply-to address in the message, which both burdens the user with this task but
also places this aspect of protocol design where it belongs.
The other prominent difference is the removal of the `Actor` trait. In
order to avoid closing over unstable references from different execution
contexts (e.g. Future transformations) we turned all remaining methods that
were on this trait into messages: the behavior receives the
`ActorContext` as an argument during processing and the lifecycle hooks
have been converted into Signals.
A side-effect of this is that behaviors can now be tested in isolation without
having to be packaged into an Actor, tests can run fully synchronously without
having to worry about timeouts and spurious failures. Another side-effect is
that behaviors can nicely be composed and decorated, see `tap`, or
`widened` combinators; nothing about these is special or internal, new
combinators can be written as external libraries or tailor-made for each project.

View file

@ -3,9 +3,9 @@
@@@ warning
This module is currently marked as @ref:[may change](common/may-change.md) in the sense
of being the subject of active research. This means that API or semantics can
change without warning or deprecation period and it is not recommended to use
this module in production just yet—you have been warned.
of being the subject of active research. This means that API or semantics can
change without warning or deprecation period and it is not recommended to use
this module in production just yet—you have been warned.
@@@
@ -13,12 +13,12 @@ As discussed in @ref:[Actor Systems](general/actor-systems.md) (and following ch
sending messages between independent units of computation, but how does that
look like? In all of the following these imports are assumed:
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #imports }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #imports }
With these in place we can define our first Actor, and of course it will say
hello!
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #hello-world-actor }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #hello-world-actor }
This small piece of code defines two message types, one for commanding the
Actor to greet someone and one that the Actor will use to confirm that it has
@ -28,10 +28,12 @@ supplies so that the `HelloWorld` Actor can send back the confirmation
message.
The behavior of the Actor is defined as the `greeter` value with the help
of the `Stateless` behavior constructor—there are many different ways of
formulating behaviors as we shall see in the following. The “stateless” behavior
is not capable of changing in response to a message, it will stay the same
until the Actor is stopped by its parent.
of the `immutable` behavior constructor. This constructor is called
immutable because the behavior instance doesn't have or close over any mutable
state. Processing the next message may result in a new behavior that can
potentially be different from this one. State is updated by returning a new
behavior that holds the new immutable state. In this case we don't need to
update any state, so we return `Same`.
The type of the messages handled by this behavior is declared to be of class
`Greet`, which implies that the supplied functions `msg` argument is
@ -39,8 +41,8 @@ also typed as such. This is why we can access the `whom` and `replyTo`
members without needing to use a pattern match.
On the last line we see the `HelloWorld` Actor send a message to another
Actor, which is done using the `!` operator (pronounced “tell”). Since the
`replyTo` address is declared to be of type `ActorRef[Greeted]` the
Actor, which is done using the `tell` method (represented by the `!` operator).
Since the `replyTo` address is declared to be of type `ActorRef[Greeted]` the
compiler will only permit us to send messages of this type, other usage will
not be accepted.
@ -52,7 +54,7 @@ wrapped scope—the `HelloWorld` object.
Now we want to try out this Actor, so we must start an ActorSystem to host it:
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #hello-world }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #hello-world }
After importing the Actors protocol definition we start an Actor system from
the defined behavior.
@ -94,9 +96,9 @@ exactly what it means for computation to be distributed. The processing
units—Actors—can only communicate by exchanging messages and upon reception of a
message an Actor can do the following three fundamental actions:
1. send a finite number of messages to Actors it knows
2. create a finite number of new Actors
3. designate the behavior to be applied to the next message
1. send a finite number of messages to Actors it knows
2. create a finite number of new Actors
3. designate the behavior to be applied to the next message
The Akka Typed project expresses these actions using behaviors and addresses.
Messages can be sent to an address and behind this façade there is a behavior
@ -148,7 +150,7 @@ a message that contains their screen name and then they can post messages. The
chat room Actor will disseminate all posted messages to all currently connected
client Actors. The protocol definition could look like the following:
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-protocol }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-protocol }
Initially the client Actors only get access to an `ActorRef[GetSession]`
which allows them to make the first step. Once a clients session has been
@ -165,7 +167,7 @@ full protocol that can involve multiple Actors and that can evolve over
multiple steps. The implementation of the chat room protocol would be as simple
as the following:
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-behavior }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-behavior }
The core of this behavior is stateful, the chat room itself does not change
into something else when sessions are established, but we introduce a variable
@ -206,23 +208,17 @@ problematic, so passing an `ActorRef[PostSessionMessage]` where
In order to see this chat room in action we need to write a client Actor that can use it:
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-gabbler }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-gabbler }
From this behavior we can create an Actor that will accept a chat room session,
post a message, wait to see it published, and then terminate. The last step
requires the ability to change behavior, we need to transition from the normal
running behavior into the terminated state. This is why this Actor uses a
different behavior constructor named `Total`. This constructor takes as
argument a function from the handled message type, in this case
`SessionEvent`, to the next behavior. That next behavior must again be
of the same type as we discussed in the theory section above. Here we either
stay in the very same behavior or we terminate, and both of these cases are so
common that there are special values `Same` and `Stopped` that can be used.
The behavior is named “total” (as opposed to “partial”) because the declared
function must handle all values of its input type. Since `SessionEvent`
is a sealed trait the Scala compiler will warn us if we forget to handle one of
the subtypes; in this case it reminded us that alternatively to
`SessionGranted` we may also receive a `SessionDenied` event.
running behavior into the terminated state. This is why here we do not return
`same`, as above, but another special value `stopped`.
Since `SessionEvent` is a sealed trait the Scala compiler will warn us
if we forget to handle one of the subtypes; in this case it reminded us that
alternatively to `SessionGranted` we may also receive a
`SessionDenied` event.
Now to try things out we must start both a chat room and a gabbler and of
course we do this inside an Actor system. Since there can be only one guardian
@ -231,7 +227,7 @@ want—it complicates its logic) or the gabbler from the chat room (which is
nonsensical) or we start both of them from a third Actor—our only sensible
choice:
@@snip [IntroSpec.scala]($code$/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-main }
@@snip [IntroSpec.scala]($akka$/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-main }
In good tradition we call the `main` Actor what it is, it directly
corresponds to the `main` method in a traditional Java application. This
@ -239,19 +235,21 @@ Actor will perform its job on its own accord, we do not need to send messages
from the outside, so we declare it to be of type `NotUsed`. Actors receive not
only external messages, they also are notified of certain system events,
so-called Signals. In order to get access to those we choose to implement this
particular one using the `Stateful` behavior decorator. The
provided `signal` function will be invoked for signals (subclasses of `Signal`)
or the `mesg` function for user messages.
particular one using the `immutable` behavior decorator. The
provided `onSignal` function will be invoked for signals (subclasses of `Signal`)
or the `onMessage` function for user messages.
This particular main Actor reacts to two signals: when it is started it will
first receive the `PreStart` signal, upon which the chat room and the
gabbler are created and the session between them is initiated, and when the
This particular `main` Actor is created using `Actor.deferred`, which is like a factory for a behavior.
Creation of the behavior instance is deferred until the actor is started, as opposed to `Actor.immutable`
that creates the behavior instance immediately before the actor is running. The factory function in
`deferred` pass the `ActorContext` as parameter and that can for example be used for spawning child actors.
This ``main`` Actor creates the chat room and the gabbler and the session between them is initiated, and when the
gabbler is finished we will receive the `Terminated` event due to having
called `ctx.watch` for it. This allows us to shut down the Actor system: when
the main Actor terminates there is nothing more to do.
Therefore after creating the Actor system with the `main` Actors
`Props` we just await its termination.
`Behavior` we just await its termination.
## Status of this Project and Relation to Akka Actors
@ -285,7 +283,6 @@ have been converted into Signals.
A side-effect of this is that behaviors can now be tested in isolation without
having to be packaged into an Actor, tests can run fully synchronously without
having to worry about timeouts and spurious failures. Another side-effect is
that behaviors can nicely be composed and decorated, see the `And`,
`Or`, `Widened`, `ContextAware` combinators; nothing about
these is special or internal, new combinators can be written as external
libraries or tailor-made for each project.
that behaviors can nicely be composed and decorated, see `tap`, or
`widen` combinators; nothing about these is special or internal, new
combinators can be written as external libraries or tailor-made for each project.

View file

@ -0,0 +1,6 @@
import akka.{ AkkaBuild, Formatting, OSGi }
AkkaBuild.defaultSettings
Formatting.formatSettings
disablePlugins(MimaPlugin)

View file

@ -0,0 +1,20 @@
############################################
# Akka Typed Testkit Reference Config File #
############################################
# This is the reference config file that contains all the default settings.
# Make your edits/overrides in your application.conf.
akka.typed.test {
# factor by which to scale timeouts during tests, e.g. to account for shared
# build system load
timefactor = 1.0
# duration to wait in expectMsg and friends outside of within() block
# by default
single-expect-default = 3s
# The timeout that is added as an implicit by DefaultTimeout trait
default-timeout = 5s
}

View file

@ -1,13 +1,17 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
package akka.typed.testkit
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration.Duration
import java.util.concurrent.ConcurrentLinkedQueue
import akka.typed.{ ActorContext, ActorRef, ActorSystem, Behavior, EmptyProps, PostStop, Props, Signal }
import scala.annotation.tailrec
import scala.collection.immutable
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal
import scala.concurrent.duration.{ Duration, FiniteDuration }
/**
* All tracked effects must extend implement this type. It is deliberately
@ -32,8 +36,8 @@ object Effect {
*/
class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _mailboxCapacity: Int, _system: ActorSystem[Nothing])
extends StubbedActorContext[T](_name, _mailboxCapacity, _system) {
import akka.{ actor a }
import Effect._
import akka.{ actor a }
private val effectQueue = new ConcurrentLinkedQueue[Effect]
def getEffect(): Effect = effectQueue.poll() match {
@ -49,17 +53,31 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma
}
def hasEffects: Boolean = effectQueue.peek() != null
private var current = _initialBehavior
if (Behavior.isAlive(current)) signal(PreStart)
private var current = Behavior.validateAsInitial(Behavior.undefer(_initialBehavior, this))
def currentBehavior: Behavior[T] = current
def isAlive: Boolean = Behavior.isAlive(current)
def run(msg: T): Unit = current = Behavior.canonicalize(current.message(this, msg), current)
def signal(signal: Signal): Unit = current = Behavior.canonicalize(current.management(this, signal), current)
private def handleException: Catcher[Unit] = {
case NonFatal(e)
try Behavior.canonicalize(Behavior.interpretSignal(current, this, PostStop), current, this) // TODO why canonicalize here?
catch { case NonFatal(ex) /* ignore, real is logging */ }
throw e
}
override def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = {
def run(msg: T): Unit = {
try {
current = Behavior.canonicalize(Behavior.interpretMessage(current, this, msg), current, this)
} catch handleException
}
def signal(signal: Signal): Unit = {
try {
current = Behavior.canonicalize(Behavior.interpretSignal(current, this, signal), current, this)
} catch handleException
}
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
val ref = super.spawnAnonymous(behavior)
effectQueue.offer(Spawned(ref.path.name))
ref
@ -69,19 +87,19 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma
effectQueue.offer(Spawned(ref.path.name))
ref
}
override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = {
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
effectQueue.offer(Spawned(name))
super.spawn(behavior, name)
}
override def stop(child: ActorRef[_]): Boolean = {
override def stop[U](child: ActorRef[U]): Boolean = {
effectQueue.offer(Stopped(child.path.name))
super.stop(child)
}
override def watch[U](other: ActorRef[U]): ActorRef[U] = {
override def watch[U](other: ActorRef[U]): Unit = {
effectQueue.offer(Watched(other))
super.watch(other)
}
override def unwatch[U](other: ActorRef[U]): ActorRef[U] = {
override def unwatch[U](other: ActorRef[U]): Unit = {
effectQueue.offer(Unwatched(other))
super.unwatch(other)
}

View file

@ -1,16 +1,15 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
package akka.typed.testkit
import java.util.concurrent.{ ConcurrentLinkedQueue, ThreadLocalRandom }
import akka.actor.{ Address, RootActorPath }
import akka.typed.{ ActorRef, internal }
import java.util.concurrent.ConcurrentLinkedQueue
import akka.actor.ActorPath
import akka.actor.RootActorPath
import akka.actor.Address
import scala.collection.immutable
import scala.annotation.tailrec
import akka.actor.ActorRefProvider
import java.util.concurrent.ThreadLocalRandom
import scala.collection.immutable
/**
* Utility for receiving messages outside of an actor. No methods are provided

View file

@ -0,0 +1,106 @@
package akka.typed.testkit
import akka.{ actor untyped }
import akka.typed._
import akka.util.Helpers
import scala.collection.immutable.TreeMap
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration.FiniteDuration
import akka.annotation.InternalApi
import akka.typed.internal.ActorContextImpl
/**
* An [[ActorContext]] for synchronous execution of a [[Behavior]] that
* provides only stubs for the effects an Actor can perform and replaces
* created child Actors by a synchronous Inbox (see `Inbox.sync`).
*
* See [[EffectfulActorContext]] for more advanced uses.
*/
class StubbedActorContext[T](
val name: String,
override val mailboxCapacity: Int,
override val system: ActorSystem[Nothing]) extends ActorContextImpl[T] {
val selfInbox = Inbox[T](name)
override val self = selfInbox.ref
private var _children = TreeMap.empty[String, Inbox[_]]
private val childName = Iterator from 1 map (Helpers.base64(_))
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 spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
val i = Inbox[U](childName.next())
_children += i.ref.path.name i
i.ref
}
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] =
_children get name match {
case Some(_) throw untyped.InvalidActorNameException(s"actor name $name is already taken")
case None
// FIXME correct child path for the Inbox ref
val i = Inbox[U](name)
_children += name i
i.ref
}
/**
* Do not actually stop the child inbox, only simulate the liveness check.
* Removal is asynchronous, explicit removeInbox is needed from outside afterwards.
*/
override def stop[U](child: ActorRef[U]): Boolean = {
_children.get(child.path.name) match {
case None false
case Some(inbox) inbox.ref == child
}
}
override def watch[U](other: ActorRef[U]): Unit = ()
override def watchWith[U](other: ActorRef[U], msg: T): Unit = ()
override def unwatch[U](other: ActorRef[U]): Unit = ()
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = ()
override def cancelReceiveTimeout(): Unit = ()
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): untyped.Cancellable = new untyped.Cancellable {
override def cancel() = false
override def isCancelled = true
}
override def executionContext: ExecutionContextExecutor = system.executionContext
/**
* INTERNAL API
*/
@InternalApi private[akka] def internalSpawnAdapter[U](f: U T, name: String): ActorRef[U] = {
val n = if (name != "") s"${childName.next()}-$name" else childName.next()
val i = Inbox[U](n)
_children += i.ref.path.name i
new internal.FunctionRef[U](
self.path / i.ref.path.name,
(msg, _) { val m = f(msg); if (m != null) { selfInbox.ref ! m; i.ref ! msg } },
(self) selfInbox.ref.sorry.sendSystem(internal.DeathWatchNotification(self, null)))
}
/**
* Retrieve the inbox representing the given child actor. The passed ActorRef must be one that was returned
* by one of the spawn methods earlier.
*/
def childInbox[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
}
/**
* Retrieve the inbox representing the child actor with the given name.
*/
def childInbox[U](name: String): Inbox[U] = _children(name).asInstanceOf[Inbox[U]]
/**
* Remove the given inbox from the list of children, for example after
* having simulated its termination.
*/
def removeChildInbox(child: ActorRef[Nothing]): Unit = _children -= child.path.name
override def toString: String = s"Inbox($self)"
}

View file

@ -0,0 +1,68 @@
package akka.typed.testkit
import akka.event.Logging.{ LogEvent, StdOutLogger }
import akka.testkit.{ EventFilter, TestEvent TE }
import akka.typed.Logger
import akka.typed.Logger.{ Command, Initialize }
import scala.annotation.tailrec
import akka.typed.scaladsl.Actor
import akka.typed.Behavior
/**
* EventListener for running tests, which allows selectively filtering out
* expected messages. To use it, include something like this into
* your config
*
* <pre><code>
* akka.typed {
* loggers = ["akka.typed.testkit.TestEventListener"]
* }
* </code></pre>
*/
class TestEventListener extends Logger with StdOutLogger {
override val initialBehavior: Behavior[Command] = {
Actor.deferred[Command] { _
Actor.immutable[Command] {
case (ctx, Initialize(eventStream, replyTo))
val log = ctx.spawn(Actor.deferred[AnyRef] { childCtx
var filters: List[EventFilter] = Nil
def filter(event: LogEvent): Boolean = filters exists (f try { f(event) } catch { case e: Exception false })
def addFilter(filter: EventFilter): Unit = filters ::= filter
def removeFilter(filter: EventFilter) {
@tailrec def removeFirst(list: List[EventFilter], zipped: List[EventFilter] = Nil): List[EventFilter] = list match {
case head :: tail if head == filter tail.reverse_:::(zipped)
case head :: tail removeFirst(tail, head :: zipped)
case Nil filters // filter not found, just return original list
}
filters = removeFirst(filters)
}
Actor.immutable[AnyRef] {
case (_, TE.Mute(filters))
filters foreach addFilter
Actor.same
case (_, TE.UnMute(filters))
filters foreach removeFilter
Actor.same
case (_, event: LogEvent)
if (!filter(event)) print(event)
Actor.same
case _ Actor.unhandled
}
}, "logger")
eventStream.subscribe(log, classOf[TE.Mute])
eventStream.subscribe(log, classOf[TE.UnMute])
ctx.watch(log) // sign death pact
replyTo ! log
Actor.empty
}
}
}
}

View file

@ -0,0 +1,34 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
import com.typesafe.config.Config
import scala.concurrent.duration.FiniteDuration
import akka.util.Timeout
import akka.typed.ActorSystem
object TestKitSettings {
/**
* Reads configuration settings from `akka.typed.test` section.
*/
def apply(system: ActorSystem[_]): TestKitSettings =
apply(system.settings.config)
/**
* Reads configuration settings from given `Config` that
* must have the same layout as the `akka.typed.test` section.
*/
def apply(config: Config): TestKitSettings =
new TestKitSettings(config)
}
class TestKitSettings(val config: Config) {
import akka.util.Helpers._
val TestTimeFactor = config.getDouble("akka.typed.test.timefactor").
requiring(tf !tf.isInfinite && tf > 0, "akka.typed.test.timefactor must be positive finite double")
val SingleExpectDefaultTimeout: FiniteDuration = config.getMillisDuration("akka.typed.test.single-expect-default")
val DefaultTimeout: Timeout = Timeout(config.getMillisDuration("akka.typed.test.default-timeout"))
}

View file

@ -0,0 +1,229 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit.scaladsl
import scala.concurrent.duration._
import java.util.concurrent.BlockingDeque
import akka.typed.Behavior
import akka.typed.scaladsl.Actor
import akka.typed.ActorSystem
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.atomic.AtomicInteger
import akka.typed.ActorRef
import akka.util.Timeout
import akka.util.PrettyDuration.PrettyPrintableDuration
import scala.concurrent.Await
import com.typesafe.config.Config
import akka.typed.testkit.TestKitSettings
import akka.util.BoxedType
import scala.reflect.ClassTag
object TestProbe {
private val testActorId = new AtomicInteger(0)
def apply[M]()(implicit system: ActorSystem[_], settings: TestKitSettings): TestProbe[M] =
apply(name = "testProbe")
def apply[M](name: String)(implicit system: ActorSystem[_], settings: TestKitSettings): TestProbe[M] =
new TestProbe(name)
private def testActor[M](queue: BlockingDeque[M]): Behavior[M] = Actor.immutable { (ctx, msg)
queue.offerLast(msg)
Actor.same
}
}
class TestProbe[M](name: String)(implicit val system: ActorSystem[_], val settings: TestKitSettings) {
import TestProbe._
private val queue = new LinkedBlockingDeque[M]
private var end: Duration = Duration.Undefined
/**
* if last assertion was expectNoMsg, disable timing failure upon within()
* block end.
*/
private var lastWasNoMsg = false
private var lastMessage: Option[M] = None
val testActor: ActorRef[M] = {
implicit val timeout = Timeout(3.seconds)
val futRef = system.systemActorOf(TestProbe.testActor(queue), s"$name-${testActorId.incrementAndGet()}")
Await.result(futRef, timeout.duration + 1.second)
}
/**
* Shorthand to get the `testActor`.
*/
def ref: ActorRef[M] = testActor
/**
* Obtain current time (`System.nanoTime`) as Duration.
*/
protected def now: FiniteDuration = System.nanoTime.nanos
/**
* Obtain time remaining for execution of the innermost enclosing `within`
* block or missing that it returns the properly dilated default for this
* case from settings (key "akka.typed.test.single-expect-default").
*/
def remainingOrDefault = remainingOr(settings.SingleExpectDefaultTimeout.dilated)
/**
* Obtain time remaining for execution of the innermost enclosing `within`
* block or throw an [[AssertionError]] if no `within` block surrounds this
* call.
*/
def remaining: FiniteDuration = end match {
case f: FiniteDuration f - now
case _ throw new AssertionError("`remaining` may not be called outside of `within`")
}
/**
* Obtain time remaining for execution of the innermost enclosing `within`
* block or missing that it returns the given duration.
*/
def remainingOr(duration: FiniteDuration): FiniteDuration = end match {
case x if x eq Duration.Undefined duration
case x if !x.isFinite throw new IllegalArgumentException("`end` cannot be infinite")
case f: FiniteDuration f - now
}
private def remainingOrDilated(max: Duration): FiniteDuration = max match {
case x if x eq Duration.Undefined remainingOrDefault
case x if !x.isFinite throw new IllegalArgumentException("max duration cannot be infinite")
case f: FiniteDuration f.dilated
}
/**
* Execute code block while bounding its execution time between `min` and
* `max`. `within` blocks may be nested. All methods in this trait which
* take maximum wait times are available in a version which implicitly uses
* the remaining time governed by the innermost enclosing `within` block.
*
* Note that the timeout is scaled using Duration.dilated, which uses the
* configuration entry "akka.typed.test.timefactor", while the min Duration is not.
*
* {{{
* val ret = within(50 millis) {
* test ! Ping
* expectMsgType[Pong]
* }
* }}}
*/
def within[T](min: FiniteDuration, max: FiniteDuration)(f: T): T = {
val _max = max.dilated
val start = now
val rem = if (end == Duration.Undefined) Duration.Inf else end - start
assert(rem >= min, s"required min time $min not possible, only ${rem.pretty} left")
lastWasNoMsg = false
val max_diff = _max min rem
val prev_end = end
end = start + max_diff
val ret = try f finally end = prev_end
val diff = now - start
assert(min <= diff, s"block took ${diff.pretty}, should at least have been $min")
if (!lastWasNoMsg) {
assert(diff <= max_diff, s"block took ${diff.pretty}, exceeding ${max_diff.pretty}")
}
ret
}
/**
* Same as calling `within(0 seconds, max)(f)`.
*/
def within[T](max: FiniteDuration)(f: T): T = within(Duration.Zero, max)(f)
/**
* Same as `expectMsg(remainingOrDefault, obj)`, but correctly treating the timeFactor.
*/
def expectMsg[T <: M](obj: T): T = expectMsg_internal(remainingOrDefault, obj)
/**
* Receive one message from the test actor and assert that it equals the
* given object. Wait time is bounded by the given duration, with an
* AssertionFailure being thrown in case of timeout.
*
* @return the received object
*/
def expectMsg[T <: M](max: FiniteDuration, obj: T): T = expectMsg_internal(max.dilated, obj)
/**
* Receive one message from the test actor and assert that it equals the
* given object. Wait time is bounded by the given duration, with an
* AssertionFailure being thrown in case of timeout.
*
* @return the received object
*/
def expectMsg[T <: M](max: FiniteDuration, hint: String, obj: T): T = expectMsg_internal(max.dilated, obj, Some(hint))
private def expectMsg_internal[T <: M](max: Duration, obj: T, hint: Option[String] = None): T = {
val o = receiveOne(max)
val hintOrEmptyString = hint.map(": " + _).getOrElse("")
assert(o != null, s"timeout ($max) during expectMsg while waiting for $obj" + hintOrEmptyString)
assert(obj == o, s"expected $obj, found $o" + hintOrEmptyString)
o.asInstanceOf[T]
}
/**
* Receive one message from the internal queue of the TestActor. If the given
* duration is zero, the queue is polled (non-blocking).
*
* This method does NOT automatically scale its Duration parameter!
*/
private def receiveOne(max: Duration): M = {
val message =
if (max == 0.seconds) {
queue.pollFirst
} else if (max.isFinite) {
queue.pollFirst(max.length, max.unit)
} else {
queue.takeFirst
}
lastWasNoMsg = false
lastMessage = if (message == null) None else Some(message)
message
}
/**
* Assert that no message is received for the specified time.
*/
def expectNoMsg(max: FiniteDuration) { expectNoMsg_internal(max.dilated) }
private def expectNoMsg_internal(max: FiniteDuration) {
val o = receiveOne(max)
assert(o == null, s"received unexpected message $o")
lastWasNoMsg = true
}
/**
* Same as `expectMsgType[T](remainingOrDefault)`, but correctly treating the timeFactor.
*/
def expectMsgType[T <: M](implicit t: ClassTag[T]): T =
expectMsgClass_internal(remainingOrDefault, t.runtimeClass.asInstanceOf[Class[T]])
/**
* Receive one message from the test actor and assert that it conforms to the
* given type (after erasure). Wait time is bounded by the given duration,
* with an AssertionFailure being thrown in case of timeout.
*
* @return the received object
*/
def expectMsgType[T <: M](max: FiniteDuration)(implicit t: ClassTag[T]): T =
expectMsgClass_internal(max.dilated, t.runtimeClass.asInstanceOf[Class[T]])
private def expectMsgClass_internal[C](max: FiniteDuration, c: Class[C]): C = {
val o = receiveOne(max)
assert(o != null, s"timeout ($max) during expectMsgClass waiting for $c")
assert(BoxedType(c) isInstance o, s"expected $c, found ${o.getClass} ($o)")
o.asInstanceOf[C]
}
}

View file

@ -0,0 +1,34 @@
/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.testkit
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.reflect.ClassTag
import scala.collection.immutable
import java.util.concurrent.TimeUnit.MILLISECONDS
import akka.typed.ActorSystem
package object scaladsl {
/**
* Scala API. Scale timeouts (durations) during tests with the configured
* 'akka.test.timefactor'.
* Implicit class providing `dilated` method.
*
* {{{
* import scala.concurrent.duration._
* import akka.typed.testkit.scaladsl._
* 10.milliseconds.dilated
* }}}
*
* Uses the scaling factor from the `TestTimeFactor` in the [[TestKitSettings]]
* (in implicit scope).
*
*/
implicit class TestDuration(val duration: FiniteDuration) extends AnyVal {
def dilated(implicit settings: TestKitSettings): FiniteDuration =
(duration * settings.TestTimeFactor).asInstanceOf[FiniteDuration]
}
}

View file

@ -0,0 +1,8 @@
import akka.{ AkkaBuild, Formatting }
AkkaBuild.defaultSettings
AkkaBuild.mayChangeSettings
Formatting.formatSettings
disablePlugins(MimaPlugin)

View file

@ -0,0 +1,16 @@
package akka.typed;
import scala.concurrent.ExecutionContext;
import java.util.concurrent.Executor;
public class DispatcherSelectorTest {
// Compile time only test to verify
// dispatcher factories are accessible from Java
private DispatcherSelector def = DispatcherSelector.defaultDispatcher();
private DispatcherSelector conf = DispatcherSelector.fromConfig("somepath");
private DispatcherSelector ex = DispatcherSelector.fromExecutor((Executor) null);
private DispatcherSelector ec = DispatcherSelector.fromExecutionContext((ExecutionContext) null);
}

View file

@ -0,0 +1,81 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed;
import akka.actor.*;
import com.typesafe.config.ConfigFactory;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import java.util.Optional;
import static junit.framework.TestCase.assertSame;
import static org.junit.Assert.assertTrue;
public class ExtensionsTest extends JUnitSuite {
public static class MyExtImpl implements Extension {
}
public static class MyExtension extends ExtensionId<MyExtImpl> {
private final static MyExtension instance = new MyExtension();
private MyExtension() {
}
public static MyExtension getInstance() {
return instance;
}
public MyExtImpl createExtension(ActorSystem<?> system) {
return new MyExtImpl();
}
public static MyExtImpl get(ActorSystem<?> system) {
return instance.apply(system);
}
}
@Test
public void loadJavaExtensionsFromConfig() {
final ActorSystem<Object> system = ActorSystem.create(
"loadJavaExtensionsFromConfig",
Behavior.empty(),
Optional.empty(),
Optional.of(ConfigFactory.parseString("akka.typed.extensions += \"akka.typed.ExtensionsTest$MyExtension\"").resolve()),
Optional.empty(),
Optional.empty()
);
try {
// note that this is not the intended end user way to access it
assertTrue(system.hasExtension(MyExtension.getInstance()));
MyExtImpl instance1 = MyExtension.get(system);
MyExtImpl instance2 = MyExtension.get(system);
assertSame(instance1, instance2);
} finally {
system.terminate();
}
}
@Test
public void loadScalaExtension() {
final ActorSystem<Object> system = ActorSystem.create("loadScalaExtension", Behavior.empty());
try {
DummyExtension1 instance1 = DummyExtension1.get(system);
DummyExtension1 instance2 = DummyExtension1.get(system);
assertSame(instance1, instance2);
} finally {
system.terminate();
}
}
}

View file

@ -0,0 +1,116 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed.javadsl;
import akka.typed.*;
import akka.typed.ActorContext;
import static akka.typed.javadsl.Actor.*;
import java.util.concurrent.TimeUnit;
import scala.concurrent.duration.Duration;
@SuppressWarnings("unused")
public class ActorCompile {
interface MyMsg {}
static class MyMsgA implements MyMsg {
final ActorRef<String> replyTo;
public MyMsgA(ActorRef<String> replyTo) {
this.replyTo = replyTo;
}
}
static class MyMsgB implements MyMsg {
final String greeting;
public MyMsgB(String greeting) {
this.greeting = greeting;
}
}
Behavior<MyMsg> actor1 = immutable((ctx, msg) -> stopped(), (ctx, signal) -> same());
Behavior<MyMsg> actor2 = immutable((ctx, msg) -> unhandled());
Behavior<MyMsg> actor4 = empty();
Behavior<MyMsg> actor5 = ignore();
Behavior<MyMsg> actor6 = tap((ctx, signal) -> {}, (ctx, msg) -> {}, actor5);
Behavior<MyMsgA> actor7 = actor6.narrow();
Behavior<MyMsg> actor8 = deferred(ctx -> {
final ActorRef<MyMsg> self = ctx.getSelf();
return monitor(self, ignore());
});
Behavior<MyMsg> actor9 = widened(actor7, pf -> pf.match(MyMsgA.class, x -> x));
Behavior<MyMsg> actor10 = immutable((ctx, msg) -> stopped(actor4), (ctx, signal) -> same());
ActorSystem<MyMsg> system = ActorSystem.create("Sys", actor1);
{
Actor.<MyMsg>immutable((ctx, msg) -> {
if (msg instanceof MyMsgA) {
return immutable((ctx2, msg2) -> {
if (msg2 instanceof MyMsgB) {
((MyMsgA) msg).replyTo.tell(((MyMsgB) msg2).greeting);
ActorRef<String> adapter = ctx2.spawnAdapter(s -> new MyMsgB(s.toUpperCase()));
}
return same();
});
} else return unhandled();
});
}
{
Behavior<MyMsg> b = Actor.withTimers(timers -> {
timers.startPeriodicTimer("key", new MyMsgB("tick"), Duration.create(1, TimeUnit.SECONDS));
return Actor.ignore();
});
}
static class MyBehavior extends ExtensibleBehavior<MyMsg> {
@Override
public Behavior<MyMsg> receiveSignal(ActorContext<MyMsg> ctx, Signal msg) throws Exception {
return this;
}
@Override
public Behavior<MyMsg> receiveMessage(ActorContext<MyMsg> ctx, MyMsg msg) throws Exception {
ActorRef<String> adapter = ctx.asJava().spawnAdapter(s -> new MyMsgB(s.toUpperCase()));
return this;
}
}
// SupervisorStrategy
{
SupervisorStrategy strategy1 = SupervisorStrategy.restart();
SupervisorStrategy strategy2 = SupervisorStrategy.restart().withLoggingEnabled(false);
SupervisorStrategy strategy3 = SupervisorStrategy.resume();
SupervisorStrategy strategy4 =
SupervisorStrategy.restartWithLimit(3, Duration.create(1, TimeUnit.SECONDS));
SupervisorStrategy strategy5 =
SupervisorStrategy.restartWithBackoff(
Duration.create(200, TimeUnit.MILLISECONDS),
Duration.create(10, TimeUnit.SECONDS),
0.1);
BackoffSupervisorStrategy strategy6 =
SupervisorStrategy.restartWithBackoff(
Duration.create(200, TimeUnit.MILLISECONDS),
Duration.create(10, TimeUnit.SECONDS),
0.1);
SupervisorStrategy strategy7 = strategy6.withResetBackoffAfter(Duration.create(2, TimeUnit.SECONDS));
Behavior<MyMsg> behv =
Actor.restarter(RuntimeException.class, strategy1,
Actor.restarter(IllegalStateException.class,
strategy6, Actor.ignore()));
}
}

View file

@ -0,0 +1,339 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl;
import org.junit.ClassRule;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import scala.concurrent.duration.FiniteDuration;
import java.util.concurrent.TimeUnit;
import akka.actor.ActorSystem;
import akka.testkit.AkkaJUnitActorSystemResource;
import akka.testkit.AkkaSpec;
import akka.typed.ActorRef;
import akka.typed.Behavior;
import akka.typed.Signal;
import akka.typed.Terminated;
import akka.testkit.javadsl.TestKit;
import akka.actor.SupervisorStrategy;
import static akka.typed.javadsl.Actor.*;
public class AdapterTest extends JUnitSuite {
static akka.actor.Props untyped1() {
return akka.actor.Props.create(Untyped1.class, () -> new Untyped1());
}
static class Untyped1 extends akka.actor.AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("ping", s -> getSender().tell("pong", getSelf()))
.match(ThrowIt.class, t -> {
throw t;
})
.build();
}
}
static class Typed1 {
private final akka.actor.ActorRef ref;
private final akka.actor.ActorRef probe;
private Typed1(akka.actor.ActorRef ref, akka.actor.ActorRef probe) {
this.ref = ref;
this.probe = probe;
}
static Behavior<String> create(akka.actor.ActorRef ref, akka.actor.ActorRef probe) {
Typed1 logic = new Typed1(ref, probe);
return immutable(
(ctx, msg) -> logic.onMessage(ctx, msg),
(ctx, sig) -> logic.onSignal(ctx, sig));
}
Behavior<String> onMessage(ActorContext<String> ctx, String msg) {
if (msg.equals("send")) {
akka.actor.ActorRef replyTo = Adapter.toUntyped(ctx.getSelf());
ref.tell("ping", replyTo);
return same();
} else if (msg.equals("pong")) {
probe.tell("ok", akka.actor.ActorRef.noSender());
return same();
} else if (msg.equals("actorOf")) {
akka.actor.ActorRef child = Adapter.actorOf(ctx, untyped1());
child.tell("ping", Adapter.toUntyped(ctx.getSelf()));
return same();
} else if (msg.equals("watch")) {
Adapter.watch(ctx, ref);
return same();
} else if (msg.equals("supervise-stop")) {
akka.actor.ActorRef child = Adapter.actorOf(ctx, untyped1());
Adapter.watch(ctx, child);
child.tell(new ThrowIt3(), Adapter.toUntyped(ctx.getSelf()));
child.tell("ping", Adapter.toUntyped(ctx.getSelf()));
return same();
} else if (msg.equals("stop-child")) {
akka.actor.ActorRef child = Adapter.actorOf(ctx, untyped1());
Adapter.watch(ctx, child);
Adapter.stop(ctx, child);
return same();
} else {
return unhandled();
}
}
Behavior<String> onSignal(ActorContext<String> ctx, Signal sig) {
if (sig instanceof Terminated) {
probe.tell("terminated", akka.actor.ActorRef.noSender());
return same();
} else {
return unhandled();
}
}
}
static interface Typed2Msg {};
static final class Ping implements Typed2Msg {
public final ActorRef<String> replyTo;
public Ping(ActorRef<String> replyTo) {
this.replyTo = replyTo;
}
}
static final class StopIt implements Typed2Msg {}
static abstract class ThrowIt extends RuntimeException implements Typed2Msg {}
static class ThrowIt1 extends ThrowIt {}
static class ThrowIt2 extends ThrowIt {}
static class ThrowIt3 extends ThrowIt {}
static akka.actor.Props untyped2(ActorRef<Ping> ref, akka.actor.ActorRef probe) {
return akka.actor.Props.create(Untyped2.class, () -> new Untyped2(ref, probe));
}
static class Untyped2 extends akka.actor.AbstractActor {
private final ActorRef<Ping> ref;
private final akka.actor.ActorRef probe;
private final SupervisorStrategy strategy;
Untyped2(ActorRef<Ping> ref, akka.actor.ActorRef probe) {
this.ref = ref;
this.probe = probe;
this.strategy = strategy();
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("send", s -> {
ActorRef<String> replyTo = Adapter.toTyped(getSelf());
ref.tell(new Ping(replyTo));
})
.matchEquals("pong", s -> probe.tell("ok", getSelf()))
.matchEquals("spawn", s -> {
ActorRef<Typed2Msg> child = Adapter.spawnAnonymous(getContext(), typed2());
child.tell(new Ping(Adapter.toTyped(getSelf())));
})
.matchEquals("actorOf-props", s -> {
// this is how Cluster Sharding can be used
akka.actor.ActorRef child = getContext().actorOf(typed2Props());
child.tell(new Ping(Adapter.toTyped(getSelf())), akka.actor.ActorRef.noSender());
})
.matchEquals("watch", s -> Adapter.watch(getContext(), ref))
.match(akka.actor.Terminated.class, t -> probe.tell("terminated", getSelf()))
.matchEquals("supervise-stop", s -> testSupervice(new ThrowIt1()))
.matchEquals("supervise-resume", s -> testSupervice(new ThrowIt2()))
.matchEquals("supervise-restart", s -> testSupervice(new ThrowIt3()))
.matchEquals("stop-child", s -> {
ActorRef<Typed2Msg> child = Adapter.spawnAnonymous(getContext(), typed2());
Adapter.watch(getContext(), child);
Adapter.stop(getContext(), child);
})
.build();
}
private void testSupervice(ThrowIt t) {
ActorRef<Typed2Msg> child = Adapter.spawnAnonymous(getContext(), typed2());
Adapter.watch(getContext(), child);
child.tell(t);
child.tell(new Ping(Adapter.toTyped(getSelf())));
}
private SupervisorStrategy strategy() {
return new akka.actor.OneForOneStrategy(false, akka.japi.pf.DeciderBuilder
.match(ThrowIt1.class, e -> {
probe.tell("thrown-stop", getSelf());
return SupervisorStrategy.stop();
})
.match(ThrowIt2.class, e -> {
probe.tell("thrown-resume", getSelf());
return SupervisorStrategy.resume();
})
.match(ThrowIt3.class, e -> {
probe.tell("thrown-restart", getSelf());
// TODO Restart will not really restart the behavior
return SupervisorStrategy.restart();
})
.build());
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
}
static Behavior<Typed2Msg> typed2() {
return Actor.immutable((ctx, msg) -> {
if (msg instanceof Ping) {
ActorRef<String> replyTo = ((Ping) msg).replyTo;
replyTo.tell("pong");
return same();
} else if (msg instanceof StopIt) {
return stopped();
} else if (msg instanceof ThrowIt) {
throw (ThrowIt) msg;
} else {
return unhandled();
}
});
}
static akka.actor.Props typed2Props() {
return Adapter.props(() -> typed2());
}
@ClassRule
public static AkkaJUnitActorSystemResource actorSystemResource = new AkkaJUnitActorSystemResource("ActorSelectionTest",
AkkaSpec.testConf());
private final ActorSystem system = actorSystemResource.getSystem();
@Test
public void shouldSendMessageFromTypedToUntyped() {
TestKit probe = new TestKit(system);
akka.actor.ActorRef untypedRef = system.actorOf(untyped1());
ActorRef<String> typedRef = Adapter.spawnAnonymous(system, Typed1.create(untypedRef, probe.getRef()));
typedRef.tell("send");
probe.expectMsg("ok");
}
@Test
public void shouldSendMessageFromUntypedToTyped() {
TestKit probe = new TestKit(system);
ActorRef<Ping> typedRef = Adapter.spawnAnonymous(system, typed2()).narrow();
akka.actor.ActorRef untypedRef = system.actorOf(untyped2(typedRef, probe.getRef()));
untypedRef.tell("send", akka.actor.ActorRef.noSender());
probe.expectMsg("ok");
}
@Test
public void shouldSpawnTypedChildFromUntypedParent() {
TestKit probe = new TestKit(system);
ActorRef<Ping> ignore = Adapter.spawnAnonymous(system, ignore());
akka.actor.ActorRef untypedRef = system.actorOf(untyped2(ignore, probe.getRef()));
untypedRef.tell("spawn", akka.actor.ActorRef.noSender());
probe.expectMsg("ok");
}
@Test
public void shouldActorOfTypedChildViaPropsFromUntypedParent() {
TestKit probe = new TestKit(system);
ActorRef<Ping> ignore = Adapter.spawnAnonymous(system, ignore());
akka.actor.ActorRef untypedRef = system.actorOf(untyped2(ignore, probe.getRef()));
untypedRef.tell("actorOf-props", akka.actor.ActorRef.noSender());
probe.expectMsg("ok");
}
@Test
public void shouldActorOfUntypedChildFromTypedParent() {
TestKit probe = new TestKit(system);
akka.actor.ActorRef ignore = system.actorOf(akka.actor.Props.empty());
ActorRef<String> typedRef = Adapter.spawnAnonymous(system, Typed1.create(ignore, probe.getRef()));
typedRef.tell("actorOf");
probe.expectMsg("ok");
}
@Test
public void shouldWatchTypedFromUntyped() {
TestKit probe = new TestKit(system);
ActorRef<Typed2Msg> typedRef = Adapter.spawnAnonymous(system, typed2());
ActorRef<Ping> typedRef2 = typedRef.narrow();
akka.actor.ActorRef untypedRef = system.actorOf(untyped2(typedRef2, probe.getRef()));
untypedRef.tell("watch", akka.actor.ActorRef.noSender());
typedRef.tell(new StopIt());
probe.expectMsg("terminated");
}
@Test
public void shouldWatchUntypedFromTyped() {
TestKit probe = new TestKit(system);
akka.actor.ActorRef untypedRef = system.actorOf(untyped1());
ActorRef<String> typedRef = Adapter.spawnAnonymous(system, Typed1.create(untypedRef, probe.getRef()));
typedRef.tell("watch");
untypedRef.tell(akka.actor.PoisonPill.getInstance() , akka.actor.ActorRef.noSender());
probe.expectMsg("terminated");
}
@Test
public void shouldSuperviseTypedChildFromUntypedParent() {
TestKit probe = new TestKit(system);
ActorRef<Ping> ignore = Adapter.spawnAnonymous(system, ignore());
akka.actor.ActorRef untypedRef = system.actorOf(untyped2(ignore, probe.getRef()));
untypedRef.tell("supervise-stop", akka.actor.ActorRef.noSender());
probe.expectMsg("thrown-stop");
// ping => ok should not get through here
probe.expectMsg("terminated");
untypedRef.tell("supervise-resume", akka.actor.ActorRef.noSender());
probe.expectMsg("thrown-resume");
probe.expectMsg("ok");
untypedRef.tell("supervise-restart", akka.actor.ActorRef.noSender());
probe.expectMsg("thrown-restart");
probe.expectMsg("ok");
}
@Test
public void shouldSuperviseUntypedChildFromTypedParent() {
TestKit probe = new TestKit(system);
akka.actor.ActorRef ignore = system.actorOf(akka.actor.Props.empty());
ActorRef<String> typedRef = Adapter.spawnAnonymous(system, Typed1.create(ignore, probe.getRef()));
int originalLogLevel = system.eventStream().logLevel();
try {
// supress the logging with stack trace
system.eventStream().setLogLevel(Integer.MIN_VALUE); // OFF
// only stop supervisorStrategy
typedRef.tell("supervise-stop");
probe.expectMsg("terminated");
} finally {
system.eventStream().setLogLevel(originalLogLevel);
}
probe.expectNoMsg(FiniteDuration.create(100, TimeUnit.MILLISECONDS)); // no pong
}
@Test
public void shouldStopTypedChildFromUntypedParent() {
TestKit probe = new TestKit(system);
ActorRef<Ping> ignore = Adapter.spawnAnonymous(system, ignore());
akka.actor.ActorRef untypedRef = system.actorOf(untyped2(ignore, probe.getRef()));
untypedRef.tell("stop-child", akka.actor.ActorRef.noSender());
probe.expectMsg("terminated");
}
@Test
public void shouldStopUntypedChildFromTypedParent() {
TestKit probe = new TestKit(system);
akka.actor.ActorRef ignore = system.actorOf(akka.actor.Props.empty());
ActorRef<String> typedRef = Adapter.spawnAnonymous(system, Typed1.create(ignore, probe.getRef()));
typedRef.tell("stop-child");
probe.expectMsg("terminated");
}
}

View file

@ -0,0 +1,84 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import akka.typed.Behavior;
import akka.typed.Terminated;
import akka.typed.ActorRef;
import java.util.ArrayList;
import static akka.typed.javadsl.Actor.same;
import static akka.typed.javadsl.Actor.stopped;
/**
* Test creating [[Behavior]]s using [[BehaviorBuilder]]
*/
public class BehaviorBuilderTest extends JUnitSuite {
interface Message {
}
static final class One implements Message {
public String foo() {
return "Bar";
}
}
static final class MyList<T> extends ArrayList<T> implements Message {
};
@Test
public void shouldCompile() {
Behavior<Message> b = Actor.immutable(Message.class)
.onMessage(One.class, (ctx, o) -> {
o.foo();
return same();
})
.onMessage(One.class, o -> o.foo().startsWith("a"), (ctx, o) -> same())
.onMessageUnchecked(MyList.class, (ActorContext<Message> ctx, MyList<String> l) -> {
String first = l.get(0);
return Actor.<Message>same();
})
.onSignal(Terminated.class, (ctx, t) -> {
System.out.println("Terminating along with " + t.ref());
return stopped();
})
.build();
}
interface CounterMessage {};
static final class Increase implements CounterMessage {};
static final class Get implements CounterMessage {
final ActorRef<Got> sender;
public Get(ActorRef<Got> sender) {
this.sender = sender;
}
};
static final class Got {
final int n;
public Got(int n) {
this.n = n;
}
}
public Behavior<CounterMessage> immutableCounter(int currentValue) {
return Actor.immutable(CounterMessage.class)
.onMessage(Increase.class, (ctx, o) -> {
return immutableCounter(currentValue + 1);
})
.onMessage(Get.class, (ctx, o) -> {
o.sender.tell(new Got(currentValue));
return same();
})
.build();
}
@Test
public void testImmutableCounter() {
Behavior<CounterMessage> immutable = immutableCounter(0);
}
}

View file

@ -0,0 +1,63 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import akka.typed.Behavior;
import static org.junit.Assert.assertEquals;
/**
* Test creating [[MutableActor]]s using [[ReceiveBuilder]]
*/
public class ReceiveBuilderTest extends JUnitSuite {
@Test
public void testMutableCounter() {
Behavior<BehaviorBuilderTest.CounterMessage> mutable = Actor.mutable(ctx -> new Actor.MutableBehavior<BehaviorBuilderTest.CounterMessage>() {
int currentValue = 0;
private Behavior<BehaviorBuilderTest.CounterMessage> receiveIncrease(BehaviorBuilderTest.Increase msg) {
currentValue++;
return this;
}
private Behavior<BehaviorBuilderTest.CounterMessage> receiveGet(BehaviorBuilderTest.Get get) {
get.sender.tell(new BehaviorBuilderTest.Got(currentValue));
return this;
}
@Override
public Actor.Receive<BehaviorBuilderTest.CounterMessage> createReceive() {
return receiveBuilder()
.onMessage(BehaviorBuilderTest.Increase.class, this::receiveIncrease)
.onMessage(BehaviorBuilderTest.Get.class, this::receiveGet)
.build();
}
});
}
private static class MyMutableBehavior extends Actor.MutableBehavior<BehaviorBuilderTest.CounterMessage> {
private int value;
public MyMutableBehavior(int initialValue) {
super();
this.value = initialValue;
}
@Override
public Actor.Receive<BehaviorBuilderTest.CounterMessage> createReceive() {
assertEquals(42, value);
return receiveBuilder().build();
}
}
@Test
public void testInitializationOrder() throws Exception {
MyMutableBehavior mutable = new MyMutableBehavior(42);
assertEquals(Actor.unhandled(), mutable.receiveMessage(null, new BehaviorBuilderTest.Increase()));
}
}

View file

@ -0,0 +1,106 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed.javadsl;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import akka.Done;
import org.scalatest.junit.JUnitSuite;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.util.Timeout;
import org.junit.Test;
import akka.typed.*;
import static akka.typed.javadsl.Actor.*;
import static akka.typed.javadsl.AskPattern.*;
public class WatchTest extends JUnitSuite {
static interface Message {}
static final class RunTest<T> implements Message {
private final ActorRef<T> replyTo;
public RunTest(ActorRef<T> replyTo) {
this.replyTo = replyTo;
}
}
static final class Stop {}
static final class CustomTerminationMessage implements Message {}
// final FiniteDuration fiveSeconds = FiniteDuration.create(5, TimeUnit.SECONDS);
final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
final Behavior<Stop> exitingActor = immutable((ctx, msg) -> {
System.out.println("Stopping!");
return stopped();
});
private Behavior<RunTest<Done>> waitingForTermination(ActorRef<Done> replyWhenTerminated) {
return immutable(
(ctx, msg) -> unhandled(),
(ctx, sig) -> {
if (sig instanceof Terminated) {
replyWhenTerminated.tell(Done.getInstance());
}
return same();
}
);
}
private Behavior<Message> waitingForMessage(ActorRef<Done> replyWhenReceived) {
return immutable(
(ctx, msg) -> {
if (msg instanceof CustomTerminationMessage) {
replyWhenReceived.tell(Done.getInstance());
return same();
} else {
return unhandled();
}
}
);
}
@Test
public void shouldWatchTerminatingActor() throws Exception {
Behavior<RunTest<Done>> root = immutable((ctx, msg) -> {
ActorRef<Stop> watched = ctx.spawn(exitingActor, "exitingActor");
ctx.watch(watched);
watched.tell(new Stop());
return waitingForTermination(msg.replyTo);
});
ActorSystem<RunTest<Done>> system = ActorSystem.create("sysname", root);
try {
// Not sure why this does not compile without an explicit cast?
// system.tell(new RunTest());
CompletionStage<Done> result = AskPattern.ask((ActorRef<RunTest<Done>>)system, (ActorRef<Done> ref) -> new RunTest<Done>(ref), timeout, system.scheduler());
result.toCompletableFuture().get(3, TimeUnit.SECONDS);
} finally {
Await.ready(system.terminate(), Duration.create(10, TimeUnit.SECONDS));
}
}
@Test
public void shouldWatchWithCustomMessage() throws Exception {
Behavior<Message> root = immutable((ctx, msg) -> {
if (msg instanceof RunTest) {
ActorRef<Stop> watched = ctx.spawn(exitingActor, "exitingActor");
ctx.watchWith(watched, new CustomTerminationMessage());
watched.tell(new Stop());
return waitingForMessage(((RunTest<Done>) msg).replyTo);
} else {
return unhandled();
}
});
ActorSystem<Message> system = ActorSystem.create("sysname", root);
try {
// Not sure why this does not compile without an explicit cast?
// system.tell(new RunTest());
CompletionStage<Done> result = AskPattern.ask((ActorRef<Message>)system, (ActorRef<Done> ref) -> new RunTest<Done>(ref), timeout, system.scheduler());
result.toCompletableFuture().get(3, TimeUnit.SECONDS);
} finally {
Await.ready(system.terminate(), Duration.create(10, TimeUnit.SECONDS));
}
}
}

View file

@ -0,0 +1,207 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package jdocs.akka.typed;
//#imports
import akka.typed.ActorRef;
import akka.typed.ActorSystem;
import akka.typed.Behavior;
import akka.typed.Terminated;
import akka.typed.javadsl.Actor;
import akka.typed.javadsl.AskPattern;
import akka.util.Timeout;
//#imports
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
public class IntroTest {
//#hello-world-actor
public abstract static class HelloWorld {
//no instances of this class, it's only a name space for messages
// and static methods
private HelloWorld() {
}
public static final class Greet{
public final String whom;
public final ActorRef<Greeted> replyTo;
public Greet(String whom, ActorRef<Greeted> replyTo) {
this.whom = whom;
this.replyTo = replyTo;
}
}
public static final class Greeted {
public final String whom;
public Greeted(String whom) {
this.whom = whom;
}
}
public static final Behavior<Greet> greeter = Actor.immutable((ctx, msg) -> {
System.out.println("Hello " + msg.whom + "!");
msg.replyTo.tell(new Greeted(msg.whom));
return Actor.same();
});
}
//#hello-world-actor
public static void main(String[] args) {
//#hello-world
final ActorSystem<HelloWorld.Greet> system =
ActorSystem.create("hello", HelloWorld.greeter);
final CompletionStage<HelloWorld.Greeted> reply =
AskPattern.ask(system,
(ActorRef<HelloWorld.Greeted> replyTo) -> new HelloWorld.Greet("world", replyTo),
new Timeout(3, TimeUnit.SECONDS), system.scheduler());
reply.thenAccept(greeting -> {
System.out.println("result: " + greeting.whom);
system.terminate();
});
//#hello-world
}
//#chatroom-actor
public static class ChatRoom {
//#chatroom-protocol
static interface Command {}
public static final class GetSession implements Command {
public final String screenName;
public final ActorRef<SessionEvent> replyTo;
public GetSession(String screenName, ActorRef<SessionEvent> replyTo) {
this.screenName = screenName;
this.replyTo = replyTo;
}
}
//#chatroom-protocol
//#chatroom-behavior
private static final class PostSessionMessage implements Command {
public final String screenName;
public final String message;
public PostSessionMessage(String screenName, String message) {
this.screenName = screenName;
this.message = message;
}
}
//#chatroom-behavior
//#chatroom-protocol
static interface SessionEvent {}
public static final class SessionGranted implements SessionEvent {
public final ActorRef<PostMessage> handle;
public SessionGranted(ActorRef<PostMessage> handle) {
this.handle = handle;
}
}
public static final class SessionDenied implements SessionEvent {
public final String reason;
public SessionDenied(String reason) {
this.reason = reason;
}
}
public static final class MessagePosted implements SessionEvent {
public final String screenName;
public final String message;
public MessagePosted(String screenName, String message) {
this.screenName = screenName;
this.message = message;
}
}
public static final class PostMessage {
public final String message;
public PostMessage(String message) {
this.message = message;
}
}
//#chatroom-protocol
//#chatroom-behavior
public static Behavior<Command> behavior() {
return chatRoom(new ArrayList<ActorRef<SessionEvent>>());
}
private static Behavior<Command> chatRoom(List<ActorRef<SessionEvent>> sessions) {
return Actor.immutable(Command.class)
.onMessage(GetSession.class, (ctx, getSession) -> {
ActorRef<PostMessage> wrapper = ctx.spawnAdapter(p ->
new PostSessionMessage(getSession.screenName, p.message));
getSession.replyTo.tell(new SessionGranted(wrapper));
List<ActorRef<SessionEvent>> newSessions =
new ArrayList<ActorRef<SessionEvent>>(sessions);
newSessions.add(getSession.replyTo);
return chatRoom(newSessions);
})
.onMessage(PostSessionMessage.class, (ctx, post) -> {
MessagePosted mp = new MessagePosted(post.screenName, post.message);
sessions.forEach(s -> s.tell(mp));
return Actor.same();
})
.build();
}
//#chatroom-behavior
}
//#chatroom-actor
//#chatroom-gabbler
public static abstract class Gabbler {
private Gabbler() {
}
public static Behavior<ChatRoom.SessionEvent> behavior() {
return Actor.immutable(ChatRoom.SessionEvent.class)
.onMessage(ChatRoom.SessionDenied.class, (ctx, msg) -> {
System.out.println("cannot start chat room session: " + msg.reason);
return Actor.stopped();
})
.onMessage(ChatRoom.SessionGranted.class, (ctx, msg) -> {
msg.handle.tell(new ChatRoom.PostMessage("Hello World!"));
return Actor.same();
})
.onMessage(ChatRoom.MessagePosted.class, (ctx, msg) -> {
System.out.println("message has been posted by '" +
msg.screenName +"': " + msg.message);
return Actor.stopped();
})
.build();
}
}
//#chatroom-gabbler
public static void runChatRoom() throws Exception {
//#chatroom-main
Behavior<Void> main = Actor.deferred(ctx -> {
ActorRef<ChatRoom.Command> chatRoom =
ctx.spawn(ChatRoom.behavior(), "chatRoom");
ActorRef<ChatRoom.SessionEvent> gabbler =
ctx.spawn(Gabbler.behavior(), "gabbler");
ctx.watch(gabbler);
chatRoom.tell(new ChatRoom.GetSession("ol Gabbler", gabbler));
return Actor.immutable(Void.class)
.onSignal(Terminated.class, (c, sig) -> Actor.stopped())
.build();
});
final ActorSystem<Void> system =
ActorSystem.create("ChatRoomDemo", main);
Await.result(system.whenTerminated(), Duration.create(3, TimeUnit.SECONDS));
//#chatroom-main
}
}

View file

@ -0,0 +1,109 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package jdocs.akka.typed;
//#imports
import akka.typed.ActorRef;
import akka.typed.Behavior;
import akka.typed.javadsl.Actor;
import akka.typed.javadsl.Actor.Receive;
import akka.typed.javadsl.ActorContext;
//#imports
import java.util.ArrayList;
import java.util.List;
public class MutableIntroTest {
//#chatroom-actor
public static class ChatRoom {
//#chatroom-protocol
static interface Command {}
public static final class GetSession implements Command {
public final String screenName;
public final ActorRef<SessionEvent> replyTo;
public GetSession(String screenName, ActorRef<SessionEvent> replyTo) {
this.screenName = screenName;
this.replyTo = replyTo;
}
}
//#chatroom-protocol
//#chatroom-behavior
private static final class PostSessionMessage implements Command {
public final String screenName;
public final String message;
public PostSessionMessage(String screenName, String message) {
this.screenName = screenName;
this.message = message;
}
}
//#chatroom-behavior
//#chatroom-protocol
static interface SessionEvent {}
public static final class SessionGranted implements SessionEvent {
public final ActorRef<PostMessage> handle;
public SessionGranted(ActorRef<PostMessage> handle) {
this.handle = handle;
}
}
public static final class SessionDenied implements SessionEvent {
public final String reason;
public SessionDenied(String reason) {
this.reason = reason;
}
}
public static final class MessagePosted implements SessionEvent {
public final String screenName;
public final String message;
public MessagePosted(String screenName, String message) {
this.screenName = screenName;
this.message = message;
}
}
public static final class PostMessage {
public final String message;
public PostMessage(String message) {
this.message = message;
}
}
//#chatroom-protocol
//#chatroom-behavior
public static Behavior<Command> behavior() {
return Actor.mutable(ChatRoomBehavior::new);
}
public static class ChatRoomBehavior extends Actor.MutableBehavior<Command> {
final ActorContext<Command> ctx;
final List<ActorRef<SessionEvent>> sessions = new ArrayList<ActorRef<SessionEvent>>();
public ChatRoomBehavior(ActorContext<Command> ctx) {
this.ctx = ctx;
}
@Override
public Receive<Command> createReceive() {
return receiveBuilder()
.onMessage(GetSession.class, getSession -> {
ActorRef<PostMessage> wrapper = ctx.spawnAdapter(p ->
new PostSessionMessage(getSession.screenName, p.message));
getSession.replyTo.tell(new SessionGranted(wrapper));
sessions.add(getSession.replyTo);
return Actor.same();
})
.onMessage(PostSessionMessage.class, post -> {
MessagePosted mp = new MessagePosted(post.screenName, post.message);
sessions.forEach(s -> s.tell(mp));
return this;
})
.build();
}
}
//#chatroom-behavior
}
//#chatroom-actor
}

View file

@ -0,0 +1,4 @@
akka.typed {
# for the akka.actor.ExtensionSpec
library-extensions += "akka.typed.InstanceCountingExtension"
}

View file

@ -5,7 +5,6 @@ import scala.concurrent.Future
import com.typesafe.config.ConfigFactory
import akka.actor.DeadLetterSuppression
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.Actor.Stateful
object ActorContextSpec {
@ -73,171 +72,183 @@ object ActorContextSpec {
final case class GetAdapter(replyTo: ActorRef[Adapter], name: String = "") extends Command
final case class Adapter(a: ActorRef[Command]) extends Event
def subject(monitor: ActorRef[Monitor]): Behavior[Command] =
Actor.Stateful(
(ctx, message) message match {
def subject(monitor: ActorRef[Monitor], ignorePostStop: Boolean): Behavior[Command] =
Actor.immutable[Command] {
(ctx, message)
message match {
case ReceiveTimeout
monitor ! GotReceiveTimeout
Actor.same
case Ping(replyTo)
replyTo ! Pong1
Actor.same
case Miss(replyTo)
replyTo ! Missed
Actor.unhandled
case Renew(replyTo)
replyTo ! Renewed
subject(monitor, ignorePostStop)
case Throw(ex)
throw ex
case MkChild(name, mon, replyTo)
val child = name match {
case None ctx.spawnAnonymous(Actor.restarter[Throwable]().wrap(subject(mon, ignorePostStop)))
case Some(n) ctx.spawn(Actor.restarter[Throwable]().wrap(subject(mon, ignorePostStop)), n)
}
replyTo ! Created(child)
Actor.same
case SetTimeout(d, replyTo)
d match {
case f: FiniteDuration ctx.setReceiveTimeout(f, ReceiveTimeout)
case _ ctx.cancelReceiveTimeout()
}
replyTo ! TimeoutSet
Actor.same
case Schedule(delay, target, msg, replyTo)
replyTo ! Scheduled
ctx.schedule(delay, target, msg)
Actor.same
case Stop
Actor.stopped
case Kill(ref, replyTo)
if (ctx.stop(ref)) replyTo ! Killed
else replyTo ! NotKilled
Actor.same
case Watch(ref, replyTo)
ctx.watch(ref)
replyTo ! Watched
Actor.same
case Unwatch(ref, replyTo)
ctx.unwatch(ref)
replyTo ! Unwatched
Actor.same
case GetInfo(replyTo)
replyTo ! Info(ctx.self, ctx.system)
Actor.same
case GetChild(name, replyTo)
replyTo ! Child(ctx.child(name))
Actor.same
case GetChildren(replyTo)
replyTo ! Children(ctx.children.toSet)
Actor.same
case BecomeInert(replyTo)
replyTo ! BecameInert
Actor.immutable {
case (_, Ping(replyTo))
replyTo ! Pong2
Actor.same
case (_, Throw(ex))
throw ex
case _ Actor.unhandled
}
case BecomeCareless(replyTo)
replyTo ! BecameCareless
Actor.immutable[Command] {
case (_, _) Actor.unhandled
} onSignal {
case (_, PostStop) if ignorePostStop Actor.same // ignore PostStop here
case (_, Terminated(_)) Actor.unhandled
case (_, sig)
monitor ! GotSignal(sig)
Actor.same
}
case GetAdapter(replyTo, name)
replyTo ! Adapter(ctx.spawnAdapter(identity, name))
Actor.same
}
} onSignal {
case (_, PostStop) if ignorePostStop Actor.same // ignore PostStop here
case (ctx, signal) monitor ! GotSignal(signal); Actor.same
}
def oldSubject(monitor: ActorRef[Monitor], ignorePostStop: Boolean): Behavior[Command] = {
Actor.immutable[Command] {
case (ctx, message) message match {
case ReceiveTimeout
monitor ! GotReceiveTimeout
Actor.Same
Actor.same
case Ping(replyTo)
replyTo ! Pong1
Actor.Same
Actor.same
case Miss(replyTo)
replyTo ! Missed
Actor.Unhandled
Actor.unhandled
case Renew(replyTo)
replyTo ! Renewed
subject(monitor)
subject(monitor, ignorePostStop)
case Throw(ex)
throw ex
case MkChild(name, mon, replyTo)
val child = name match {
case None ctx.spawnAnonymous(Actor.Restarter[Throwable]().wrap(subject(mon)))
case Some(n) ctx.spawn(Actor.Restarter[Throwable]().wrap(subject(mon)), n)
case None ctx.spawnAnonymous(Actor.restarter[Throwable]().wrap(subject(mon, ignorePostStop)))
case Some(n) ctx.spawn(Actor.restarter[Throwable]().wrap(subject(mon, ignorePostStop)), n)
}
replyTo ! Created(child)
Actor.Same
Actor.same
case SetTimeout(d, replyTo)
d match {
case f: FiniteDuration ctx.setReceiveTimeout(f, ReceiveTimeout)
case _ ctx.cancelReceiveTimeout()
}
replyTo ! TimeoutSet
Actor.Same
Actor.same
case Schedule(delay, target, msg, replyTo)
replyTo ! Scheduled
ctx.schedule(delay, target, msg)
Actor.Same
case Stop Actor.Stopped
Actor.same
case Stop
Actor.stopped
case Kill(ref, replyTo)
if (ctx.stop(ref)) replyTo ! Killed
else replyTo ! NotKilled
Actor.Same
Actor.same
case Watch(ref, replyTo)
ctx.watch[Nothing](ref)
ctx.watch(ref)
replyTo ! Watched
Actor.Same
Actor.same
case Unwatch(ref, replyTo)
ctx.unwatch[Nothing](ref)
ctx.unwatch(ref)
replyTo ! Unwatched
Actor.Same
Actor.same
case GetInfo(replyTo)
replyTo ! Info(ctx.self, ctx.system)
Actor.Same
Actor.same
case GetChild(name, replyTo)
replyTo ! Child(ctx.child(name))
Actor.Same
Actor.same
case GetChildren(replyTo)
replyTo ! Children(ctx.children.toSet)
Actor.Same
Actor.same
case BecomeInert(replyTo)
replyTo ! BecameInert
Actor.Stateless {
Actor.immutable[Command] {
case (_, Ping(replyTo))
replyTo ! Pong2
Actor.same
case (_, Throw(ex))
throw ex
case _ ()
case _ Actor.same
}
case BecomeCareless(replyTo)
replyTo ! BecameCareless
Actor.Stateful(
(ctx, message) Actor.Unhandled,
(ctx, signal) signal match {
case Terminated(_) Actor.Unhandled
case sig
monitor ! GotSignal(sig)
Actor.Same
})
case GetAdapter(replyTo, name)
replyTo ! Adapter(ctx.spawnAdapter(identity, name))
Actor.Same
},
(ctx, signal) { monitor ! GotSignal(signal); Actor.Same })
def oldSubject(monitor: ActorRef[Monitor]): Behavior[Command] = {
import ScalaDSL._
FullTotal {
case Sig(ctx, signal)
monitor ! GotSignal(signal)
Same
case Msg(ctx, message) message match {
case ReceiveTimeout
monitor ! GotReceiveTimeout
Same
case Ping(replyTo)
replyTo ! Pong1
Same
case Miss(replyTo)
replyTo ! Missed
Unhandled
case Renew(replyTo)
replyTo ! Renewed
subject(monitor)
case Throw(ex)
throw ex
case MkChild(name, mon, replyTo)
val child = name match {
case None ctx.spawnAnonymous(patterns.Restarter[Command, Throwable](subject(mon), false)())
case Some(n) ctx.spawn(patterns.Restarter[Command, Throwable](subject(mon), false)(), n)
}
replyTo ! Created(child)
Same
case SetTimeout(d, replyTo)
d match {
case f: FiniteDuration ctx.setReceiveTimeout(f, ReceiveTimeout)
case _ ctx.cancelReceiveTimeout()
}
replyTo ! TimeoutSet
Same
case Schedule(delay, target, msg, replyTo)
replyTo ! Scheduled
ctx.schedule(delay, target, msg)
Same
case Stop Stopped
case Kill(ref, replyTo)
if (ctx.stop(ref)) replyTo ! Killed
else replyTo ! NotKilled
Same
case Watch(ref, replyTo)
ctx.watch[Nothing](ref)
replyTo ! Watched
Same
case Unwatch(ref, replyTo)
ctx.unwatch[Nothing](ref)
replyTo ! Unwatched
Same
case GetInfo(replyTo)
replyTo ! Info(ctx.self, ctx.system)
Same
case GetChild(name, replyTo)
replyTo ! Child(ctx.child(name))
Same
case GetChildren(replyTo)
replyTo ! Children(ctx.children.toSet)
Same
case BecomeInert(replyTo)
replyTo ! BecameInert
Full {
case Msg(_, Ping(replyTo))
replyTo ! Pong2
Same
case Msg(_, Throw(ex))
throw ex
case _ Same
}
case BecomeCareless(replyTo)
replyTo ! BecameCareless
Full {
case Sig(_, Terminated(_)) Unhandled
case Sig(_, sig)
Actor.immutable[Command] {
case _ Actor.unhandled
} onSignal {
case (_, PostStop) if ignorePostStop Actor.same // ignore PostStop here
case (_, Terminated(_)) Actor.unhandled
case (_, sig)
monitor ! GotSignal(sig)
Same
Actor.same
}
case GetAdapter(replyTo, name)
replyTo ! Adapter(ctx.spawnAdapter(identity, name))
Same
Actor.same
}
} onSignal {
case (_, PostStop) if ignorePostStop Actor.same // ignore PostStop here
case (_, signal)
monitor ! GotSignal(signal)
Actor.same
}
}
@ -250,6 +261,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
| lifecycle = off
| autoreceive = off
| }
| typed.loggers = ["akka.typed.testkit.TestEventListener"]
|}""".stripMargin)) {
import ActorContextSpec._
@ -264,7 +276,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
/**
* The behavior against which to run all the tests.
*/
def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command]
def behavior(ctx: scaladsl.ActorContext[Event], ignorePostStop: Boolean): Behavior[Command]
implicit def system: ActorSystem[TypedSpec.Command]
@ -272,16 +284,12 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
if (system eq nativeSystem) suite + "Native"
else suite + "Adapted"
def setup(name: String, wrapper: Option[Actor.Restarter.Apply[_]] = None)(
def setup(name: String, wrapper: Option[Actor.Restarter[_]] = None, ignorePostStop: Boolean = true)(
proc: (scaladsl.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(behavior(ctx))).getOrElse(behavior(ctx))
val steps =
startWith.withKeepTraces(true)(ctx.spawn(props, "subject"))
.expectMessage(expectTimeout) { (msg, ref)
msg should ===(GotSignal(PreStart))
ref
}
val props = wrapper.map(_.wrap(behavior(ctx, ignorePostStop))).getOrElse(behavior(ctx, ignorePostStop))
val steps = startWith.withKeepTraces(true)(ctx.spawn(props, "subject"))
proc(ctx, steps)
})
@ -299,11 +307,8 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
val s =
startWith.keep { subj
subj ! MkChild(name, monitor, self)
}.expectMultipleMessages(expectTimeout, 2) { (msgs, subj)
val child = msgs match {
case Created(child) :: ChildEvent(GotSignal(PreStart)) :: Nil child
case ChildEvent(GotSignal(PreStart)) :: Created(child) :: Nil child
}
}.expectMessage(expectTimeout) { (msg, subj)
val Created(child) = msg
(subj, child)
}
@ -345,32 +350,30 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
}
})
def `01 must correctly wire the lifecycle hooks`(): Unit = sync(setup("ctx01", Some(Actor.Restarter[Throwable]())) { (ctx, startWith)
val self = ctx.self
val ex = new Exception("KABOOM1")
startWith { subj
val log = muteExpectedException[Exception]("KABOOM1", occurrences = 1)
subj ! Throw(ex)
(subj, log)
}.expectMessage(expectTimeout) {
case (msg, (subj, log))
msg should ===(GotSignal(PreRestart))
log.assertDone(expectTimeout)
subj
}.expectMessage(expectTimeout) { (msg, subj)
msg should ===(GotSignal(PreStart))
ctx.stop(subj)
}.expectMessage(expectTimeout) { (msg, _)
msg should ===(GotSignal(PostStop))
}
})
def `01 must correctly wire the lifecycle hooks`(): Unit =
sync(setup("ctx01", Some(Actor.restarter[Throwable]()), ignorePostStop = false) { (ctx, startWith)
val self = ctx.self
val ex = new Exception("KABOOM1")
startWith { subj
val log = muteExpectedException[Exception]("KABOOM1", occurrences = 1)
subj ! Throw(ex)
(subj, log)
}.expectMessage(expectTimeout) {
case (msg, (subj, log))
msg should ===(GotSignal(PreRestart))
log.assertDone(expectTimeout)
ctx.stop(subj)
}.expectMessage(expectTimeout) { (msg, _)
msg should ===(GotSignal(PostStop))
}
})
def `02 must not signal PostStop after voluntary termination`(): Unit = sync(setup("ctx02") { (ctx, startWith)
def `02 must signal PostStop after voluntary termination`(): Unit = sync(setup("ctx02", ignorePostStop = false) { (ctx, startWith)
startWith.keep { subj
ctx.watch(subj)
stop(subj)
}.expectTermination(expectTimeout) { (t, subj)
t.ref should ===(subj)
}.expectMessage(expectTimeout) {
case (msg, _)
msg should ===(GotSignal(PostStop))
}
})
@ -382,11 +385,9 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
val log = muteExpectedException[Exception]("KABOOM2", occurrences = 1)
child ! Throw(ex)
(subj, child, log)
}.expectMultipleMessages(expectTimeout, 2) {
case (msgs, (subj, child, log))
msgs should ===(
ChildEvent(GotSignal(PreRestart)) ::
ChildEvent(GotSignal(PreStart)) :: Nil)
}.expectMessage(expectTimeout) {
case (msg, (subj, child, log))
msg should ===(ChildEvent(GotSignal(PreRestart)))
log.assertDone(expectTimeout)
child ! BecomeInert(self) // necessary to avoid PostStop/Terminated interference
(subj, child)
@ -420,7 +421,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
}
})
def `05 must reset behavior upon Restart`(): Unit = sync(setup("ctx05", Some(Actor.Restarter[Exception]())) { (ctx, startWith)
def `05 must reset behavior upon Restart`(): Unit = sync(setup("ctx05", Some(Actor.restarter[Exception]())) { (ctx, startWith)
val self = ctx.self
val ex = new Exception("KABOOM05")
startWith
@ -428,28 +429,25 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
.stimulate(_ ! Ping(self), _ Pong2) { subj
val log = muteExpectedException[Exception]("KABOOM05")
subj ! Throw(ex)
(subj, log)
}.expectMessage(expectTimeout) {
case (msg, (subj, log))
msg should ===(GotSignal(PreStart))
log.assertDone(expectTimeout)
subj
subj
}
.stimulate(_ ! Ping(self), _ Pong1)
})
def `06 must not reset behavior upon Resume`(): Unit = sync(setup("ctx06", Some(Actor.Restarter[Exception](resume = true))) { (ctx, startWith)
val self = ctx.self
val ex = new Exception("KABOOM06")
startWith
.stimulate(_ ! BecomeInert(self), _ BecameInert)
.stimulate(_ ! Ping(self), _ Pong2).keep { subj
muteExpectedException[Exception]("KABOOM06", occurrences = 1)
subj ! Throw(ex)
}.stimulate(_ ! Ping(self), _ Pong2)
})
def `06 must not reset behavior upon Resume`(): Unit = sync(setup(
"ctx06",
Some(Actor.restarter[Exception](SupervisorStrategy.resume))) { (ctx, startWith)
val self = ctx.self
val ex = new Exception("KABOOM06")
startWith
.stimulate(_ ! BecomeInert(self), _ BecameInert)
.stimulate(_ ! Ping(self), _ Pong2).keep { subj
muteExpectedException[Exception]("KABOOM06", occurrences = 1)
subj ! Throw(ex)
}.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", ignorePostStop = false) { (ctx, startWith)
val self = ctx.self
val ex = new Exception("KABOOM07")
startWith
@ -464,11 +462,9 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
def `08 must not stop non-child actor`(): Unit = sync(setup("ctx08") { (ctx, startWith)
val self = ctx.self
startWith.mkChild(Some("A"), ctx.spawnAdapter(ChildEvent), self) { pair
(pair._1, pair._2, ctx.spawn(behavior(ctx), "A"))
}.expectMessage(expectTimeout) {
case (msg, (subj, child, other))
msg should ===(GotSignal(PreStart))
startWith.mkChild(Some("A"), ctx.spawnAdapter(ChildEvent), self) {
case (subj, child)
val other = ctx.spawn(behavior(ctx, ignorePostStop = true), "A")
subj ! Kill(other, ctx.self)
child
}.expectMessageKeep(expectTimeout) { (msg, _)
@ -526,7 +522,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
}
})
def `13 must terminate upon not handling Terminated`(): Unit = sync(setup("ctx13") { (ctx, startWith)
def `13 must terminate upon not handling Terminated`(): Unit = sync(setup("ctx13", ignorePostStop = false) { (ctx, startWith)
val self = ctx.self
startWith.mkChild(None, ctx.spawnAdapter(ChildEvent), self).keep {
case (subj, child)
@ -542,6 +538,9 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
child ! Stop
}.expectMessage(expectTimeout) {
case (msg, (subj, child))
msg should ===(ChildEvent(GotSignal(PostStop)))
}.expectMessage(expectTimeout) {
case (msg, _)
msg should ===(GotSignal(PostStop))
}
})
@ -590,7 +589,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
}
})
def `40 must create a working adapter`(): Unit = sync(setup("ctx40") { (ctx, startWith)
def `40 must create a working adapter`(): Unit = sync(setup("ctx40", ignorePostStop = false) { (ctx, startWith)
startWith.keep { subj
subj ! GetAdapter(ctx.self)
}.expectMessage(expectTimeout) { (msg, subj)
@ -620,8 +619,8 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
trait Normal extends Tests {
override def suite = "normal"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
subject(ctx.self)
override def behavior(ctx: scaladsl.ActorContext[Event], ignorePostStop: Boolean): Behavior[Command] =
subject(ctx.self, ignorePostStop)
}
object `An ActorContext (native)` extends Normal with NativeSystem
object `An ActorContext (adapted)` extends Normal with AdaptedSystem
@ -629,105 +628,34 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
trait Widened extends Tests {
import Actor._
override def suite = "widened"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
subject(ctx.self).widen { case x x }
override def behavior(ctx: scaladsl.ActorContext[Event], ignorePostStop: Boolean): Behavior[Command] =
subject(ctx.self, ignorePostStop).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
trait Deferred extends Tests {
override def suite = "deferred"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
Actor.Deferred(_ subject(ctx.self))
override def behavior(ctx: scaladsl.ActorContext[Event], ignorePostStop: Boolean): Behavior[Command] =
Actor.deferred(_ subject(ctx.self, ignorePostStop))
}
object `An ActorContext with deferred Behavior (native)` extends Deferred with NativeSystem
object `An ActorContext with deferred Behavior (adapted)` extends Deferred with AdaptedSystem
trait NestedDeferred extends Tests {
override def suite = "deferred"
override def behavior(ctx: scaladsl.ActorContext[Event], ignorePostStop: Boolean): Behavior[Command] =
Actor.deferred(_ Actor.deferred(_ subject(ctx.self, ignorePostStop)))
}
object `An ActorContext with nested deferred Behavior (native)` extends NestedDeferred with NativeSystem
object `An ActorContext with nested deferred Behavior (adapted)` extends NestedDeferred with AdaptedSystem
trait Tap extends Tests {
override def suite = "tap"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
Actor.Tap((_, _) (), (_, _) (), subject(ctx.self))
override def behavior(ctx: scaladsl.ActorContext[Event], ignorePostStop: Boolean): Behavior[Command] =
Actor.tap((_, _) (), (_, _) (), subject(ctx.self, ignorePostStop))
}
object `An ActorContext with Tap (old-native)` extends Tap with NativeSystem
object `An ActorContext with Tap (old-adapted)` extends Tap with AdaptedSystem
trait NormalOld extends Tests {
override def suite = "basic"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
oldSubject(ctx.self)
}
object `An ActorContext (old-native)` extends NormalOld with NativeSystem
object `An ActorContext (old-adapted)` extends NormalOld with AdaptedSystem
trait WidenedOld extends Tests {
import ScalaDSL._
override def suite = "widened"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
oldSubject(ctx.self).widen { case x x }
}
object `An ActorContext with widened Behavior (old-native)` extends WidenedOld with NativeSystem
object `An ActorContext with widened Behavior (old-adapted)` extends WidenedOld with AdaptedSystem
trait SynchronousSelfOld extends Tests {
override def suite = "synchronous"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] = ScalaDSL.SynchronousSelf(self oldSubject(ctx.self))
}
object `An ActorContext with SynchronousSelf (old-native)` extends SynchronousSelfOld with NativeSystem
object `An ActorContext with SynchronousSelf (old-adapted)` extends SynchronousSelfOld with AdaptedSystem
trait NonMatchingTapOld extends Tests {
override def suite = "TapNonMatch"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] = ScalaDSL.Tap({ case null }, oldSubject(ctx.self))
}
object `An ActorContext with non-matching Tap (old-native)` extends NonMatchingTapOld with NativeSystem
object `An ActorContext with non-matching Tap (old-adapted)` extends NonMatchingTapOld with AdaptedSystem
trait MatchingTapOld extends Tests {
override def suite = "TapMatch"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] = ScalaDSL.Tap({ case _ }, oldSubject(ctx.self))
}
object `An ActorContext with matching Tap (old-native)` extends MatchingTapOld with NativeSystem
object `An ActorContext with matching Tap (old-adapted)` extends MatchingTapOld with AdaptedSystem
private val stoppingBehavior = ScalaDSL.Full[Command] { case ScalaDSL.Msg(_, Stop) ScalaDSL.Stopped }
trait AndLeftOld extends Tests {
override def suite = "andLeft"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
ScalaDSL.And(oldSubject(ctx.self), stoppingBehavior)
}
object `An ActorContext with And (left, native)` extends AndLeftOld with NativeSystem
object `An ActorContext with And (left, adapted)` extends AndLeftOld with AdaptedSystem
trait AndRightOld extends Tests {
override def suite = "andRight"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
ScalaDSL.And(stoppingBehavior, oldSubject(ctx.self))
}
object `An ActorContext with And (right, native)` extends AndRightOld with NativeSystem
object `An ActorContext with And (right, adapted)` extends AndRightOld with AdaptedSystem
trait OrLeftOld extends Tests {
override def suite = "orLeft"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
ScalaDSL.Or(oldSubject(ctx.self), stoppingBehavior)
override def stop(ref: ActorRef[Command]) = {
ref ! Stop
ref ! Stop
}
}
object `An ActorContext with Or (left, native)` extends OrLeftOld with NativeSystem
object `An ActorContext with Or (left, adapted)` extends OrLeftOld with AdaptedSystem
trait OrRightOld extends Tests {
override def suite = "orRight"
override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] =
ScalaDSL.Or(stoppingBehavior, oldSubject(ctx.self))
override def stop(ref: ActorRef[Command]) = {
ref ! Stop
ref ! Stop
}
}
object `An ActorContext with Or (right, native)` extends OrRightOld with NativeSystem
object `An ActorContext with Or (right, adapted)` extends OrRightOld with AdaptedSystem
}

View file

@ -5,13 +5,10 @@ package akka.typed
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import org.scalatest.concurrent.ScalaFutures
import akka.util.Timeout
import akka.pattern.AskTimeoutException
import ScalaDSL._
import akka.typed.scaladsl.Actor._
import akka.typed.scaladsl.AskPattern._
object AskSpec {
@ -32,13 +29,13 @@ class AskSpec extends TypedSpec with ScalaFutures {
implicit def executor: ExecutionContext =
system.executionContext
val behavior: Behavior[Msg] = Total[Msg] {
case foo @ Foo(_)
val behavior: Behavior[Msg] = immutable[Msg] {
case (_, foo: Foo)
foo.replyTo ! "foo"
Same
case Stop(r)
same
case (_, Stop(r))
r ! (())
Stopped
stopped
}
def `must fail the future if the actor is already terminated`(): Unit = {
@ -68,8 +65,9 @@ class AskSpec extends TypedSpec with ScalaFutures {
/** See issue #19947 (MatchError with adapted ActorRef) */
def `must fail the future if the actor doesn't exist`(): Unit = {
val noSuchActor: ActorRef[Msg] = system match {
case adaptedSys: adapter.ActorSystemAdapter[_]
adapter.actorRefAdapter(adaptedSys.untyped.provider.resolveActorRef("/foo/bar"))
case adaptedSys: akka.typed.internal.adapter.ActorSystemAdapter[_]
import akka.typed.scaladsl.adapter._
adaptedSys.untyped.provider.resolveActorRef("/foo/bar")
case _
fail("this test must only run in an adapted actor system")
}

View file

@ -0,0 +1,620 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import akka.typed.scaladsl.{ Actor SActor }
import akka.typed.javadsl.{ Actor JActor, ActorContext JActorContext }
import akka.japi.function.{ Function F1e, Function2 F2, Procedure2 P2 }
import akka.japi.pf.{ FI, PFBuilder }
import java.util.function.{ Function F1 }
import akka.Done
import akka.typed.testkit.{ EffectfulActorContext, Inbox }
class BehaviorSpec extends TypedSpec {
sealed trait Command {
def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Nil
}
case object GetSelf extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Self(ctx.asScala.self) :: Nil
}
// Behavior under test must return Unhandled
case object Miss extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Missed :: Nil
}
// Behavior under test must return same
case object Ignore extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Ignored :: Nil
}
case object Ping extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Pong :: Nil
}
case object Swap extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Swapped :: Nil
}
case class GetState()(s: State) extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = s :: Nil
}
case class AuxPing(id: Int) extends Command {
override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Pong :: Nil
}
case object Stop extends Command
sealed trait Event
case class GotSignal(signal: Signal) extends Event
case class Self(self: ActorRef[Command]) extends Event
case object Missed extends Event
case object Ignored extends Event
case object Pong extends Event
case object Swapped extends Event
sealed trait State extends Event { def next: State }
val StateA: State = new State { override def toString = "StateA"; override def next = StateB }
val StateB: State = new State { override def toString = "StateB"; override def next = StateA }
trait Common {
type Aux >: Null <: AnyRef
def system: ActorSystem[TypedSpec.Command]
def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux)
def checkAux(signal: Signal, aux: Aux): Unit = ()
def checkAux(command: Command, aux: Aux): Unit = ()
case class Init(behv: Behavior[Command], inbox: Inbox[Event], aux: Aux) {
def mkCtx(): Setup = {
val ctx = new EffectfulActorContext("ctx", behv, 1000, system)
val msgs = inbox.receiveAll()
Setup(ctx, inbox, aux)
}
}
case class Setup(ctx: EffectfulActorContext[Command], inbox: Inbox[Event], aux: Aux)
def init(): Init = {
val inbox = Inbox[Event]("evt")
val (behv, aux) = behavior(inbox.ref)
Init(behv, inbox, aux)
}
def init(factory: ActorRef[Event] (Behavior[Command], Aux)): Init = {
val inbox = Inbox[Event]("evt")
val (behv, aux) = factory(inbox.ref)
Init(behv, inbox, aux)
}
def mkCtx(requirePreStart: Boolean = false): Setup =
init().mkCtx()
implicit class Check(val setup: Setup) {
def check(signal: Signal): Setup = {
setup.ctx.signal(signal)
setup.inbox.receiveAll() should ===(GotSignal(signal) :: Nil)
checkAux(signal, setup.aux)
setup
}
def check(command: Command): Setup = {
setup.ctx.run(command)
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
checkAux(command, setup.aux)
setup
}
def check2(command: Command): Setup = {
setup.ctx.run(command)
val expected = command.expectedResponse(setup.ctx)
setup.inbox.receiveAll() should ===(expected ++ expected)
checkAux(command, setup.aux)
setup
}
}
val ex = new Exception("mine!")
}
trait Siphon extends Common {
override type Aux = Inbox[Command]
override def checkAux(command: Command, aux: Aux): Unit = {
aux.receiveAll() should ===(command :: Nil)
}
}
trait SignalSiphon extends Common {
override type Aux = Inbox[Either[Signal, Command]]
override def checkAux(command: Command, aux: Aux): Unit = {
aux.receiveAll() should ===(Right(command) :: Nil)
}
override def checkAux(signal: Signal, aux: Aux): Unit = {
aux.receiveAll() should ===(Left(signal) :: Nil)
}
}
trait Lifecycle extends Common {
def `must react to PreStart`(): Unit = {
mkCtx(requirePreStart = true)
}
def `must react to PostStop`(): Unit = {
mkCtx().check(PostStop)
}
def `must react to PostStop after a message`(): Unit = {
mkCtx().check(GetSelf).check(PostStop)
}
def `must react to PreRestart`(): Unit = {
mkCtx().check(PreRestart)
}
def `must react to PreRestart after a message`(): Unit = {
mkCtx().check(GetSelf).check(PreRestart)
}
def `must react to Terminated`(): Unit = {
mkCtx().check(Terminated(Inbox("x").ref)(null))
}
def `must react to Terminated after a message`(): Unit = {
mkCtx().check(GetSelf).check(Terminated(Inbox("x").ref)(null))
}
def `must react to a message after Terminated`(): Unit = {
mkCtx().check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
}
}
trait Messages extends Common {
def `must react to two messages`(): Unit = {
mkCtx().check(Ping).check(Ping)
}
def `must react to a message after missing one`(): Unit = {
mkCtx().check(Miss).check(Ping)
}
def `must react to a message after ignoring one`(): Unit = {
mkCtx().check(Ignore).check(Ping)
}
}
trait Unhandled extends Common {
def `must return Unhandled`(): Unit = {
val Setup(ctx, inbox, aux) = mkCtx()
Behavior.interpretMessage(ctx.currentBehavior, ctx, Miss) should be(Behavior.UnhandledBehavior)
inbox.receiveAll() should ===(Missed :: Nil)
checkAux(Miss, aux)
}
}
trait Stoppable extends Common {
def `must stop`(): Unit = {
val Setup(ctx, inbox, aux) = mkCtx()
ctx.run(Stop)
ctx.currentBehavior should be(Behavior.StoppedBehavior)
checkAux(Stop, aux)
}
}
trait Become extends Common with Unhandled {
private implicit val inbox = Inbox[State]("state")
def `must be in state A`(): Unit = {
mkCtx().check(GetState()(StateA))
}
def `must switch to state B`(): Unit = {
mkCtx().check(Swap).check(GetState()(StateB))
}
def `must switch back to state A`(): Unit = {
mkCtx().check(Swap).check(Swap).check(GetState()(StateA))
}
}
trait BecomeWithLifecycle extends Become with Lifecycle {
def `must react to PostStop after swap`(): Unit = {
mkCtx().check(Swap).check(PostStop)
}
def `must react to PostStop after a message after swap`(): Unit = {
mkCtx().check(Swap).check(GetSelf).check(PostStop)
}
def `must react to PreRestart after swap`(): Unit = {
mkCtx().check(Swap).check(PreRestart)
}
def `must react to PreRestart after a message after swap`(): Unit = {
mkCtx().check(Swap).check(GetSelf).check(PreRestart)
}
def `must react to Terminated after swap`(): Unit = {
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null))
}
def `must react to Terminated after a message after swap`(): Unit = {
mkCtx().check(Swap).check(GetSelf).check(Terminated(Inbox("x").ref)(null))
}
def `must react to a message after Terminated after swap`(): Unit = {
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
}
}
/**
* This targets behavior wrappers to ensure that the wrapper does not
* hold on to the changed behavior. Wrappers must be immutable.
*/
trait Reuse extends Common {
def `must be reusable`(): Unit = {
val i = init()
i.mkCtx().check(GetState()(StateA)).check(Swap).check(GetState()(StateB))
i.mkCtx().check(GetState()(StateA)).check(Swap).check(GetState()(StateB))
}
}
private def mkFull(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] = {
SActor.immutable[Command] {
case (ctx, GetSelf)
monitor ! Self(ctx.self)
SActor.same
case (ctx, Miss)
monitor ! Missed
SActor.unhandled
case (ctx, Ignore)
monitor ! Ignored
SActor.same
case (ctx, Ping)
monitor ! Pong
mkFull(monitor, state)
case (ctx, Swap)
monitor ! Swapped
mkFull(monitor, state.next)
case (ctx, GetState())
monitor ! state
SActor.same
case (ctx, Stop) SActor.stopped
case (_, _) SActor.unhandled
} onSignal {
case (ctx, signal)
monitor ! GotSignal(signal)
SActor.same
}
}
trait FullBehavior extends Messages with BecomeWithLifecycle with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = mkFull(monitor) null
}
object `A Full Behavior (native)` extends FullBehavior with NativeSystem
object `A Full Behavior (adapted)` extends FullBehavior with AdaptedSystem
trait ImmutableBehavior extends Messages with BecomeWithLifecycle with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor, StateA) null
private def behv(monitor: ActorRef[Event], state: State): Behavior[Command] = {
SActor.immutable[Command] {
case (ctx, GetSelf)
monitor ! Self(ctx.self)
SActor.same
case (_, Miss)
monitor ! Missed
SActor.unhandled
case (_, Ignore)
monitor ! Ignored
SActor.same
case (_, Ping)
monitor ! Pong
behv(monitor, state)
case (_, Swap)
monitor ! Swapped
behv(monitor, state.next)
case (_, GetState())
monitor ! state
SActor.same
case (_, Stop) SActor.stopped
case (_, _: AuxPing) SActor.unhandled
} onSignal {
case (ctx, signal)
monitor ! GotSignal(signal)
SActor.same
}
}
}
object `A immutable Behavior (native)` extends ImmutableBehavior with NativeSystem
object `A immutable Behavior (adapted)` extends ImmutableBehavior with AdaptedSystem
trait ImmutableWithSignalScalaBehavior extends Messages with BecomeWithLifecycle with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor) null
def behv(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] =
SActor.immutable[Command] {
(ctx, msg)
msg match {
case GetSelf
monitor ! Self(ctx.self)
SActor.same
case Miss
monitor ! Missed
SActor.unhandled
case Ignore
monitor ! Ignored
SActor.same
case Ping
monitor ! Pong
behv(monitor, state)
case Swap
monitor ! Swapped
behv(monitor, state.next)
case GetState()
monitor ! state
SActor.same
case Stop SActor.stopped
case _: AuxPing SActor.unhandled
}
} onSignal {
case (ctx, sig)
monitor ! GotSignal(sig)
SActor.same
}
}
object `A ImmutableWithSignal Behavior (scala,native)` extends ImmutableWithSignalScalaBehavior with NativeSystem
object `A ImmutableWithSignal Behavior (scala,adapted)` extends ImmutableWithSignalScalaBehavior with AdaptedSystem
trait ImmutableScalaBehavior extends Messages with Become with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor, StateA) null
def behv(monitor: ActorRef[Event], state: State): Behavior[Command] =
SActor.immutable[Command] { (ctx, msg)
msg match {
case GetSelf
monitor ! Self(ctx.self)
SActor.same
case Miss
monitor ! Missed
SActor.unhandled
case Ignore
monitor ! Ignored
SActor.same
case Ping
monitor ! Pong
behv(monitor, state)
case Swap
monitor ! Swapped
behv(monitor, state.next)
case GetState()
monitor ! state
SActor.same
case Stop SActor.stopped
case _: AuxPing SActor.unhandled
}
}
}
object `A immutable Behavior (scala,native)` extends ImmutableScalaBehavior with NativeSystem
object `A immutable Behavior (scala,adapted)` extends ImmutableScalaBehavior with AdaptedSystem
trait MutableScalaBehavior extends Messages with Become with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor) null
def behv(monitor: ActorRef[Event]): Behavior[Command] =
SActor.mutable[Command] { ctx
new SActor.MutableBehavior[Command] {
private var state: State = StateA
override def onMessage(msg: Command): Behavior[Command] = {
msg match {
case GetSelf
monitor ! Self(ctx.self)
this
case Miss
monitor ! Missed
SActor.unhandled
case Ignore
monitor ! Ignored
SActor.same // this or same works the same way
case Ping
monitor ! Pong
this
case Swap
monitor ! Swapped
state = state.next
this
case GetState()
monitor ! state
this
case Stop SActor.stopped
case _: AuxPing SActor.unhandled
}
}
}
}
}
object `A mutable Behavior (scala,native)` extends MutableScalaBehavior with NativeSystem
object `A mutable Behavior (scala,adapted)` extends MutableScalaBehavior with AdaptedSystem
trait WidenedScalaBehavior extends ImmutableWithSignalScalaBehavior with Reuse with Siphon {
import SActor.BehaviorDecorators
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Command]("widenedListener")
super.behavior(monitor)._1.widen[Command] { case c inbox.ref ! c; c } inbox
}
}
object `A widened Behavior (scala,native)` extends WidenedScalaBehavior with NativeSystem
object `A widened Behavior (scala,adapted)` extends WidenedScalaBehavior with AdaptedSystem
trait DeferredScalaBehavior extends ImmutableWithSignalScalaBehavior {
override type Aux = Inbox[Done]
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Done]("deferredListener")
(SActor.deferred(ctx {
inbox.ref ! Done
super.behavior(monitor)._1
}), inbox)
}
override def checkAux(signal: Signal, aux: Aux): Unit =
aux.receiveAll() should ===(Done :: Nil)
}
object `A deferred Behavior (scala,native)` extends DeferredScalaBehavior with NativeSystem
object `A deferred Behavior (scala,adapted)` extends DeferredScalaBehavior with AdaptedSystem
trait TapScalaBehavior extends ImmutableWithSignalScalaBehavior with Reuse with SignalSiphon {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Either[Signal, Command]]("tapListener")
(SActor.tap((_, msg) inbox.ref ! Right(msg), (_, sig) inbox.ref ! Left(sig), super.behavior(monitor)._1), inbox)
}
}
object `A tap Behavior (scala,native)` extends TapScalaBehavior with NativeSystem
object `A tap Behavior (scala,adapted)` extends TapScalaBehavior with AdaptedSystem
trait RestarterScalaBehavior extends ImmutableWithSignalScalaBehavior with Reuse {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
SActor.restarter[Exception]().wrap(super.behavior(monitor)._1) null
}
}
object `A restarter Behavior (scala,native)` extends RestarterScalaBehavior with NativeSystem
object `A restarter Behavior (scala,adapted)` extends RestarterScalaBehavior with AdaptedSystem
/*
* function converters for Java, to ease the pain on Scala 2.11
*/
def fs(f: (JActorContext[Command], Signal) Behavior[Command]) =
new F2[JActorContext[Command], Signal, Behavior[Command]] {
override def apply(ctx: JActorContext[Command], sig: Signal) = f(ctx, sig)
}
def fc(f: (JActorContext[Command], Command) Behavior[Command]) =
new F2[JActorContext[Command], Command, Behavior[Command]] {
override def apply(ctx: JActorContext[Command], command: Command) = f(ctx, command)
}
def ps(f: (JActorContext[Command], Signal) Unit) =
new P2[JActorContext[Command], Signal] {
override def apply(ctx: JActorContext[Command], sig: Signal) = f(ctx, sig)
}
def pc(f: (JActorContext[Command], Command) Unit) =
new P2[JActorContext[Command], Command] {
override def apply(ctx: JActorContext[Command], command: Command) = f(ctx, command)
}
def pf(f: PFBuilder[Command, Command] PFBuilder[Command, Command]) =
new F1[PFBuilder[Command, Command], PFBuilder[Command, Command]] {
override def apply(in: PFBuilder[Command, Command]) = f(in)
}
def fi(f: Command Command) =
new FI.Apply[Command, Command] {
override def apply(in: Command) = f(in)
}
def df(f: JActorContext[Command] Behavior[Command]) =
new F1e[JActorContext[Command], Behavior[Command]] {
override def apply(in: JActorContext[Command]) = f(in)
}
trait ImmutableWithSignalJavaBehavior extends Messages with BecomeWithLifecycle with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor) null
def behv(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] =
JActor.immutable(
fc((ctx, msg) msg match {
case GetSelf
monitor ! Self(ctx.getSelf)
SActor.same
case Miss
monitor ! Missed
SActor.unhandled
case Ignore
monitor ! Ignored
SActor.same
case Ping
monitor ! Pong
behv(monitor, state)
case Swap
monitor ! Swapped
behv(monitor, state.next)
case GetState()
monitor ! state
SActor.same
case Stop SActor.stopped
case _: AuxPing SActor.unhandled
}),
fs((ctx, sig) {
monitor ! GotSignal(sig)
SActor.same
}))
}
object `A ImmutableWithSignal Behavior (java,native)` extends ImmutableWithSignalJavaBehavior with NativeSystem
object `A ImmutableWithSignal Behavior (java,adapted)` extends ImmutableWithSignalJavaBehavior with AdaptedSystem
trait ImmutableJavaBehavior extends Messages with Become with Stoppable {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor, StateA) null
def behv(monitor: ActorRef[Event], state: State): Behavior[Command] =
JActor.immutable {
fc((ctx, msg)
msg match {
case GetSelf
monitor ! Self(ctx.getSelf)
SActor.same
case Miss
monitor ! Missed
SActor.unhandled
case Ignore
monitor ! Ignored
SActor.same
case Ping
monitor ! Pong
behv(monitor, state)
case Swap
monitor ! Swapped
behv(monitor, state.next)
case GetState()
monitor ! state
SActor.same
case Stop SActor.stopped
case _: AuxPing SActor.unhandled
})
}
}
object `A immutable Behavior (java,native)` extends ImmutableJavaBehavior with NativeSystem
object `A immutable Behavior (java,adapted)` extends ImmutableJavaBehavior with AdaptedSystem
trait WidenedJavaBehavior extends ImmutableWithSignalJavaBehavior with Reuse with Siphon {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Command]("widenedListener")
JActor.widened(super.behavior(monitor)._1, pf(_.`match`(classOf[Command], fi(x { inbox.ref ! x; x })))) inbox
}
}
object `A widened Behavior (java,native)` extends WidenedJavaBehavior with NativeSystem
object `A widened Behavior (java,adapted)` extends WidenedJavaBehavior with AdaptedSystem
trait DeferredJavaBehavior extends ImmutableWithSignalJavaBehavior {
override type Aux = Inbox[Done]
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Done]("deferredListener")
(JActor.deferred(df(ctx {
inbox.ref ! Done
super.behavior(monitor)._1
})), inbox)
}
override def checkAux(signal: Signal, aux: Aux): Unit =
aux.receiveAll() should ===(Done :: Nil)
}
object `A deferred Behavior (java,native)` extends DeferredJavaBehavior with NativeSystem
object `A deferred Behavior (java,adapted)` extends DeferredJavaBehavior with AdaptedSystem
trait TapJavaBehavior extends ImmutableWithSignalJavaBehavior with Reuse with SignalSiphon {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
val inbox = Inbox[Either[Signal, Command]]("tapListener")
(JActor.tap(
pc((_, msg) inbox.ref ! Right(msg)),
ps((_, sig) inbox.ref ! Left(sig)),
super.behavior(monitor)._1), inbox)
}
}
object `A tap Behavior (java,native)` extends TapJavaBehavior with NativeSystem
object `A tap Behavior (java,adapted)` extends TapJavaBehavior with AdaptedSystem
trait RestarterJavaBehavior extends ImmutableWithSignalJavaBehavior with Reuse {
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
JActor.restarter(classOf[Exception], SupervisorStrategy.restart, super.behavior(monitor)._1) null
}
}
object `A restarter Behavior (java,native)` extends RestarterJavaBehavior with NativeSystem
object `A restarter Behavior (java,adapted)` extends RestarterJavaBehavior with AdaptedSystem
}

View file

@ -0,0 +1,167 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.control.NoStackTrace
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.Actor.BehaviorDecorators
import akka.typed.scaladsl.AskPattern._
import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings }
import akka.typed.testkit.scaladsl._
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class DeferredSpec extends TypedSpec {
sealed trait Command
case object Ping extends Command
sealed trait Event
case object Pong extends Event
case object Started extends Event
def target(monitor: ActorRef[Event]): Behavior[Command] =
Actor.immutable((ctx, cmd) cmd match {
case Ping
monitor ! Pong
Actor.same
})
trait StubbedTests {
def system: ActorSystem[TypedSpec.Command]
def mkCtx(behv: Behavior[Command]): EffectfulActorContext[Command] =
new EffectfulActorContext("ctx", behv, 1000, system)
def `must create underlying deferred behavior immediately`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = Actor.deferred[Command] { _
inbox.ref ! Started
target(inbox.ref)
}
val ctx = mkCtx(behv)
// it's supposed to be created immediately (not waiting for first message)
inbox.receiveMsg() should ===(Started)
}
def `must stop when exception from factory`(): Unit = {
val inbox = Inbox[Event]("evt")
val exc = new RuntimeException("simulated exc from factory") with NoStackTrace
val behv = Actor.deferred[Command] { _
inbox.ref ! Started
throw exc
}
intercept[RuntimeException] {
mkCtx(behv)
} should ===(exc)
inbox.receiveMsg() should ===(Started)
}
}
trait RealTests extends StartSupport {
implicit def system: ActorSystem[TypedSpec.Command]
implicit val testSettings = TestKitSettings(system)
def `must create underlying`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.deferred[Command] { _
probe.ref ! Started
target(probe.ref)
}
probe.expectNoMsg(100.millis) // not yet
start(behv)
// it's supposed to be created immediately (not waiting for first message)
probe.expectMsg(Started)
}
def `must stop when exception from factory`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.deferred[Command] { ctx
val child = ctx.spawnAnonymous(Actor.deferred[Command] { _
probe.ref ! Started
throw new RuntimeException("simulated exc from factory") with NoStackTrace
})
ctx.watch(child)
Actor.immutable[Command]((_, _) Actor.same).onSignal {
case (_, Terminated(`child`))
probe.ref ! Pong
Actor.stopped
}
}
start(behv)
probe.expectMsg(Started)
probe.expectMsg(Pong)
}
def `must stop when deferred result it Stopped`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.deferred[Command] { ctx
val child = ctx.spawnAnonymous(Actor.deferred[Command](_ Actor.stopped))
ctx.watch(child)
Actor.immutable[Command]((_, _) Actor.same).onSignal {
case (_, Terminated(`child`))
probe.ref ! Pong
Actor.stopped
}
}
start(behv)
probe.expectMsg(Pong)
}
def `must create underlying when nested`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.deferred[Command] { _
Actor.deferred[Command] { _
probe.ref ! Started
target(probe.ref)
}
}
start(behv)
probe.expectMsg(Started)
}
def `must undefer underlying when wrapped by widen`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.deferred[Command] { _
probe.ref ! Started
target(probe.ref)
}.widen[Command] {
case m m
}
probe.expectNoMsg(100.millis) // not yet
val ref = start(behv)
// it's supposed to be created immediately (not waiting for first message)
probe.expectMsg(Started)
ref ! Ping
probe.expectMsg(Pong)
}
def `must undefer underlying when wrapped by monitor`(): Unit = {
// monitor is implemented with tap, so this is testing both
val probe = TestProbe[Event]("evt")
val monitorProbe = TestProbe[Command]("monitor")
val behv = Actor.monitor(monitorProbe.ref, Actor.deferred[Command] { _
probe.ref ! Started
target(probe.ref)
})
probe.expectNoMsg(100.millis) // not yet
val ref = start(behv)
// it's supposed to be created immediately (not waiting for first message)
probe.expectMsg(Started)
ref ! Ping
monitorProbe.expectMsg(Ping)
probe.expectMsg(Pong)
}
}
object `A DeferredBehavior (stubbed, native)` extends StubbedTests with NativeSystem
object `A DeferredBehavior (stubbed, adapted)` extends StubbedTests with AdaptedSystem
object `A DeferredBehavior (real, native)` extends RealTests with NativeSystem
object `A DeferredBehavior (real, adapted)` extends RealTests with AdaptedSystem
}

View file

@ -0,0 +1,161 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import java.util.concurrent.atomic.AtomicInteger
import com.typesafe.config.{ Config, ConfigFactory }
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.Future
class DummyExtension1 extends Extension
object DummyExtension1 extends ExtensionId[DummyExtension1] {
def createExtension(system: ActorSystem[_]) = new DummyExtension1
def get(system: ActorSystem[_]): DummyExtension1 = apply(system)
}
class SlowExtension extends Extension
object SlowExtension extends ExtensionId[SlowExtension] {
def createExtension(system: ActorSystem[_]) = {
Thread.sleep(25)
new SlowExtension
}
}
class FailingToLoadExtension extends Extension
object FailingToLoadExtension extends ExtensionId[FailingToLoadExtension] {
def createExtension(system: ActorSystem[_]) = {
throw new RuntimeException("I cannot be trusted!")
}
}
class MultiExtension(val n: Int) extends Extension
class MultiExtensionId(n: Int) extends ExtensionId[MultiExtension] {
def createExtension(system: ActorSystem[_]): MultiExtension = new MultiExtension(n)
}
object InstanceCountingExtension extends ExtensionId[DummyExtension1] {
val createCount = new AtomicInteger(0)
override def createExtension(system: ActorSystem[_]): DummyExtension1 = {
createCount.addAndGet(1)
new DummyExtension1
}
}
class ExtensionsSpec extends TypedSpecSetup {
object `The extensions subsystem` {
def `01 should return the same instance for the same id`(): Unit =
withEmptyActorSystem("ExtensionsSpec01") { system
val instance1 = system.registerExtension(DummyExtension1)
val instance2 = system.registerExtension(DummyExtension1)
instance1 should be theSameInstanceAs instance2
val instance3 = DummyExtension1(system)
instance3 should be theSameInstanceAs instance2
val instance4 = DummyExtension1.get(system)
instance4 should be theSameInstanceAs instance3
}
def `02 should return the same instance for the same id concurrently`(): Unit =
withEmptyActorSystem("ExtensionsSpec02") { system
// not exactly water tight but better than nothing
import system.executionContext
val futures = (0 to 1000).map(n
Future {
system.registerExtension(SlowExtension)
}
)
val instances = Future.sequence(futures).futureValue
instances.reduce { (a, b)
a should be theSameInstanceAs b
b
}
}
def `03 should load extensions from the configuration`(): Unit =
withEmptyActorSystem("ExtensionsSpec03", Some(ConfigFactory.parseString(
"""
akka.typed.extensions = ["akka.typed.DummyExtension1$", "akka.typed.SlowExtension$"]
"""))
) { system
system.hasExtension(DummyExtension1) should ===(true)
system.extension(DummyExtension1) shouldBe a[DummyExtension1]
system.hasExtension(SlowExtension) should ===(true)
system.extension(SlowExtension) shouldBe a[SlowExtension]
}
def `04 handle extensions that fail to initialize`(): Unit = {
def create(): Unit = {
ActorSystem[Any]("ExtensionsSpec04", Behavior.EmptyBehavior, config = Some(ConfigFactory.parseString(
"""
akka.typed.extensions = ["akka.typed.FailingToLoadExtension$"]
""")))
}
intercept[RuntimeException] {
create()
}
// and keeps happening (a retry to create on each access)
intercept[RuntimeException] {
create()
}
}
def `05 support multiple instances of the same type of extension (with different ids)`(): Unit =
withEmptyActorSystem("ExtensionsSpec06") { system
val id1 = new MultiExtensionId(1)
val id2 = new MultiExtensionId(2)
system.registerExtension(id1).n should ===(1)
system.registerExtension(id2).n should ===(2)
system.registerExtension(id1).n should ===(1)
}
def `06 allow for auto-loading of library-extensions`(): Unit =
withEmptyActorSystem("ExtensionsSpec06") { system
val listedExtensions = system.settings.config.getStringList("akka.typed.library-extensions")
listedExtensions.size should be > 0
// could be initalized by other tests, so at least once
InstanceCountingExtension.createCount.get() should be > 0
}
def `07 fail the system if a library-extension cannot be loaded`(): Unit =
intercept[RuntimeException] {
withEmptyActorSystem(
"ExtensionsSpec07",
Some(ConfigFactory.parseString("""akka.library-extensions += "akka.typed.FailingToLoadExtension$"""))
) { _ () }
}
def `08 fail the system if a library-extension cannot be loaded`(): Unit =
intercept[RuntimeException] {
withEmptyActorSystem(
"ExtensionsSpec08",
Some(ConfigFactory.parseString("""akka.library-extensions += "akka.typed.MissingExtension"""))
) { _ () }
}
def `09 load an extension implemented in Java`(): Unit =
withEmptyActorSystem("ExtensionsSpec09") { system
// no way to make apply work cleanly with extensions implemented in Java
val instance1 = ExtensionsTest.MyExtension.get(system)
val instance2 = ExtensionsTest.MyExtension.get(system)
instance1 should be theSameInstanceAs instance2
}
def withEmptyActorSystem[T](name: String, config: Option[Config] = None)(f: ActorSystem[_] T): T = {
val system = ActorSystem[Any](name, Behavior.EmptyBehavior, config = config)
try f(system) finally system.terminate().futureValue
}
}
}

View file

@ -3,11 +3,10 @@
*/
package akka.typed
import ScalaDSL._
import scala.concurrent.duration._
import com.typesafe.config.ConfigFactory
import org.junit.runner.RunWith
import akka.testkit.AkkaSpec
import akka.typed.scaladsl.Actor._
import akka.util.Timeout
@RunWith(classOf[org.scalatest.junit.JUnitRunner])
@ -19,7 +18,7 @@ class PerformanceSpec extends TypedSpec(
override def setTimeout = Timeout(20.seconds)
object `A static behavior` {
object `A immutable behavior` {
case class Ping(x: Int, pong: ActorRef[Pong], report: ActorRef[Pong])
case class Pong(x: Int, ping: ActorRef[Ping], report: ActorRef[Pong])
@ -28,15 +27,20 @@ class PerformanceSpec extends TypedSpec(
StepWise[Pong] { (ctx, startWith)
startWith {
val pinger = SelfAware[Ping](self Static { msg
val pinger = immutable[Ping] { (ctx, msg)
if (msg.x == 0) {
msg.report ! Pong(0, self, msg.report)
} else msg.pong ! Pong(msg.x - 1, self, msg.report)
}) // FIXME .withDispatcher(executor)
msg.report ! Pong(0, ctx.self, msg.report)
same
} else {
msg.pong ! Pong(msg.x - 1, ctx.self, msg.report)
same
}
} // FIXME .withDispatcher(executor)
val ponger = SelfAware[Pong](self Static { msg
msg.ping ! Ping(msg.x, self, msg.report)
}) // FIXME .withDispatcher(executor)
val ponger = immutable[Pong] { (ctx, msg)
msg.ping ! Ping(msg.x, ctx.self, msg.report)
same
} // FIXME .withDispatcher(executor)
val actors =
for (i 1 to pairs)

View file

@ -3,12 +3,12 @@
*/
package akka.typed
class DeploymentConfigSpec extends TypedSpecSetup {
class PropsSpec extends TypedSpecSetup {
val dispatcherFirst = DispatcherDefault(MailboxCapacity(666, DispatcherFromConfig("pool")))
val mailboxFirst = MailboxCapacity(999) withNext dispatcherFirst
object `A DeploymentConfig` {
object `A Props` {
def `must get first dispatcher`(): Unit = {
dispatcherFirst.firstOrElse[DispatcherSelector](null) should ===(dispatcherFirst)
@ -31,7 +31,7 @@ class DeploymentConfigSpec extends TypedSpecSetup {
}
def `must yield all configs of some type`(): Unit = {
dispatcherFirst.allOf[DispatcherSelector] should ===(DispatcherDefault() :: DispatcherFromConfig("pool") :: Nil)
dispatcherFirst.allOf[DispatcherSelector] should ===(DispatcherSelector.default() :: DispatcherSelector.fromConfig("pool") :: Nil)
mailboxFirst.allOf[MailboxCapacity] should ===(List(999, 666).map(MailboxCapacity(_)))
}

View file

@ -0,0 +1,423 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import scala.concurrent.duration._
import akka.typed.scaladsl.Actor._
import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings }
import scala.util.control.NoStackTrace
import akka.typed.testkit.scaladsl._
import akka.typed.scaladsl.AskPattern._
import scala.concurrent.Await
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class RestarterSpec extends TypedSpec {
sealed trait Command
case object Ping extends Command
case class Throw(e: Throwable) extends Command
case object NextState extends Command
case object GetState extends Command
case class CreateChild[T](behavior: Behavior[T], name: String) extends Command
sealed trait Event
case object Pong extends Event
case class GotSignal(signal: Signal) extends Event
case class State(n: Int, children: Map[String, ActorRef[Command]]) extends Event
case object Started extends Event
class Exc1(msg: String = "exc-1") extends RuntimeException(msg) with NoStackTrace
class Exc2 extends Exc1("exc-2")
class Exc3(msg: String = "exc-3") extends RuntimeException(msg) with NoStackTrace
def target(monitor: ActorRef[Event], state: State = State(0, Map.empty)): Behavior[Command] =
immutable[Command] { (ctx, cmd)
cmd match {
case Ping
monitor ! Pong
same
case NextState
target(monitor, state.copy(n = state.n + 1))
case GetState
val reply = state.copy(children = ctx.children.map(c c.path.name c.upcast[Command]).toMap)
monitor ! reply
same
case CreateChild(childBehv, childName)
ctx.spawn(childBehv, childName)
same
case Throw(e)
throw e
}
} onSignal {
case (ctx, sig)
monitor ! GotSignal(sig)
same
}
class FailingConstructor(monitor: ActorRef[Event]) extends MutableBehavior[Command] {
monitor ! Started
throw new RuntimeException("simulated exc from constructor") with NoStackTrace
override def onMessage(msg: Command): Behavior[Command] = {
monitor ! Pong
same
}
}
trait StubbedTests {
def system: ActorSystem[TypedSpec.Command]
def mkCtx(behv: Behavior[Command]): EffectfulActorContext[Command] =
new EffectfulActorContext("ctx", behv, 1000, system)
def `must receive message`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Throwable]().wrap(target(inbox.ref))
val ctx = mkCtx(behv)
ctx.run(Ping)
inbox.receiveMsg() should ===(Pong)
}
def `must stop when no restarter`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = target(inbox.ref)
val ctx = mkCtx(behv)
intercept[Exc3] {
ctx.run(Throw(new Exc3))
}
inbox.receiveMsg() should ===(GotSignal(PostStop))
}
def `must stop when unhandled exception`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Exc1]().wrap(target(inbox.ref))
val ctx = mkCtx(behv)
intercept[Exc3] {
ctx.run(Throw(new Exc3))
}
inbox.receiveMsg() should ===(GotSignal(PostStop))
}
def `must restart when handled exception`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Exc1]().wrap(target(inbox.ref))
val ctx = mkCtx(behv)
ctx.run(NextState)
ctx.run(GetState)
inbox.receiveMsg() should ===(State(1, Map.empty))
ctx.run(Throw(new Exc2))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
ctx.run(GetState)
inbox.receiveMsg() should ===(State(0, Map.empty))
}
def `must resume when handled exception`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Exc1](SupervisorStrategy.resume).wrap(target(inbox.ref))
val ctx = mkCtx(behv)
ctx.run(NextState)
ctx.run(GetState)
inbox.receiveMsg() should ===(State(1, Map.empty))
ctx.run(Throw(new Exc2))
ctx.run(GetState)
inbox.receiveMsg() should ===(State(1, Map.empty))
}
def `must support nesting to handle different exceptions`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Exc3]().wrap(restarter[Exc2](SupervisorStrategy.resume).wrap(target(inbox.ref)))
val ctx = mkCtx(behv)
ctx.run(NextState)
ctx.run(GetState)
inbox.receiveMsg() should ===(State(1, Map.empty))
// resume
ctx.run(Throw(new Exc2))
ctx.run(GetState)
inbox.receiveMsg() should ===(State(1, Map.empty))
// restart
ctx.run(Throw(new Exc3))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
ctx.run(GetState)
inbox.receiveMsg() should ===(State(0, Map.empty))
// stop
intercept[Exc1] {
ctx.run(Throw(new Exc1))
}
inbox.receiveMsg() should ===(GotSignal(PostStop))
}
def `must not catch fatal error`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Throwable]().wrap(target(inbox.ref))
val ctx = mkCtx(behv)
intercept[StackOverflowError] {
ctx.run(Throw(new StackOverflowError))
}
inbox.receiveAll() should ===(Nil)
}
def `must stop after restart retries limit`(): Unit = {
val inbox = Inbox[Event]("evt")
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 2, withinTimeRange = 1.minute)
val behv = restarter[Exc1](strategy).wrap(target(inbox.ref))
val ctx = mkCtx(behv)
ctx.run(Throw(new Exc1))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
ctx.run(Throw(new Exc1))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
intercept[Exc1] {
ctx.run(Throw(new Exc1))
}
inbox.receiveMsg() should ===(GotSignal(PostStop))
}
def `must reset retry limit after withinTimeRange`(): Unit = {
val inbox = Inbox[Event]("evt")
val withinTimeRange = 2.seconds
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 2, withinTimeRange)
val behv = restarter[Exc1](strategy).wrap(target(inbox.ref))
val ctx = mkCtx(behv)
ctx.run(Throw(new Exc1))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
ctx.run(Throw(new Exc1))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
Thread.sleep((2.seconds + 100.millis).toMillis)
ctx.run(Throw(new Exc1))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
ctx.run(Throw(new Exc1))
inbox.receiveMsg() should ===(GotSignal(PreRestart))
intercept[Exc1] {
ctx.run(Throw(new Exc1))
}
inbox.receiveMsg() should ===(GotSignal(PostStop))
}
def `must stop at first exception when restart retries limit is 0`(): Unit = {
val inbox = Inbox[Event]("evt")
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 0, withinTimeRange = 1.minute)
val behv = restarter[Exc1](strategy).wrap(target(inbox.ref))
val ctx = mkCtx(behv)
intercept[Exc1] {
ctx.run(Throw(new Exc1))
}
inbox.receiveMsg() should ===(GotSignal(PostStop))
}
def `must create underlying deferred behavior immediately`(): Unit = {
val inbox = Inbox[Event]("evt")
val behv = restarter[Exception]().wrap(deferred[Command] { _
inbox.ref ! Started
target(inbox.ref)
})
val ctx = mkCtx(behv)
// it's supposed to be created immediately (not waiting for first message)
inbox.receiveMsg() should ===(Started)
}
}
trait RealTests extends StartSupport {
import akka.typed.scaladsl.adapter._
implicit def system: ActorSystem[TypedSpec.Command]
implicit val testSettings = TestKitSettings(system)
def `must receive message`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Throwable]().wrap(target(probe.ref))
val ref = start(behv)
ref ! Ping
probe.expectMsg(Pong)
}
def `must stop when no restarter`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = target(probe.ref)
val ref = start(behv)
ref ! Throw(new Exc3)
probe.expectMsg(GotSignal(PostStop))
}
def `must stop when unhandled exception`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exc1]().wrap(target(probe.ref))
val ref = start(behv)
ref ! Throw(new Exc3)
probe.expectMsg(GotSignal(PostStop))
}
def `must restart when handled exception`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exc1]().wrap(target(probe.ref))
val ref = start(behv)
ref ! NextState
ref ! GetState
probe.expectMsg(State(1, Map.empty))
ref ! Throw(new Exc2)
probe.expectMsg(GotSignal(PreRestart))
ref ! GetState
probe.expectMsg(State(0, Map.empty))
}
def `must NOT stop children when restarting`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exc1]().wrap(target(probe.ref))
val ref = start(behv)
val childProbe = TestProbe[Event]("childEvt")
val childName = nextName()
ref ! CreateChild(target(childProbe.ref), childName)
ref ! GetState
probe.expectMsgType[State].children.keySet should contain(childName)
ref ! Throw(new Exc1)
probe.expectMsg(GotSignal(PreRestart))
ref ! GetState
// TODO document this difference compared to classic actors, and that
// children can be stopped if needed in PreRestart
probe.expectMsgType[State].children.keySet should contain(childName)
}
def `must resume when handled exception`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exc1](SupervisorStrategy.resume).wrap(target(probe.ref))
val ref = start(behv)
ref ! NextState
ref ! GetState
probe.expectMsg(State(1, Map.empty))
ref ! Throw(new Exc2)
ref ! GetState
probe.expectMsg(State(1, Map.empty))
}
def `must support nesting to handle different exceptions`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exc3]().wrap(restarter[Exc2](SupervisorStrategy.resume).wrap(target(probe.ref)))
val ref = start(behv)
ref ! NextState
ref ! GetState
probe.expectMsg(State(1, Map.empty))
// resume
ref ! Throw(new Exc2)
ref ! GetState
probe.expectMsg(State(1, Map.empty))
// restart
ref ! Throw(new Exc3)
probe.expectMsg(GotSignal(PreRestart))
ref ! GetState
probe.expectMsg(State(0, Map.empty))
// stop
ref ! Throw(new Exc1)
probe.expectMsg(GotSignal(PostStop))
}
def `must restart after exponential backoff`(): Unit = {
val probe = TestProbe[Event]("evt")
val startedProbe = TestProbe[Event]("started")
val minBackoff = 1.seconds
val strategy = SupervisorStrategy.restartWithBackoff(minBackoff, 10.seconds, 0.0)
.withResetBackoffAfter(10.seconds)
val behv = restarter[Exc1](strategy).wrap(deferred[Command] { _
startedProbe.ref ! Started
target(probe.ref)
})
val ref = start(behv)
startedProbe.expectMsg(Started)
ref ! NextState
ref ! Throw(new Exc1)
probe.expectMsg(GotSignal(PreRestart))
ref ! Ping // dropped due to backoff
startedProbe.expectNoMsg(minBackoff - 100.millis)
probe.expectNoMsg(minBackoff + 100.millis)
startedProbe.expectMsg(Started)
ref ! GetState
probe.expectMsg(State(0, Map.empty))
// one more time
ref ! NextState
ref ! Throw(new Exc1)
probe.expectMsg(GotSignal(PreRestart))
ref ! Ping // dropped due to backoff
startedProbe.expectNoMsg((minBackoff * 2) - 100.millis)
probe.expectNoMsg((minBackoff * 2) + 100.millis)
startedProbe.expectMsg(Started)
ref ! GetState
probe.expectMsg(State(0, Map.empty))
}
def `must reset exponential backoff count after reset timeout`(): Unit = {
val probe = TestProbe[Event]("evt")
val minBackoff = 1.seconds
val strategy = SupervisorStrategy.restartWithBackoff(minBackoff, 10.seconds, 0.0)
.withResetBackoffAfter(100.millis)
val behv = restarter[Exc1](strategy).wrap(target(probe.ref))
val ref = start(behv)
ref ! NextState
ref ! Throw(new Exc1)
probe.expectMsg(GotSignal(PreRestart))
ref ! Ping // dropped due to backoff
probe.expectNoMsg(minBackoff + 100.millis)
ref ! GetState
probe.expectMsg(State(0, Map.empty))
// one more time after the reset timeout
probe.expectNoMsg(strategy.resetBackoffAfter + 100.millis)
ref ! NextState
ref ! Throw(new Exc1)
probe.expectMsg(GotSignal(PreRestart))
ref ! Ping // dropped due to backoff
// backoff was reset, so restarted after the minBackoff
probe.expectNoMsg(minBackoff + 100.millis)
ref ! GetState
probe.expectMsg(State(0, Map.empty))
}
def `must create underlying deferred behavior immediately`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exception]().wrap(deferred[Command] { _
probe.ref ! Started
target(probe.ref)
})
probe.expectNoMsg(100.millis) // not yet
start(behv)
// it's supposed to be created immediately (not waiting for first message)
probe.expectMsg(Started)
}
def `must stop when exception from MutableBehavior constructor`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = restarter[Exception]().wrap(mutable[Command](_ new FailingConstructor(probe.ref)))
val ref = start(behv)
probe.expectMsg(Started)
ref ! Ping
probe.expectNoMsg(100.millis)
}
}
object `A restarter (stubbed, native)` extends StubbedTests with NativeSystem
object `A restarter (stubbed, adapted)` extends StubbedTests with AdaptedSystem
object `A restarter (real, native)` extends RealTests with NativeSystem
object `A restarter (real, adapted)` extends RealTests with AdaptedSystem
}

View file

@ -5,6 +5,9 @@ package akka.typed
import scala.concurrent.duration.FiniteDuration
import java.util.concurrent.TimeoutException
import akka.typed.scaladsl.Actor._
import scala.concurrent.duration.Deadline
/**
@ -35,7 +38,6 @@ import scala.concurrent.duration.Deadline
*/
@deprecated("to be replaced by process DSL", "2.4-M2")
object StepWise {
import ScalaDSL._
sealed trait AST
private final case class Thunk(f: () Any) extends AST
@ -107,8 +109,8 @@ object StepWise {
}
def apply[T](f: (scaladsl.ActorContext[T], StartWith[T]) Steps[T, _]): Behavior[T] =
Full[Any] {
case Sig(ctx, PreStart) run(ctx, f(ctx.asInstanceOf[ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ())
deferred[Any] { ctx
run(ctx, f(ctx.asInstanceOf[scaladsl.ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ())
}.narrow
private def throwTimeout(trace: Trace, message: String): Nothing =
@ -133,27 +135,35 @@ object StepWise {
case ThunkV(f) :: tail run(ctx, tail, f(value))
case Message(t, f, trace) :: tail
ctx.setReceiveTimeout(t, ReceiveTimeout)
Full {
case Msg(_, ReceiveTimeout) throwTimeout(trace, s"timeout of $t expired while waiting for a message")
case Msg(_, msg)
immutable[Any] {
case (_, ReceiveTimeout) throwTimeout(trace, s"timeout of $t expired while waiting for a message")
case (_, msg)
ctx.cancelReceiveTimeout()
run(ctx, tail, f(msg, value))
case Sig(_, other) throwIllegalState(trace, s"unexpected $other while waiting for a message")
} onSignal {
case (_, PostStop)
// ignore PostStop here
run(ctx, ops, value)
case (_, other) throwIllegalState(trace, s"unexpected $other while waiting for a message")
}
case MultiMessage(t, c, f, trace) :: tail
val deadline = Deadline.now + t
def behavior(count: Int, acc: List[Any]): Behavior[Any] = {
ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout)
Full {
case Msg(_, ReceiveTimeout)
immutable[Any] {
case (_, ReceiveTimeout)
throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
case Msg(_, msg)
case (_, msg)
val nextCount = count + 1
if (nextCount == c) {
ctx.cancelReceiveTimeout()
run(ctx, tail, f((msg :: acc).reverse, value))
} else behavior(nextCount, msg :: acc)
case Sig(_, other) throwIllegalState(trace, s"unexpected $other while waiting for $c messages (got $count valid ones)")
} onSignal {
case (_, PostStop)
// ignore PostStop here
run(ctx, ops, value)
case (_, other) throwIllegalState(trace, s"unexpected $other while waiting for $c messages (got $count valid ones)")
}
}
behavior(0, Nil)
@ -161,16 +171,20 @@ object StepWise {
val deadline = Deadline.now + t
def behavior(count: Int, acc: List[Either[Signal, Any]]): Behavior[Any] = {
ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout)
Full {
case Msg(_, ReceiveTimeout)
immutable[Any] {
case (_, ReceiveTimeout)
throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
case Msg(_, msg)
case (_, msg)
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)
} onSignal {
case (_, PostStop)
// ignore PostStop here
run(ctx, ops, value)
case (_, other)
val nextCount = count + 1
if (nextCount == c) {
ctx.cancelReceiveTimeout()
@ -181,14 +195,20 @@ object StepWise {
behavior(0, Nil)
case Termination(t, f, trace) :: tail
ctx.setReceiveTimeout(t, ReceiveTimeout)
Full {
case Msg(_, ReceiveTimeout) throwTimeout(trace, s"timeout of $t expired while waiting for termination")
case Sig(_, t: Terminated)
immutable[Any] {
case (_, ReceiveTimeout) throwTimeout(trace, s"timeout of $t expired while waiting for termination")
case other throwIllegalState(trace, s"unexpected $other while waiting for termination")
} onSignal {
case (_, PostStop)
// ignore PostStop here
run(ctx, ops, value)
case (_, t: Terminated)
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

@ -0,0 +1,230 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import scala.concurrent.duration._
import scala.util.control.NoStackTrace
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.AskPattern._
import akka.typed.scaladsl.TimerScheduler
import akka.typed.testkit.TestKitSettings
import akka.typed.testkit.scaladsl._
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class TimerSpec extends TypedSpec("""
#akka.loglevel = DEBUG
""") {
sealed trait Command
case class Tick(n: Int) extends Command
case object Bump extends Command
case class SlowThenBump(latch: CountDownLatch) extends Command
case object End extends Command
case class Throw(e: Throwable) extends Command
case object Cancel extends Command
case class SlowThenThrow(latch: CountDownLatch, e: Throwable) extends Command
sealed trait Event
case class Tock(n: Int) extends Event
case class GotPostStop(timerActive: Boolean) extends Event
case class GotPreRestart(timerActive: Boolean) extends Event
class Exc extends RuntimeException("simulated exc") with NoStackTrace
trait RealTests extends StartSupport {
implicit def system: ActorSystem[TypedSpec.Command]
implicit val testSettings = TestKitSettings(system)
val interval = 1.second
val dilatedInterval = interval.dilated
def target(monitor: ActorRef[Event], timer: TimerScheduler[Command], bumpCount: Int): Behavior[Command] = {
def bump(): Behavior[Command] = {
val nextCount = bumpCount + 1
timer.startPeriodicTimer("T", Tick(nextCount), interval)
target(monitor, timer, nextCount)
}
Actor.immutable[Command] { (ctx, cmd)
cmd match {
case Tick(n)
monitor ! Tock(n)
Actor.same
case Bump
bump()
case SlowThenBump(latch)
latch.await(10, TimeUnit.SECONDS)
bump()
case End
Actor.stopped
case Cancel
timer.cancel("T")
Actor.same
case Throw(e)
throw e
case SlowThenThrow(latch, e)
latch.await(10, TimeUnit.SECONDS)
throw e
}
} onSignal {
case (ctx, PreRestart)
monitor ! GotPreRestart(timer.isTimerActive("T"))
Actor.same
case (ctx, PostStop)
monitor ! GotPostStop(timer.isTimerActive("T"))
Actor.same
}
}
def `01 must schedule non-repeated ticks`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.withTimers[Command] { timer
timer.startSingleTimer("T", Tick(1), 10.millis)
target(probe.ref, timer, 1)
}
val ref = start(behv)
probe.expectMsg(Tock(1))
probe.expectNoMsg(100.millis)
ref ! End
probe.expectMsg(GotPostStop(false))
}
def `02 must schedule repeated ticks`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(1), interval)
target(probe.ref, timer, 1)
}
val ref = start(behv)
probe.within((interval * 4) - 100.millis) {
probe.expectMsg(Tock(1))
probe.expectMsg(Tock(1))
probe.expectMsg(Tock(1))
}
ref ! End
probe.expectMsg(GotPostStop(false))
}
def `03 must replace timer`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(1), interval)
target(probe.ref, timer, 1)
}
val ref = start(behv)
probe.expectMsg(Tock(1))
val latch = new CountDownLatch(1)
// next Tock(1) enqueued in mailboxed, but should be discarded because of new timer
ref ! SlowThenBump(latch)
probe.expectNoMsg(interval + 100.millis)
latch.countDown()
probe.expectMsg(Tock(2))
ref ! End
probe.expectMsg(GotPostStop(false))
}
def `04 must cancel timer`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(1), interval)
target(probe.ref, timer, 1)
}
val ref = start(behv)
probe.expectMsg(Tock(1))
ref ! Cancel
probe.expectNoMsg(dilatedInterval + 100.millis)
ref ! End
probe.expectMsg(GotPostStop(false))
}
def `05 must discard timers from old incarnation after restart, alt 1`(): Unit = {
val probe = TestProbe[Event]("evt")
val startCounter = new AtomicInteger(0)
val behv = Actor.restarter[Exception]().wrap(Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(startCounter.incrementAndGet()), interval)
target(probe.ref, timer, 1)
})
val ref = start(behv)
probe.expectMsg(Tock(1))
val latch = new CountDownLatch(1)
// next Tock(1) is enqueued in mailbox, but should be discarded by new incarnation
ref ! SlowThenThrow(latch, new Exc)
probe.expectNoMsg(interval + 100.millis)
latch.countDown()
probe.expectMsg(GotPreRestart(false))
probe.expectNoMsg(interval / 2)
probe.expectMsg(Tock(2))
ref ! End
probe.expectMsg(GotPostStop(false))
}
def `06 must discard timers from old incarnation after restart, alt 2`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.restarter[Exception]().wrap(Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(1), interval)
target(probe.ref, timer, 1)
})
val ref = start(behv)
probe.expectMsg(Tock(1))
// change state so that we see that the restart starts over again
ref ! Bump
probe.expectMsg(Tock(2))
val latch = new CountDownLatch(1)
// next Tock(2) is enqueued in mailbox, but should be discarded by new incarnation
ref ! SlowThenThrow(latch, new Exc)
probe.expectNoMsg(interval + 100.millis)
latch.countDown()
probe.expectMsg(GotPreRestart(false))
probe.expectMsg(Tock(1))
ref ! End
probe.expectMsg(GotPostStop(false))
}
def `07 must cancel timers when stopped from exception`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(1), interval)
target(probe.ref, timer, 1)
}
val ref = start(behv)
ref ! Throw(new Exc)
probe.expectMsg(GotPostStop(false))
}
def `08 must cancel timers when stopped voluntarily`(): Unit = {
val probe = TestProbe[Event]("evt")
val behv = Actor.withTimers[Command] { timer
timer.startPeriodicTimer("T", Tick(1), interval)
target(probe.ref, timer, 1)
}
val ref = start(behv)
ref ! End
probe.expectMsg(GotPostStop(false))
}
}
object `A Restarter (real, native)` extends RealTests with NativeSystem
object `A Restarter (real, adapted)` extends RealTests with AdaptedSystem
}

View file

@ -7,68 +7,107 @@ import org.scalatest.refspec.RefSpec
import org.scalatest.Matchers
import org.scalatest.BeforeAndAfterAll
import akka.testkit.AkkaSpec
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.Future
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import akka.util.Timeout
import scala.reflect.ClassTag
import akka.actor.ActorInitializationException
import language.existentials
import akka.testkit.EventFilter
import akka.testkit.TestEvent.Mute
import akka.typed.scaladsl.Actor._
import org.scalatest.concurrent.ScalaFutures
import org.scalactic.TypeCheckedTripleEquals
import org.scalactic.CanEqual
import org.junit.runner.RunWith
import scala.util.control.NonFatal
import org.scalatest.exceptions.TestFailedException
import akka.util.TypedMultiMap
import akka.typed.scaladsl.AskPattern
import scala.util.control.NoStackTrace
import akka.typed.testkit.{ Inbox, TestKitSettings }
import org.scalatest.time.Span
/**
* Helper class for writing tests for typed Actors with ScalaTest.
*/
@RunWith(classOf[org.scalatest.junit.JUnitRunner])
class TypedSpecSetup extends RefSpec with Matchers with BeforeAndAfterAll with ScalaFutures with TypeCheckedTripleEquals
class TypedSpecSetup extends RefSpec with Matchers with BeforeAndAfterAll with ScalaFutures with TypeCheckedTripleEquals {
// TODO hook this up with config like in akka-testkit/AkkaSpec?
implicit val akkaPatience = PatienceConfig(3.seconds, Span(100, org.scalatest.time.Millis))
}
/**
* Helper class for writing tests against both ActorSystemImpl and ActorSystemAdapter.
*/
class TypedSpec(val config: Config) extends TypedSpecSetup {
abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
import TypedSpec._
import AskPattern._
def this() = this(ConfigFactory.empty)
def this(config: String) = this(ConfigFactory.parseString(config))
// extension point
def setTimeout: Timeout = Timeout(1.minute)
lazy val nativeSystem = ActorSystem(AkkaSpec.getCallerName(classOf[TypedSpec]), guardian(), config = Some(config withFallback AkkaSpec.testConf))
lazy val adaptedSystem = ActorSystem.adapter(AkkaSpec.getCallerName(classOf[TypedSpec]), guardian(), config = Some(config withFallback AkkaSpec.testConf))
private var nativeSystemUsed = false
lazy val nativeSystem: ActorSystem[TypedSpec.Command] = {
val sys = ActorSystem(AkkaSpec.getCallerName(classOf[TypedSpec]), guardian(), config = Some(config withFallback AkkaSpec.testConf))
nativeSystemUsed = true
sys
}
private var adaptedSystemUsed = false
lazy val adaptedSystem: ActorSystem[TypedSpec.Command] = {
val sys = ActorSystem.adapter(AkkaSpec.getCallerName(classOf[TypedSpec]), guardian(), config = Some(config withFallback AkkaSpec.testConf))
adaptedSystemUsed = true
sys
}
trait StartSupport {
def system: ActorSystem[TypedSpec.Command]
private val nameCounter = Iterator.from(0)
def nextName(prefix: String = "a"): String = s"$prefix-${nameCounter.next()}"
def start[T](behv: Behavior[T]): ActorRef[T] = {
import akka.typed.scaladsl.AskPattern._
import akka.typed.testkit.scaladsl._
implicit val testSettings = TestKitSettings(system)
Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated)
}
}
trait NativeSystem {
def system = nativeSystem
def system: ActorSystem[TypedSpec.Command] = nativeSystem
}
trait AdaptedSystem {
def system = adaptedSystem
def system: ActorSystem[TypedSpec.Command] = adaptedSystem
}
implicit val timeout = setTimeout
implicit val patience = PatienceConfig(3.seconds)
implicit def scheduler = nativeSystem.scheduler
override def afterAll(): Unit = {
Await.result(nativeSystem ? (Terminate(_)), timeout.duration): Status
Await.result(adaptedSystem ? (Terminate(_)), timeout.duration): Status
if (nativeSystemUsed)
Await.result(nativeSystem.terminate, timeout.duration)
if (adaptedSystemUsed)
Await.result(adaptedSystem.terminate, timeout.duration)
}
// TODO remove after basing on ScalaTest 3 with async support
import akka.testkit._
def await[T](f: Future[T]): T = Await.result(f, timeout.duration * 1.1)
lazy val blackhole = await(nativeSystem ? Create(ScalaDSL.Full[Any] { case _ ScalaDSL.Same }, "blackhole"))
lazy val blackhole = await(nativeSystem ? Create(immutable[Any] { case _ same }, "blackhole"))
/**
* Run an Actor-based test. The test procedure is most conveniently
@ -87,12 +126,19 @@ class TypedSpec(val config: Config) extends TypedSpecSetup {
try await(f) match {
case Success ()
case Failed(ex)
println(system.printTree)
throw unwrap(ex)
unwrap(ex) match {
case ex2: TypedSpec.SimulatedException
throw ex2
case _
println(system.printTree)
throw unwrap(ex)
}
case Timedout
println(system.printTree)
fail("test timed out")
} catch {
case ex: TypedSpec.SimulatedException
throw ex
case NonFatal(ex)
println(system.printTree)
throw ex
@ -130,7 +176,6 @@ class TypedSpec(val config: Config) extends TypedSpecSetup {
}
object TypedSpec {
import ScalaDSL._
import akka.{ typed t }
sealed abstract class Start
@ -146,28 +191,31 @@ object TypedSpec {
case class Failed(thr: Throwable) extends Status
case object Timedout extends Status
class SimulatedException(message: String) extends RuntimeException(message) with NoStackTrace
def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] =
FullTotal {
case Sig(ctx, t @ Terminated(test))
immutable[Command] {
case (ctx, r: RunTest[t])
val test = ctx.spawn(r.behavior, r.name)
ctx.schedule(r.timeout, r.replyTo, Timedout)
ctx.watch(test)
guardian(outstanding + ((test, r.replyTo)))
case (_, Terminate(reply))
reply ! Success
stopped
case (ctx, c: Create[t])
c.replyTo ! ctx.spawn(c.behavior, c.name)
same
} onSignal {
case (ctx, t @ Terminated(test))
outstanding get test match {
case Some(reply)
if (t.failure eq null) reply ! Success
else reply ! Failed(t.failure)
guardian(outstanding - test)
case None Same
case None same
}
case _: Sig[_] Same
case Msg(ctx, r: RunTest[t])
val test = ctx.spawn(r.behavior, r.name)
ctx.schedule(r.timeout, r.replyTo, Timedout)
ctx.watch(test)
guardian(outstanding + ((test, r.replyTo)))
case Msg(_, Terminate(reply))
reply ! Success
Stopped
case Msg(ctx, c: Create[t])
c.replyTo ! ctx.spawn(c.behavior, c.name)
Same
case _ same
}
def getCallerName(clazz: Class[_]): String = {
@ -189,12 +237,11 @@ class TypedSpecSpec extends TypedSpec {
implicit def system: ActorSystem[TypedSpec.Command]
def `must report failures`(): Unit = {
a[TestFailedException] must be thrownBy {
a[TypedSpec.SimulatedException] must be thrownBy {
sync(runTest("failure")(StepWise[String]((ctx, startWith)
startWith {
fail("expected")
}
)))
throw new TypedSpec.SimulatedException("expected")
})))
}
}
}

View file

@ -0,0 +1,71 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import scala.concurrent._
import scala.concurrent.duration._
import akka.typed.scaladsl.Actor._
import akka.typed.scaladsl.AskPattern._
import akka.testkit._
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class WatchSpec extends TypedSpec {
trait Tests {
implicit def system: ActorSystem[TypedSpec.Command]
def `get notified of actor termination`(): Unit = {
case object Stop
case class StartWatching(watchee: ActorRef[Stop.type])
val terminator = Await.result(system ? TypedSpec.Create(immutable[Stop.type] {
case (ctx, `Stop`) stopped
}, "t1"), 3.seconds /*.dilated*/ )
val receivedTerminationSignal: Promise[Unit] = Promise()
val watcher = Await.result(system ? TypedSpec.Create(immutable[StartWatching] {
case (ctx, StartWatching(watchee)) ctx.watch(watchee); same
}.onSignal {
case (ctx, Terminated(_)) receivedTerminationSignal.success(()); stopped
}, "w1"), 3.seconds /*.dilated*/ )
watcher ! StartWatching(terminator)
terminator ! Stop
Await.result(receivedTerminationSignal.future, 3.seconds /*.dilated*/ )
}
def `get notified of actor termination with a custom message`(): Unit = {
case object Stop
sealed trait Message
case object CustomTerminationMessage extends Message
case class StartWatchingWith(watchee: ActorRef[Stop.type], msg: CustomTerminationMessage.type) extends Message
val terminator = Await.result(system ? TypedSpec.Create(immutable[Stop.type] {
case (ctx, `Stop`) stopped
}, "t2"), 3.seconds /*.dilated*/ )
val receivedTerminationSignal: Promise[Unit] = Promise()
val watcher = Await.result(system ? TypedSpec.Create(immutable[Message] {
case (ctx, StartWatchingWith(watchee, msg))
ctx.watchWith(watchee, msg)
same
case (ctx, `CustomTerminationMessage`)
receivedTerminationSignal.success(())
stopped
}, "w2"), 3.seconds /*.dilated*/ )
watcher ! StartWatchingWith(terminator, CustomTerminationMessage)
terminator ! Stop
Await.result(receivedTerminationSignal.future, 3.seconds /*.dilated*/ )
}
}
object `Actor monitoring (native)` extends Tests with NativeSystem
object `Actor monitoring (adapted)` extends Tests with AdaptedSystem
}

View file

@ -4,17 +4,17 @@
package akka.typed
package internal
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.Actor._
import org.scalactic.ConversionCheckedTripleEquals
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.exceptions.TestFailedException
import org.scalatest._
import org.junit.runner.RunWith
import org.scalatest._
@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
@ -22,7 +22,14 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
def `must be creatable`(): Unit = {
val parent = new DebugRef[String](sys.path / "creatable", true)
val cell = new ActorCell(sys, Deferred[String](() { parent ! "created"; Static { s parent ! s } }), ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String](_ {
parent ! "created"
immutable[String] {
case (_, s)
parent ! s
same
}
}), ec, 1000, parent)
debugCell(cell) {
ec.queueSize should ===(0)
cell.sendSystem(Create())
@ -40,7 +47,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Deferred[String](() { parent ! "created"; throw ??? }), ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String](_ { parent ! "created"; throw ??? }), ec, 1000, parent)
cell.setSelf(self)
debugCell(cell) {
ec.queueSize should ===(0)
@ -60,7 +67,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Deferred[String](() { parent ! "created"; Stopped }), ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String](_ { parent ! "created"; stopped }), ec, 1000, parent)
cell.setSelf(self)
debugCell(cell) {
ec.queueSize should ===(0)
@ -77,10 +84,10 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
}
}
def `must be able to terminate after PreStart`(): Unit = {
def `must be able to terminate after being started`(): Unit = {
val parent = new DebugRef[String](sys.path / "terminate", true)
val self = new DebugRef[String](sys.path / "terminateSelf", true)
val cell = new ActorCell(sys, Deferred(() { parent ! "created"; Full[String] { case Sig(ctx, PreStart) Stopped } }), ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String](_ { parent ! "created"; stopped }), ec, 1000, parent)
cell.setSelf(self)
debugCell(cell) {
ec.queueSize should ===(0)
@ -101,7 +108,8 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Deferred(() { parent ! "created"; Static[String](s throw ex) }), ec, 1000, parent)
val behavior = deferred[String](_ { parent ! "created"; immutable[String] { case (s, _) throw ex } })
val cell = new ActorCell(sys, behavior, ec, 1000, parent)
cell.setSelf(self)
debugCell(cell) {
ec.queueSize should ===(0)
@ -124,7 +132,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Deferred(() { parent ! "created"; Same[String] }), ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String](_ { parent ! "created"; same[String] }), ec, 1000, parent)
cell.setSelf(self)
debugCell(cell) {
ec.queueSize should ===(0)
@ -150,7 +158,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Deferred(() { parent ! "created"; Unhandled[String] }), ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String](_ { parent ! "created"; unhandled[String] }), ec, 1000, parent)
cell.setSelf(self)
debugCell(cell) {
ec.queueSize should ===(0)
@ -181,7 +189,14 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
*/
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, SelfAware[String] { self Static { s self ! s; parent ! s } }, ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String] { ctx
immutable[String] {
case (_, s)
ctx.self ! s
parent ! s
same
}
}, ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -214,7 +229,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Empty[String], ec, 1000, parent)
val cell = new ActorCell(sys, Actor.empty[String], ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -238,7 +253,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, ContextAware[String] { ctx ctx.watch(parent); Empty }, ec, 1000, parent)
val cell = new ActorCell(sys, deferred[String] { ctx ctx.watch(parent); Actor.empty }, ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -270,7 +285,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Stopped[String], ec, 1000, parent)
val cell = new ActorCell(sys, stopped[String], ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -289,7 +304,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Empty[String], ec, 1000, parent)
val cell = new ActorCell(sys, Actor.empty[String], ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -309,7 +324,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, Empty[String], ec, 1000, parent)
val cell = new ActorCell(sys, Actor.empty[String], ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -326,7 +341,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
def `must send messages to deadLetters after being terminated`(): Unit = {
val parent = new DebugRef[String](sys.path / "sendDeadLetters", true)
val cell = new ActorCell(sys, Stopped[String], ec, 1000, parent)
val cell = new ActorCell(sys, stopped[String], ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {
@ -347,9 +362,9 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
*/
def `must not terminate before children have terminated`(): Unit = {
val parent = new DebugRef[ActorRef[Nothing]](sys.path / "waitForChild", true)
val cell = new ActorCell(sys, ContextAware[String] { ctx
ctx.spawn(SelfAware[String] { self parent ! self; Empty }, "child")
Empty
val cell = new ActorCell(sys, deferred[String] { ctx
ctx.spawn(deferred[String] { ctx parent ! ctx.self; Actor.empty }, "child")
Actor.empty
}, ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
@ -380,9 +395,9 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
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, ContextAware[String] { ctx
ctx.watch(ctx.spawn(SelfAware[String] { self parent ! self; Empty }, "child"))
Empty
val cell = new ActorCell(sys, deferred[String] { ctx
ctx.watch(ctx.spawn(deferred[String] { ctx parent ! ctx.self; Actor.empty }, "child"))
Actor.empty
}, ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
@ -416,7 +431,11 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala
def `must not terminate twice if failing in PostStop`(): Unit = {
val parent = new DebugRef[String](sys.path / "terminateProperlyPostStop", true)
val cell = new ActorCell(sys, Full[String] { case Sig(_, PostStop) ??? }, ec, 1000, parent)
val cell = new ActorCell(sys, immutable[String] {
case _ unhandled
} onSignal {
case (_, PostStop) ???
}, ec, 1000, parent)
val ref = new LocalActorRef(parent.path / "child", cell)
cell.setSelf(ref)
debugCell(cell) {

View file

@ -4,21 +4,22 @@
package akka.typed
package internal
import akka.Done
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.Actor._
import akka.typed.testkit.Inbox
import akka.util.Timeout
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 org.scalatest.concurrent.{ Eventually, ScalaFutures }
import scala.concurrent.duration._
import scala.concurrent.{ Future, Promise }
import scala.util.control.NonFatal
import akka.util.Timeout
import akka.Done
@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)
@ -41,7 +42,7 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca
}
def `must start the guardian actor and terminate when it terminates`(): Unit = {
val t = withSystem("a", Total[Probe] { p p.replyTo ! p.msg; Stopped }, doTerminate = false) { sys
val t = withSystem("a", immutable[Probe] { case (_, p) p.replyTo ! p.msg; stopped }, doTerminate = false) { sys
val inbox = Inbox[String]("a")
sys ! Probe("hello", inbox.ref)
eventually { inbox.hasMessages should ===(true) }
@ -54,10 +55,12 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca
def `must terminate the guardian actor`(): Unit = {
val inbox = Inbox[String]("terminate")
val sys = system("terminate", Full[Probe] {
case Sig(ctx, PostStop)
val sys = system("terminate", immutable[Probe] {
case (_, _) unhandled
} onSignal {
case (ctx, PostStop)
inbox.ref ! "done"
Same
same
})
sys.terminate().futureValue
inbox.receiveAll() should ===("done" :: Nil)
@ -66,19 +69,19 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca
def `must log to the event stream`(): Unit = pending
def `must have a name`(): Unit =
withSystem("name", Empty[String]) { sys
withSystem("name", Actor.empty[String]) { sys
sys.name should ===(suite + "-name")
}
def `must report its uptime`(): Unit =
withSystem("uptime", Empty[String]) { sys
withSystem("uptime", Actor.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", Empty[String]) { sys
withSystem("thread", Actor.empty[String]) { sys
val p = Promise[Int]
sys.threadFactory.newThread(new Runnable {
def run(): Unit = p.success(42)
@ -87,7 +90,7 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca
}
def `must be able to run Futures`(): Unit =
withSystem("futures", Empty[String]) { sys
withSystem("futures", Actor.empty[String]) { sys
val f = Future(42)(sys.executionContext)
f.futureValue should ===(42)
}
@ -100,21 +103,25 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca
// this is essential to complete ActorCellSpec, see there
def `must correctly treat Watch dead letters`(): Unit =
withSystem("deadletters", Empty[String]) { sys
withSystem("deadletters", Actor.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)
}
def `must start system actors and mangle their names`(): Unit = {
withSystem("systemActorOf", Empty[String]) { sys
withSystem("systemActorOf", Actor.empty[String]) { sys
import akka.typed.scaladsl.AskPattern._
implicit val timeout = Timeout(1.second)
implicit val sched = sys.scheduler
case class Doner(ref: ActorRef[Done])
val ref1, ref2 = sys.systemActorOf(Static[Doner](_.ref ! Done), "empty").futureValue
val ref1, ref2 = sys.systemActorOf(immutable[Doner] {
case (_, doner)
doner.ref ! Done
same
}, "empty").futureValue
(ref1 ? Doner).futureValue should ===(Done)
(ref2 ? Doner).futureValue should ===(Done)
val RE = "(\\d+)-empty".r

View file

@ -5,14 +5,18 @@ package akka.typed
package internal
import akka.{ actor a, event e }
import scala.concurrent._
import com.typesafe.config.ConfigFactory
import java.util.concurrent.ThreadFactory
import akka.typed.testkit.Inbox
import akka.util.Timeout
private[typed] class ActorSystemStub(val name: String)
extends ActorRef[Nothing](a.RootActorPath(a.Address("akka", name)) / "user")
with ActorSystem[Nothing] with ActorRefImpl[Nothing] {
extends ActorSystem[Nothing] with ActorRef[Nothing] with ActorRefImpl[Nothing] {
override val path: a.ActorPath = a.RootActorPath(a.Address("akka", name)) / "user"
override val settings: Settings = new Settings(getClass.getClassLoader, ConfigFactory.empty, name)
@ -57,8 +61,16 @@ private[typed] class ActorSystemStub(val name: String)
val receptionistInbox = Inbox[patterns.Receptionist.Command]("receptionist")
override def receptionist: ActorRef[patterns.Receptionist.Command] = receptionistInbox.ref
def systemActorOf[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig)(implicit timeout: Timeout): Future[ActorRef[U]] = {
def systemActorOf[U](behavior: Behavior[U], name: String, props: Props)(implicit timeout: Timeout): Future[ActorRef[U]] = {
Future.failed(new UnsupportedOperationException("ActorSystemStub cannot create system actors"))
}
def registerExtension[T <: Extension](ext: ExtensionId[T]): T =
throw new UnsupportedOperationException("ActorSystemStub cannot register extensions")
def extension[T <: Extension](ext: ExtensionId[T]): T =
throw new UnsupportedOperationException("ActorSystemStub cannot register extensions")
def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean =
throw new UnsupportedOperationException("ActorSystemStub cannot register extensions")
}

View file

@ -8,8 +8,8 @@ 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[typed] class DebugRef[T](override val path: a.ActorPath, override val isLocal: Boolean)
extends ActorRef[T] with ActorRefImpl[T] {
private val q = new ConcurrentLinkedQueue[Either[SystemMessage, T]]

View file

@ -4,27 +4,31 @@
package akka.typed
package internal
import scala.concurrent.duration._
import akka.Done
import akka.event.Logging._
import akka.typed.ScalaDSL._
import akka.typed.scaladsl.Actor._
import akka.typed.scaladsl.AskPattern._
import akka.typed.testkit.Inbox
import com.typesafe.config.ConfigFactory
import java.util.concurrent.Executor
import org.scalatest.concurrent.Eventually
import org.scalatest.concurrent.PatienceConfiguration.Timeout
import scala.concurrent.duration._
object EventStreamSpec {
@volatile var logged = Vector.empty[LogEvent]
class MyLogger extends Logger {
def initialBehavior: Behavior[Logger.Command] =
ContextAware { ctx
Total {
case Logger.Initialize(es, replyTo)
replyTo ! ctx.watch(ctx.spawn(Static { (ev: LogEvent) logged :+= ev }, "logger"))
Empty
}
immutable {
case (ctx, Logger.Initialize(es, replyTo))
val logger = ctx.spawn(immutable[LogEvent] { (_, ev: LogEvent)
logged :+= ev
same
}, "logger")
ctx.watch(logger)
replyTo ! logger
empty
}
}
@ -262,7 +266,7 @@ class EventStreamSpec extends TypedSpec(EventStreamSpec.config) with Eventually
}
def `must unsubscribe an actor upon termination`(): Unit = {
val ref = nativeSystem ? TypedSpec.Create(Total[Done](d Stopped), "tester") futureValue Timeout(1.second)
val ref = nativeSystem ? TypedSpec.Create(immutable[Done] { case _ stopped }, "tester") futureValue Timeout(1.second)
es.subscribe(ref, classOf[Done]) should ===(true)
es.subscribe(ref, classOf[Done]) should ===(false)
ref ! Done
@ -270,7 +274,7 @@ class EventStreamSpec extends TypedSpec(EventStreamSpec.config) with Eventually
}
def `must unsubscribe the actor, when it subscribes already in terminated state`(): Unit = {
val ref = nativeSystem ? TypedSpec.Create(Stopped[Done], "tester") futureValue Timeout(1.second)
val ref = nativeSystem ? TypedSpec.Create(stopped[Done], "tester") futureValue Timeout(1.second)
val wait = new DebugRef[Done](root / "wait", true)
ref.sorry.sendSystem(Watch(ref, wait))
eventually(wait.hasSignal should ===(true))

View file

@ -4,20 +4,23 @@
package akka.typed.patterns
import Receptionist._
import akka.typed.ScalaDSL._
import akka.typed.scaladsl.AskPattern._
import scala.concurrent.duration._
import akka.typed._
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.Actor._
import akka.typed.testkit.{ Effect, EffectfulActorContext, Inbox }
class ReceptionistSpec extends TypedSpec {
trait ServiceA
case object ServiceKeyA extends ServiceKey[ServiceA]
val behaviorA = Static[ServiceA](msg ())
val behaviorA = Actor.empty[ServiceA]
trait ServiceB
case object ServiceKeyB extends ServiceKey[ServiceB]
val behaviorB = Static[ServiceB](msg ())
val behaviorB = Actor.empty[ServiceB]
trait CommonTests {
implicit def system: ActorSystem[TypedSpec.Command]

View file

@ -0,0 +1,254 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.scaladsl.adapter
import scala.concurrent.duration._
import scala.util.control.NoStackTrace
import akka.{ actor untyped }
import akka.typed.ActorRef
import akka.typed.Behavior
import akka.typed.Terminated
import akka.typed.scaladsl.Actor
import akka.{ actor untyped }
import akka.annotation.ApiMayChange
import akka.annotation.DoNotInherit
import akka.annotation.InternalApi
import akka.testkit._
object AdapterSpec {
val untyped1: untyped.Props = untyped.Props(new Untyped1)
class Untyped1 extends untyped.Actor {
def receive = {
case "ping" sender() ! "pong"
case t: ThrowIt throw t
}
}
def typed1(ref: untyped.ActorRef, probe: ActorRef[String]): Behavior[String] =
Actor.immutable[String] {
(ctx, msg)
msg match {
case "send"
val replyTo = ctx.self.toUntyped
ref.tell("ping", replyTo)
Actor.same
case "pong"
probe ! "ok"
Actor.same
case "actorOf"
val child = ctx.actorOf(untyped1)
child.tell("ping", ctx.self.toUntyped)
Actor.same
case "watch"
ctx.watch(ref)
Actor.same
case "supervise-stop"
val child = ctx.actorOf(untyped1)
ctx.watch(child)
child ! ThrowIt3
child.tell("ping", ctx.self.toUntyped)
Actor.same
case "stop-child"
val child = ctx.actorOf(untyped1)
ctx.watch(child)
ctx.stop(child)
Actor.same
}
} onSignal {
case (ctx, Terminated(ref))
probe ! "terminated"
Actor.same
}
sealed trait Typed2Msg
final case class Ping(replyTo: ActorRef[String]) extends Typed2Msg
case object StopIt extends Typed2Msg
sealed trait ThrowIt extends RuntimeException with Typed2Msg with NoStackTrace
case object ThrowIt1 extends ThrowIt
case object ThrowIt2 extends ThrowIt
case object ThrowIt3 extends ThrowIt
def untyped2(ref: ActorRef[Ping], probe: ActorRef[String]): untyped.Props =
untyped.Props(new Untyped2(ref, probe))
class Untyped2(ref: ActorRef[Ping], probe: ActorRef[String]) extends untyped.Actor {
override val supervisorStrategy = untyped.OneForOneStrategy() {
({
case ThrowIt1
probe ! "thrown-stop"
untyped.SupervisorStrategy.Stop
case ThrowIt2
probe ! "thrown-resume"
untyped.SupervisorStrategy.Resume
case ThrowIt3
probe ! "thrown-restart"
// TODO Restart will not really restart the behavior
untyped.SupervisorStrategy.Restart
}: untyped.SupervisorStrategy.Decider).orElse(untyped.SupervisorStrategy.defaultDecider)
}
def receive = {
case "send" ref ! Ping(self) // implicit conversion
case "pong" probe ! "ok"
case "spawn"
val child = context.spawnAnonymous(typed2)
child ! Ping(self)
case "actorOf-props"
// this is how Cluster Sharding can be used
val child = context.actorOf(typed2Props)
child ! Ping(self)
case "watch"
context.watch(ref)
case untyped.Terminated(_)
probe ! "terminated"
case "supervise-stop"
testSupervice(ThrowIt1)
case "supervise-resume"
testSupervice(ThrowIt2)
case "supervise-restart"
testSupervice(ThrowIt3)
case "stop-child"
val child = context.spawnAnonymous(typed2)
context.watch(child)
context.stop(child)
}
private def testSupervice(t: ThrowIt): Unit = {
val child = context.spawnAnonymous(typed2)
context.watch(child)
child ! t
child ! Ping(self)
}
}
def typed2: Behavior[Typed2Msg] =
Actor.immutable { (ctx, msg)
msg match {
case Ping(replyTo)
replyTo ! "pong"
Actor.same
case StopIt
Actor.stopped
case t: ThrowIt
throw t
}
}
def typed2Props: untyped.Props = PropsAdapter(typed2)
}
class AdapterSpec extends AkkaSpec {
import AdapterSpec._
"Adapted actors" must {
"send message from typed to untyped" in {
val probe = TestProbe()
val untypedRef = system.actorOf(untyped1)
val typedRef = system.spawnAnonymous(typed1(untypedRef, probe.ref))
typedRef ! "send"
probe.expectMsg("ok")
}
"send message from untyped to typed" in {
val probe = TestProbe()
val typedRef = system.spawnAnonymous(typed2)
val untypedRef = system.actorOf(untyped2(typedRef, probe.ref))
untypedRef ! "send"
probe.expectMsg("ok")
}
"spawn typed child from untyped parent" in {
val probe = TestProbe()
val ign = system.spawnAnonymous(Actor.ignore[Ping])
val untypedRef = system.actorOf(untyped2(ign, probe.ref))
untypedRef ! "spawn"
probe.expectMsg("ok")
}
"actorOf typed child via Props from untyped parent" in {
val probe = TestProbe()
val ign = system.spawnAnonymous(Actor.ignore[Ping])
val untypedRef = system.actorOf(untyped2(ign, probe.ref))
untypedRef ! "actorOf-props"
probe.expectMsg("ok")
}
"actorOf untyped child from typed parent" in {
val probe = TestProbe()
val ignore = system.actorOf(untyped.Props.empty)
val typedRef = system.spawnAnonymous(typed1(ignore, probe.ref))
typedRef ! "actorOf"
probe.expectMsg("ok")
}
"watch typed from untyped" in {
val probe = TestProbe()
val typedRef = system.spawnAnonymous(typed2)
val untypedRef = system.actorOf(untyped2(typedRef, probe.ref))
untypedRef ! "watch"
typedRef ! StopIt
probe.expectMsg("terminated")
}
"watch untyped from typed" in {
val probe = TestProbe()
val untypedRef = system.actorOf(untyped1)
val typedRef = system.spawnAnonymous(typed1(untypedRef, probe.ref))
typedRef ! "watch"
untypedRef ! untyped.PoisonPill
probe.expectMsg("terminated")
}
"supervise typed child from untyped parent" in {
val probe = TestProbe()
val ign = system.spawnAnonymous(Actor.ignore[Ping])
val untypedRef = system.actorOf(untyped2(ign, probe.ref))
untypedRef ! "supervise-stop"
probe.expectMsg("thrown-stop")
// ping => ok should not get through here
probe.expectMsg("terminated")
untypedRef ! "supervise-resume"
probe.expectMsg("thrown-resume")
probe.expectMsg("ok")
untypedRef ! "supervise-restart"
probe.expectMsg("thrown-restart")
probe.expectMsg("ok")
}
"supervise untyped child from typed parent" in {
val probe = TestProbe()
val ignore = system.actorOf(untyped.Props.empty)
val typedRef = system.spawnAnonymous(typed1(ignore, probe.ref))
// only stop supervisorStrategy
typedRef ! "supervise-stop"
probe.expectMsg("terminated")
probe.expectNoMsg(100.millis) // no pong
}
"stop typed child from untyped parent" in {
val probe = TestProbe()
val ignore = system.spawnAnonymous(Actor.ignore[Ping])
val untypedRef = system.actorOf(untyped2(ignore, probe.ref))
untypedRef ! "stop-child"
probe.expectMsg("terminated")
}
"stop untyped child from typed parent" in {
val probe = TestProbe()
val ignore = system.actorOf(untyped.Props.empty)
val typedRef = system.spawnAnonymous(typed1(ignore, probe.ref))
typedRef ! "stop-child"
probe.expectMsg("terminated")
}
}
}

View file

@ -5,14 +5,12 @@ package docs.akka.typed
//#imports
import akka.typed._
import akka.typed.scaladsl.Actor._
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.AskPattern._
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.concurrent.Await
//#imports
import akka.testkit.AkkaSpec
import akka.typed.TypedSpec
object IntroSpec {
@ -21,9 +19,10 @@ object IntroSpec {
final case class Greet(whom: String, replyTo: ActorRef[Greeted])
final case class Greeted(whom: String)
val greeter = Stateless[Greet] { (_, msg)
val greeter = Actor.immutable[Greet] { (_, msg)
println(s"Hello ${msg.whom}!")
msg.replyTo ! Greeted(msg.whom)
Actor.same
}
}
//#hello-world-actor
@ -50,8 +49,11 @@ object IntroSpec {
//#chatroom-protocol
//#chatroom-behavior
def chatRoom(sessions: List[ActorRef[SessionEvent]] = List.empty): Behavior[Command] =
Stateful[Command] { (ctx, msg)
val behavior: Behavior[Command] =
chatRoom(List.empty)
private def chatRoom(sessions: List[ActorRef[SessionEvent]]): Behavior[Command] =
Actor.immutable[Command] { (ctx, msg)
msg match {
case GetSession(screenName, client)
val wrapper = ctx.spawnAdapter {
@ -62,7 +64,7 @@ object IntroSpec {
case PostSessionMessage(screenName, message)
val mp = MessagePosted(screenName, message)
sessions foreach (_ ! mp)
Same
Actor.same
}
}
//#chatroom-behavior
@ -97,42 +99,42 @@ class IntroSpec extends TypedSpec {
import ChatRoom._
val gabbler =
Stateful[SessionEvent] { (_, msg)
Actor.immutable[SessionEvent] { (_, msg)
msg match {
//#chatroom-gabbler
// We document that the compiler warns about the missing handler for `SessionDenied`
case SessionDenied(reason)
println(s"cannot start chat room session: $reason")
Stopped
Actor.stopped
//#chatroom-gabbler
case SessionGranted(handle)
handle ! PostMessage("Hello World!")
Same
Actor.same
case MessagePosted(screenName, message)
println(s"message has been posted by '$screenName': $message")
Stopped
Actor.stopped
}
}
//#chatroom-gabbler
//#chatroom-main
val main: Behavior[akka.NotUsed] =
Stateful(
behavior = (_, _) Unhandled,
signal = { (ctx, sig)
sig match {
case PreStart
val chatRoom = ctx.spawn(ChatRoom.chatRoom(), "chatroom")
val gabblerRef = ctx.spawn(gabbler, "gabbler")
ctx.watch(gabblerRef)
chatRoom ! GetSession("ol Gabbler", gabblerRef)
Same
case Terminated(ref)
Stopped
case _
Unhandled
Actor.deferred { ctx
val chatRoom = ctx.spawn(ChatRoom.behavior, "chatroom")
val gabblerRef = ctx.spawn(gabbler, "gabbler")
ctx.watch(gabblerRef)
chatRoom ! GetSession("ol Gabbler", gabblerRef)
Actor.immutable[akka.NotUsed] {
(_, _) Actor.unhandled
} onSignal {
case (ctx, Terminated(ref))
Actor.stopped
}
})
}
val system = ActorSystem("ChatRoomDemo", main)
Await.result(system.whenTerminated, 1.second)
Await.result(system.whenTerminated, 3.seconds)
//#chatroom-main
}

View file

@ -0,0 +1,113 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.akka.typed
//#imports
import akka.typed._
import akka.typed.scaladsl.Actor
import akka.typed.scaladsl.ActorContext
import akka.typed.scaladsl.AskPattern._
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.concurrent.Await
//#imports
object MutableIntroSpec {
//#chatroom-actor
object ChatRoom {
//#chatroom-protocol
sealed trait Command
final case class GetSession(screenName: String, replyTo: ActorRef[SessionEvent])
extends Command
//#chatroom-protocol
//#chatroom-behavior
private final case class PostSessionMessage(screenName: String, message: String)
extends Command
//#chatroom-behavior
//#chatroom-protocol
sealed trait SessionEvent
final case class SessionGranted(handle: ActorRef[PostMessage]) extends SessionEvent
final case class SessionDenied(reason: String) extends SessionEvent
final case class MessagePosted(screenName: String, message: String) extends SessionEvent
final case class PostMessage(message: String)
//#chatroom-protocol
//#chatroom-behavior
def behavior(): Behavior[Command] =
Actor.mutable[Command](ctx new ChatRoomBehavior(ctx))
class ChatRoomBehavior(ctx: ActorContext[Command]) extends Actor.MutableBehavior[Command] {
private var sessions: List[ActorRef[SessionEvent]] = List.empty
override def onMessage(msg: Command): Behavior[Command] = {
msg match {
case GetSession(screenName, client)
val wrapper = ctx.spawnAdapter {
p: PostMessage PostSessionMessage(screenName, p.message)
}
client ! SessionGranted(wrapper)
sessions = client :: sessions
this
case PostSessionMessage(screenName, message)
val mp = MessagePosted(screenName, message)
sessions foreach (_ ! mp)
this
}
}
}
//#chatroom-behavior
}
//#chatroom-actor
}
class MutableIntroSpec extends TypedSpec {
import MutableIntroSpec._
def `must chat`(): Unit = {
//#chatroom-gabbler
import ChatRoom._
val gabbler =
Actor.immutable[SessionEvent] { (_, msg)
msg match {
case SessionDenied(reason)
println(s"cannot start chat room session: $reason")
Actor.stopped
case SessionGranted(handle)
handle ! PostMessage("Hello World!")
Actor.same
case MessagePosted(screenName, message)
println(s"message has been posted by '$screenName': $message")
Actor.stopped
}
}
//#chatroom-gabbler
//#chatroom-main
val main: Behavior[akka.NotUsed] =
Actor.deferred { ctx
val chatRoom = ctx.spawn(ChatRoom.behavior(), "chatroom")
val gabblerRef = ctx.spawn(gabbler, "gabbler")
ctx.watch(gabblerRef)
chatRoom ! GetSession("ol Gabbler", gabblerRef)
Actor.immutable[akka.NotUsed] {
(_, _) Actor.unhandled
} onSignal {
case (ctx, Terminated(ref))
Actor.stopped
}
}
val system = ActorSystem("ChatRoomDemo", main)
Await.result(system.whenTerminated, 1.second)
//#chatroom-main
}
}

View file

@ -8,7 +8,7 @@ disablePlugins(MimaPlugin)
initialCommands := """
import akka.typed._
import ScalaDSL._
import import akka.typed.scaladsl.Actor
import scala.concurrent._
import duration._
import akka.util.Timeout

View file

@ -1,385 +0,0 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed.javadsl;
import static akka.typed.scaladsl.Actor.Empty;
import static akka.typed.scaladsl.Actor.Ignore;
import static akka.typed.scaladsl.Actor.Same;
import static akka.typed.scaladsl.Actor.Stopped;
import static akka.typed.scaladsl.Actor.Unhandled;
import java.util.function.Function;
import akka.japi.function.Function2;
import akka.japi.function.Procedure2;
import akka.japi.pf.PFBuilder;
import akka.typed.ActorRef;
import akka.typed.Behavior;
import akka.typed.PreStart;
import akka.typed.Signal;
import akka.typed.patterns.Restarter;
import akka.typed.scaladsl.Actor.Widened;
import scala.reflect.ClassTag;
public abstract class Actor {
/*
* This DSL is implemented in Java in order to ensure consistent usability from Java,
* taking possible Scala oddities out of the equation. There is some duplication in
* the behavior implementations, but that is unavoidable if both DSLs shall offer the
* same runtime performance (especially concerning allocations for function converters).
*/
private static class Deferred<T> extends Behavior<T> {
final akka.japi.function.Function<ActorContext<T>, Behavior<T>> producer;
public Deferred(akka.japi.function.Function<ActorContext<T>, Behavior<T>> producer) {
this.producer = producer;
}
@Override
public Behavior<T> management(akka.typed.ActorContext<T> ctx, Signal msg) throws Exception {
if (msg instanceof PreStart) {
return Behavior.preStart(producer.apply(ctx), ctx);
} else
throw new IllegalStateException("Deferred behavior must receive PreStart as first signal");
}
@Override
public Behavior<T> message(akka.typed.ActorContext<T> ctx, T msg) throws Exception {
throw new IllegalStateException("Deferred behavior must receive PreStart as first signal");
}
}
private static class Stateful<T> extends Behavior<T> {
final Function2<ActorContext<T>, T, Behavior<T>> message;
final Function2<ActorContext<T>, Signal, Behavior<T>> signal;
public Stateful(Function2<ActorContext<T>, T, Behavior<T>> message,
Function2<ActorContext<T>, Signal, Behavior<T>> signal) {
this.signal = signal;
this.message = message;
}
@Override
public Behavior<T> management(akka.typed.ActorContext<T> ctx, Signal msg) throws Exception {
return signal.apply(ctx, msg);
}
@Override
public Behavior<T> message(akka.typed.ActorContext<T> ctx, T msg) throws Exception {
return message.apply(ctx, msg);
}
}
private static class Stateless<T> extends Behavior<T> {
final Procedure2<ActorContext<T>, T> message;
public Stateless(Procedure2<ActorContext<T>, T> message) {
this.message = message;
}
@Override
public Behavior<T> management(akka.typed.ActorContext<T> ctx, Signal msg) throws Exception {
return Same();
}
@Override
public Behavior<T> message(akka.typed.ActorContext<T> ctx, T msg) throws Exception {
message.apply(ctx, msg);
return Same();
}
}
private static class Tap<T> extends Behavior<T> {
final Procedure2<ActorContext<T>, Signal> signal;
final Procedure2<ActorContext<T>, T> message;
final Behavior<T> behavior;
public Tap(Procedure2<ActorContext<T>, Signal> signal, Procedure2<ActorContext<T>, T> message,
Behavior<T> behavior) {
this.signal = signal;
this.message = message;
this.behavior = behavior;
}
private Behavior<T> canonicalize(Behavior<T> behv) {
if (Behavior.isUnhandled(behv))
return Unhandled();
else if (behv == Same())
return Same();
else if (Behavior.isAlive(behv))
return new Tap<T>(signal, message, behv);
else
return Stopped();
}
@Override
public Behavior<T> management(akka.typed.ActorContext<T> ctx, Signal msg) throws Exception {
signal.apply(ctx, msg);
return canonicalize(behavior.management(ctx, msg));
}
@Override
public Behavior<T> message(akka.typed.ActorContext<T> ctx, T msg) throws Exception {
message.apply(ctx, msg);
return canonicalize(behavior.message(ctx, msg));
}
}
/**
* Mode selector for the {@link #restarter} wrapper that decides whether an actor
* upon a failure shall be restarted (to clean initial state) or resumed (keep
* on running, with potentially compromised state).
*
* Resuming is less safe. If you use <code>OnFailure.RESUME</code> you should at least
* not hold mutable data fields or collections within the actor as those might
* be in an inconsistent state (the exception might have interrupted normal
* processing); avoiding mutable state is possible by returning a fresh
* behavior with the new state after every message.
*/
public enum OnFailure {
RESUME, RESTART;
}
private static Function2<Object, Object, Object> _unhandledFun = (ctx, msg) -> Unhandled();
@SuppressWarnings("unchecked")
private static <T> Function2<ActorContext<T>, Signal, Behavior<T>> unhandledFun() {
return (Function2<ActorContext<T>, Signal, Behavior<T>>) (Object) _unhandledFun;
}
private static Procedure2<Object, Object> _doNothing = (ctx, msg) -> {
};
@SuppressWarnings("unchecked")
private static <T> Procedure2<ActorContext<T>, Signal> doNothing() {
return (Procedure2<ActorContext<T>, Signal>) (Object) _doNothing;
}
/**
* Construct an actor behavior that can react to incoming messages but not to
* lifecycle signals. After spawning this actor from another actor (or as the
* guardian of an {@link akka.typed.ActorSystem}) it will be executed within an
* {@link ActorContext} that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called stateful because processing the next message
* results in a new behavior that can potentially be different from this one.
* If no change is desired, use {@link #same}.
*
* @param message
* the function that describes how this actor reacts to the next
* message
* @return the behavior
*/
static public <T> Behavior<T> stateful(Function2<ActorContext<T>, T, Behavior<T>> message) {
return new Stateful<T>(message, unhandledFun());
}
/**
* Construct an actor behavior that can react to both incoming messages and
* lifecycle signals. After spawning this actor from another actor (or as the
* guardian of an {@link akka.typed.ActorSystem}) it will be executed within an
* {@link ActorContext} that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called stateful because processing the next message
* results in a new behavior that can potentially be different from this one.
* If no change is desired, use {@link #same}.
*
* @param message
* the function that describes how this actor reacts to the next
* message
* @param signal
* the function that describes how this actor reacts to the given
* signal
* @return the behavior
*/
static public <T> Behavior<T> stateful(Function2<ActorContext<T>, T, Behavior<T>> message,
Function2<ActorContext<T>, Signal, Behavior<T>> signal) {
return new Stateful<T>(message, signal);
}
/**
* Construct an actor behavior that can react to incoming messages but not to
* lifecycle signals. After spawning this actor from another actor (or as the
* guardian of an {@link akka.typed.ActorSystem}) it will be executed within an
* {@link ActorContext} that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called stateless because it cannot be replaced by
* another one after it has been installed. It is most useful for leaf actors
* that do not create child actors themselves.
*
* @param message
* the function that describes how this actor reacts to the next
* message
* @return the behavior
*/
static public <T> Behavior<T> stateless(Procedure2<ActorContext<T>, T> message) {
return new Stateless<T>(message);
}
/**
* Return this behavior from message processing in order to advise the system
* to reuse the previous behavior. This is provided in order to avoid the
* allocation overhead of recreating the current behavior where that is not
* necessary.
*
* @return pseudo-behavior marking no change
*/
static public <T> Behavior<T> same() {
return Same();
}
/**
* Return this behavior from message processing in order to advise the system
* to reuse the previous behavior, including the hint that the message has not
* been handled. This hint may be used by composite behaviors that delegate
* (partial) handling to other behaviors.
*
* @return pseudo-behavior marking unhandled
*/
static public <T> Behavior<T> unhandled() {
return Unhandled();
}
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure. The PostStop
* signal that results from stopping this actor will NOT be passed to the
* current behavior, it will be effectively ignored.
*
* @return the inert behavior
*/
static public <T> Behavior<T> stopped() {
return Stopped();
}
/**
* A behavior that treats every incoming message as unhandled.
*
* @return the empty behavior
*/
static public <T> Behavior<T> empty() {
return Empty();
}
/**
* A behavior that ignores every incoming message and returns same.
*
* @return the inert behavior
*/
static public <T> Behavior<T> ignore() {
return Ignore();
}
/**
* Behavior decorator that allows you to perform any side-effect before a
* signal or message is delivered to the wrapped behavior. The wrapped
* behavior can evolve (i.e. be stateful) without needing to be wrapped in a
* <code>tap(...)</code> call again.
*
* @param signal
* procedure to invoke with the {@link ActorContext} and the
* {@link akka.typed.Signal} as arguments before delivering the signal to
* the wrapped behavior
* @param message
* procedure to invoke with the {@link ActorContext} and the received
* message as arguments before delivering the signal to the wrapped
* behavior
* @param behavior
* initial behavior to be wrapped
* @return the decorated behavior
*/
static public <T> Behavior<T> tap(Procedure2<ActorContext<T>, Signal> signal, Procedure2<ActorContext<T>, T> message,
Behavior<T> behavior) {
return new Tap<T>(signal, message, behavior);
}
/**
* Behavior decorator that copies all received message to the designated
* monitor {@link akka.typed.ActorRef} before invoking the wrapped behavior. The
* wrapped behavior can evolve (i.e. be stateful) without needing to be
* wrapped in a <code>monitor(...)</code> call again.
*
* @param monitor
* ActorRef to which to copy all received messages
* @param behavior
* initial behavior to be wrapped
* @return the decorated behavior
*/
static public <T> Behavior<T> monitor(ActorRef<T> monitor, Behavior<T> behavior) {
return new Tap<T>(doNothing(), (ctx, msg) -> monitor.tell(msg), behavior);
}
/**
* Wrap the given behavior such that it is restarted (i.e. reset to its
* initial state) whenever it throws an exception of the given class or a
* subclass thereof. Exceptions that are not subtypes of <code>Thr</code> will not be
* caught and thus lead to the termination of the actor.
*
* It is possible to specify that the actor shall not be restarted but
* resumed. This entails keeping the same state as before the exception was
* thrown and is thus less safe. If you use <code>OnFailure.RESUME</code> you should at
* least not hold mutable data fields or collections within the actor as those
* might be in an inconsistent state (the exception might have interrupted
* normal processing); avoiding mutable state is possible by returning a fresh
* behavior with the new state after every message.
*
* @param clazz
* the type of exceptions that shall be caught
* @param mode
* whether to restart or resume the actor upon a caught failure
* @param initialBehavior
* the initial behavior, that is also restored during a restart
* @return the wrapped behavior
*/
static public <T, Thr extends Throwable> Behavior<T> restarter(Class<Thr> clazz, OnFailure mode,
Behavior<T> initialBehavior) {
final ClassTag<Thr> catcher = akka.japi.Util.classTag(clazz);
return new Restarter<T, Thr>(initialBehavior, mode == OnFailure.RESUME, initialBehavior, catcher);
}
/**
* Widen the wrapped Behavior by placing a funnel in front of it: the supplied
* PartialFunction decides which message to pull in (those that it is defined
* at) and may transform the incoming message to place them into the wrapped
* Behaviors type hierarchy. Signals are not transformed.
*
* <code><pre>
* Behavior&lt;String> s = stateless((ctx, msg) -> System.out.println(msg))
* Behavior&lt;Number> n = widened(s, pf -> pf.
* match(BigInteger.class, i -> "BigInteger(" + i + ")").
* match(BigDecimal.class, d -> "BigDecimal(" + d + ")")
* // drop all other kinds of Number
* );
* </pre></code>
*
* @param behavior
* the behavior that will receive the selected messages
* @param selector
* a partial function builder for describing the selection and
* transformation
* @return a behavior of the widened type
*/
static public <T, U> Behavior<U> widened(Behavior<T> behavior, Function<PFBuilder<U, T>, PFBuilder<U, T>> selector) {
return new Widened<T, U>(behavior, selector.apply(new PFBuilder<>()).build());
}
/**
* Wrap a behavior factory so that it runs upon PreStart, i.e. behavior
* creation is deferred to the child actor instead of running within the
* parent.
*
* @param producer
* behavior factory that takes the child actors context as argument
* @return the deferred behavior
*/
static public <T> Behavior<T> deferred(akka.japi.function.Function<ActorContext<T>, Behavior<T>> producer) {
return new Deferred<T>(producer);
}
}

View file

@ -1,159 +0,0 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed.javadsl;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import akka.actor.Cancellable;
import akka.typed.ActorRef;
import akka.typed.ActorSystem;
import akka.typed.Behavior;
import akka.typed.DeploymentConfig;
import scala.concurrent.ExecutionContextExecutor;
import scala.concurrent.duration.FiniteDuration;
/**
* An Actor is given by the combination of a {@link akka.typed.Behavior} and a context in which
* this behavior is executed. As per the Actor Model an Actor can perform the
* following actions when processing a message:
*
* <ul>
* <li>send a finite number of messages to other Actors it knows</li>
* <li>create a finite number of Actors</li>
* <li>designate the behavior for the next message</li>
* </ul>
*
* In Akka the first capability is accessed by using the {@link akka.typed.ActorRef#tell}
* method, the second is provided by {@link akka.typed.ActorContext#spawn} and the
* third is implicit in the signature of {@link akka.typed.Behavior} in that the next behavior
* is always returned from the message processing logic.
*
* An <code>ActorContext</code> in addition provides access to the Actors own identity
* ({@link #getSelf self}), the {@link akka.typed.ActorSystem} it is part of, methods for querying the list
* of child Actors it created, access to {@link akka.typed.Terminated DeathWatch} and timed
* message scheduling.
*/
public interface ActorContext<T> {
/**
* The identity of this Actor, bound to the lifecycle of this Actor instance.
* An Actor with the same name that lives before or after this instance will
* have a different {@link akka.typed.ActorRef}.
*/
public ActorRef<T> getSelf();
/**
* Return the mailbox capacity that was configured by the parent for this
* actor.
*/
public int getMailboxCapacity();
/**
* The {@link akka.typed.ActorSystem} to which this Actor belongs.
*/
public ActorSystem<Void> getSystem();
/**
* The list of child Actors created by this Actor during its lifetime that are
* still alive, in no particular order.
*/
public List<ActorRef<Void>> getChildren();
/**
* The named child Actor if it is alive.
*/
public Optional<ActorRef<Void>> getChild(String name);
/**
* Create a child Actor from the given {@link akka.typed.Behavior} under a randomly chosen name.
* It is good practice to name Actors wherever practical.
*/
public <U> ActorRef<U> spawnAnonymous(Behavior<U> behavior);
/**
* Create a child Actor from the given {@link akka.typed.Behavior} under a randomly chosen name.
* It is good practice to name Actors wherever practical.
*/
public <U> ActorRef<U> spawnAnonymous(Behavior<U> behavior, DeploymentConfig deployment);
/**
* Create a child Actor from the given {@link akka.typed.Behavior} and with the given name.
*/
public <U> ActorRef<U> spawn(Behavior<U> behavior, String name);
/**
* Create a child Actor from the given {@link akka.typed.Behavior} and with the given name.
*/
public <U> ActorRef<U> spawn(Behavior<U> behavior, String name, DeploymentConfig deployment);
/**
* Force the child Actor under the given name to terminate after it finishes
* processing its current message. Nothing happens if the ActorRef does not
* refer to a current child actor.
*
* @return whether the passed-in {@link akka.typed.ActorRef} points to a current child Actor
*/
public boolean stop(ActorRef<?> child);
/**
* Register for {@link akka.typed.Terminated} notification once the Actor identified by the
* given {@link akka.typed.ActorRef} terminates. This notification is also generated when the
* {@link akka.typed.ActorSystem} to which the referenced Actor belongs is declared as failed
* (e.g. in reaction to being unreachable).
*/
public <U> ActorRef<U> watch(ActorRef<U> other);
/**
* Revoke the registration established by {@link #watch}. A {@link akka.typed.Terminated}
* notification will not subsequently be received for the referenced Actor.
*/
public <U> ActorRef<U> unwatch(ActorRef<U> other);
/**
* Schedule the sending of a notification in case no other message is received
* during the given period of time. The timeout starts anew with each received
* message. Provide <code>scala.concurrent.duration.Duration.Undefined</code> to switch off this mechanism.
*/
public void setReceiveTimeout(FiniteDuration d, T msg);
/**
* Cancel the sending of receive timeout notifications.
*/
public void cancelReceiveTimeout();
/**
* Schedule the sending of the given message to the given target Actor after
* the given time period has elapsed. The scheduled action can be cancelled by
* invoking {@link akka.actor.Cancellable#cancel} on the returned handle.
*/
public <U> Cancellable schedule(FiniteDuration delay, ActorRef<U> target, U msg);
/**
* This Actors execution context. It can be used to run asynchronous tasks
* like <code>scala.concurrent.Future</code> combinators.
*/
public ExecutionContextExecutor getExecutionContext();
/**
* Create a child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*
* The name of the child actor will be composed of a unique identifier
* starting with a dollar sign to which the given <code>name</code> argument is appended,
* with an inserted hyphen between these two parts. Therefore the given <code>name</code>
* argument does not need to be unique within the scope of the parent actor.
*/
public <U> ActorRef<U> createAdapter(Function<U, T> f, String name);
/**
* Create an anonymous child actor that will wrap messages such that other
* Actors protocols can be ingested by this Actor. You are strongly advised
* to cache these ActorRefs or to stop them when no longer needed.
*/
public <U> ActorRef<U> createAdapter(Function<U, T> f);
}

View file

@ -12,5 +12,19 @@ akka.typed {
# Default mailbox capacity for actors where nothing else is configured by
# their parent, see also class akka.typed.MailboxCapacity
mailbox-capacity = 1000
# List FQCN of `akka.typed.ExtensionId`s which shall be loaded at actor system startup.
# Should be on the format: 'extensions = ["com.example.MyExtId1", "com.example.MyExtId2"]' etc.
# See the Akka Documentation for more info about Extensions
extensions = []
# List FQCN of extensions which shall be loaded at actor system startup.
# Library extensions are regular extensions that are loaded at startup and are
# available for third party library authors to enable auto-loading of extensions when
# present on the classpath. This is done by appending entries:
# 'library-extensions += "Extension"' in the library `reference.conf`.
#
# Should not be set by end user applications in 'application.conf', use the extensions property for that
#
library-extensions = ${?akka.library-extensions} []
}

View file

@ -3,14 +3,6 @@
*/
package akka.typed
import scala.concurrent.duration.Duration
import scala.collection.immutable.TreeMap
import akka.util.Helpers
import akka.{ actor untyped }
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.ExecutionContextExecutor
import java.util.Optional
import java.util.ArrayList
import akka.annotation.DoNotInherit
import akka.annotation.ApiMayChange
@ -20,133 +12,17 @@ import akka.annotation.ApiMayChange
*/
@DoNotInherit
@ApiMayChange
trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext[T] {
override def getChild(name: String): Optional[ActorRef[Void]] =
child(name) match {
case Some(c) Optional.of(c.upcast[Void])
case None Optional.empty()
}
trait ActorContext[T] {
// this should be a pure interface, i.e. only abstract methods
override def getChildren(): java.util.List[akka.typed.ActorRef[Void]] = {
val c = children
val a = new ArrayList[ActorRef[Void]](c.size)
val i = c.iterator
while (i.hasNext) a.add(i.next().upcast[Void])
a
}
/**
* Get the `javadsl` of this `ActorContext`.
*/
def asJava: javadsl.ActorContext[T]
override def getExecutionContext(): scala.concurrent.ExecutionContextExecutor =
executionContext
override def getMailboxCapacity(): Int =
mailboxCapacity
override def getSelf(): akka.typed.ActorRef[T] =
self
override def getSystem(): akka.typed.ActorSystem[Void] =
system.asInstanceOf[ActorSystem[Void]]
override def spawn[U](behavior: akka.typed.Behavior[U], name: String): akka.typed.ActorRef[U] =
spawn(behavior, name, EmptyDeploymentConfig)
override def spawnAnonymous[U](behavior: akka.typed.Behavior[U]): akka.typed.ActorRef[U] =
spawnAnonymous(behavior, EmptyDeploymentConfig)
override def createAdapter[U](f: java.util.function.Function[U, T]): akka.typed.ActorRef[U] =
spawnAdapter(f.apply _)
override def createAdapter[U](f: java.util.function.Function[U, T], name: String): akka.typed.ActorRef[U] =
spawnAdapter(f.apply _, name)
/**
* Get the `scaladsl` of this `ActorContext`.
*/
def asScala: scaladsl.ActorContext[T]
}
/**
* An [[ActorContext]] for synchronous execution of a [[Behavior]] that
* provides only stubs for the effects an Actor can perform and replaces
* created child Actors by a synchronous Inbox (see `Inbox.sync`).
*
* See [[EffectfulActorContext]] for more advanced uses.
*/
class StubbedActorContext[T](
val name: String,
override val mailboxCapacity: Int,
override val system: ActorSystem[Nothing]) extends ActorContext[T] {
val selfInbox = Inbox[T](name)
override val self = selfInbox.ref
private var _children = TreeMap.empty[String, Inbox[_]]
private val childName = Iterator from 1 map (Helpers.base64(_))
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 spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = {
val i = Inbox[U](childName.next())
_children += i.ref.path.name i
i.ref
}
override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] =
_children get name match {
case Some(_) throw untyped.InvalidActorNameException(s"actor name $name is already taken")
case None
// FIXME correct child path for the Inbox ref
val i = Inbox[U](name)
_children += name i
i.ref
}
/**
* Do not actually stop the child inbox, only simulate the liveness check.
* Removal is asynchronous, explicit removeInbox is needed from outside afterwards.
*/
override def stop(child: ActorRef[_]): Boolean = {
_children.get(child.path.name) match {
case None false
case Some(inbox) inbox.ref == child
}
}
override def watch[U](other: ActorRef[U]): ActorRef[U] = other
override def unwatch[U](other: ActorRef[U]): ActorRef[U] = other
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = ()
override def cancelReceiveTimeout(): Unit = ()
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): untyped.Cancellable = new untyped.Cancellable {
override def cancel() = false
override def isCancelled = true
}
override def executionContext: ExecutionContextExecutor = system.executionContext
override def spawnAdapter[U](f: U T, name: String = ""): ActorRef[U] = {
val n = if (name != "") s"${childName.next()}-$name" else childName.next()
val i = Inbox[U](n)
_children += i.ref.path.name i
new internal.FunctionRef[U](
self.path / i.ref.path.name,
(msg, _) { val m = f(msg); if (m != null) { selfInbox.ref ! m; i.ref ! msg } },
(self) selfInbox.ref.sorry.sendSystem(internal.DeathWatchNotification(self, null)))
}
/**
* Retrieve the inbox representing the given child actor. The passed ActorRef must be one that was returned
* by one of the spawn methods earlier.
*/
def childInbox[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
}
/**
* Retrieve the inbox representing the child actor with the given name.
*/
def childInbox[U](name: String): Inbox[U] = _children(name).asInstanceOf[Inbox[U]]
/**
* Remove the given inbox from the list of children, for example after
* having simulated its termination.
*/
def removeChildInbox(child: ActorRef[Nothing]): Unit = _children -= child.path.name
override def toString: String = s"Inbox($self)"
}

View file

@ -17,7 +17,8 @@ import scala.concurrent.Future
* [[EventStream]] on a best effort basis
* (i.e. this delivery is not reliable).
*/
abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[ActorRef[_]] { this: internal.ActorRefImpl[T]
trait ActorRef[-T] extends java.lang.Comparable[ActorRef[_]] {
this: internal.ActorRefImpl[T]
/**
* Send a message to the Actor referenced by this ActorRef using *at-most-once*
@ -26,17 +27,16 @@ abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[Act
def tell(msg: T): Unit
/**
* Send a message to the Actor referenced by this ActorRef using *at-most-once*
* messaging semantics.
* Narrow the type of this `ActorRef, which is always a safe operation.
*/
def !(msg: T): Unit = tell(msg)
def narrow[U <: T]: ActorRef[U]
/**
* Unsafe utility method for widening the type accepted by this ActorRef;
* provided to avoid having to use `asInstanceOf` on the full reference type,
* which would unfortunately also work on non-ActorRefs.
*/
def upcast[U >: T @uncheckedVariance]: ActorRef[U] = this.asInstanceOf[ActorRef[U]]
def upcast[U >: T @uncheckedVariance]: ActorRef[U]
/**
* The hierarchical path name of the referenced Actor. The lifecycle of the
@ -44,36 +44,28 @@ abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[Act
* and more than one Actor instance can exist with the same path at different
* points in time, but not concurrently.
*/
final val path: a.ActorPath = _path
def path: a.ActorPath
/**
* Comparison takes path and the unique id of the actor cell into account.
*/
final override def compareTo(other: ActorRef[_]) = {
val x = this.path compareTo other.path
if (x == 0) if (this.path.uid < other.path.uid) -1 else if (this.path.uid == other.path.uid) 0 else 1
else x
}
final override def hashCode: Int = path.uid
/**
* Equals takes path and the unique id of the actor cell into account.
*/
final override def equals(that: Any): Boolean = that match {
case other: ActorRef[_] path.uid == other.path.uid && path == other.path
case _ false
}
final override def toString: String = s"Actor[${path}#${path.uid}]"
}
object ActorRef {
implicit final class ActorRefOps[-T](val ref: ActorRef[T]) extends AnyVal {
/**
* Send a message to the Actor referenced by this ActorRef using *at-most-once*
* messaging semantics.
*/
def !(msg: T): Unit = ref.tell(msg)
}
// FIXME factory methods for below for Java (trait + object)
/**
* Create an ActorRef from a Future, buffering up to the given number of
* 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)
def apply[T](f: Future[ActorRef[T]], bufferSize: Int = 1000): ActorRef[T] =
new internal.FutureRef(FuturePath, bufferSize, f)
/**
* Create an ActorRef by providing a function that is invoked for sending

View file

@ -11,10 +11,11 @@ import akka.actor.setup.ActorSystemSetup
import com.typesafe.config.{ Config, ConfigFactory }
import scala.concurrent.{ ExecutionContextExecutor, Future }
import akka.typed.adapter.{ ActorSystemAdapter, PropsAdapter }
import akka.typed.internal.adapter.{ ActorSystemAdapter, PropsAdapter }
import akka.util.Timeout
import akka.annotation.DoNotInherit
import akka.annotation.ApiMayChange
import java.util.Optional
/**
* An ActorSystem is home to a hierarchy of Actors. It is created using
@ -25,7 +26,7 @@ import akka.annotation.ApiMayChange
*/
@DoNotInherit
@ApiMayChange
trait ActorSystem[-T] extends ActorRef[T] { this: internal.ActorRefImpl[T]
abstract class ActorSystem[-T] extends ActorRef[T] with Extensions { this: internal.ActorRefImpl[T]
/**
* The name of this actor system, used to distinguish multiple ones within
@ -133,7 +134,7 @@ trait ActorSystem[-T] extends ActorRef[T] { this: internal.ActorRefImpl[T] ⇒
/**
* Ask the system guardian of this system to create an actor from the given
* behavior and deployment and with the given name. The name does not need to
* behavior and props and with the given name. The name does not need to
* be unique since the guardian will prefix it with a running number when
* creating the child actor. The timeout sets the timeout used for the [[akka.typed.scaladsl.AskPattern$]]
* invocation when asking the guardian.
@ -142,7 +143,7 @@ trait ActorSystem[-T] extends ActorRef[T] { this: internal.ActorRefImpl[T] ⇒
* to which messages can immediately be sent by using the [[ActorRef$.apply[T](s*]]
* method.
*/
def systemActorOf[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig)(implicit timeout: Timeout): Future[ActorRef[U]]
def systemActorOf[U](behavior: Behavior[U], name: String, props: Props = Props.empty)(implicit timeout: Timeout): Future[ActorRef[U]]
/**
* Return a reference to this systems [[akka.typed.patterns.Receptionist$]].
@ -154,36 +155,66 @@ object ActorSystem {
import internal._
/**
* Create an ActorSystem implementation that is optimized for running
* Scala API: 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, guardianBehavior: Behavior[T],
guardianDeployment: DeploymentConfig = EmptyDeploymentConfig,
config: Option[Config] = None,
classLoader: Option[ClassLoader] = None,
executionContext: Option[ExecutionContext] = None): ActorSystem[T] = {
guardianProps: Props = Props.empty,
config: Option[Config] = None,
classLoader: Option[ClassLoader] = None,
executionContext: Option[ExecutionContext] = None): ActorSystem[T] = {
Behavior.validateAsInitial(guardianBehavior)
val cl = classLoader.getOrElse(akka.actor.ActorSystem.findClassLoader())
val appConfig = config.getOrElse(ConfigFactory.load(cl))
new ActorSystemImpl(name, appConfig, cl, executionContext, guardianBehavior, guardianDeployment)
new ActorSystemImpl(name, appConfig, cl, executionContext, guardianBehavior, guardianProps)
}
/**
* Java API: Create an ActorSystem implementation that is optimized for running
* Akka Typed [[Behavior]] hierarchiesthis system cannot run untyped
* [[akka.actor.Actor]] instances.
*/
def create[T](name: String, guardianBehavior: Behavior[T],
guardianProps: Optional[Props],
config: Optional[Config],
classLoader: Optional[ClassLoader],
executionContext: Optional[ExecutionContext]): ActorSystem[T] = {
import scala.compat.java8.OptionConverters._
apply(name, guardianBehavior, guardianProps.asScala.getOrElse(EmptyProps), config.asScala, classLoader.asScala, executionContext.asScala)
}
/**
* Java API: Create an ActorSystem implementation that is optimized for running
* Akka Typed [[Behavior]] hierarchiesthis system cannot run untyped
* [[akka.actor.Actor]] instances.
*/
def create[T](name: String, guardianBehavior: Behavior[T]): ActorSystem[T] =
apply(name, guardianBehavior)
/**
* 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, guardianBehavior: Behavior[T],
guardianDeployment: DeploymentConfig = EmptyDeploymentConfig,
guardianProps: Props = Props.empty,
config: Option[Config] = None,
classLoader: Option[ClassLoader] = None,
executionContext: Option[ExecutionContext] = None,
actorSystemSettings: ActorSystemSetup = ActorSystemSetup.empty): ActorSystem[T] = {
// TODO I'm not sure how useful this mode is for end-users. It has the limitation that untyped top level
// actors can't be created, because we have a custom user guardian. I would imagine that if you have
// a system of both untyped and typed actors (e.g. adding some typed actors to an existing application)
// you would start an untyped.ActorSystem and spawn typed actors from that system or from untyped actors.
// Same thing with `wrap` below.
Behavior.validateAsInitial(guardianBehavior)
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(guardianBehavior, guardianDeployment)), actorSystemSettings)
val untyped = new a.ActorSystemImpl(name, appConfig, cl, executionContext,
Some(PropsAdapter(() guardianBehavior, guardianProps)), actorSystemSettings)
untyped.start()
new ActorSystemAdapter(untyped)
}

View file

@ -3,7 +3,11 @@
*/
package akka.typed
import akka.annotation.InternalApi
import scala.annotation.tailrec
import akka.util.LineNumbers
import akka.annotation.{ DoNotInherit, InternalApi }
import akka.typed.scaladsl.{ ActorContext SAC }
import akka.util.OptionVal
/**
* The behavior of an actor defines how it reacts to the messages that it
@ -13,14 +17,32 @@ import akka.annotation.InternalApi
* its child actors.
*
* Behaviors can be formulated in a number of different ways, either by
* creating a derived class or by employing factory methods like the ones
* in the [[ScalaDSL$]] object.
* using the DSLs in [[akka.typed.scaladsl.Actor]] and [[akka.typed.javadsl.Actor]]
* or extending the abstract [[ExtensibleBehavior]] class.
*
* Closing over ActorContext makes a Behavior immobile: it cannot be moved to
* another context and executed there, and therefore it cannot be replicated or
* forked either.
*
* This base class is not meant to be extended by user code. If you do so, you may
* lose binary compatibility.
*/
abstract class Behavior[T] {
@InternalApi
@DoNotInherit
sealed abstract class Behavior[T] {
/**
* Narrow the type of this Behavior, which is always a safe operation. This
* method is necessary to implement the contravariant nature of Behavior
* (which cannot be expressed directly due to type inference problems).
*/
final def narrow[U <: T]: Behavior[U] = this.asInstanceOf[Behavior[U]]
}
/**
* Extension point for implementing custom behaviors in addition to the existing
* set of behaviors available through the DSLs in [[akka.typed.scaladsl.Actor]] and [[akka.typed.javadsl.Actor]]
*/
abstract class ExtensibleBehavior[T] extends Behavior[T] {
/**
* Process an incoming [[Signal]] and return the next behavior. This means
* that all lifecycle hooks, ReceiveTimeout, Terminated and Failed messages
@ -29,15 +51,15 @@ abstract class Behavior[T] {
* The returned behavior can in addition to normal behaviors be one of the
* canned special objects:
*
* * returning `Stopped` will terminate this Behavior
* * returning `Same` designates to reuse the current Behavior
* * returning `Unhandled` keeps the same Behavior and signals that the message was not yet handled
* * returning `stopped` will terminate this Behavior
* * returning `same` designates to reuse the current Behavior
* * returning `unhandled` keeps the same Behavior and signals that the message was not yet handled
*
* Code calling this method should use [[Behavior$]] `canonicalize` to replace
* the special objects with real Behaviors.
*/
@throws(classOf[Exception])
def management(ctx: ActorContext[T], msg: Signal): Behavior[T]
def receiveSignal(ctx: ActorContext[T], msg: Signal): Behavior[T]
/**
* Process an incoming message and return the next behavior.
@ -45,128 +67,214 @@ abstract class Behavior[T] {
* The returned behavior can in addition to normal behaviors be one of the
* canned special objects:
*
* * returning `Stopped` will terminate this Behavior
* * returning `Same` designates to reuse the current Behavior
* * returning `Unhandled` keeps the same Behavior and signals that the message was not yet handled
* * returning `stopped` will terminate this Behavior
* * returning `same` designates to reuse the current Behavior
* * returning `unhandled` keeps the same Behavior and signals that the message was not yet handled
*
* Code calling this method should use [[Behavior$]] `canonicalize` to replace
* the special objects with real Behaviors.
*/
@throws(classOf[Exception])
def message(ctx: ActorContext[T], msg: T): Behavior[T]
def receiveMessage(ctx: ActorContext[T], msg: T): Behavior[T]
/**
* Narrow the type of this Behavior, which is always a safe operation. This
* method is necessary to implement the contravariant nature of Behavior
* (which cannot be expressed directly due to type inference problems).
*/
def narrow[U <: T]: Behavior[U] = this.asInstanceOf[Behavior[U]]
}
object Behavior {
/**
* Return this behavior from message processing in order to advise the
* system to reuse the previous behavior. This is provided in order to
* avoid the allocation overhead of recreating the current behavior where
* that is not necessary.
*/
def same[T]: Behavior[T] = SameBehavior.asInstanceOf[Behavior[T]]
/**
* Return this behavior from message processing in order to advise the
* system to reuse the previous behavior, including the hint that the
* message has not been handled. This hint may be used by composite
* behaviors that delegate (partial) handling to other behaviors.
*/
def unhandled[T]: Behavior[T] = UnhandledBehavior.asInstanceOf[Behavior[T]]
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure.
*
* The PostStop signal that results from stopping this actor will be passed to the
* current behavior. All other messages and signals will effectively be
* ignored.
*/
def stopped[T]: Behavior[T] = StoppedBehavior.asInstanceOf[Behavior[T]]
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure.
*
* The PostStop signal that results from stopping this actor will be passed to the
* given `postStop` behavior. All other messages and signals will effectively be
* ignored.
*/
def stopped[T](postStop: Behavior[T]): Behavior[T] =
new StoppedBehavior(OptionVal.Some(postStop))
/**
* A behavior that treats every incoming message as unhandled.
*/
def empty[T]: Behavior[T] = EmptyBehavior.asInstanceOf[Behavior[T]]
/**
* A behavior that ignores every incoming message and returns same.
*/
def ignore[T]: Behavior[T] = IgnoreBehavior.asInstanceOf[Behavior[T]]
/**
* INTERNAL API.
*/
@SerialVersionUID(1L)
private[akka] object emptyBehavior extends Behavior[Any] {
override def management(ctx: ActorContext[Any], msg: Signal): Behavior[Any] = ScalaDSL.Unhandled
override def message(ctx: ActorContext[Any], msg: Any): Behavior[Any] = ScalaDSL.Unhandled
@InternalApi
private[akka] object EmptyBehavior extends Behavior[Any] {
override def toString = "Empty"
}
/**
* INTERNAL API.
*/
@SerialVersionUID(1L)
private[akka] object ignoreBehavior extends Behavior[Any] {
override def management(ctx: ActorContext[Any], msg: Signal): Behavior[Any] = ScalaDSL.Same
override def message(ctx: ActorContext[Any], msg: Any): Behavior[Any] = ScalaDSL.Same
@InternalApi
private[akka] object IgnoreBehavior extends Behavior[Any] {
override def toString = "Ignore"
}
/**
* INTERNAL API.
*/
@SerialVersionUID(1L)
private[akka] object unhandledBehavior extends Behavior[Nothing] {
override def management(ctx: ActorContext[Nothing], msg: Signal): Behavior[Nothing] = throw new UnsupportedOperationException("Not Implemented")
override def message(ctx: ActorContext[Nothing], msg: Nothing): Behavior[Nothing] = throw new UnsupportedOperationException("Not Implemented")
@InternalApi
private[akka] object UnhandledBehavior extends Behavior[Nothing] {
override def toString = "Unhandled"
}
/**
* INTERNAL API
*/
@InternalApi private[akka] val unhandledSignal: (ActorContext[Nothing], Signal) Behavior[Nothing] =
(_, _) unhandledBehavior
@InternalApi private[akka] val unhandledSignal: PartialFunction[(ActorContext[Nothing], Signal), Behavior[Nothing]] = {
case (_, _) UnhandledBehavior
}
/**
* INTERNAL API.
* Not placed in internal.BehaviorImpl because Behavior is sealed.
*/
@InternalApi
private[akka] final case class DeferredBehavior[T](factory: SAC[T] Behavior[T]) extends Behavior[T] {
/** "undefer" the deferred behavior */
@throws(classOf[Exception])
def apply(ctx: ActorContext[T]): Behavior[T] = factory(ctx.asScala)
override def toString: String = s"Deferred(${LineNumbers(factory)})"
}
/**
* INTERNAL API.
*/
@SerialVersionUID(1L)
private[akka] object sameBehavior extends Behavior[Nothing] {
override def management(ctx: ActorContext[Nothing], msg: Signal): Behavior[Nothing] = throw new UnsupportedOperationException("Not Implemented")
override def message(ctx: ActorContext[Nothing], msg: Nothing): Behavior[Nothing] = throw new UnsupportedOperationException("Not Implemented")
private[akka] object SameBehavior extends Behavior[Nothing] {
override def toString = "Same"
}
/**
* INTERNAL API.
*/
@SerialVersionUID(1L)
private[akka] object stoppedBehavior extends Behavior[Nothing] {
override def management(ctx: ActorContext[Nothing], msg: Signal): Behavior[Nothing] = {
assert(
msg == PostStop || msg.isInstanceOf[Terminated],
s"stoppedBehavior received $msg (only PostStop or Terminated expected)")
this
}
override def message(ctx: ActorContext[Nothing], msg: Nothing): Behavior[Nothing] = throw new UnsupportedOperationException("Not Implemented")
private[akka] object StoppedBehavior extends StoppedBehavior[Nothing](OptionVal.None)
/**
* INTERNAL API: When the cell is stopping this behavior is used, so
* that PostStop can be sent to previous behavior from `finishTerminate`.
*/
private[akka] class StoppedBehavior[T](val postStop: OptionVal[Behavior[T]]) extends Behavior[T] {
override def toString = "Stopped"
}
/**
* Given a possibly special behavior (same or unhandled) and a
* current behavior (which defines the meaning of encountering a `Same`
* current behavior (which defines the meaning of encountering a `same`
* behavior) this method computes the next behavior, suitable for passing a
* message or signal.
*/
def canonicalize[T](behavior: Behavior[T], current: Behavior[T]): Behavior[T] =
@tailrec
def canonicalize[T](behavior: Behavior[T], current: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
behavior match {
case `sameBehavior` current
case `unhandledBehavior` current
case other other
case SameBehavior current
case UnhandledBehavior current
case deferred: DeferredBehavior[T] canonicalize(deferred(ctx), deferred, ctx)
case other other
}
@tailrec
def undefer[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] = {
behavior match {
case innerDeferred: DeferredBehavior[T] undefer(innerDeferred(ctx), ctx)
case _ behavior
}
}
/**
* Validate the given behavior as a suitable initial actor behavior; most
* notably the behavior can neither be `Same` nor `Unhandled`. Starting
* notably the behavior can neither be `same` nor `unhandled`. Starting
* out with a `Stopped` behavior is allowed, though.
*/
def validateAsInitial[T](behavior: Behavior[T]): Behavior[T] =
behavior match {
case `sameBehavior` | `unhandledBehavior`
case SameBehavior | UnhandledBehavior
throw new IllegalArgumentException(s"cannot use $behavior as initial behavior")
case x x
}
/**
* Validate the given behavior as initial, pass it a [[PreStart]] message
* and canonicalize the result.
* Returns true if the given behavior is not stopped.
*/
def preStart[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] = {
val b = validateAsInitial(behavior)
if (isAlive(b)) canonicalize(b.management(ctx, PreStart), b) else b
def isAlive[T](behavior: Behavior[T]): Boolean = behavior match {
case _: StoppedBehavior[_] false
case _ true
}
/**
* Returns true if the given behavior is not stopped.
* Returns true if the given behavior is the special `unhandled` marker.
*/
def isAlive[T](behavior: Behavior[T]): Boolean = behavior ne stoppedBehavior
def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq UnhandledBehavior
/**
* Returns true if the given behavior is the special `Unhandled` marker.
*/
def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq unhandledBehavior
def isDeferred[T](behavior: Behavior[T]): Boolean = behavior match {
case _: DeferredBehavior[T] true
case _ false
}
/**
* Execute the behavior with the given message
*/
def interpretMessage[T](behavior: Behavior[T], ctx: ActorContext[T], msg: T): Behavior[T] =
interpret(behavior, ctx, msg)
/**
* Execute the behavior with the given signal
*/
def interpretSignal[T](behavior: Behavior[T], ctx: ActorContext[T], signal: Signal): Behavior[T] =
interpret(behavior, ctx, signal)
private def interpret[T](behavior: Behavior[T], ctx: ActorContext[T], msg: Any): Behavior[T] =
behavior match {
case SameBehavior | UnhandledBehavior throw new IllegalArgumentException(s"cannot execute with [$behavior] as behavior")
case d: DeferredBehavior[_] throw new IllegalArgumentException(s"deferred [$d] should not be passed to interpreter")
case IgnoreBehavior SameBehavior.asInstanceOf[Behavior[T]]
case s: StoppedBehavior[T] s
case EmptyBehavior UnhandledBehavior.asInstanceOf[Behavior[T]]
case ext: ExtensibleBehavior[T]
val possiblyDeferredResult = msg match {
case signal: Signal ext.receiveSignal(ctx, signal)
case msg ext.receiveMessage(ctx, msg.asInstanceOf[T])
}
undefer(possiblyDeferredResult, ctx)
}
}

View file

@ -1,185 +0,0 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
import scala.concurrent.{ ExecutionContext, ExecutionContextExecutor }
import java.util.concurrent.{ Executor, Executors }
import scala.reflect.ClassTag
import scala.annotation.tailrec
/**
* Data structure for describing an actors deployment details like which
* executor to run it on. For each type of setting (e.g. [[DispatcherSelector]]
* or [[MailboxCapacity]]) the FIRST occurrence is used when creating the
* actor; this means that adding configuration using the "with" methods
* overrides what was configured previously.
*
* Deliberately not sealed in order to emphasize future extensibility by the
* frameworkthis is not intended to be extended by user code.
*
* The DeploymentConfig includes a `next` reference so that it can form an
* internally linked list. Traversal of this list stops when encountering the
* [[EmptyDeploymentConfig$]] object.
*/
abstract class DeploymentConfig extends Product with Serializable {
/**
* Reference to the tail of this DeploymentConfig list.
*/
def next: DeploymentConfig
/**
* Create a copy of this DeploymentConfig node with its `next` reference
* replaced by the given object. <b>This does NOT append the given list
* of configuration nodes to the current list!</b>
*/
def withNext(next: DeploymentConfig): DeploymentConfig
/**
* Prepend a selection of the [[ActorSystem]] default executor to this DeploymentConfig.
*/
def withDispatcherDefault: DeploymentConfig = DispatcherDefault(this)
/**
* Prepend a selection of the executor found at the given Config path to this DeploymentConfig.
* The path is relative to the configuration root of the [[ActorSystem]] that looks up the
* executor.
*/
def withDispatcherFromConfig(path: String): DeploymentConfig = DispatcherFromConfig(path, this)
/**
* Prepend a selection of the given executor to this DeploymentConfig.
*/
def withDispatcherFromExecutor(executor: Executor): DeploymentConfig = DispatcherFromExecutor(executor, this)
/**
* Prepend a selection of the given execution context to this DeploymentConfig.
*/
def withDispatcherFromExecutionContext(ec: ExecutionContext): DeploymentConfig = DispatcherFromExecutionContext(ec, this)
/**
* Prepend the given mailbox capacity configuration to this DeploymentConfig.
*/
def withMailboxCapacity(capacity: Int): DeploymentConfig = MailboxCapacity(capacity, this)
/**
* Find the first occurrence of a configuration node of the given type, falling
* back to the provided default if none is found.
*/
def firstOrElse[T <: DeploymentConfig: ClassTag](default: T): T = {
@tailrec def rec(d: DeploymentConfig): T = {
d match {
case EmptyDeploymentConfig default
case t: T t
case _ rec(d.next)
}
}
rec(this)
}
/**
* Retrieve all configuration nodes of a given type in the order that they
* are present in this DeploymentConfig. The `next` reference for all returned
* nodes will be the [[EmptyDeploymentConfig$]].
*/
def allOf[T <: DeploymentConfig: ClassTag]: List[DeploymentConfig] = {
@tailrec def select(d: DeploymentConfig, acc: List[DeploymentConfig]): List[DeploymentConfig] =
d match {
case EmptyDeploymentConfig acc.reverse
case t: T select(d.next, (d withNext EmptyDeploymentConfig) :: acc)
case _ select(d.next, acc)
}
select(this, Nil)
}
/**
* Remove all configuration nodes of a given type and return the resulting
* DeploymentConfig.
*/
def filterNot[T <: DeploymentConfig: ClassTag]: DeploymentConfig = {
@tailrec def select(d: DeploymentConfig, acc: List[DeploymentConfig]): List[DeploymentConfig] =
d match {
case EmptyDeploymentConfig acc
case t: T select(d.next, acc)
case _ select(d.next, d :: acc)
}
@tailrec def link(l: List[DeploymentConfig], acc: DeploymentConfig): DeploymentConfig =
l match {
case d :: ds link(ds, d withNext acc)
case Nil acc
}
link(select(this, Nil), EmptyDeploymentConfig)
}
}
/**
* Configure the maximum mailbox capacity for the actor. If more messages are
* enqueued because the actor does not process them quickly enough then further
* messages will be dropped.
*
* The default mailbox capacity that is used when this option is not given is
* taken from the `akka.typed.mailbox-capacity` configuration setting.
*/
final case class MailboxCapacity(capacity: Int, next: DeploymentConfig = EmptyDeploymentConfig) extends DeploymentConfig {
override def withNext(next: DeploymentConfig): DeploymentConfig = copy(next = next)
}
/**
* The empty configuration node, used as a terminator for the internally linked
* list of each DeploymentConfig.
*/
case object EmptyDeploymentConfig extends DeploymentConfig {
override def next = throw new NoSuchElementException("EmptyDeploymentConfig has no next")
override def withNext(next: DeploymentConfig): DeploymentConfig = next
}
/**
* Subclasses of this type describe which thread pool shall be used to run
* the actor to which this configuration is applied.
*
* The default configuration if none of these options are present is to run
* the actor on the same executor as its parent.
*/
sealed trait DispatcherSelector extends DeploymentConfig
/**
* Use the [[ActorSystem]] default executor to run the actor.
*/
sealed case class DispatcherDefault(next: DeploymentConfig) extends DispatcherSelector {
override def withNext(next: DeploymentConfig): DeploymentConfig = copy(next = next)
}
object DispatcherDefault {
// this is hidden in order to avoid having people match on this object
private val empty = DispatcherDefault(EmptyDeploymentConfig)
/**
* Retrieve an instance for this configuration node with empty `next` reference.
*/
def apply(): DispatcherDefault = empty
}
/**
* Look up an executor definition in the [[ActorSystem]] configuration.
* ExecutorServices created in this fashion will be shut down when the
* ActorSystem terminates.
*/
final case class DispatcherFromConfig(path: String, next: DeploymentConfig = EmptyDeploymentConfig) extends DispatcherSelector {
override def withNext(next: DeploymentConfig): DeploymentConfig = copy(next = next)
}
/**
* Directly use the given Executor whenever the actor needs to be run.
* No attempt will be made to shut down this thread pool, even if it is an
* instance of ExecutorService.
*/
final case class DispatcherFromExecutor(executor: Executor, next: DeploymentConfig = EmptyDeploymentConfig) extends DispatcherSelector {
override def withNext(next: DeploymentConfig): DeploymentConfig = copy(next = next)
}
/**
* Directly use the given ExecutionContext whenever the actor needs to be run.
* No attempt will be made to shut down this thread pool, even if it is an
* instance of ExecutorService.
*/
final case class DispatcherFromExecutionContext(ec: ExecutionContext, next: DeploymentConfig = EmptyDeploymentConfig) extends DispatcherSelector {
override def withNext(next: DeploymentConfig): DeploymentConfig = copy(next = next)
}

View file

@ -5,8 +5,6 @@ package akka.typed
import akka.{ event e }
import akka.event.Logging.{ LogEvent, LogLevel, StdOutLogger }
import akka.testkit.{ EventFilter, TestEvent TE }
import scala.annotation.tailrec
/**
* An EventStream allows local actors to register for certain message types, including
@ -66,42 +64,31 @@ object Logger {
}
class DefaultLogger extends Logger with StdOutLogger {
import ScalaDSL._
import Logger._
val initialBehavior =
ContextAware[Command] { ctx
Total {
case Initialize(eventStream, replyTo)
val log = ctx.spawn(Deferred[AnyRef] { ()
var filters: List[EventFilter] = Nil
val initialBehavior = {
// TODO avoid depending on dsl here?
import scaladsl.Actor._
deferred[Command] { _
immutable[Command] {
case (ctx, Initialize(eventStream, replyTo))
val log = ctx.spawn(deferred[AnyRef] { childCtx
def filter(event: LogEvent): Boolean = filters exists (f try { f(event) } catch { case e: Exception false })
def addFilter(filter: EventFilter): Unit = filters ::= filter
def removeFilter(filter: EventFilter) {
@tailrec def removeFirst(list: List[EventFilter], zipped: List[EventFilter] = Nil): List[EventFilter] = list match {
case head :: tail if head == filter tail.reverse_:::(zipped)
case head :: tail removeFirst(tail, head :: zipped)
case Nil filters // filter not found, just return original list
}
filters = removeFirst(filters)
}
Static {
case TE.Mute(filters) filters foreach addFilter
case TE.UnMute(filters) filters foreach removeFilter
case event: LogEvent if (!filter(event)) print(event)
immutable[AnyRef] {
case (_, event: LogEvent)
print(event)
same
case _ unhandled
}
}, "logger")
eventStream.subscribe(log, classOf[TE.Mute])
eventStream.subscribe(log, classOf[TE.UnMute])
ctx.watch(log) // sign death pact
replyTo ! log
Empty
empty
}
}
}
}
class DefaultLoggingFilter(settings: Settings, eventStream: EventStream) extends e.DefaultLoggingFilter(() eventStream.logLevel)

View file

@ -0,0 +1,145 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import akka.annotation.DoNotInherit
/**
* Marker trait/interface for extensions. An extension can be registered in the ActorSystem and is guaranteed to only
* have one instance per [[ActorSystem]] instance per [[ExtensionId]]. The extension internals must be thread safe.
* For mutable state it should be preferred to use an `Actor` rather than extensions as first choice.
*
* @see [[ExtensionId]]
*/
trait Extension
/**
* Identifier and factory for an extension. Is used to look up an extension from the `ActorSystem`, and possibly create
* an instance if no instance was already registered. The extension can also be listed in the actor system configuration
* to have it eagerly loaded and registered on actor system startup.
*
* *Scala API*
*
* The `ExtensionId` for an extension written in Scala is best done by letting it be the companion object of the
* extension. If the extension will be used from Java special care needs to be taken to provide a `get` method with the
* concrete extension type as return (as this will not be inferred correctly by the Java compiler with the default
* implementation)
*
* Example:
*
* {{{
* object MyExt extends ExtensionId[Ext] {
*
* override def createExtension(system: ActorSystem[_]): MyExt = new MyExt(system)
*
* // Java API: retrieve the extension instance for the given system.
* def get(system: ActorSystem[_]): MyExt = apply(system)
* }
*
* class MyExt(system: ActorSystem[_]) extends Extension {
* ...
* }
*
* // can be loaded eagerly on system startup through configuration
* // note that the name is the JVM/Java class name, with a dollar sign in the end
* // and not the Scala object name
* akka.typed.extensions = ["com.example.MyExt$"]
*
* // Allows access like this from Scala
* MyExt().someMethodOnTheExtension()
* // and from Java
* MyExt.get(system).someMethodOnTheExtension()
* }}}
*
* *Java API*
*
* To implement an extension in Java you should first create an `ExtensionId` singleton by implementing a static method
* called `getInstance`, this is needed to be able to list the extension among the `akka.typed.extensions` in the configuration
* and have it loaded when the actor system starts up.
*
* {{{
*
* public class MyExt extends AbstractExtensionId<MyExtImpl> {
* // single instance of the identifier
* private final static MyExt instance = new MyExt();
*
* // protect against other instances than the singleton
* private MyExt() {}
*
* // This static method singleton accessor is needed to be able to enable the extension through config when
* // implementing extensions in Java.
* public static MyExt getInstance() {
* return instance;
* }
*
* public MyExtImpl createExtension(ActorSystem<?> system) {
* return new MyExtImpl();
* }
*
* // convenience accessor
* public static MyExtImpl get(ActorSystem<?> system) {
* return instance.apply(system);
* }
* }
*
* public class MyExtImpl implements Extension {
* ...
* }
*
* // can be loaded eagerly on system startup through configuration
* akka.typed.extensions = ["com.example.MyExt"]
*
* // Allows access like this from Scala
* MyExt.someMethodOnTheExtension()
* // and from Java
* MyExt.get(system).someMethodOnTheExtension()
* }}}
*
* @tparam T The concrete extension type
*/
abstract class ExtensionId[T <: Extension] {
/**
* Create the extension, will be invoked at most one time per actor system where the extension is registered.
*/
def createExtension(system: ActorSystem[_]): T
/**
* Lookup or create an instance of the extension identified by this id.
*/
final def apply(system: ActorSystem[_]): T = system.registerExtension(this)
override final def hashCode: Int = System.identityHashCode(this)
override final def equals(other: Any): Boolean = this eq other.asInstanceOf[AnyRef]
}
/**
* API for registering and looking up extensions.
*
* Not intended to be extended by user code.
*/
@DoNotInherit
trait Extensions {
/**
* Registers the provided extension and creates its payload, if this extension isn't already registered
* This method has putIfAbsent-semantics, this method can potentially block, waiting for the initialization
* of the payload, if is in the process of registration from another Thread of execution
*/
def registerExtension[T <: Extension](ext: ExtensionId[T]): T
/**
* Returns the payload that is associated with the provided extension
* throws an IllegalStateException if it is not registered.
* This method can potentially block, waiting for the initialization
* of the payload, if is in the process of registration from another Thread of execution
*/
def extension[T <: Extension](ext: ExtensionId[T]): T
/**
* Returns whether the specified extension is already registered, this method can potentially block, waiting for the initialization
* of the payload, if is in the process of registration from another Thread of execution
*/
def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean
}

View file

@ -25,16 +25,7 @@ final case class DeadLetter(msg: Any)
* 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.
*/
sealed abstract class PreStart extends Signal
final case object PreStart extends PreStart {
def instance: PreStart = this
}
trait Signal
/**
* Lifecycle signal that is fired upon restart of the Actor before replacing
@ -51,10 +42,6 @@ final case object PreRestart extends PreRestart {
* 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).
*/
sealed abstract class PostStop extends Signal
final case object PostStop extends PostStop {
@ -68,8 +55,9 @@ final case object PostStop extends PostStop {
* 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).
* have occurred. This message is also sent when the watched actor is on a node
* that has been removed from the cluster when using akka-cluster or has been
* marked unreachable when using akka-remote directly.
*/
final case class Terminated(ref: ActorRef[Nothing])(failed: Throwable) extends Signal {
def wasFailed: Boolean = failed ne null

View file

@ -0,0 +1,270 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
import java.util.concurrent.Executor
import akka.annotation.{ ApiMayChange, DoNotInherit, InternalApi }
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext
import scala.reflect.ClassTag
object Props {
/**
* Empty props instance, should seldom be needed in user code but can be useful as a default props where
* custom configuration of an actor is possible.
*/
val empty: Props = EmptyProps
}
/**
* Data structure for describing an actors props details like which
* executor to run it on. For each type of setting (e.g. [[DispatcherSelector]]
* or [[MailboxCapacity]]) the FIRST occurrence is used when creating the
* actor; this means that adding configuration using the "with" methods
* overrides what was configured previously.
*
* Deliberately not sealed in order to emphasize future extensibility by the
* frameworkthis is not intended to be extended by user code.
*
*/
@DoNotInherit
@ApiMayChange
abstract class Props private[akka] () extends Product with Serializable {
/**
* Reference to the tail of this Props list.
*
* The `next` reference is here so that it can form an
* internally linked list. Traversal of this list stops when encountering the
* [[EmptyProps]] object.
*
* INTERNAL API
*/
@InternalApi
private[akka] def next: Props
/**
* Create a copy of this Props node with its `next` reference
* replaced by the given object. <b>This does NOT append the given list
* of configuration nodes to the current list!</b>
*
* INTERNAL API
*/
@InternalApi
private[akka] def withNext(next: Props): Props
/**
* Prepend a selection of the [[ActorSystem]] default executor to this Props.
*/
def withDispatcherDefault: Props = DispatcherDefault(this)
/**
* Prepend a selection of the executor found at the given Config path to this Props.
* The path is relative to the configuration root of the [[ActorSystem]] that looks up the
* executor.
*/
def withDispatcherFromConfig(path: String): Props = DispatcherFromConfig(path, this)
/**
* Prepend a selection of the given executor to this Props.
*/
def withDispatcherFromExecutor(executor: Executor): Props = DispatcherFromExecutor(executor, this)
/**
* Prepend a selection of the given execution context to this Props.
*/
def withDispatcherFromExecutionContext(ec: ExecutionContext): Props = DispatcherFromExecutionContext(ec, this)
/**
* Prepend the given mailbox capacity configuration to this Props.
*/
def withMailboxCapacity(capacity: Int): Props = MailboxCapacity(capacity, this)
/**
* Find the first occurrence of a configuration node of the given type, falling
* back to the provided default if none is found.
*
* INTERNAL API
*/
@InternalApi
private[akka] def firstOrElse[T <: Props: ClassTag](default: T): T = {
@tailrec def rec(d: Props): T = {
d match {
case EmptyProps default
case t: T t
case _ rec(d.next)
}
}
rec(this)
}
/**
* Retrieve all configuration nodes of a given type in the order that they
* are present in this Props. The `next` reference for all returned
* nodes will be the [[EmptyProps]].
*
* INTERNAL API
*/
@InternalApi
private[akka] def allOf[T <: Props: ClassTag]: List[Props] = {
@tailrec def select(d: Props, acc: List[Props]): List[Props] =
d match {
case EmptyProps acc.reverse
case t: T select(d.next, (d withNext EmptyProps) :: acc)
case _ select(d.next, acc)
}
select(this, Nil)
}
/**
* Remove all configuration nodes of a given type and return the resulting
* Props.
*/
@InternalApi
private[akka] def filterNot[T <: Props: ClassTag]: Props = {
@tailrec def select(d: Props, acc: List[Props]): List[Props] =
d match {
case EmptyProps acc
case t: T select(d.next, acc)
case _ select(d.next, d :: acc)
}
@tailrec def link(l: List[Props], acc: Props): Props =
l match {
case d :: ds link(ds, d withNext acc)
case Nil acc
}
link(select(this, Nil), EmptyProps)
}
}
/**
* Configure the maximum mailbox capacity for the actor. If more messages are
* enqueued because the actor does not process them quickly enough then further
* messages will be dropped.
*
* The default mailbox capacity that is used when this option is not given is
* taken from the `akka.typed.mailbox-capacity` configuration setting.
*/
@InternalApi
private[akka] final case class MailboxCapacity(capacity: Int, next: Props = Props.empty) extends Props {
private[akka] override def withNext(next: Props): Props = copy(next = next)
}
/**
* The empty configuration node, used as a terminator for the internally linked
* list of each Props.
*/
@InternalApi
private[akka] case object EmptyProps extends Props {
override def next = throw new NoSuchElementException("EmptyProps has no next")
override def withNext(next: Props): Props = next
}
/**
* Not intended for user extension.
*/
@DoNotInherit
abstract class DispatcherSelector extends Props
/**
* Factories for [[DispatcherSelector]]s which describe which thread pool shall be used to run
* the actor to which this configuration is applied. Se the individual factory methods for details
* on the options.
*
* The default configuration if none of these options are present is to run
* the actor on the same executor as its parent.
*/
object DispatcherSelector {
/**
* Scala API:
* Run the actor on the same executor as its parent.
*/
def default(): DispatcherSelector = DispatcherDefault()
/**
* Java API:
* Run the actor on the same executor as its parent.
*/
def defaultDispatcher(): DispatcherSelector = default()
/**
* Look up an executor definition in the [[ActorSystem]] configuration.
* ExecutorServices created in this fashion will be shut down when the
* ActorSystem terminates.
*/
def fromConfig(path: String): DispatcherSelector = DispatcherFromConfig(path)
/**
* Directly use the given Executor whenever the actor needs to be run.
* No attempt will be made to shut down this thread pool when the [[ActorSystem]] terminates.
*/
def fromExecutor(executor: Executor): DispatcherSelector = DispatcherFromExecutor(executor)
/**
* Directly use the given ExecutionContext whenever the actor needs to be run.
* No attempt will be made to shut down this thread pool when the [[ActorSystem]] terminates.
*/
def fromExecutionContext(executionContext: ExecutionContext): DispatcherSelector =
DispatcherFromExecutionContext(executionContext)
}
/**
* Use the [[ActorSystem]] default executor to run the actor.
*
* INTERNAL API
*/
@DoNotInherit
@InternalApi
private[akka] sealed case class DispatcherDefault(next: Props) extends DispatcherSelector {
@InternalApi
override def withNext(next: Props): Props = copy(next = next)
}
object DispatcherDefault {
// this is hidden in order to avoid having people match on this object
private val empty = DispatcherDefault(EmptyProps)
/**
* Retrieve an instance for this configuration node with empty `next` reference.
*/
def apply(): DispatcherDefault = empty
}
/**
* Look up an executor definition in the [[ActorSystem]] configuration.
* ExecutorServices created in this fashion will be shut down when the
* ActorSystem terminates.
*
* INTERNAL API
*/
@InternalApi
private[akka] final case class DispatcherFromConfig(path: String, next: Props = Props.empty) extends DispatcherSelector {
override def withNext(next: Props): Props = copy(next = next)
}
/**
* Directly use the given Executor whenever the actor needs to be run.
* No attempt will be made to shut down this thread pool, even if it is an
* instance of ExecutorService.
*
* INTERNAL API
*/
@InternalApi
private[akka] final case class DispatcherFromExecutor(executor: Executor, next: Props = Props.empty) extends DispatcherSelector {
override def withNext(next: Props): Props = copy(next = next)
}
/**
* Directly use the given ExecutionContext whenever the actor needs to be run.
* No attempt will be made to shut down this thread pool, even if it is an
* instance of ExecutorService.
*
* INTERNAL API
*/
@InternalApi
private[akka] final case class DispatcherFromExecutionContext(ec: ExecutionContext, next: Props = Props.empty) extends DispatcherSelector {
override def withNext(next: Props): Props = copy(next = next)
}

View file

@ -1,494 +0,0 @@
/**
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import scala.annotation.tailrec
import Behavior._
import akka.util.LineNumbers
/**
* This object holds several behavior factories and combinators that can be
* used to construct Behavior instances.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
object ScalaDSL {
// FIXME check that all behaviors can cope with not getting PreStart as first message
implicit class BehaviorDecorators[T](val behavior: Behavior[T]) extends AnyVal {
/**
* Widen the type of this Behavior by providing a filter function that permits
* only a subtype of the widened set of messages.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def widen[U >: T](matcher: PartialFunction[U, T]): Behavior[U] = Widened(behavior, matcher)
/**
* Combine the two behaviors such that incoming messages are distributed
* to both of them, each one evolving its state independently.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def &&(other: Behavior[T]): Behavior[T] = And(behavior, other)
/**
* Combine the two behaviors such that incoming messages are given first to
* the left behavior and are then only passed on to the right behavior if
* the left one returned Unhandled.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def ||(other: Behavior[T]): Behavior[T] = Or(behavior, other)
}
/**
* Widen the wrapped Behavior by placing a funnel in front of it: the supplied
* PartialFunction decides which message to pull in (those that it is defined
* at) and may transform the incoming message to place them into the wrapped
* Behaviors type hierarchy. Signals are not transformed.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Widened[T, U >: T](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends Behavior[U] {
private def postProcess(ctx: ActorContext[U], behv: Behavior[T]): Behavior[U] =
if (isUnhandled(behv)) Unhandled
else if (isAlive(behv)) {
val next = canonicalize(behv, behavior)
if (next eq behavior) Same else Widened(next, matcher)
} else Stopped
override def management(ctx: ActorContext[U], msg: Signal): Behavior[U] =
postProcess(ctx, behavior.management(ctx.asInstanceOf[ActorContext[T]], msg))
override def message(ctx: ActorContext[U], msg: U): Behavior[U] =
if (matcher.isDefinedAt(msg))
postProcess(ctx, behavior.message(ctx.asInstanceOf[ActorContext[T]], matcher(msg)))
else Unhandled
override def toString: String = s"${behavior.toString}.widen(${LineNumbers(matcher)})"
}
/**
* Wrap a behavior factory so that it runs upon PreStart, i.e. behavior creation
* is deferred to the child actor instead of running within the parent.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Deferred[T](factory: () Behavior[T]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
if (msg != PreStart) throw new IllegalStateException(s"Deferred must receive PreStart as first message (got $msg)")
Behavior.preStart(factory(), ctx)
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
throw new IllegalStateException(s"Deferred must receive PreStart as first message (got $msg)")
override def toString: String = s"Deferred(${LineNumbers(factory)})"
}
/**
* Return this behavior from message processing in order to advise the
* system to reuse the previous behavior. This is provided in order to
* avoid the allocation overhead of recreating the current behavior where
* that is not necessary.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def Same[T]: Behavior[T] = sameBehavior.asInstanceOf[Behavior[T]]
/**
* Return this behavior from message processing in order to advise the
* system to reuse the previous behavior, including the hint that the
* message has not been handled. This hint may be used by composite
* behaviors that delegate (partial) handling to other behaviors.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def Unhandled[T]: Behavior[T] = unhandledBehavior.asInstanceOf[Behavior[T]]
/*
* TODO write a Behavior that waits for all child actors to stop and then
* runs some cleanup before stopping. The factory for this behavior should
* stop and watch all children to get the process started.
*/
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure. The PostStop
* signal that results from stopping this actor will NOT be passed to the
* current behavior, it will be effectively ignored.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def Stopped[T]: Behavior[T] = stoppedBehavior.asInstanceOf[Behavior[T]]
/**
* This behavior does not handle any inputs, it is completely inert.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def Empty[T]: Behavior[T] = emptyBehavior.asInstanceOf[Behavior[T]]
/**
* This behavior does not handle any inputs, it is completely inert.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def Ignore[T]: Behavior[T] = ignoreBehavior.asInstanceOf[Behavior[T]]
/**
* Algebraic Data Type modeling either a [[Msg message]] or a
* [[Sig signal]], including the [[ActorContext]]. This type is
* used by several of the behaviors defined in this DSL, see for example
* [[Full]].
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
sealed trait MessageOrSignal[T]
/**
* A message bundled together with the current [[ActorContext]].
*/
@SerialVersionUID(1L)
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Msg[T](ctx: scaladsl.ActorContext[T], msg: T) extends MessageOrSignal[T]
/**
* A signal bundled together with the current [[ActorContext]].
*/
@SerialVersionUID(1L)
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Sig[T](ctx: scaladsl.ActorContext[T], signal: Signal) extends MessageOrSignal[T]
/**
* This type of behavior allows to handle all incoming messages within
* the same user-provided partial function, be that a user message or a system
* signal. For messages that do not match the partial function the same
* behavior is emitted without change. This does entail that unhandled
* failures of child actors will lead to a failure in this actor.
*
* For the lifecycle notifications pertaining to the actor itself this
* behavior includes a fallback mechanism: an unhandled [[PreRestart]] signal
* will terminate all child actors (transitively) and then emit a [[PostStop]]
* signal in addition.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Full[T](behavior: PartialFunction[MessageOrSignal[T], Behavior[T]]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
lazy val fallback: (MessageOrSignal[T]) Behavior[T] = {
case Sig(context, PreRestart)
context.children foreach { child
context.unwatch[Nothing](child)
context.stop(child)
}
behavior.applyOrElse(Sig(context, PostStop), fallback)
case _ Unhandled
}
behavior.applyOrElse(Sig(ctx, msg), fallback)
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
behavior.applyOrElse(Msg(ctx, msg), unhandledFunction)
}
override def toString = s"Full(${LineNumbers(behavior)})"
}
/**
* This type of behavior expects a total function that describes the actors
* reaction to all system signals or user messages, without providing a
* fallback mechanism for either. If you use partial function literal syntax
* to create the supplied function then any message not matching the list of
* cases will fail this actor with a [[scala.MatchError]].
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class FullTotal[T](behavior: MessageOrSignal[T] Behavior[T]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal) = behavior(Sig(ctx, msg))
override def message(ctx: ActorContext[T], msg: T) = behavior(Msg(ctx, msg))
override def toString = s"FullTotal(${LineNumbers(behavior)})"
}
/**
* This type of behavior is created from a total function from the declared
* message type to the next behavior, which means that all possible incoming
* messages for the given type must be handled. All system signals are
* ignored by this behavior, which implies that a failure of a child actor
* will be escalated unconditionally.
*
* This behavior type is most useful for leaf actors that do not create child
* actors themselves.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Total[T](behavior: T Behavior[T]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = msg match {
case _ Unhandled
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = behavior(msg)
override def toString = s"Total(${LineNumbers(behavior)})"
}
/**
* This type of Behavior is created from a partial function from the declared
* message type to the next behavior, flagging all unmatched messages as
* [[#Unhandled]]. All system signals are
* ignored by this behavior, which implies that a failure of a child actor
* will be escalated unconditionally.
*
* This behavior type is most useful for leaf actors that do not create child
* actors themselves.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Partial[T](behavior: PartialFunction[T, Behavior[T]]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = msg match {
case _ Unhandled
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = behavior.applyOrElse(msg, unhandledFunction)
override def toString = s"Partial(${LineNumbers(behavior)})"
}
/**
* This type of Behavior wraps another Behavior while allowing you to perform
* some action upon each received message or signal. It is most commonly used
* for logging or tracing what a certain Actor does.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Tap[T](f: PartialFunction[MessageOrSignal[T], Unit], behavior: Behavior[T]) extends Behavior[T] {
private def canonical(behv: Behavior[T]): Behavior[T] =
if (isUnhandled(behv)) Unhandled
else if (behv eq sameBehavior) Same
else if (isAlive(behv)) Tap(f, behv)
else Stopped
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
f.applyOrElse(Sig(ctx, msg), unitFunction)
canonical(behavior.management(ctx, msg))
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
f.applyOrElse(Msg(ctx, msg), unitFunction)
canonical(behavior.message(ctx, msg))
}
override def toString = s"Tap(${LineNumbers(f)},$behavior)"
}
object Tap {
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def monitor[T](monitor: ActorRef[T], behavior: Behavior[T]): Tap[T] = Tap({ case Msg(_, msg) monitor ! msg }, behavior)
}
/**
* This type of behavior is a variant of [[Total]] that does not
* allow the actor to change behavior. It is an efficient choice for stateless
* actors, possibly entering such a behavior after finishing its
* initialization (which may be modeled using any of the other behavior types).
*
* This behavior type is most useful for leaf actors that do not create child
* actors themselves.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Static[T](behavior: T Unit) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = Unhandled
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
behavior(msg)
this
}
override def toString = s"Static(${LineNumbers(behavior)})"
}
/**
* This behavior allows sending messages to itself without going through the
* Actors mailbox. A message sent like this will be processed before the next
* message is taken out of the mailbox. In case of Actor failures outstanding
* messages that were sent to the synchronous self reference will be lost.
*
* This decorator is useful for passing messages between the left and right
* sides of [[And]] and [[Or]] combinators.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class SynchronousSelf[T](f: ActorRef[T] Behavior[T]) extends Behavior[T] {
private class B extends Behavior[T] {
private val inbox = Inbox[T]("synchronousSelf")
private var _behavior = Behavior.validateAsInitial(f(inbox.ref))
private def behavior = _behavior
private def setBehavior(ctx: ActorContext[T], b: Behavior[T]): Unit =
_behavior = canonicalize(b, _behavior)
// FIXME should we protect against infinite loops?
@tailrec private def run(ctx: ActorContext[T], next: Behavior[T]): Behavior[T] = {
setBehavior(ctx, next)
if (inbox.hasMessages) run(ctx, behavior.message(ctx, inbox.receiveMsg()))
else if (isUnhandled(next)) Unhandled
else if (isAlive(next)) this
else Stopped
}
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] =
run(ctx, behavior.management(ctx, msg))
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
run(ctx, behavior.message(ctx, msg))
override def toString: String = s"SynchronousSelf($behavior)"
}
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
if (msg != PreStart) throw new IllegalStateException(s"SynchronousSelf must receive PreStart as first message (got $msg)")
Behavior.preStart(new B(), ctx)
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
throw new IllegalStateException(s"SynchronousSelf must receive PreStart as first message (got $msg)")
override def toString: String = s"SynchronousSelf(${LineNumbers(f)})"
}
/**
* A behavior combinator that feeds incoming messages and signals both into
* the left and right sub-behavior and allows them to evolve independently of
* each other. When one of the sub-behaviors terminates the other takes over
* exclusively.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class And[T](left: Behavior[T], right: Behavior[T]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] = {
val l = left.management(ctx, msg)
val r = right.management(ctx, msg)
if (isUnhandled(l) && isUnhandled(r)) Unhandled
else {
val nextLeft = canonicalize(l, left)
val nextRight = canonicalize(r, right)
val leftAlive = isAlive(nextLeft)
val rightAlive = isAlive(nextRight)
if (leftAlive && rightAlive) And(nextLeft, nextRight)
else if (leftAlive) nextLeft
else if (rightAlive) nextRight
else Stopped
}
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
val l = left.message(ctx, msg)
val r = right.message(ctx, msg)
if (isUnhandled(l) && isUnhandled(r)) Unhandled
else {
val nextLeft = canonicalize(l, left)
val nextRight = canonicalize(r, right)
val leftAlive = isAlive(nextLeft)
val rightAlive = isAlive(nextRight)
if (leftAlive && rightAlive) And(nextLeft, nextRight)
else if (leftAlive) nextLeft
else if (rightAlive) nextRight
else Stopped
}
}
}
/**
* A behavior combinator that feeds incoming messages and signals either into
* the left or right sub-behavior and allows them to evolve independently of
* each other. The message or signal is passed first into the left sub-behavior
* and only if that results in [[#Unhandled]] is it passed to the right
* sub-behavior. When one of the sub-behaviors terminates the other takes over
* exclusively.
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
final case class Or[T](left: Behavior[T], right: Behavior[T]) extends Behavior[T] {
override def management(ctx: ActorContext[T], msg: Signal): Behavior[T] =
left.management(ctx, msg) match {
case b if isUnhandled(b)
val r = right.management(ctx, msg)
if (isUnhandled(r)) Unhandled
else {
val nr = canonicalize(r, right)
if (isAlive(nr)) Or(left, nr) else left
}
case nl
val next = canonicalize(nl, left)
if (isAlive(next)) Or(next, right) else right
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
left.message(ctx, msg) match {
case b if isUnhandled(b)
val r = right.message(ctx, msg)
if (isUnhandled(r)) Unhandled
else {
val nr = canonicalize(r, right)
if (isAlive(nr)) Or(left, nr) else left
}
case nl
val next = canonicalize(nl, left)
if (isAlive(next)) Or(next, right) else right
}
}
// TODO
// final case class Selective[T](timeout: FiniteDuration, selector: PartialFunction[T, Behavior[T]], onTimeout: () Behavior[T])
/**
* A behavior decorator that extracts the self [[ActorRef]] while receiving the
* the first signal or message and uses that to construct the real behavior
* (which will then also receive that signal or message).
*
* Example:
* {{{
* SelfAware[MyCommand] { self =>
* Simple {
* case cmd =>
* }
* }
* }}}
*
* This can also be used together with implicitly sender-capturing message
* types:
* {{{
* final case class OtherMsg(msg: String)(implicit val replyTo: ActorRef[Reply])
*
* SelfAware[MyCommand] { implicit self =>
* Simple {
* case cmd =>
* other ! OtherMsg("hello") // assuming Reply <: MyCommand
* }
* }
* }}}
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def SelfAware[T](behavior: ActorRef[T] Behavior[T]): Behavior[T] =
new Behavior[T] {
override def management(ctx: ActorContext[T], sig: Signal): Behavior[T] =
if (sig == PreStart) Behavior.preStart(behavior(ctx.self), ctx)
else throw new IllegalStateException(s"SelfAware must receive PreStart as first message (got $sig)")
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
throw new IllegalStateException(s"SelfAware must receive PreStart as first message (got $msg)")
}
/**
* A behavior decorator that extracts the [[ActorContext]] while receiving the
* the first signal or message and uses that to construct the real behavior
* (which will then also receive that signal or message).
*
* Example:
* {{{
* ContextAware[MyCommand] { ctx => Simple {
* case cmd =>
* ...
* }
* }
* }}}
*/
@deprecated("use akka.typed.scaladsl.Actor", "2.5.0")
def ContextAware[T](behavior: scaladsl.ActorContext[T] Behavior[T]): Behavior[T] =
new Behavior[T] {
override def management(ctx: ActorContext[T], sig: Signal): Behavior[T] =
if (sig == PreStart) Behavior.preStart(behavior(ctx), ctx)
else throw new IllegalStateException(s"ContextAware must receive PreStart as first message (got $sig)")
override def message(ctx: ActorContext[T], msg: T): Behavior[T] =
throw new IllegalStateException(s"ContextAware must receive PreStart as first message (got ${msg.getClass})")
}
/**
* INTERNAL API.
*/
private[akka] val _unhandledFunction = (_: Any) Unhandled[Nothing]
/**
* INTERNAL API.
*/
private[akka] def unhandledFunction[T, U] = _unhandledFunction.asInstanceOf[(T Behavior[U])]
/**
* INTERNAL API.
*/
private[akka] val _unitFunction = (_: Any) ()
/**
* INTERNAL API.
*/
private[akka] def unitFunction[T, U] = _unhandledFunction.asInstanceOf[(T Behavior[U])]
}

View file

@ -0,0 +1,119 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
import akka.annotation.InternalApi
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration.Duration
object SupervisorStrategy {
/**
* Resume means keeping the same state as before the exception was
* thrown and is thus less safe than `restart`.
*/
val resume: SupervisorStrategy = Resume(loggingEnabled = true)
/**
* Restart immediately without any limit on number of restart retries.
*/
val restart: SupervisorStrategy = Restart(-1, Duration.Zero, loggingEnabled = true)
/**
* Restart with a limit of number of restart retries.
* The number of restarts are limited to a number of restart attempts (`maxNrOfRetries`)
* within a time range (`withinTimeRange`). When the time window has elapsed without reaching
* `maxNrOfRetries` the restart count is reset.
*
* @param maxNrOfRetries the number of times a child actor is allowed to be restarted,
* if the limit is exceeded the child actor is stopped
* @param withinTimeRange duration of the time window for maxNrOfRetries
*/
def restartWithLimit(maxNrOfRetries: Int, withinTimeRange: FiniteDuration): SupervisorStrategy =
Restart(maxNrOfRetries, withinTimeRange, loggingEnabled = true)
/**
* It supports exponential back-off between the given `minBackoff` and
* `maxBackoff` durations. For example, if `minBackoff` is 3 seconds and
* `maxBackoff` 30 seconds the start attempts will be delayed with
* 3, 6, 12, 24, 30, 30 seconds. The exponential back-off counter is reset
* if the actor is not terminated within the `minBackoff` duration.
*
* In addition to the calculated exponential back-off an additional
* random delay based the given `randomFactor` is added, e.g. 0.2 adds up to 20%
* delay. The reason for adding a random delay is to avoid that all failing
* actors hit the backend resource at the same time.
*
* During the back-off incoming messages are dropped.
*
* If no new exception occurs within the `minBackoff` duration the exponentially
* increased back-off timeout is reset.
*
* @param minBackoff minimum (initial) duration until the child actor will
* started again, if it is terminated
* @param maxBackoff the exponential back-off is capped to this duration
* @param randomFactor after calculation of the exponential back-off an additional
* random delay based on this factor is added, e.g. `0.2` adds up to `20%` delay.
* In order to skip this additional delay pass in `0`.
*/
def restartWithBackoff(
minBackoff: FiniteDuration,
maxBackoff: FiniteDuration,
randomFactor: Double): BackoffSupervisorStrategy =
new Backoff(minBackoff, maxBackoff, randomFactor, resetBackoffAfter = minBackoff, loggingEnabled = true)
/**
* INTERNAL API
*/
@InternalApi private[akka] case class Resume(loggingEnabled: Boolean) extends SupervisorStrategy {
override def withLoggingEnabled(enabled: Boolean): SupervisorStrategy =
copy(loggingEnabled = enabled)
}
/**
* INTERNAL API
*/
@InternalApi private[akka] final case class Restart(
maxNrOfRetries: Int,
withinTimeRange: FiniteDuration,
loggingEnabled: Boolean) extends SupervisorStrategy {
override def withLoggingEnabled(enabled: Boolean): SupervisorStrategy =
copy(loggingEnabled = enabled)
}
/**
* INTERNAL API
*/
@InternalApi private[akka] final case class Backoff(
minBackoff: FiniteDuration,
maxBackoff: FiniteDuration,
randomFactor: Double,
resetBackoffAfter: FiniteDuration,
loggingEnabled: Boolean) extends BackoffSupervisorStrategy {
override def withLoggingEnabled(enabled: Boolean): SupervisorStrategy =
copy(loggingEnabled = enabled)
override def withResetBackoffAfter(timeout: FiniteDuration): BackoffSupervisorStrategy =
copy(resetBackoffAfter = timeout)
}
}
sealed abstract class SupervisorStrategy {
def loggingEnabled: Boolean
def withLoggingEnabled(on: Boolean): SupervisorStrategy
}
sealed abstract class BackoffSupervisorStrategy extends SupervisorStrategy {
def resetBackoffAfter: FiniteDuration
/**
* The back-off algorithm is reset if the actor does not crash within the
* specified `resetBackoffAfter`. By default, the `resetBackoffAfter` has
* the same value as `minBackoff`.
*/
def withResetBackoffAfter(timeout: FiniteDuration): BackoffSupervisorStrategy
}

View file

@ -1,61 +0,0 @@
/**
* Copyright (C) 2016-2017 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] = _initialBehavior
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 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, PreStart), PreStart)
override def postStop(): Unit =
next(behavior.management(ctx, PostStop), PostStop)
}

View file

@ -1,61 +0,0 @@
/**
* Copyright (C) 2016-2017 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 val system = ActorSystemAdapter(ctx.system)
override def mailboxCapacity = 1 << 29 // FIXME
override def children = ctx.children.map(ActorRefAdapter(_))
override def child(name: String) = ctx.child(name).map(ActorRefAdapter(_))
override def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig) =
ctx.spawnAnonymous(behavior, deployment)
override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig) =
ctx.spawn(behavior, name, deployment)
override def stop(child: ActorRef[_]) =
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, name: String = ""): ActorRef[U] = {
val cell = ctx.asInstanceOf[akka.actor.ActorCell]
val ref = cell.addFunctionRef((_, msg) ctx.self ! f(msg.asInstanceOf[U]), name)
ActorRefAdapter[U](ref)
}
}

View file

@ -1,19 +0,0 @@
/**
* Copyright (C) 2016-2017 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

@ -1,24 +0,0 @@
/**
* Copyright (C) 2016-2017 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(b: Behavior[_], deploy: DeploymentConfig): a.Props = new a.Props(a.Deploy(), classOf[ActorAdapter[_]], (b: AnyRef) :: Nil)
def apply[T](p: a.Props): Behavior[T] = {
assert(p.clazz == classOf[ActorAdapter[_]], "typed.Actor must have typed.Props")
p.args match {
case (initial: Behavior[_]) :: Nil
// FIXME queue size
initial.asInstanceOf[Behavior[T]]
case _ throw new AssertionError("typed.Actor args must be right")
}
}
}

View file

@ -1,43 +0,0 @@
/**
* Copyright (C) 2016-2017 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](behavior: Behavior[T], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] =
ActorRefAdapter(sys.actorOf(PropsAdapter(Behavior.validateAsInitial(behavior), deployment)))
def spawn[T](behavior: Behavior[T], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] =
ActorRefAdapter(sys.actorOf(PropsAdapter(Behavior.validateAsInitial(behavior), deployment), name))
}
implicit class ActorContextOps(val ctx: akka.actor.ActorContext) extends AnyVal {
def spawnAnonymous[T](behavior: Behavior[T], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] =
ActorRefAdapter(ctx.actorOf(PropsAdapter(Behavior.validateAsInitial(behavior), deployment)))
def spawn[T](behavior: Behavior[T], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] =
ActorRefAdapter(ctx.actorOf(PropsAdapter(Behavior.validateAsInitial(behavior), deployment), 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

@ -6,18 +6,20 @@ package internal
import akka.actor.InvalidActorNameException
import akka.util.Helpers
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.concurrent.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.annotation.tailrec
import scala.util.control.NonFatal
import scala.util.control.Exception.Catcher
import akka.event.Logging.Error
import akka.event.Logging
import akka.typed.Behavior.StoppedBehavior
import akka.util.OptionVal
/**
* INTERNAL API
@ -58,6 +60,7 @@ object ActorCell {
final val SuspendedState = 1
final val SuspendedWaitForChildrenState = 2
/** compile time constant */
final val Debug = false
}
@ -70,7 +73,7 @@ private[typed] class ActorCell[T](
override val executionContext: ExecutionContextExecutor,
override val mailboxCapacity: Int,
val parent: ActorRefImpl[Nothing])
extends ActorContext[T] with Runnable with SupervisionMechanics[T] with DeathWatch[T] {
extends ActorContextImpl[T] with Runnable with SupervisionMechanics[T] with DeathWatch[T] {
import ActorCell._
/*
@ -100,11 +103,11 @@ private[typed] class ActorCell[T](
protected def ctx: ActorContext[T] = this
override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig): ActorRef[U] = {
override def spawn[U](behavior: Behavior[U], name: String, props: Props): ActorRef[U] = {
if (childrenMap contains name) throw InvalidActorNameException(s"actor name [$name] is not unique")
if (terminatingMap contains name) throw InvalidActorNameException(s"actor name [$name] is not yet free")
val dispatcher = deployment.firstOrElse[DispatcherSelector](DispatcherFromExecutionContext(executionContext))
val capacity = deployment.firstOrElse(MailboxCapacity(system.settings.DefaultMailboxCapacity))
val dispatcher = props.firstOrElse[DispatcherSelector](DispatcherFromExecutionContext(executionContext))
val capacity = props.firstOrElse(MailboxCapacity(system.settings.DefaultMailboxCapacity))
val cell = new ActorCell[U](system, Behavior.validateAsInitial(behavior), system.dispatchers.lookup(dispatcher), capacity.capacity, self)
// TODO uid is still needed
val ref = new LocalActorRef[U](self.path / name, cell)
@ -115,13 +118,13 @@ private[typed] class ActorCell[T](
}
private var nextName = 0L
override def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig): ActorRef[U] = {
override def spawnAnonymous[U](behavior: Behavior[U], props: Props): ActorRef[U] = {
val name = Helpers.base64(nextName)
nextName += 1
spawn(behavior, name, deployment)
spawn(behavior, name, props)
}
override def stop(child: ActorRef[_]): Boolean = {
override def stop[U](child: ActorRef[U]): Boolean = {
val name = child.path.name
childrenMap.get(name) match {
case None false
@ -145,7 +148,7 @@ private[typed] class ActorCell[T](
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): Cancellable =
system.scheduler.scheduleOnce(delay)(target ! msg)(ExecutionContexts.sameThreadExecutionContext)
override def spawnAdapter[U](f: U T, _name: String = ""): ActorRef[U] = {
override private[akka] def internalSpawnAdapter[U](f: U T, _name: String): ActorRef[U] = {
val baseName = Helpers.base64(nextName, new java.lang.StringBuilder("$!"))
nextName += 1
val name = if (_name != "") s"$baseName-${_name}" else baseName
@ -323,8 +326,25 @@ private[typed] class ActorCell[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())
else {
b match {
case s: StoppedBehavior[T]
// use StoppedBehavior with previous behavior or an explicitly given `postStop` behavior
// until Terminate is received, i.e until finishTerminate is invoked, and there PostStop
// will be signaled to the previous/postStop behavior
s.postStop match {
case OptionVal.None
// use previous as the postStop behavior
behavior = new Behavior.StoppedBehavior(OptionVal.Some(behavior))
case OptionVal.Some(postStop)
// use the given postStop behavior, but canonicalize it
behavior = new Behavior.StoppedBehavior(OptionVal.Some(Behavior.canonicalize(postStop, behavior, ctx)))
}
self.sendSystem(Terminate())
case _
behavior = Behavior.canonicalize(b, behavior, ctx)
}
}
}
private def unhandled(msg: Any): Unit = msg match {
@ -351,7 +371,7 @@ private[typed] class ActorCell[T](
*/
private def processMessage(msg: T): Unit = {
if (Debug) println(s"[$thread] $self processing message $msg")
next(behavior.message(this, msg), msg)
next(Behavior.interpretMessage(behavior, this, msg), msg)
if (Thread.interrupted())
throw new InterruptedException("Interrupted while processing actor messages")
}

View file

@ -0,0 +1,71 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
package internal
import akka.annotation.InternalApi
import java.util.Optional
import java.util.ArrayList
import scala.concurrent.ExecutionContextExecutor
/**
* INTERNAL API
*/
@InternalApi private[akka] trait ActorContextImpl[T] extends ActorContext[T] with javadsl.ActorContext[T] with scaladsl.ActorContext[T] {
override def asJava: javadsl.ActorContext[T] = this
override def asScala: scaladsl.ActorContext[T] = this
override def getChild(name: String): Optional[ActorRef[Void]] =
child(name) match {
case Some(c) Optional.of(c.upcast[Void])
case None Optional.empty()
}
override def getChildren: java.util.List[akka.typed.ActorRef[Void]] = {
val c = children
val a = new ArrayList[ActorRef[Void]](c.size)
val i = c.iterator
while (i.hasNext) a.add(i.next().upcast[Void])
a
}
override def getExecutionContext: ExecutionContextExecutor =
executionContext
override def getMailboxCapacity: Int =
mailboxCapacity
override def getSelf: akka.typed.ActorRef[T] =
self
override def getSystem: akka.typed.ActorSystem[Void] =
system.asInstanceOf[ActorSystem[Void]]
override def spawn[U](behavior: akka.typed.Behavior[U], name: String): akka.typed.ActorRef[U] =
spawn(behavior, name, Props.empty)
override def spawnAnonymous[U](behavior: akka.typed.Behavior[U]): akka.typed.ActorRef[U] =
spawnAnonymous(behavior, Props.empty)
override def spawnAdapter[U](f: U T, name: String): ActorRef[U] =
internalSpawnAdapter(f, name)
override def spawnAdapter[U](f: U T): ActorRef[U] =
internalSpawnAdapter(f, "")
override def spawnAdapter[U](f: java.util.function.Function[U, T]): akka.typed.ActorRef[U] =
internalSpawnAdapter(f.apply _, "")
override def spawnAdapter[U](f: java.util.function.Function[U, T], name: String): akka.typed.ActorRef[U] =
internalSpawnAdapter(f.apply _, name)
/**
* INTERNAL API: Needed to make Scala 2.12 compiler happy.
* Otherwise "ambiguous reference to overloaded definition" because Function is lambda.
*/
@InternalApi private[akka] def internalSpawnAdapter[U](f: U T, _name: String): ActorRef[U]
}

View file

@ -12,6 +12,7 @@ import scala.util.control.NonFatal
import scala.concurrent.Future
import java.util.ArrayList
import scala.util.{ Success, Failure }
import scala.annotation.unchecked.uncheckedVariance
/**
* Every ActorRef is also an ActorRefImpl, but these two methods shall be
@ -22,13 +23,38 @@ import scala.util.{ Success, Failure }
private[typed] trait ActorRefImpl[-T] extends ActorRef[T] {
def sendSystem(signal: SystemMessage): Unit
def isLocal: Boolean
final override def narrow[U <: T]: ActorRef[U] = this.asInstanceOf[ActorRef[U]]
final override def upcast[U >: T @uncheckedVariance]: ActorRef[U] = this.asInstanceOf[ActorRef[U]]
/**
* Comparison takes path and the unique id of the actor cell into account.
*/
final override def compareTo(other: ActorRef[_]) = {
val x = this.path compareTo other.path
if (x == 0) if (this.path.uid < other.path.uid) -1 else if (this.path.uid == other.path.uid) 0 else 1
else x
}
final override def hashCode: Int = path.uid
/**
* Equals takes path and the unique id of the actor cell into account.
*/
final override def equals(that: Any): Boolean = that match {
case other: ActorRef[_] path.uid == other.path.uid && path == other.path
case _ false
}
override def toString: String = s"Actor[${path}#${path.uid}]"
}
/**
* 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] {
private[typed] class LocalActorRef[-T](override val path: a.ActorPath, cell: ActorCell[T])
extends ActorRef[T] 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
@ -41,7 +67,8 @@ private[typed] class LocalActorRef[-T](_path: a.ActorPath, cell: ActorCell[T])
* terminates (meaning: no Hawking radiation).
*/
private[typed] object BlackholeActorRef
extends ActorRef[Any](a.RootActorPath(a.Address("akka.typed.internal", "blackhole"))) with ActorRefImpl[Any] {
extends ActorRef[Any] with ActorRefImpl[Any] {
override val path: a.ActorPath = a.RootActorPath(a.Address("akka.typed.internal", "blackhole"))
override def tell(msg: Any): Unit = ()
override def sendSystem(signal: SystemMessage): Unit = ()
final override def isLocal: Boolean = true
@ -82,7 +109,7 @@ private[typed] final class FunctionRef[-T](
/**
* 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] {
private[typed] abstract class WatchableRef[-T](override val path: a.ActorPath) extends ActorRef[T] with ActorRefImpl[T] {
import WatchableRef._
/**

View file

@ -24,21 +24,23 @@ import java.util.concurrent.atomic.AtomicInteger
import akka.typed.scaladsl.AskPattern
object ActorSystemImpl {
import ScalaDSL._
sealed trait SystemCommand
case class CreateSystemActor[T](behavior: Behavior[T], name: String, deployment: DeploymentConfig)(val replyTo: ActorRef[ActorRef[T]]) extends SystemCommand
case class CreateSystemActor[T](behavior: Behavior[T], name: String, props: Props)(val replyTo: ActorRef[ActorRef[T]]) extends SystemCommand
val systemGuardianBehavior: Behavior[SystemCommand] =
ContextAware { ctx
val systemGuardianBehavior: Behavior[SystemCommand] = {
import scaladsl.Actor
Actor.deferred { _
var i = 1
Static {
case create: CreateSystemActor[t]
Actor.immutable {
case (ctx, create: CreateSystemActor[t])
val name = s"$i-${create.name}"
i += 1
create.replyTo ! ctx.spawn(create.behavior, name, create.deployment)
create.replyTo ! ctx.spawn(create.behavior, name, create.props)
Actor.same
}
}
}
}
/*
@ -68,13 +70,13 @@ Distributed Data:
*/
private[typed] class ActorSystemImpl[-T](
override val name: String,
_config: Config,
_cl: ClassLoader,
_ec: Option[ExecutionContext],
_userGuardianBehavior: Behavior[T],
_userGuardianDeployment: DeploymentConfig)
extends ActorRef[T](a.RootActorPath(a.Address("akka", name)) / "user") with ActorSystem[T] with ActorRefImpl[T] {
override val name: String,
_config: Config,
_cl: ClassLoader,
_ec: Option[ExecutionContext],
_userGuardianBehavior: Behavior[T],
_userGuardianProps: Props)
extends ActorSystem[T] with ActorRef[T] with ActorRefImpl[T] with ExtensionsImpl {
import ActorSystemImpl._
@ -83,6 +85,8 @@ private[typed] class ActorSystemImpl[-T](
"invalid ActorSystem name [" + name +
"], must contain only word characters (i.e. [a-zA-Z0-9] plus non-leading '-' or '_')")
final override val path: a.ActorPath = a.RootActorPath(a.Address("akka", name)) / "user"
override val settings: Settings = new Settings(_cl, _config, name)
override def logConfiguration(): Unit = log.info(settings.toString)
@ -160,7 +164,7 @@ private[typed] class ActorSystemImpl[-T](
*/
private object eventStreamStub extends e.EventStream(null, false) {
override def subscribe(ref: a.ActorRef, ch: Class[_]): Boolean =
throw new UnsupportedOperationException("cannot use this eventstream for subscribing")
throw new UnsupportedOperationException("Cannot use this eventstream for subscribing")
override def publish(event: AnyRef): Unit = eventStream.publish(event)
}
/**
@ -182,13 +186,15 @@ private[typed] class ActorSystemImpl[-T](
private val terminationPromise: Promise[Terminated] = Promise()
private val rootPath: a.ActorPath = a.RootActorPath(a.Address("typed", name))
private val rootPath: a.ActorPath = a.RootActorPath(a.Address("akka", 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")
new ActorRef[Nothing] with ActorRefImpl[Nothing] {
override def path: a.ActorPath = rootPath
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))
@ -208,9 +214,9 @@ private[typed] class ActorSystemImpl[-T](
override def isLocal: Boolean = true
}
private def createTopLevel[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig): ActorRefImpl[U] = {
val dispatcher = deployment.firstOrElse[DispatcherSelector](DispatcherFromExecutionContext(executionContext))
val capacity = deployment.firstOrElse(MailboxCapacity(settings.DefaultMailboxCapacity))
private def createTopLevel[U](behavior: Behavior[U], name: String, props: Props): ActorRefImpl[U] = {
val dispatcher = props.firstOrElse[DispatcherSelector](DispatcherFromExecutionContext(executionContext))
val capacity = props.firstOrElse(MailboxCapacity(settings.DefaultMailboxCapacity))
val cell = new ActorCell(this, behavior, dispatchers.lookup(dispatcher), capacity.capacity, theOneWhoWalksTheBubblesOfSpaceTime)
val ref = new LocalActorRef(rootPath / name, cell)
cell.setSelf(ref)
@ -219,17 +225,19 @@ private[typed] class ActorSystemImpl[-T](
ref
}
private val systemGuardian: ActorRefImpl[SystemCommand] = createTopLevel(systemGuardianBehavior, "system", EmptyDeploymentConfig)
private val systemGuardian: ActorRefImpl[SystemCommand] = createTopLevel(systemGuardianBehavior, "system", EmptyProps)
override val receptionist: ActorRef[patterns.Receptionist.Command] =
ActorRef(systemActorOf(patterns.Receptionist.behavior, "receptionist")(settings.untyped.CreationTimeout))
private val userGuardian: ActorRefImpl[T] = createTopLevel(_userGuardianBehavior, "user", _userGuardianDeployment)
private val userGuardian: ActorRefImpl[T] = createTopLevel(_userGuardianBehavior, "user", _userGuardianProps)
// now we can start up the loggers
eventStream.startUnsubscriber(this)
eventStream.startDefaultLoggers(this)
loadExtensions()
override def terminate(): Future[Terminated] = {
theOneWhoWalksTheBubblesOfSpaceTime.sendSystem(Terminate())
terminationPromise.future
@ -237,7 +245,8 @@ private[typed] class ActorSystemImpl[-T](
override def whenTerminated: Future[Terminated] = terminationPromise.future
override def deadLetters[U]: ActorRefImpl[U] =
new ActorRef[U](rootPath) with ActorRefImpl[U] {
new ActorRef[U] with ActorRefImpl[U] {
override def path: a.ActorPath = rootPath
override def tell(msg: U): Unit = eventStream.publish(DeadLetter(msg))
override def sendSystem(signal: SystemMessage): Unit = {
signal match {
@ -253,10 +262,10 @@ private[typed] class ActorSystemImpl[-T](
override def sendSystem(msg: SystemMessage): Unit = userGuardian.sendSystem(msg)
override def isLocal: Boolean = true
def systemActorOf[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig)(implicit timeout: Timeout): Future[ActorRef[U]] = {
def systemActorOf[U](behavior: Behavior[U], name: String, props: Props)(implicit timeout: Timeout): Future[ActorRef[U]] = {
import AskPattern._
implicit val sched = scheduler
systemGuardian ? CreateSystemActor(behavior, name, deployment)
systemGuardian ? CreateSystemActor(behavior, name, props)
}
def printTree: String = {

View file

@ -0,0 +1,179 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
package internal
import akka.util.LineNumbers
import akka.annotation.InternalApi
import akka.typed.{ ActorContext AC }
import akka.typed.scaladsl.{ ActorContext SAC }
import akka.typed.scaladsl.Actor
import scala.reflect.ClassTag
import scala.annotation.tailrec
/**
* INTERNAL API
*/
@InternalApi private[akka] object BehaviorImpl {
import Behavior._
private val _nullFun = (_: Any) null
private def nullFun[T] = _nullFun.asInstanceOf[Any T]
implicit class ContextAs[T](val ctx: AC[T]) extends AnyVal {
def as[U] = ctx.asInstanceOf[AC[U]]
}
def widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]): Behavior[U] = {
behavior match {
case d: DeferredBehavior[T]
DeferredBehavior[U] { ctx
val c = ctx.asInstanceOf[akka.typed.ActorContext[T]]
val b = Behavior.validateAsInitial(Behavior.undefer(d, c))
Widened(b, matcher)
}
case _
Widened(behavior, matcher)
}
}
private final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends ExtensibleBehavior[U] {
@tailrec
private def canonical(b: Behavior[T], ctx: AC[T]): Behavior[U] = {
if (isUnhandled(b)) unhandled
else if ((b eq SameBehavior) || (b eq this)) same
else if (!Behavior.isAlive(b)) Behavior.stopped
else {
b match {
case d: DeferredBehavior[T] canonical(Behavior.undefer(d, ctx), ctx)
case _ Widened(b, matcher)
}
}
}
override def receiveSignal(ctx: AC[U], signal: Signal): Behavior[U] =
canonical(Behavior.interpretSignal(behavior, ctx.as[T], signal), ctx.as[T])
override def receiveMessage(ctx: AC[U], msg: U): Behavior[U] =
matcher.applyOrElse(msg, nullFun) match {
case null unhandled
case transformed canonical(Behavior.interpretMessage(behavior, ctx.as[T], transformed), ctx.as[T])
}
override def toString: String = s"${behavior.toString}.widen(${LineNumbers(matcher)})"
}
class ImmutableBehavior[T](
val onMessage: (SAC[T], T) Behavior[T],
onSignal: PartialFunction[(SAC[T], Signal), Behavior[T]] = Behavior.unhandledSignal.asInstanceOf[PartialFunction[(SAC[T], Signal), Behavior[T]]])
extends ExtensibleBehavior[T] {
override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] =
onSignal.applyOrElse((ctx.asScala, msg), Behavior.unhandledSignal.asInstanceOf[PartialFunction[(SAC[T], Signal), Behavior[T]]])
override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx.asScala, msg)
override def toString = s"Immutable(${LineNumbers(onMessage)})"
}
def tap[T](
onMessage: Function2[SAC[T], T, _],
onSignal: Function2[SAC[T], Signal, _],
behavior: Behavior[T]): Behavior[T] = {
intercept[T, T](
beforeMessage = (ctx, msg) {
onMessage(ctx, msg)
msg
},
beforeSignal = (ctx, sig) {
onSignal(ctx, sig)
true
},
afterMessage = (ctx, msg, b) b, // TODO optimize by using more ConstantFun
afterSignal = (ctx, sig, b) b,
behavior)(ClassTag(classOf[Any]))
}
/**
* Intercept another `behavior` by invoking `beforeMessage` for
* messages of type `U`. That can be another type than the type of
* the behavior. `beforeMessage` may transform the incoming message,
* or discard it by returning `null`. Note that `beforeMessage` is
* only invoked for messages of type `U`.
*
* Signals can also be intercepted but not transformed. They can
* be discarded by returning `false` from the `beforeOnSignal` function.
*
* The returned behavior from processing messages and signals can also be
* intercepted, e.g. to return another `Behavior`. The passed message to
* `afterMessage` is the message returned from `beforeMessage` (possibly
* different than the incoming message).
*/
def intercept[T, U <: Any: ClassTag](
beforeMessage: Function2[SAC[U], U, T],
beforeSignal: Function2[SAC[T], Signal, Boolean],
afterMessage: Function3[SAC[T], T, Behavior[T], Behavior[T]],
afterSignal: Function3[SAC[T], Signal, Behavior[T], Behavior[T]],
behavior: Behavior[T],
toStringPrefix: String = "Intercept"): Behavior[T] = {
behavior match {
case d: DeferredBehavior[T]
DeferredBehavior[T] { ctx
val c = ctx.asInstanceOf[akka.typed.ActorContext[T]]
val b = Behavior.validateAsInitial(Behavior.undefer(d, c))
Intercept(beforeMessage, beforeSignal, afterMessage, afterSignal, b, toStringPrefix)
}
case _
Intercept(beforeMessage, beforeSignal, afterMessage, afterSignal, behavior, toStringPrefix)
}
}
private final case class Intercept[T, U <: Any: ClassTag](
beforeOnMessage: Function2[SAC[U], U, T],
beforeOnSignal: Function2[SAC[T], Signal, Boolean],
afterMessage: Function3[SAC[T], T, Behavior[T], Behavior[T]],
afterSignal: Function3[SAC[T], Signal, Behavior[T], Behavior[T]],
behavior: Behavior[T],
toStringPrefix: String = "Intercept") extends ExtensibleBehavior[T] {
@tailrec
private def canonical(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] = {
if (isUnhandled(b)) unhandled
else if ((b eq SameBehavior) || (b eq this)) same
else if (!Behavior.isAlive(b)) b
else {
b match {
case d: DeferredBehavior[T] canonical(Behavior.undefer(d, ctx), ctx)
case _ Intercept(beforeOnMessage, beforeOnSignal, afterMessage, afterSignal, b)
}
}
}
override def receiveSignal(ctx: AC[T], signal: Signal): Behavior[T] = {
val next: Behavior[T] =
if (beforeOnSignal(ctx.asScala, signal))
Behavior.interpretSignal(behavior, ctx, signal)
else
same
canonical(afterSignal(ctx.asScala, signal, next), ctx)
}
override def receiveMessage(ctx: AC[T], msg: T): Behavior[T] = {
msg match {
case m: U
val msg2 = beforeOnMessage(ctx.asScala.asInstanceOf[SAC[U]], m)
val next: Behavior[T] =
if (msg2 == null)
same
else
Behavior.interpretMessage(behavior, ctx, msg2)
canonical(afterMessage(ctx.asScala, msg2, next), ctx)
case _
val next: Behavior[T] = Behavior.interpretMessage(behavior, ctx, msg)
canonical(afterMessage(ctx.asScala, msg, next), ctx)
}
}
override def toString = s"$toStringPrefix(${LineNumbers(beforeOnMessage)},${LineNumbers(beforeOnSignal)},$behavior)"
}
}

View file

@ -41,21 +41,34 @@ private[typed] trait DeathWatch[T] {
type ARImpl = ActorRefImpl[Nothing]
private var watching = Set.empty[ARImpl]
/**
* This map holds a [[None]] for actors for which we send a [[Terminated]] notification on termination,
* ``Some(message)`` for actors for which we send a custom termination message.
*/
private var watching = Map.empty[ARImpl, Option[T]]
private var watchedBy = Set.empty[ARImpl]
final def watch[U](_a: ActorRef[U]): ActorRef[U] = {
val a = _a.sorry
final def watch[U](subject: ActorRef[U]): Unit = {
val a = subject.sorry
if (a != self && !watching.contains(a)) {
maintainAddressTerminatedSubscription(a) {
a.sendSystem(Watch(a, self))
watching += a
watching = watching.updated(a, None)
}
}
a
}
final def unwatch[U](_a: ActorRef[U]): ActorRef[U] = {
final def watchWith[U](subject: ActorRef[U], msg: T): Unit = {
val a = subject.sorry
if (a != self && !watching.contains(a)) {
maintainAddressTerminatedSubscription(a) {
a.sendSystem(Watch(a, self))
watching = watching.updated(a, Some(msg))
}
}
}
final def unwatch[U](_a: ActorRef[U]): Unit = {
val a = _a.sorry
if (a != self && watching.contains(a)) {
a.sendSystem(Unwatch(a, self))
@ -63,7 +76,6 @@ private[typed] trait DeathWatch[T] {
watching -= a
}
}
a
}
/**
@ -72,14 +84,21 @@ private[typed] trait DeathWatch[T] {
*/
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)
}
watching.get(actor) match {
case None // We're apparently no longer watching this actor.
case Some(optionalMessage)
maintainAddressTerminatedSubscription(actor) {
watching -= actor
}
if (maySend) {
optionalMessage match {
case None
val t = Terminated(actor)(failure)
next(Behavior.interpretSignal(behavior, ctx, t), t)
case Some(msg)
next(Behavior.interpretMessage(behavior, ctx, msg), msg)
}
}
}
if (isTerminating && terminatingMap.isEmpty) {
finishTerminate()
@ -120,9 +139,9 @@ private[typed] trait DeathWatch[T] {
if (watching.nonEmpty) {
maintainAddressTerminatedSubscription() {
try {
watching.foreach(watchee watchee.sendSystem(Unwatch(watchee, self)))
watching.foreach { case (watchee, _) watchee.sendSystem(Unwatch(watchee, self)) }
} finally {
watching = Set.empty
watching = Map.empty
}
}
}
@ -137,7 +156,7 @@ private[typed] trait DeathWatch[T] {
if (system.settings.untyped.DebugLifecycle) publish(Debug(self.path.toString, clazz(behavior), s"now watched by $watcher"))
}
} else if (!watcheeSelf && watcherSelf) {
watch[Nothing](watchee)
watch(watchee)
} else {
publish(Warning(self.path.toString, clazz(behavior), "BUG: illegal Watch(%s,%s) for %s".format(watchee, watcher, self)))
}
@ -153,7 +172,7 @@ private[typed] trait DeathWatch[T] {
if (system.settings.untyped.DebugLifecycle) publish(Debug(self.path.toString, clazz(behavior), s"no longer watched by $watcher"))
}
} else if (!watcheeSelf && watcherSelf) {
unwatch[Nothing](watchee)
unwatch(watchee)
} else {
publish(Warning(self.path.toString, clazz(behavior), "BUG: illegal Unwatch(%s,%s) for %s".format(watchee, watcher, self)))
}
@ -165,7 +184,7 @@ private[typed] trait DeathWatch[T] {
for (a watchedBy; if a.path.address == address) watchedBy -= a
}
for (a watching; if a.path.address == address) {
for ((a, _) watching; if a.path.address == address) {
self.sendSystem(DeathWatchNotification(a, null))
}
}
@ -183,7 +202,7 @@ private[typed] trait DeathWatch[T] {
}
if (isNonLocal(change)) {
def hasNonLocalAddress: Boolean = ((watching exists isNonLocal) || (watchedBy exists isNonLocal))
def hasNonLocalAddress: Boolean = ((watching.keysIterator exists isNonLocal) || (watchedBy exists isNonLocal))
val had = hasNonLocalAddress
val result = block
val has = hasNonLocalAddress

View file

@ -27,7 +27,6 @@ import akka.typed.scaladsl.AskPattern
*/
private[typed] class EventStreamImpl(private val debug: Boolean)(implicit private val timeout: Timeout) extends EventStream {
import e.Logging._
import ScalaDSL._
import EventStreamImpl._
private val unsubscriberPromise = Promise[ActorRef[Command]]
@ -40,26 +39,32 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
def startUnsubscriber(sys: ActorSystem[Nothing]): Unit =
unsubscriberPromise.completeWith(sys.systemActorOf(unsubscriberBehavior, "eventStreamUnsubscriber"))
private val unsubscriberBehavior = Deferred { ()
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"registering unsubscriber with $this"))
Full[Command] {
case Msg(ctx, Register(actor))
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"watching $actor in order to unsubscribe from EventStream when it terminates"))
ctx.watch[Nothing](actor)
Same
private val unsubscriberBehavior = {
// TODO avoid depending on dsl here?
import scaladsl.Actor
Actor.deferred[Command] { _
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"registering unsubscriber with $this"))
Actor.immutable[Command] { (ctx, msg)
msg match {
case Register(actor)
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"watching $actor in order to unsubscribe from EventStream when it terminates"))
ctx.watch(actor)
Actor.same
case Msg(ctx, UnregisterIfNoMoreSubscribedChannels(actor)) if hasSubscriptions(actor) Same
// hasSubscriptions can be slow, but it's better for this actor to take the hit than the EventStream
case UnregisterIfNoMoreSubscribedChannels(actor) if hasSubscriptions(actor) Actor.same
// hasSubscriptions can be slow, but it's better for this actor to take the hit than the EventStream
case Msg(ctx, UnregisterIfNoMoreSubscribedChannels(actor))
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unwatching $actor, since has no subscriptions"))
ctx.unwatch[Nothing](actor)
Same
case Sig(ctx, Terminated(actor))
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unsubscribe $actor from $this, because it was terminated"))
unsubscribe(actor)
Same
case UnregisterIfNoMoreSubscribedChannels(actor)
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unwatching $actor, since has no subscriptions"))
ctx.unwatch(actor)
Actor.same
}
} onSignal {
case (_, Terminated(actor))
if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unsubscribe $actor from $this, because it was terminated"))
unsubscribe(actor)
Actor.same
}
}
}
@ -119,7 +124,10 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
* started. Its log level can be defined by configuration setting
* <code>akka.stdout-loglevel</code>.
*/
private[typed] class StandardOutLogger extends ActorRef[LogEvent](StandardOutLoggerPath) with ActorRefImpl[LogEvent] with StdOutLogger {
private[typed] class StandardOutLogger extends ActorRef[LogEvent] with ActorRefImpl[LogEvent] with StdOutLogger {
override def path: a.ActorPath = StandardOutLoggerPath
override def tell(message: LogEvent): Unit =
if (message == null) throw a.InvalidMessageException("Message must not be null")
else print(message)
@ -142,9 +150,14 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
private val StandardOutLogger = new StandardOutLogger
private val UnhandledMessageForwarder = Static[a.UnhandledMessage] {
case a.UnhandledMessage(msg, sender, rcp)
publish(Debug(rcp.path.toString, rcp.getClass, "unhandled message from " + sender + ": " + msg))
private val UnhandledMessageForwarder = {
// TODO avoid depending on dsl here?
import scaladsl.Actor.{ same, immutable }
immutable[a.UnhandledMessage] {
case (_, a.UnhandledMessage(msg, sender, rcp))
publish(Debug(rcp.path.toString, rcp.getClass, "unhandled message from " + sender + ": " + msg))
same
}
}
def startStdoutLogger(settings: Settings) {

View file

@ -0,0 +1,120 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed.internal
import java.util.concurrent.{ ConcurrentHashMap, CountDownLatch }
import akka.annotation.InternalApi
import akka.typed.{ ActorSystem, Extension, ExtensionId, Extensions }
import scala.annotation.tailrec
import scala.util.{ Failure, Success, Try }
import scala.collection.JavaConverters._
/**
* Actor system extensions registry
*
* INTERNAL API
*/
@InternalApi
trait ExtensionsImpl extends Extensions { self: ActorSystem[_]
private val extensions = new ConcurrentHashMap[ExtensionId[_], AnyRef]
/**
* Hook for ActorSystem to load extensions on startup
*/
protected final def loadExtensions() {
/**
* @param throwOnLoadFail Throw exception when an extension fails to load (needed for backwards compatibility)
*/
def loadExtensions(key: String, throwOnLoadFail: Boolean): Unit = {
settings.config.getStringList(key).asScala.foreach { extensionIdFQCN
// it is either a Scala object or it is a Java class with a static singleton accessor
val idTry = dynamicAccess.getObjectFor[AnyRef](extensionIdFQCN)
.recoverWith { case _ idFromJavaSingletonAccessor(extensionIdFQCN) }
idTry match {
case Success(id: ExtensionId[_]) registerExtension(id)
case Success(_)
if (!throwOnLoadFail) log.error("[{}] is not an 'ExtensionId', skipping...", extensionIdFQCN)
else throw new RuntimeException(s"[$extensionIdFQCN] is not an 'ExtensionId'")
case Failure(problem)
if (!throwOnLoadFail) log.error(problem, "While trying to load extension [{}], skipping...", extensionIdFQCN)
else throw new RuntimeException(s"While trying to load extension [$extensionIdFQCN]", problem)
}
}
}
def idFromJavaSingletonAccessor(extensionIdFQCN: String): Try[ExtensionId[Extension]] =
dynamicAccess.getClassFor[ExtensionId[Extension]](extensionIdFQCN).flatMap[ExtensionId[Extension]] { clazz: Class[_]
Try {
val singletonAccessor = clazz.getDeclaredMethod("getInstance")
singletonAccessor.invoke(null).asInstanceOf[ExtensionId[Extension]]
}
}
// eager initialization of CoordinatedShutdown
// TODO coordinated shutdown for akka typed
// CoordinatedShutdown(self)
loadExtensions("akka.typed.library-extensions", throwOnLoadFail = true)
loadExtensions("akka.typed.extensions", throwOnLoadFail = false)
}
final override def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean = findExtension(ext) != null
final override def extension[T <: Extension](ext: ExtensionId[T]): T = findExtension(ext) match {
case null throw new IllegalArgumentException("Trying to get non-registered extension [" + ext + "]")
case some some.asInstanceOf[T]
}
final override def registerExtension[T <: Extension](ext: ExtensionId[T]): T =
findExtension(ext) match {
case null createExtensionInstance(ext)
case existing existing.asInstanceOf[T]
}
private def createExtensionInstance[T <: Extension](ext: ExtensionId[T]): T = {
val inProcessOfRegistration = new CountDownLatch(1)
extensions.putIfAbsent(ext, inProcessOfRegistration) match { // Signal that registration is in process
case null try { // Signal was successfully sent
// Create and initialize the extension
ext.createExtension(self) match {
case null throw new IllegalStateException("Extension instance created as 'null' for extension [" + ext + "]")
case instance
// Replace our in process signal with the initialized extension
extensions.replace(ext, inProcessOfRegistration, instance)
instance
}
} catch {
case t: Throwable
//In case shit hits the fan, remove the inProcess signal and escalate to caller
extensions.replace(ext, inProcessOfRegistration, t)
throw t
} finally {
//Always notify listeners of the inProcess signal
inProcessOfRegistration.countDown()
}
case other
//Someone else is in process of registering an extension for this Extension, retry
registerExtension(ext)
}
}
/**
* Returns any extension registered to the specified Extension or returns null if not registered
*/
@tailrec
private def findExtension[T <: Extension](ext: ExtensionId[T]): T = extensions.get(ext) match {
case c: CountDownLatch
//Registration in process, await completion and retry
c.await()
findExtension(ext)
case t: Throwable throw t //Initialization failed, throw same again
case other other.asInstanceOf[T] //could be a T or null, in which case we return the null as T
}
}

View file

@ -0,0 +1,278 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package internal
import java.util.concurrent.ThreadLocalRandom
import scala.annotation.tailrec
import scala.concurrent.duration.Deadline
import scala.concurrent.duration.FiniteDuration
import scala.reflect.ClassTag
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal
import akka.actor.DeadLetterSuppression
import akka.annotation.InternalApi
import akka.event.Logging
import akka.typed.ActorContext
import akka.typed.Behavior
import akka.typed.Behavior.DeferredBehavior
import akka.typed.ExtensibleBehavior
import akka.typed.PreRestart
import akka.typed.Signal
import akka.typed.SupervisorStrategy._
import akka.typed.scaladsl.Actor._
import akka.util.OptionVal
import akka.typed.scaladsl.Actor
/**
* INTERNAL API
*/
@InternalApi private[akka] object Restarter {
def apply[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T], strategy: SupervisorStrategy): Behavior[T] =
Actor.deferred[T] { ctx
val c = ctx.asInstanceOf[akka.typed.ActorContext[T]]
val startedBehavior = initialUndefer(c, initialBehavior)
strategy match {
case Restart(-1, _, loggingEnabled)
new Restarter(initialBehavior, startedBehavior, loggingEnabled)
case r: Restart
new LimitedRestarter(initialBehavior, startedBehavior, r, retries = 0, deadline = OptionVal.None)
case Resume(loggingEnabled) new Resumer(startedBehavior, loggingEnabled)
case b: Backoff
val backoffRestarter =
new BackoffRestarter(
initialBehavior.asInstanceOf[Behavior[Any]],
startedBehavior.asInstanceOf[Behavior[Any]],
b, restartCount = 0, blackhole = false)
backoffRestarter.asInstanceOf[Behavior[T]]
}
}
def initialUndefer[T](ctx: ActorContext[T], initialBehavior: Behavior[T]): Behavior[T] =
Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
}
/**
* INTERNAL API
*/
@InternalApi private[akka] abstract class Supervisor[T, Thr <: Throwable: ClassTag] extends ExtensibleBehavior[T] {
protected def loggingEnabled: Boolean
/**
* Current behavior
*/
protected def behavior: Behavior[T]
/**
* Wrap next behavior in a concrete restarter again.
*/
protected def wrap(nextBehavior: Behavior[T], afterException: Boolean): Supervisor[T, Thr]
protected def handleException(ctx: ActorContext[T], startedBehavior: Behavior[T]): Catcher[Supervisor[T, Thr]]
protected def restart(ctx: ActorContext[T], initialBehavior: Behavior[T], startedBehavior: Behavior[T]): Supervisor[T, Thr] = {
try Behavior.interpretSignal(startedBehavior, ctx, PreRestart) catch {
case NonFatal(ex) publish(ctx, Logging.Error(ex, ctx.asScala.self.path.toString, behavior.getClass,
"failure during PreRestart"))
}
// no need to canonicalize, it's done in the calling methods
wrap(Restarter.initialUndefer(ctx, initialBehavior), afterException = true)
}
@tailrec
protected final def canonical(b: Behavior[T], ctx: ActorContext[T], afterException: Boolean): Behavior[T] =
if (Behavior.isUnhandled(b)) Behavior.unhandled
else if ((b eq Behavior.SameBehavior) || (b eq behavior)) Behavior.same
else if (!Behavior.isAlive(b)) b
else {
b match {
case d: DeferredBehavior[T] canonical(Behavior.undefer(d, ctx), ctx, afterException)
case b wrap(b, afterException)
}
}
override def receiveSignal(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
try {
val b = Behavior.interpretSignal(behavior, ctx, signal)
canonical(b, ctx, afterException = false)
} catch handleException(ctx, behavior)
}
override def receiveMessage(ctx: ActorContext[T], msg: T): Behavior[T] = {
try {
val b = Behavior.interpretMessage(behavior, ctx, msg)
canonical(b, ctx, afterException = false)
} catch handleException(ctx, behavior)
}
protected def log(ctx: ActorContext[T], ex: Thr): Unit = {
if (loggingEnabled)
publish(ctx, Logging.Error(ex, ctx.asScala.self.toString, behavior.getClass, ex.getMessage))
}
protected final def publish(ctx: ActorContext[T], e: Logging.LogEvent): Unit =
try ctx.asScala.system.eventStream.publish(e) catch { case NonFatal(_) }
}
/**
* INTERNAL API
*/
@InternalApi private[akka] final class Resumer[T, Thr <: Throwable: ClassTag](
override val behavior: Behavior[T], override val loggingEnabled: Boolean) extends Supervisor[T, Thr] {
override def handleException(ctx: ActorContext[T], startedBehavior: Behavior[T]): Catcher[Supervisor[T, Thr]] = {
case NonFatal(ex: Thr)
log(ctx, ex)
wrap(startedBehavior, afterException = true)
}
override protected def wrap(nextBehavior: Behavior[T], afterException: Boolean): Supervisor[T, Thr] =
new Resumer[T, Thr](nextBehavior, loggingEnabled)
}
/**
* INTERNAL API
*/
@InternalApi private[akka] final class Restarter[T, Thr <: Throwable: ClassTag](
initialBehavior: Behavior[T], override val behavior: Behavior[T],
override val loggingEnabled: Boolean) extends Supervisor[T, Thr] {
override def handleException(ctx: ActorContext[T], startedBehavior: Behavior[T]): Catcher[Supervisor[T, Thr]] = {
case NonFatal(ex: Thr)
log(ctx, ex)
restart(ctx, initialBehavior, startedBehavior)
}
override protected def wrap(nextBehavior: Behavior[T], afterException: Boolean): Supervisor[T, Thr] =
new Restarter[T, Thr](initialBehavior, nextBehavior, loggingEnabled)
}
/**
* INTERNAL API
*/
@InternalApi private[akka] final class LimitedRestarter[T, Thr <: Throwable: ClassTag](
initialBehavior: Behavior[T], override val behavior: Behavior[T],
strategy: Restart, retries: Int, deadline: OptionVal[Deadline]) extends Supervisor[T, Thr] {
override def loggingEnabled: Boolean = strategy.loggingEnabled
private def deadlineHasTimeLeft: Boolean = deadline match {
case OptionVal.None true
case OptionVal.Some(d) d.hasTimeLeft
}
override def handleException(ctx: ActorContext[T], startedBehavior: Behavior[T]): Catcher[Supervisor[T, Thr]] = {
case NonFatal(ex: Thr)
log(ctx, ex)
if (deadlineHasTimeLeft && retries >= strategy.maxNrOfRetries)
throw ex
else
restart(ctx, initialBehavior, startedBehavior)
}
override protected def wrap(nextBehavior: Behavior[T], afterException: Boolean): Supervisor[T, Thr] = {
if (afterException) {
val timeLeft = deadlineHasTimeLeft
val newRetries = if (timeLeft) retries + 1 else 1
val newDeadline = if (deadline.isDefined && timeLeft) deadline else OptionVal.Some(Deadline.now + strategy.withinTimeRange)
new LimitedRestarter[T, Thr](initialBehavior, nextBehavior, strategy, newRetries, newDeadline)
} else
new LimitedRestarter[T, Thr](initialBehavior, nextBehavior, strategy, retries, deadline)
}
}
/**
* INTERNAL API
*/
@InternalApi private[akka] object BackoffRestarter {
/**
* Calculates an exponential back off delay.
*/
def calculateDelay(
restartCount: Int,
minBackoff: FiniteDuration,
maxBackoff: FiniteDuration,
randomFactor: Double): FiniteDuration = {
val rnd = 1.0 + ThreadLocalRandom.current().nextDouble() * randomFactor
if (restartCount >= 30) // Duration overflow protection (> 100 years)
maxBackoff
else
maxBackoff.min(minBackoff * math.pow(2, restartCount)) * rnd match {
case f: FiniteDuration f
case _ maxBackoff
}
}
case object ScheduledRestart
final case class ResetRestartCount(current: Int) extends DeadLetterSuppression
}
/**
* INTERNAL API
*/
@InternalApi private[akka] final class BackoffRestarter[T, Thr <: Throwable: ClassTag](
initialBehavior: Behavior[Any], override val behavior: Behavior[Any],
strategy: Backoff, restartCount: Int, blackhole: Boolean) extends Supervisor[Any, Thr] {
// TODO using Any here because the scheduled messages can't be of type T.
import BackoffRestarter._
override def loggingEnabled: Boolean = strategy.loggingEnabled
override def receiveSignal(ctx: ActorContext[Any], signal: Signal): Behavior[Any] = {
if (blackhole) {
ctx.asScala.system.eventStream.publish(Dropped(signal, ctx.asScala.self))
Behavior.same
} else
super.receiveSignal(ctx, signal)
}
override def receiveMessage(ctx: ActorContext[Any], msg: Any): Behavior[Any] = {
// intercept the scheduled messages and drop incoming messages if we are in backoff mode
msg match {
case ScheduledRestart
// actual restart after scheduled backoff delay
val restartedBehavior = Restarter.initialUndefer(ctx, initialBehavior)
ctx.asScala.schedule(strategy.resetBackoffAfter, ctx.asScala.self, ResetRestartCount(restartCount))
new BackoffRestarter[T, Thr](initialBehavior, restartedBehavior, strategy, restartCount, blackhole = false)
case ResetRestartCount(current)
if (current == restartCount)
new BackoffRestarter[T, Thr](initialBehavior, behavior, strategy, restartCount = 0, blackhole)
else
Behavior.same
case _
if (blackhole) {
ctx.asScala.system.eventStream.publish(Dropped(msg, ctx.asScala.self))
Behavior.same
} else
super.receiveMessage(ctx, msg)
}
}
override def handleException(ctx: ActorContext[Any], startedBehavior: Behavior[Any]): Catcher[Supervisor[Any, Thr]] = {
case NonFatal(ex: Thr)
log(ctx, ex)
// actual restart happens after the scheduled backoff delay
try Behavior.interpretSignal(behavior, ctx, PreRestart) catch {
case NonFatal(ex2) publish(ctx, Logging.Error(ex2, ctx.asScala.self.path.toString, behavior.getClass,
"failure during PreRestart"))
}
val restartDelay = calculateDelay(restartCount, strategy.minBackoff, strategy.maxBackoff, strategy.randomFactor)
ctx.asScala.schedule(restartDelay, ctx.asScala.self, ScheduledRestart)
new BackoffRestarter[T, Thr](initialBehavior, startedBehavior, strategy, restartCount + 1, blackhole = true)
}
override protected def wrap(nextBehavior: Behavior[Any], afterException: Boolean): Supervisor[Any, Thr] = {
if (afterException)
throw new IllegalStateException("wrap not expected afterException in BackoffRestarter")
else
new BackoffRestarter[T, Thr](initialBehavior, nextBehavior, strategy, restartCount, blackhole)
}
}

View file

@ -4,10 +4,11 @@
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
import akka.typed.Behavior.{ DeferredBehavior, undefer, validateAsInitial }
import akka.typed.Behavior.StoppedBehavior
import akka.util.OptionVal
/**
* INTERNAL API
@ -69,8 +70,8 @@ private[typed] trait SupervisionMechanics[T] {
behavior = initialBehavior
if (system.settings.untyped.DebugLifecycle)
publish(Logging.Debug(self.path.toString, clazz(behavior), "started"))
if (Behavior.isAlive(behavior)) next(behavior.management(ctx, PreStart), PreStart)
else self.sendSystem(Terminate())
behavior = validateAsInitial(undefer(behavior, ctx))
if (!Behavior.isAlive(behavior)) self.sendSystem(Terminate())
true
}
@ -88,9 +89,19 @@ private[typed] trait SupervisionMechanics[T] {
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")) }
try a match {
case null // skip PostStop
case _: DeferredBehavior[_]
// Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination.
case s: StoppedBehavior[_] s.postStop match {
case OptionVal.Some(postStop) Behavior.interpretSignal(postStop, ctx, PostStop)
case OptionVal.None // no postStop behavior defined
}
case _ Behavior.interpretSignal(a, 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 {

View file

@ -0,0 +1,147 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
package internal
import scala.concurrent.duration.FiniteDuration
import akka.actor.Cancellable
import akka.annotation.ApiMayChange
import akka.annotation.DoNotInherit
import akka.annotation.InternalApi
import akka.dispatch.ExecutionContexts
import akka.typed.ActorRef
import akka.typed.ActorRef.ActorRefOps
import akka.typed.javadsl
import akka.typed.scaladsl
import akka.typed.scaladsl.ActorContext
import scala.reflect.ClassTag
/**
* INTERNAL API
*/
@InternalApi private[akka] object TimerSchedulerImpl {
final case class Timer[T](key: Any, msg: T, repeat: Boolean, generation: Int, task: Cancellable)
final case class TimerMsg(key: Any, generation: Int, owner: AnyRef)
def withTimers[T](factory: TimerSchedulerImpl[T] Behavior[T]): Behavior[T] = {
scaladsl.Actor.deferred[T] { ctx
val timerScheduler = new TimerSchedulerImpl[T](ctx)
val behavior = factory(timerScheduler)
timerScheduler.intercept(behavior)
}
}
}
/**
* INTERNAL API
*/
@InternalApi private[akka] class TimerSchedulerImpl[T](ctx: ActorContext[T])
extends scaladsl.TimerScheduler[T] with javadsl.TimerScheduler[T] {
import TimerSchedulerImpl._
// FIXME change to a class specific logger, see issue #21219
private val log = ctx.system.log
private var timers: Map[Any, Timer[T]] = Map.empty
private val timerGen = Iterator from 1
override def startPeriodicTimer(key: Any, msg: T, interval: FiniteDuration): Unit =
startTimer(key, msg, interval, repeat = true)
override def startSingleTimer(key: Any, msg: T, timeout: FiniteDuration): Unit =
startTimer(key, msg, timeout, repeat = false)
private def startTimer(key: Any, msg: T, timeout: FiniteDuration, repeat: Boolean): Unit = {
timers.get(key) match {
case Some(t) cancelTimer(t)
case None
}
val nextGen = timerGen.next()
val timerMsg = TimerMsg(key, nextGen, this)
val task =
if (repeat)
ctx.system.scheduler.schedule(timeout, timeout) {
ctx.self.upcast ! timerMsg
}(ExecutionContexts.sameThreadExecutionContext)
else
ctx.system.scheduler.scheduleOnce(timeout) {
ctx.self.upcast ! timerMsg
}(ExecutionContexts.sameThreadExecutionContext)
val nextTimer = Timer(key, msg, repeat, nextGen, task)
log.debug("Start timer [{}] with generation [{}]", key, nextGen)
timers = timers.updated(key, nextTimer)
}
override def isTimerActive(key: Any): Boolean =
timers.contains(key)
override def cancel(key: Any): Unit = {
timers.get(key) match {
case None // already removed/canceled
case Some(t) cancelTimer(t)
}
}
private def cancelTimer(timer: Timer[T]): Unit = {
log.debug("Cancel timer [{}] with generation [{}]", timer.key, timer.generation)
timer.task.cancel()
timers -= timer.key
}
override def cancelAll(): Unit = {
log.debug("Cancel all timers")
timers.valuesIterator.foreach { timer
timer.task.cancel()
}
timers = Map.empty
}
private def interceptTimerMsg(ctx: ActorContext[TimerMsg], timerMsg: TimerMsg): T = {
timers.get(timerMsg.key) match {
case None
// it was from canceled timer that was already enqueued in mailbox
log.debug("Received timer [{}] that has been removed, discarding", timerMsg.key)
null.asInstanceOf[T] // message should be ignored
case Some(t)
if (timerMsg.owner ne this) {
// after restart, it was from an old instance that was enqueued in mailbox before canceled
log.debug("Received timer [{}] from old restarted instance, discarding", timerMsg.key)
null.asInstanceOf[T] // message should be ignored
} else if (timerMsg.generation == t.generation) {
// valid timer
log.debug("Received timer [{}]", timerMsg.key)
if (!t.repeat)
timers -= t.key
t.msg
} else {
// it was from an old timer that was enqueued in mailbox before canceled
log.debug(
"Received timer [{}] from from old generation [{}], expected generation [{}], discarding",
timerMsg.key, timerMsg.generation, t.generation)
null.asInstanceOf[T] // message should be ignored
}
}
}
def intercept(behavior: Behavior[T]): Behavior[T] = {
// The scheduled TimerMsg is intercepted to guard against old messages enqueued
// in mailbox before timer was canceled.
// Intercept some signals to cancel timers when when restarting and stopping.
BehaviorImpl.intercept[T, TimerMsg](
beforeMessage = interceptTimerMsg,
beforeSignal = (ctx, sig) {
sig match {
case PreRestart | PostStop cancelAll()
case _ // unhandled
}
true
},
afterMessage = (ctx, msg, b) b, // TODO optimize by using more ConstantFun
afterSignal = (ctx, sig, b) b,
behavior)(ClassTag(classOf[TimerSchedulerImpl.TimerMsg]))
}
}

View file

@ -0,0 +1,106 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package internal
package adapter
import akka.{ actor a }
import akka.annotation.InternalApi
import akka.util.OptionVal
/**
* INTERNAL API
*/
@InternalApi private[typed] class ActorAdapter[T](_initialBehavior: Behavior[T]) extends a.Actor {
import Behavior._
import ActorRefAdapter.toUntyped
var behavior: Behavior[T] = _initialBehavior
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.interpretSignal(behavior, ctx, msg), msg)
case a.ReceiveTimeout
next(Behavior.interpretMessage(behavior, ctx, ctx.receiveTimeoutMsg), ctx.receiveTimeoutMsg)
case msg: T @unchecked
next(Behavior.interpretMessage(behavior, ctx, msg), msg)
}
private def next(b: Behavior[T], msg: Any): Unit = {
if (Behavior.isUnhandled(b)) unhandled(msg)
else {
b match {
case s: StoppedBehavior[T]
// use StoppedBehavior with previous behavior or an explicitly given `postStop` behavior
// until Terminate is received, i.e until postStop is invoked, and there PostStop
// will be signaled to the previous/postStop behavior
s.postStop match {
case OptionVal.None
// use previous as the postStop behavior
behavior = new Behavior.StoppedBehavior(OptionVal.Some(behavior))
case OptionVal.Some(postStop)
// use the given postStop behavior, but canonicalize it
behavior = new Behavior.StoppedBehavior(OptionVal.Some(Behavior.canonicalize(postStop, behavior, ctx)))
}
context.stop(self)
case _
behavior = Behavior.canonicalize(b, behavior, ctx)
}
}
}
override def unhandled(msg: Any): Unit = msg match {
case Terminated(ref) throw a.DeathPactException(toUntyped(ref))
case msg: Signal // that's ok
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 = {
behavior = validateAsInitial(undefer(behavior, ctx))
if (!isAlive(behavior)) context.stop(self)
}
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
Behavior.interpretSignal(behavior, ctx, PreRestart)
behavior = Behavior.stopped
}
override def postRestart(reason: Throwable): Unit = {
behavior = validateAsInitial(undefer(behavior, ctx))
if (!isAlive(behavior)) context.stop(self)
}
override def postStop(): Unit = {
behavior match {
case null // skip PostStop
case _: DeferredBehavior[_]
// Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination.
case s: StoppedBehavior[_] s.postStop match {
case OptionVal.Some(postStop) Behavior.interpretSignal(postStop, ctx, PostStop)
case OptionVal.None // no postStop behavior defined
}
case b Behavior.interpretSignal(b, ctx, PostStop)
}
behavior = Behavior.stopped
}
}

View file

@ -0,0 +1,108 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package internal
package adapter
import akka.{ actor a }
import scala.concurrent.duration._
import scala.concurrent.ExecutionContextExecutor
import akka.annotation.InternalApi
/**
* INTERNAL API. Wrapping an [[akka.actor.ActorContext]] as an [[ActorContext]].
*/
@InternalApi private[typed] class ActorContextAdapter[T](val untyped: a.ActorContext) extends ActorContextImpl[T] {
import ActorRefAdapter.toUntyped
override def self = ActorRefAdapter(untyped.self)
override val system = ActorSystemAdapter(untyped.system)
override def mailboxCapacity = 1 << 29 // FIXME
override def children = untyped.children.map(ActorRefAdapter(_))
override def child(name: String) = untyped.child(name).map(ActorRefAdapter(_))
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty) =
ActorContextAdapter.spawnAnonymous(untyped, behavior, props)
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty) =
ActorContextAdapter.spawn(untyped, behavior, name, props)
override def stop[U](child: ActorRef[U]) =
toUntyped(child) match {
case f: akka.actor.FunctionRef
val cell = untyped.asInstanceOf[akka.actor.ActorCell]
cell.removeFunctionRef(f)
case c
untyped.child(child.path.name) match {
case Some(`c`)
untyped.stop(c)
true
case _
false // none of our business
}
}
override def watch[U](other: ActorRef[U]) = { untyped.watch(toUntyped(other)) }
override def watchWith[U](other: ActorRef[U], msg: T) = { untyped.watchWith(toUntyped(other), msg) }
override def unwatch[U](other: ActorRef[U]) = { untyped.unwatch(toUntyped(other)) }
var receiveTimeoutMsg: T = null.asInstanceOf[T]
override def setReceiveTimeout(d: FiniteDuration, msg: T) = {
receiveTimeoutMsg = msg
untyped.setReceiveTimeout(d)
}
override def cancelReceiveTimeout(): Unit = {
receiveTimeoutMsg = null.asInstanceOf[T]
untyped.setReceiveTimeout(Duration.Undefined)
}
override def executionContext: ExecutionContextExecutor = untyped.dispatcher
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
import untyped.dispatcher
untyped.system.scheduler.scheduleOnce(delay, toUntyped(target), msg)
}
override private[akka] def internalSpawnAdapter[U](f: U T, _name: String): ActorRef[U] = {
val cell = untyped.asInstanceOf[akka.actor.ActorCell]
val ref = cell.addFunctionRef((_, msg) untyped.self ! f(msg.asInstanceOf[U]), _name)
ActorRefAdapter[U](ref)
}
}
/**
* INTERNAL API
*/
@InternalApi private[typed] object ActorContextAdapter {
private def toUntypedImp[U](ctx: ActorContext[_]): a.ActorContext =
ctx match {
case adapter: ActorContextAdapter[_] adapter.untyped
case _
throw new UnsupportedOperationException("only adapted untyped ActorContext permissible " +
s"($ctx of class ${ctx.getClass.getName})")
}
def toUntyped2[U](ctx: ActorContext[_]): a.ActorContext = toUntypedImp(ctx)
def toUntyped[U](ctx: scaladsl.ActorContext[_]): a.ActorContext =
ctx match {
case c: ActorContext[_] toUntypedImp(c)
case _
throw new UnsupportedOperationException("unknown ActorContext type " +
s"($ctx of class ${ctx.getClass.getName})")
}
def toUntyped[U](ctx: javadsl.ActorContext[_]): a.ActorContext =
ctx match {
case c: ActorContext[_] toUntypedImp(c)
case _
throw new UnsupportedOperationException("unknown ActorContext type " +
s"($ctx of class ${ctx.getClass.getName})")
}
def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], props: Props): ActorRef[T] = {
Behavior.validateAsInitial(behavior)
ActorRefAdapter(ctx.actorOf(PropsAdapter(() behavior, props)))
}
def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, props: Props): ActorRef[T] = {
Behavior.validateAsInitial(behavior)
ActorRefAdapter(ctx.actorOf(PropsAdapter(() behavior, props), name))
}
}

View file

@ -0,0 +1,48 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package internal
package adapter
import akka.{ actor a }
import akka.annotation.InternalApi
import akka.dispatch.sysmsg
/**
* INTERNAL API
*/
@InternalApi private[typed] class ActorRefAdapter[-T](val untyped: a.InternalActorRef)
extends ActorRef[T] with internal.ActorRefImpl[T] {
override def path: a.ActorPath = untyped.path
override def tell(msg: T): Unit = untyped ! msg
override def isLocal: Boolean = untyped.isLocal
override def sendSystem(signal: internal.SystemMessage): Unit =
ActorRefAdapter.sendSystemMessage(untyped, signal)
}
private[typed] object ActorRefAdapter {
def apply[T](untyped: a.ActorRef): ActorRef[T] = new ActorRefAdapter(untyped.asInstanceOf[a.InternalActorRef])
def toUntyped[U](ref: ActorRef[U]): akka.actor.InternalActorRef =
ref match {
case adapter: ActorRefAdapter[_] adapter.untyped
case _
throw new UnsupportedOperationException("only adapted untyped ActorRefs permissible " +
s"($ref of class ${ref.getClass.getName})")
}
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

@ -2,6 +2,7 @@
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package internal
package adapter
import akka.{ actor a, dispatch d }
@ -9,34 +10,41 @@ import akka.dispatch.sysmsg
import scala.concurrent.ExecutionContextExecutor
import akka.util.Timeout
import scala.concurrent.Future
import akka.annotation.InternalApi
import scala.annotation.unchecked.uncheckedVariance
/**
* Lightweight wrapper for presenting an untyped ActorSystem to a Behavior (via the context).
* INTERNAL API. 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 persist 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] {
@InternalApi private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl)
extends ActorSystem[T] with ActorRef[T] with internal.ActorRefImpl[T] with ExtensionsImpl {
import ActorSystemAdapter._
import ActorRefAdapter.sendSystemMessage
// 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)
final override val path: a.ActorPath = a.RootActorPath(a.Address("akka", untyped.name)) / "user"
override def toString: String = untyped.toString
// 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")
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
}
@ -64,9 +72,9 @@ private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl)
override lazy val whenTerminated: scala.concurrent.Future[akka.typed.Terminated] =
untyped.whenTerminated.map(t Terminated(ActorRefAdapter(t.actor))(null))(sameThreadExecutionContext)
def systemActorOf[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig)(implicit timeout: Timeout): Future[ActorRef[U]] = {
val ref = untyped.systemActorOf(PropsAdapter(behavior, deployment), name)
Future.successful(ref)
def systemActorOf[U](behavior: Behavior[U], name: String, props: Props)(implicit timeout: Timeout): Future[ActorRef[U]] = {
val ref = untyped.systemActorOf(PropsAdapter(() behavior, props), name)
Future.successful(ActorRefAdapter(ref))
}
}
@ -74,9 +82,24 @@ private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl)
private[typed] object ActorSystemAdapter {
def apply(untyped: a.ActorSystem): ActorSystem[Nothing] = new ActorSystemAdapter(untyped.asInstanceOf[a.ActorSystemImpl])
object ReceptionistExtension extends a.ExtensionKey[ReceptionistExtension]
def toUntyped[U](sys: ActorSystem[_]): a.ActorSystem =
sys match {
case adapter: ActorSystemAdapter[_] adapter.untyped
case _ throw new UnsupportedOperationException("only adapted untyped ActorSystem permissible " +
s"($sys of class ${sys.getClass.getName})")
}
object ReceptionistExtension extends a.ExtensionId[ReceptionistExtension] with a.ExtensionIdProvider {
override def get(system: a.ActorSystem): ReceptionistExtension = super.get(system)
override def lookup = ReceptionistExtension
override def createExtension(system: a.ExtendedActorSystem): ReceptionistExtension =
new ReceptionistExtension(system)
}
class ReceptionistExtension(system: a.ExtendedActorSystem) extends a.Extension {
val receptionist: ActorRef[patterns.Receptionist.Command] =
ActorRefAdapter(system.systemActorOf(PropsAdapter(patterns.Receptionist.behavior, EmptyDeploymentConfig), "receptionist"))
ActorRefAdapter(system.systemActorOf(
PropsAdapter(() patterns.Receptionist.behavior, EmptyProps),
"receptionist"))
}
}

View file

@ -2,11 +2,16 @@
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package internal
package adapter
import akka.{ event e }
import akka.annotation.InternalApi
class EventStreamAdapter(untyped: e.EventStream) extends EventStream {
/**
* INTERNAL API
*/
@InternalApi private[typed] class EventStreamAdapter(untyped: e.EventStream) extends EventStream {
def logLevel: e.Logging.LogLevel = untyped.logLevel
def publish[T](event: T): Unit = untyped.publish(event.asInstanceOf[AnyRef])
@ -16,19 +21,22 @@ class EventStreamAdapter(untyped: e.EventStream) extends EventStream {
def subscribe[T](subscriber: ActorRef[T], to: Class[T]): Boolean =
subscriber match {
case adapter: ActorRefAdapter[_] untyped.subscribe(adapter.untyped, to)
case _ throw new UnsupportedOperationException("cannot subscribe native typed ActorRef")
case _
throw new UnsupportedOperationException("Cannot subscribe native typed ActorRef")
}
def unsubscribe[T](subscriber: ActorRef[T]): Unit =
subscriber match {
case adapter: ActorRefAdapter[_] untyped.unsubscribe(adapter.untyped)
case _ throw new UnsupportedOperationException("cannot unsubscribe native typed ActorRef")
case _
throw new UnsupportedOperationException("Cannot unsubscribe native typed ActorRef")
}
def unsubscribe[T](subscriber: ActorRef[T], from: Class[T]): Boolean =
subscriber match {
case adapter: ActorRefAdapter[_] untyped.unsubscribe(adapter.untyped, from)
case _ throw new UnsupportedOperationException("cannot unsubscribe native typed ActorRef")
case _
throw new UnsupportedOperationException("Cannot unsubscribe native typed ActorRef")
}
}

View file

@ -0,0 +1,22 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed
package internal
package adapter
import akka.typed.Behavior
import akka.typed.EmptyProps
import akka.typed.Props
import akka.annotation.InternalApi
/**
* INTERNAL API
*/
@InternalApi private[akka] object PropsAdapter {
def apply[T](behavior: () Behavior[T], deploy: Props = Props.empty): akka.actor.Props = {
// FIXME use Props, e.g. dispatcher
akka.actor.Props(new ActorAdapter(behavior()))
}
}

View file

@ -0,0 +1,280 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl
import java.util.function.{ Function JFunction }
import scala.reflect.ClassTag
import akka.util.OptionVal
import akka.japi.function.{ Function2 JapiFunction2 }
import akka.japi.function.Procedure2
import akka.japi.pf.PFBuilder
import akka.typed.Behavior
import akka.typed.ExtensibleBehavior
import akka.typed.Signal
import akka.typed.ActorRef
import akka.typed.SupervisorStrategy
import akka.typed.scaladsl.{ ActorContext SAC }
import akka.typed.internal.BehaviorImpl
import akka.typed.internal.Restarter
import akka.typed.internal.TimerSchedulerImpl
object Actor {
private val _unitFunction = (_: SAC[Any], _: Any) ()
private def unitFunction[T] = _unitFunction.asInstanceOf[((SAC[T], Signal) Unit)]
/**
* `deferred` is a factory for a behavior. Creation of the behavior instance is deferred until
* the actor is started, as opposed to `Actor.immutable` that creates the behavior instance
* immediately before the actor is running. The `factory` function pass the `ActorContext`
* as parameter and that can for example be used for spawning child actors.
*
* `deferred` is typically used as the outer most behavior when spawning an actor, but it
* can also be returned as the next behavior when processing a message or signal. In that
* case it will be "undeferred" immediately after it is returned, i.e. next message will be
* processed by the undeferred behavior.
*/
def deferred[T](factory: akka.japi.function.Function[ActorContext[T], Behavior[T]]): Behavior[T] =
Behavior.DeferredBehavior(ctx factory.apply(ctx.asJava))
/**
* Factory for creating a [[MutableBehavior]] that typically holds mutable state as
* instance variables in the concrete [[MutableBehavior]] implementation class.
*
* Creation of the behavior instance is deferred, i.e. it is created via the `factory`
* function. The reason for the deferred creation is to avoid sharing the same instance in
* multiple actors, and to create a new instance when the actor is restarted.
*
* @param producer
* behavior factory that takes the child actors context as argument
* @return the deferred behavior
*/
def mutable[T](factory: akka.japi.function.Function[ActorContext[T], MutableBehavior[T]]): Behavior[T] =
deferred(factory)
/**
* Mutable behavior can be implemented by extending this class and implement the
* abstract method [[MutableBehavior#onMessage]] and optionally override
* [[MutableBehavior#onSignal]].
*
* Instances of this behavior should be created via [[Actor#mutable]] and if
* the [[ActorContext]] is needed it can be passed as a constructor parameter
* from the factory function.
*
* @see [[Actor#mutable]]
*/
abstract class MutableBehavior[T] extends ExtensibleBehavior[T] {
private var _receive: OptionVal[Receive[T]] = OptionVal.None
private def receive: Receive[T] = _receive match {
case OptionVal.None
val receive = createReceive
_receive = OptionVal.Some(receive)
receive
case OptionVal.Some(r) r
}
@throws(classOf[Exception])
override final def receiveMessage(ctx: akka.typed.ActorContext[T], msg: T): Behavior[T] =
receive.receiveMessage(msg)
@throws(classOf[Exception])
override final def receiveSignal(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] =
receive.receiveSignal(msg)
def createReceive: Receive[T]
def receiveBuilder: ReceiveBuilder[T] = ReceiveBuilder.create
}
/**
* Return this behavior from message processing in order to advise the
* system to reuse the previous behavior. This is provided in order to
* avoid the allocation overhead of recreating the current behavior where
* that is not necessary.
*/
def same[T]: Behavior[T] = Behavior.same
/**
* Return this behavior from message processing in order to advise the
* system to reuse the previous behavior, including the hint that the
* message has not been handled. This hint may be used by composite
* behaviors that delegate (partial) handling to other behaviors.
*/
def unhandled[T]: Behavior[T] = Behavior.unhandled
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure.
*
* The PostStop signal that results from stopping this actor will be passed to the
* current behavior. All other messages and signals will effectively be
* ignored.
*/
def stopped[T]: Behavior[T] = Behavior.stopped
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure.
*
* The PostStop signal that results from stopping this actor will be passed to the
* given `postStop` behavior. All other messages and signals will effectively be
* ignored.
*/
def stopped[T](postStop: Behavior[T]): Behavior[T] = Behavior.stopped(postStop)
/**
* A behavior that treats every incoming message as unhandled.
*/
def empty[T]: Behavior[T] = Behavior.empty
/**
* A behavior that ignores every incoming message and returns same.
*/
def ignore[T]: Behavior[T] = Behavior.ignore
/**
* Construct an actor behavior that can react to incoming messages but not to
* lifecycle signals. After spawning this actor from another actor (or as the
* guardian of an [[akka.typed.ActorSystem]]) it will be executed within an
* [[ActorContext]] that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called immutable because the behavior instance doesn't
* have or close over any mutable state. Processing the next message
* results in a new behavior that can potentially be different from this one.
* State is updated by returning a new behavior that holds the new immutable
* state.
*/
def immutable[T](onMessage: JapiFunction2[ActorContext[T], T, Behavior[T]]): Behavior[T] =
new BehaviorImpl.ImmutableBehavior((ctx, msg) onMessage.apply(ctx.asJava, msg))
/**
* Construct an actor behavior that can react to both incoming messages and
* lifecycle signals. After spawning this actor from another actor (or as the
* guardian of an [[akka.typed.ActorSystem]]) it will be executed within an
* [[ActorContext]] that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called immutable because the behavior instance doesn't
* have or close over any mutable state. Processing the next message
* results in a new behavior that can potentially be different from this one.
* State is updated by returning a new behavior that holds the new immutable
* state.
*/
def immutable[T](
onMessage: JapiFunction2[ActorContext[T], T, Behavior[T]],
onSignal: JapiFunction2[ActorContext[T], Signal, Behavior[T]]): Behavior[T] = {
new BehaviorImpl.ImmutableBehavior(
(ctx, msg) onMessage.apply(ctx.asJava, msg),
{ case (ctx, sig) onSignal.apply(ctx.asJava, sig) })
}
/**
* Constructs an actor behavior builder that can build a behavior that can react to both
* incoming messages and lifecycle signals.
*
* This constructor is called immutable because the behavior instance doesn't
* have or close over any mutable state. Processing the next message
* results in a new behavior that can potentially be different from this one.
* State is updated by returning a new behavior that holds the new immutable
* state. If no change is desired, use {@link #same}.
*
* @param type the supertype of all messages accepted by this behavior
* @return the behavior builder
*/
def immutable[T](`type`: Class[T]): BehaviorBuilder[T] = BehaviorBuilder.create[T]
/**
* This type of Behavior wraps another Behavior while allowing you to perform
* some action upon each received message or signal. It is most commonly used
* for logging or tracing what a certain Actor does.
*/
def tap[T](
onMessage: Procedure2[ActorContext[T], T],
onSignal: Procedure2[ActorContext[T], Signal],
behavior: Behavior[T]): Behavior[T] = {
BehaviorImpl.tap(
(ctx, msg) onMessage.apply(ctx.asJava, msg),
(ctx, sig) onSignal.apply(ctx.asJava, sig),
behavior)
}
/**
* Behavior decorator that copies all received message to the designated
* monitor [[akka.typed.ActorRef]] before invoking the wrapped behavior. The
* wrapped behavior can evolve (i.e. return different behavior) without needing to be
* wrapped in a `monitor` call again.
*/
def monitor[T](monitor: ActorRef[T], behavior: Behavior[T]): Behavior[T] = {
BehaviorImpl.tap(
(ctx, msg) monitor ! msg,
unitFunction,
behavior)
}
/**
* Wrap the given behavior such that it is restarted (i.e. reset to its
* initial state) whenever it throws an exception of the given class or a
* subclass thereof. Exceptions that are not subtypes of `Thr` will not be
* caught and thus lead to the termination of the actor.
*
* It is possible to specify different supervisor strategies, such as restart,
* resume, backoff.
*/
def restarter[T, Thr <: Throwable](
clazz: Class[Thr],
strategy: SupervisorStrategy,
initialBehavior: Behavior[T]): Behavior[T] = {
Restarter(Behavior.validateAsInitial(initialBehavior), strategy)(ClassTag(clazz))
}
/**
* Widen the wrapped Behavior by placing a funnel in front of it: the supplied
* PartialFunction decides which message to pull in (those that it is defined
* at) and may transform the incoming message to place them into the wrapped
* Behaviors type hierarchy. Signals are not transformed.
*
* Example:
* {{{
* Behavior&lt;String> s = immutable((ctx, msg) -> {
* System.out.println(msg);
* return same();
* });
* Behavior&lt;Number> n = widened(s, pf -> pf.
* match(BigInteger.class, i -> "BigInteger(" + i + ")").
* match(BigDecimal.class, d -> "BigDecimal(" + d + ")")
* // drop all other kinds of Number
* );
* }}}
*
* @param behavior
* the behavior that will receive the selected messages
* @param selector
* a partial function builder for describing the selection and
* transformation
* @return a behavior of the widened type
*/
def widened[T, U](behavior: Behavior[T], selector: JFunction[PFBuilder[U, T], PFBuilder[U, T]]): Behavior[U] =
BehaviorImpl.widened(behavior, selector.apply(new PFBuilder).build())
/**
* Support for scheduled `self` messages in an actor.
* It takes care of the lifecycle of the timers such as cancelling them when the actor
* is restarted or stopped.
* @see [[TimerScheduler]]
*/
def withTimers[T](factory: akka.japi.function.Function[TimerScheduler[T], Behavior[T]]): Behavior[T] =
TimerSchedulerImpl.withTimers(timers factory.apply(timers))
trait Receive[T] {
def receiveMessage(msg: T): Behavior[T]
def receiveSignal(msg: Signal): Behavior[T]
}
}

View file

@ -0,0 +1,173 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl
import java.util.function.{ Function JFunction }
import akka.annotation.DoNotInherit
import akka.annotation.ApiMayChange
import akka.typed.ActorRef
import akka.typed.ActorSystem
import java.util.Optional
import akka.typed.Behavior
import akka.typed.Props
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.ExecutionContextExecutor
/**
* An Actor is given by the combination of a [[Behavior]] and a context in
* which this behavior is executed. As per the Actor Model an Actor can perform
* the following actions when processing a message:
*
* - send a finite number of messages to other Actors it knows
* - create a finite number of Actors
* - designate the behavior for the next message
*
* In Akka the first capability is accessed by using the `tell` method
* on an [[ActorRef]], the second is provided by [[ActorContext#spawn]]
* and the third is implicit in the signature of [[Behavior]] in that the next
* behavior is always returned from the message processing logic.
*
* An `ActorContext` in addition provides access to the Actors own identity (`getSelf`),
* the [[ActorSystem]] it is part of, methods for querying the list of child Actors it
* created, access to [[Terminated DeathWatch]] and timed message scheduling.
*/
@DoNotInherit
@ApiMayChange
trait ActorContext[T] {
// this must be a pure interface, i.e. only abstract methods
/**
* Get the `scaladsl` of this `ActorContext`.
*/
def asScala: akka.typed.scaladsl.ActorContext[T]
/**
* The identity of this Actor, bound to the lifecycle of this Actor instance.
* An Actor with the same name that lives before or after this instance will
* have a different [[ActorRef]].
*/
def getSelf: ActorRef[T]
/**
* Return the mailbox capacity that was configured by the parent for this actor.
*/
def getMailboxCapacity: Int
/**
* The [[ActorSystem]] to which this Actor belongs.
*/
def getSystem: ActorSystem[Void]
/**
* The list of child Actors created by this Actor during its lifetime that
* are still alive, in no particular order.
*/
def getChildren: java.util.List[ActorRef[Void]]
/**
* The named child Actor if it is alive.
*/
def getChild(name: String): Optional[ActorRef[Void]]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] under a randomly chosen name.
* It is good practice to name Actors wherever practical.
*/
def spawnAnonymous[U](behavior: Behavior[U]): ActorRef[U]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] under a randomly chosen name.
* It is good practice to name Actors wherever practical.
*/
def spawnAnonymous[U](behavior: Behavior[U], props: Props): ActorRef[U]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] and with the given name.
*/
def spawn[U](behavior: Behavior[U], name: String): ActorRef[U]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] and with the given name.
*/
def spawn[U](behavior: Behavior[U], name: String, props: Props): ActorRef[U]
/**
* Force the child Actor under the given name to terminate after it finishes
* processing its current message. Nothing happens if the ActorRef does not
* refer to a current child actor.
*
* @return whether the passed-in [[ActorRef]] points to a current child Actor
*/
def stop[U](child: ActorRef[U]): Boolean
/**
* Register for [[Terminated]] notification once the Actor identified by the
* given [[ActorRef]] terminates. This message is also sent when the watched actor
* is on a node that has been removed from the cluster when using akka-cluster
* or has been marked unreachable when using akka-remote directly.
*/
def watch[U](other: ActorRef[U]): Unit
/**
* Register for termination notification with a custom message once the Actor identified by the
* given [[ActorRef]] terminates. This message is also sent when the watched actor
* is on a node that has been removed from the cluster when using akka-cluster
* or has been marked unreachable when using akka-remote directly.
*/
def watchWith[U](other: ActorRef[U], msg: T): Unit
/**
* Revoke the registration established by `watch`. A [[Terminated]]
* notification will not subsequently be received for the referenced Actor.
*/
def unwatch[U](other: ActorRef[U]): Unit
/**
* Schedule the sending of a notification in case no other
* message is received during the given period of time. The timeout starts anew
* with each received message. Provide `Duration.Undefined` to switch off this
* mechanism.
*/
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
* the given time period has elapsed. The scheduled action can be cancelled
* by invoking [[akka.actor.Cancellable#cancel]] on the returned
* handle.
*/
def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): akka.actor.Cancellable
/**
* This Actors execution context. It can be used to run asynchronous tasks
* like [[scala.concurrent.Future]] combinators.
*/
def getExecutionContext: ExecutionContextExecutor
/**
* Create a child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*
* The name of the child actor will be composed of a unique identifier
* starting with a dollar sign to which the given `name` argument is
* appended, with an inserted hyphen between these two parts. Therefore
* the given `name` argument does not need to be unique within the scope
* of the parent actor.
*/
def spawnAdapter[U](f: JFunction[U, T], name: String): ActorRef[U]
/**
* Create an anonymous child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*/
def spawnAdapter[U](f: JFunction[U, T]): ActorRef[U]
}

View file

@ -0,0 +1,115 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl
import akka.typed.Behavior
import akka.typed.Props
import akka.typed.EmptyProps
import akka.typed.ActorRef
import akka.typed.internal.adapter.ActorRefAdapter
import akka.typed.scaladsl.adapter._
import akka.typed.ActorSystem
import akka.typed.internal.adapter.ActorContextAdapter
import akka.japi.Creator
/**
* Java API: Adapters between typed and untyped actors and actor systems.
* The underlying `ActorSystem` is the untyped [[akka.actor.ActorSystem]]
* which runs Akka Typed [[akka.typed.Behavior]] on an emulation layer. In this
* system typed and untyped actors can coexist.
*
* These methods make it possible to create typed child actor from untyped
* parent actor, and the opposite untyped child from typed parent.
* `watch` is also supported in both directions.
*
* There are also converters (`toTyped`, `toUntyped`) between untyped
* [[akka.actor.ActorRef]] and typed [[akka.typed.ActorRef]], and between untyped
* [[akka.actor.ActorSystem]] and typed [[akka.typed.ActorSystem]].
*/
object Adapter {
def spawnAnonymous[T](sys: akka.actor.ActorSystem, behavior: Behavior[T]): ActorRef[T] =
spawnAnonymous(sys, behavior, EmptyProps)
def spawnAnonymous[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], props: Props): ActorRef[T] =
sys.spawnAnonymous(behavior, props)
def spawn[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], name: String): ActorRef[T] =
spawn(sys, behavior, name, EmptyProps)
def spawn[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], name: String, props: Props): ActorRef[T] =
sys.spawn(behavior, name, props)
def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T]): ActorRef[T] =
spawnAnonymous(ctx, behavior, EmptyProps)
def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], props: Props): ActorRef[T] =
ctx.spawnAnonymous(behavior, props)
def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String): ActorRef[T] =
spawn(ctx, behavior, name, EmptyProps)
def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, props: Props): ActorRef[T] =
ctx.spawn(behavior, name, props)
def toTyped(sys: akka.actor.ActorSystem): ActorSystem[Void] =
sys.toTyped.asInstanceOf[ActorSystem[Void]]
def toUntyped(sys: ActorSystem[_]): akka.actor.ActorSystem =
sys.toUntyped
def watch[U](ctx: akka.actor.ActorContext, other: ActorRef[U]): Unit =
ctx.watch(other)
def unwatch[U](ctx: akka.actor.ActorContext, other: ActorRef[U]): Unit =
ctx.unwatch(other)
def stop(ctx: akka.actor.ActorContext, child: ActorRef[_]): Unit =
ctx.stop(child)
def watch[U](ctx: ActorContext[_], other: akka.actor.ActorRef): Unit =
ctx.watch(other)
def unwatch[U](ctx: ActorContext[_], other: akka.actor.ActorRef): Unit =
ctx.unwatch(other)
def stop(ctx: ActorContext[_], child: akka.actor.ActorRef): Boolean =
ctx.stop(child)
def actorOf(ctx: ActorContext[_], props: akka.actor.Props): akka.actor.ActorRef =
ActorContextAdapter.toUntyped(ctx).actorOf(props)
def actorOf(ctx: ActorContext[_], props: akka.actor.Props, name: String): akka.actor.ActorRef =
ActorContextAdapter.toUntyped(ctx).actorOf(props, name)
def toUntyped(ref: ActorRef[_]): akka.actor.ActorRef =
ref.toUntyped
def toTyped[T](ref: akka.actor.ActorRef): ActorRef[T] =
ref
/**
* Wrap [[akka.typed.Behavior]] in an untyped [[akka.actor.Props]], i.e. when
* spawning a typed child actor from an untyped parent actor.
* This is normally not needed because you can use the extension methods
* `spawn` and `spawnAnonymous` with an untyped `ActorContext`, but it's needed
* when using typed actors with an existing library/tool that provides an API that
* takes an untyped [[akka.actor.Props]] parameter. Cluster Sharding is an
* example of that.
*/
def props[T](behavior: Creator[Behavior[T]], deploy: Props): akka.actor.Props =
akka.typed.internal.adapter.PropsAdapter(() behavior.create(), deploy)
/**
* Wrap [[akka.typed.Behavior]] in an untyped [[akka.actor.Props]], i.e. when
* spawning a typed child actor from an untyped parent actor.
* This is normally not needed because you can use the extension methods
* `spawn` and `spawnAnonymous` with an untyped `ActorContext`, but it's needed
* when using typed actors with an existing library/tool that provides an API that
* takes an untyped [[akka.actor.Props]] parameter. Cluster Sharding is an
* example of that.
*/
def props[T](behavior: Creator[Behavior[T]]): akka.actor.Props =
props(behavior, EmptyProps)
}

View file

@ -0,0 +1,14 @@
package akka.typed
package javadsl
import java.util.concurrent.CompletionStage
import scala.compat.java8.FutureConverters
import akka.util.Timeout
import akka.actor.Scheduler
import scaladsl.AskPattern._
import akka.japi.function.Function
object AskPattern {
def ask[T, U](actor: ActorRef[T], message: Function[ActorRef[U], T], timeout: Timeout, scheduler: Scheduler): CompletionStage[U] =
FutureConverters.toJava[U](actor.?(message.apply)(timeout, scheduler))
}

View file

@ -0,0 +1,268 @@
/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl
import scala.annotation.tailrec
import akka.japi.function.{ Function, Function2, Predicate }
import akka.annotation.InternalApi
import akka.typed
import akka.typed.{ Behavior, ExtensibleBehavior, Signal }
import akka.typed.Behavior.unhandled
import BehaviorBuilder._
/**
* Used for creating a [[Behavior]] by 'chaining' message and signal handlers.
*
* When handling a message or signal, this [[Behavior]] will consider all handlers in the order they were added,
* looking for the first handler for which both the type and the (optional) predicate match.
*
* @tparam T the common superclass of all supported messages.
*/
class BehaviorBuilder[T] private (
private val messageHandlers: List[Case[T, T]],
private val signalHandlers: List[Case[T, Signal]]
) {
def build(): Behavior[T] = new BuiltBehavior(messageHandlers.reverse, signalHandlers.reverse)
/**
* Add a new case to the message handling.
*
* @param type type of message to match
* @param handler action to apply if the type matches
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def onMessage[M <: T](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withMessage(`type`, None, (i1: ActorContext[T], msg: T) handler.apply(i1, msg.asInstanceOf[M]))
/**
* Add a new predicated case to the message handling.
*
* @param type type of message to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def onMessage[M <: T](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withMessage(
`type`,
Some((t: T) test.test(t.asInstanceOf[M])),
(i1: ActorContext[T], msg: T) handler.apply(i1, msg.asInstanceOf[M])
)
/**
* Add a new case to the message handling without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>List.class</code> and <code>(List&lt;String&gt; list) -> {...}</code>
*
* @param type type of message to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def onMessageUnchecked[M <: T](`type`: Class[_ <: T], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withMessage(`type`, None, (i1: ActorContext[T], msg: T) handler.apply(i1, msg.asInstanceOf[M]))
/**
* Add a new case to the message handling matching equal messages.
*
* @param msg the message to compare to
* @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended
*/
def onMessageEquals(msg: T, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
withMessage(msg.getClass, Some(_.equals(msg)), (ctx: ActorContext[T], _: T) handler.apply(ctx))
/**
* Add a new case to the signal handling.
*
* @param type type of signal to match
* @param handler action to apply if the type matches
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def onSignal[M <: Signal](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withSignal(`type`, None, (ctx: ActorContext[T], signal: Signal) handler.apply(ctx, signal.asInstanceOf[M]))
/**
* Add a new predicated case to the signal handling.
*
* @param type type of signals to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def onSignal[M <: Signal](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withSignal(
`type`,
Some((t: Signal) test.test(t.asInstanceOf[M])),
(ctx: ActorContext[T], signal: Signal) handler.apply(ctx, signal.asInstanceOf[M])
)
/**
* Add a new case to the signal handling without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>GenMsg.class</code> and <code>(ActorContext<Message> ctx, GenMsg&lt;String&gt; list) -> {...}</code>
*
* @param type type of signal to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def onSignalUnchecked[M <: Signal](`type`: Class[_ <: Signal], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withSignal(`type`, None, (ctx: ActorContext[T], signal: Signal) handler.apply(ctx, signal.asInstanceOf[M]))
/**
* Add a new case to the signal handling matching equal signals.
*
* @param signal the signal to compare to
* @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended
*/
def onSignalEquals(signal: Signal, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
withSignal(signal.getClass, Some(_.equals(signal)), (ctx: ActorContext[T], _: Signal) handler.apply(ctx))
private def withMessage(`type`: Class[_ <: T], test: Option[T Boolean], handler: (ActorContext[T], T) Behavior[T]): BehaviorBuilder[T] =
new BehaviorBuilder[T](Case[T, T](`type`, test, handler) +: messageHandlers, signalHandlers)
private def withSignal[M <: Signal](`type`: Class[M], test: Option[Signal Boolean], handler: (ActorContext[T], Signal) Behavior[T]): BehaviorBuilder[T] =
new BehaviorBuilder[T](messageHandlers, Case[T, Signal](`type`, test, handler) +: signalHandlers)
}
object BehaviorBuilder {
def create[T]: BehaviorBuilder[T] = new BehaviorBuilder[T](Nil, Nil)
import scala.language.existentials
/** INTERNAL API */
@InternalApi
private[javadsl] final case class Case[BT, MT](`type`: Class[_ <: MT], test: Option[MT Boolean], handler: (ActorContext[BT], MT) Behavior[BT])
/**
* Start a new behavior chain starting with this case.
*
* @param type type of message to match
* @param handler action to apply if the type matches
* @tparam T type of behavior to create
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def message[T, M <: T](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessage(`type`, handler)
/**
* Start a new behavior chain starting with this predicated case.
*
* @param type type of message to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam T type of behavior to create
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def message[T, M <: T](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessage(`type`, test, handler)
/**
* Start a new behavior chain starting with a handler without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>List.class</code> and <code>(List&lt;String&gt; list) -> {...}</code>
*
* @param type type of message to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def messageUnchecked[T, M <: T](`type`: Class[_ <: T], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessageUnchecked(`type`, handler)
/**
* Start a new behavior chain starting with a handler for equal messages.
*
* @param msg the message to compare to
* @param handler action to apply when the message matches
* @tparam T type of behavior to create
* @return a new behavior with the specified handling appended
*/
def messageEquals[T](msg: T, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessageEquals(msg, handler)
/**
* Start a new behavior chain starting with this signal case.
*
* @param type type of signal to match
* @param handler action to apply if the type matches
* @tparam T type of behavior to create
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def signal[T, M <: Signal](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignal(`type`, handler)
/**
* Start a new behavior chain starting with this predicated signal case.
*
* @param type type of signals to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam T type of behavior to create
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def signal[T, M <: Signal](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignal(`type`, test, handler)
/**
* Start a new behavior chain starting with this unchecked signal case.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>GenMsg.class</code> and <code>(ActorContext<Message> ctx, GenMsg&lt;String&gt; list) -> {...}</code>
*
* @param type type of signal to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def signalUnchecked[T, M <: Signal](`type`: Class[_ <: Signal], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignalUnchecked(`type`, handler)
/**
* Start a new behavior chain starting with a handler for this specific signal.
*
* @param signal the signal to compare to
* @param handler action to apply when the message matches
* @tparam T type of behavior to create
* @return a new behavior with the specified handling appended
*/
def signalEquals[T](signal: Signal, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignalEquals(signal, handler)
}
private class BuiltBehavior[T](
private val messageHandlers: List[Case[T, T]],
private val signalHandlers: List[Case[T, Signal]]
) extends ExtensibleBehavior[T] {
override def receiveMessage(ctx: typed.ActorContext[T], msg: T): Behavior[T] = receive[T](ctx.asJava, msg, messageHandlers)
override def receiveSignal(ctx: typed.ActorContext[T], msg: Signal): Behavior[T] = receive[Signal](ctx.asJava, msg, signalHandlers)
@tailrec
private def receive[M](ctx: ActorContext[T], msg: M, handlers: List[Case[T, M]]): Behavior[T] =
handlers match {
case Case(cls, predicate, handler) :: tail
if (cls.isAssignableFrom(msg.getClass) && (predicate.isEmpty || predicate.get.apply(msg))) handler(ctx, msg)
else receive[M](ctx, msg, tail)
case _
unhandled[T]
}
}

View file

@ -0,0 +1,169 @@
/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl
import scala.annotation.tailrec
import akka.japi.function.{ Creator, Function, Predicate }
import akka.typed.javadsl.Actor.Receive
import akka.typed.{ Behavior, Signal }
import ReceiveBuilder._
import akka.annotation.InternalApi
/**
* Used when implementing [[Actor.MutableBehavior]].
*
* When handling a message or signal, this [[Behavior]] will consider all handlers in the order they were added,
* looking for the first handler for which both the type and the (optional) predicate match.
*
* @tparam T the common superclass of all supported messages.
*/
class ReceiveBuilder[T] private (
private val messageHandlers: List[Case[T, T]],
private val signalHandlers: List[Case[T, Signal]]
) {
def build(): Receive[T] = new BuiltReceive(messageHandlers.reverse, signalHandlers.reverse)
/**
* Add a new case to the message handling.
*
* @param type type of message to match
* @param handler action to apply if the type matches
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def onMessage[M <: T](`type`: Class[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withMessage(`type`, None, msg handler.apply(msg.asInstanceOf[M]))
/**
* Add a new predicated case to the message handling.
*
* @param type type of message to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def onMessage[M <: T](`type`: Class[M], test: Predicate[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withMessage(
`type`,
Some((t: T) test.test(t.asInstanceOf[M])),
msg handler.apply(msg.asInstanceOf[M])
)
/**
* Add a new case to the message handling without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>List.class</code> and <code>(List&lt;String&gt; list) -> {...}</code>
*
* @param type type of message to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def onMessageUnchecked[M <: T](`type`: Class[_ <: T], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withMessage(`type`, None, msg handler.apply(msg.asInstanceOf[M]))
/**
* Add a new case to the message handling matching equal messages.
*
* @param msg the message to compare to
* @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended
*/
def onMessageEquals(msg: T, handler: Creator[Behavior[T]]): ReceiveBuilder[T] =
withMessage(msg.getClass, Some(_.equals(msg)), _ handler.create())
/**
* Add a new case to the signal handling.
*
* @param type type of signal to match
* @param handler action to apply if the type matches
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def onSignal[M <: Signal](`type`: Class[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withSignal(`type`, None, signal handler.apply(signal.asInstanceOf[M]))
/**
* Add a new predicated case to the signal handling.
*
* @param type type of signals to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def onSignal[M <: Signal](`type`: Class[M], test: Predicate[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withSignal(
`type`,
Some((t: Signal) test.test(t.asInstanceOf[M])),
signal handler.apply(signal.asInstanceOf[M])
)
/**
* Add a new case to the signal handling without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>GenMsg.class</code> and <code>(ActorContext<Message> ctx, GenMsg&lt;String&gt; list) -> {...}</code>
*
* @param type type of signal to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def onSignalUnchecked[M <: Signal](`type`: Class[_ <: Signal], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withSignal(`type`, None, signal handler.apply(signal.asInstanceOf[M]))
/**
* Add a new case to the signal handling matching equal signals.
*
* @param signal the signal to compare to
* @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended
*/
def onSignalEquals(signal: Signal, handler: Creator[Behavior[T]]): ReceiveBuilder[T] =
withSignal(signal.getClass, Some(_.equals(signal)), _ handler.create())
private def withMessage(`type`: Class[_ <: T], test: Option[T Boolean], handler: T Behavior[T]): ReceiveBuilder[T] =
new ReceiveBuilder[T](Case[T, T](`type`, test, handler) +: messageHandlers, signalHandlers)
private def withSignal[M <: Signal](`type`: Class[M], test: Option[Signal Boolean], handler: Signal Behavior[T]): ReceiveBuilder[T] =
new ReceiveBuilder[T](messageHandlers, Case[T, Signal](`type`, test, handler) +: signalHandlers)
}
object ReceiveBuilder {
def create[T]: ReceiveBuilder[T] = new ReceiveBuilder[T](Nil, Nil)
import scala.language.existentials
/** INTERNAL API */
@InternalApi
private[javadsl] final case class Case[BT, MT](`type`: Class[_ <: MT], test: Option[MT Boolean], handler: MT Behavior[BT])
}
/**
* Receive type for [[Actor.MutableBehavior]]
*/
private class BuiltReceive[T](
private val messageHandlers: List[Case[T, T]],
private val signalHandlers: List[Case[T, Signal]]
) extends Receive[T] {
override def receiveMessage(msg: T): Behavior[T] = receive[T](msg, messageHandlers)
override def receiveSignal(msg: Signal): Behavior[T] = receive[Signal](msg, signalHandlers)
@tailrec
private def receive[M](msg: M, handlers: List[Case[T, M]]): Behavior[T] =
handlers match {
case Case(cls, predicate, handler) :: tail
if (cls.isAssignableFrom(msg.getClass) && (predicate.isEmpty || predicate.get.apply(msg))) handler(msg)
else receive[M](msg, tail)
case _
Actor.unhandled
}
}

View file

@ -0,0 +1,62 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.javadsl
import scala.concurrent.duration.FiniteDuration
/**
* Support for scheduled `self` messages in an actor.
* It is used with `Actor.withTimers`, which also takes care of the
* lifecycle of the timers such as cancelling them when the actor
* is restarted or stopped.
*
* `TimerScheduler` is not thread-safe, i.e. it must only be used within
* the actor that owns it.
*/
trait TimerScheduler[T] {
/**
* Start a periodic timer that will send `msg` to the `self` actor at
* a fixed `interval`.
*
* Each timer has a key and if a new timer with same key is started
* the previous is cancelled and it's guaranteed that a message from the
* previous timer is not received, even though it might already be enqueued
* in the mailbox when the new timer is started.
*/
def startPeriodicTimer(key: Any, msg: T, interval: FiniteDuration): Unit
/**
* * Start a timer that will send `msg` once to the `self` actor after
* the given `timeout`.
*
* Each timer has a key and if a new timer with same key is started
* the previous is cancelled and it's guaranteed that a message from the
* previous timer is not received, even though it might already be enqueued
* in the mailbox when the new timer is started.
*/
def startSingleTimer(key: Any, msg: T, timeout: FiniteDuration): Unit
/**
* Check if a timer with a given `key` is active.
*/
def isTimerActive(key: Any): Boolean
/**
* Cancel a timer with a given `key`.
* If canceling a timer that was already canceled, or key never was used to start a timer
* this operation will do nothing.
*
* It is guaranteed that a message from a canceled timer, including its previous incarnation
* for the same key, will not be received by the actor, even though the message might already
* be enqueued in the mailbox when cancel is called.
*/
def cancel(key: Any): Unit
/**
* Cancel all timers.
*/
def cancelAll(): Unit
}

View file

@ -3,11 +3,11 @@
*/
package akka.typed.patterns
import akka.typed.ScalaDSL._
import akka.typed.ActorRef
import akka.typed.Behavior
import akka.typed.Terminated
import akka.util.TypedMultiMap
import akka.typed.scaladsl.Actor._
/**
* A Receptionist is an entry point into an Actor hierarchy where select Actors
@ -72,17 +72,21 @@ object Receptionist {
private type KV[K <: AbstractServiceKey] = ActorRef[K#Type]
private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = Full {
case Msg(ctx, r: Register[t])
ctx.watch(r.address)
r.replyTo ! Registered(r.key, r.address)
behavior(map.inserted(r.key)(r.address))
case Msg(ctx, f: Find[t])
val set = map get f.key
f.replyTo ! Listing(f.key, set)
Same
case Sig(ctx, Terminated(ref))
private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = immutable[Command] { (ctx, msg)
msg match {
case r: Register[t]
ctx.watch(r.address)
r.replyTo ! Registered(r.key, r.address)
behavior(map.inserted(r.key)(r.address))
case f: Find[t]
val set = map get f.key
f.replyTo ! Listing(f.key, set)
same
}
} onSignal {
case (ctx, Terminated(ref))
behavior(map valueRemoved ref)
case x unhandled
}
}

View file

@ -1,97 +0,0 @@
/**
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.typed
package patterns
import scala.reflect.ClassTag
import scala.util.control.NonFatal
import akka.event.Logging
import akka.typed.scaladsl.Actor._
/**
* 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 restart is canonicalizing PreStart result correctly
*/
final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T], resume: Boolean)(
behavior: Behavior[T] = initialBehavior) extends Behavior[T] {
private def restart(ctx: ActorContext[T]): Behavior[T] = {
try behavior.management(ctx, PreRestart) catch { case NonFatal(_) }
Behavior.canonicalize(initialBehavior.management(ctx, PreStart), initialBehavior)
}
private def canonical(b: Behavior[T]): Behavior[T] =
if (Behavior.isUnhandled(b)) Unhandled
else if (b eq Behavior.sameBehavior) Same
else if (!Behavior.isAlive(b)) Stopped
else if (b eq behavior) Same
else Restarter[T, Thr](initialBehavior, resume)(b)
override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
val b =
try behavior.management(ctx, signal)
catch {
case ex: Thr
ctx.system.eventStream.publish(Logging.Error(ex, ctx.self.toString, behavior.getClass, ex.getMessage))
if (resume) behavior else restart(ctx)
}
canonical(b)
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
val b =
try behavior.message(ctx, msg)
catch {
case ex: Thr
ctx.system.eventStream.publish(Logging.Error(ex, ctx.self.toString, behavior.getClass, ex.getMessage))
if (resume) behavior else restart(ctx)
}
canonical(b)
}
}
/**
* 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 MutableRestarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T], resume: Boolean) extends Behavior[T] {
private[this] var current = initialBehavior
private def restart(ctx: ActorContext[T]): Behavior[T] = {
try current.management(ctx, PreRestart) catch { case NonFatal(_) }
current = initialBehavior
current.management(ctx, PreStart)
}
override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
val b =
try current.management(ctx, signal)
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 Stopped
}
override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
val b =
try current.message(ctx, msg)
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 Stopped
}
}

View file

@ -4,153 +4,19 @@
package akka.typed
package scaladsl
import akka.util.LineNumbers
import scala.reflect.ClassTag
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.ExecutionContextExecutor
import scala.deprecatedInheritance
import akka.typed.{ ActorContext AC }
import akka.annotation.ApiMayChange
import akka.annotation.DoNotInherit
/**
* An Actor is given by the combination of a [[Behavior]] and a context in
* which this behavior is executed. As per the Actor Model an Actor can perform
* the following actions when processing a message:
*
* - send a finite number of messages to other Actors it knows
* - create a finite number of Actors
* - designate the behavior for the next message
*
* In Akka the first capability is accessed by using the `!` or `tell` method
* on an [[ActorRef]], the second is provided by [[ActorContext#spawn]]
* and the third is implicit in the signature of [[Behavior]] in that the next
* behavior is always returned from the message processing logic.
*
* An `ActorContext` in addition provides access to the Actors own identity (`self`),
* the [[ActorSystem]] it is part of, methods for querying the list of child Actors it
* created, access to [[Terminated DeathWatch]] and timed message scheduling.
*/
@DoNotInherit
@ApiMayChange
trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T]
/**
* The identity of this Actor, bound to the lifecycle of this Actor instance.
* An Actor with the same name that lives before or after this instance will
* have a different [[ActorRef]].
*/
def self: ActorRef[T]
/**
* Return the mailbox capacity that was configured by the parent for this actor.
*/
def mailboxCapacity: Int
/**
* The [[ActorSystem]] to which this Actor belongs.
*/
def system: ActorSystem[Nothing]
/**
* The list of child Actors created by this Actor during its lifetime that
* are still alive, in no particular order.
*/
def children: Iterable[ActorRef[Nothing]]
/**
* The named child Actor if it is alive.
*/
def child(name: String): Option[ActorRef[Nothing]]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] under a randomly chosen name.
* It is good practice to name Actors wherever practical.
*/
def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] and with the given name.
*/
def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U]
/**
* Force the child Actor under the given name to terminate after it finishes
* processing its current message. Nothing happens if the ActorRef does not
* refer to a current child actor.
*
* @return whether the passed-in [[ActorRef]] points to a current child Actor
*/
def stop(child: ActorRef[_]): Boolean
/**
* Register for [[Terminated]] notification once the Actor identified by the
* given [[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[U](other: ActorRef[U]): ActorRef[U]
/**
* Revoke the registration established by `watch`. A [[Terminated]]
* notification will not subsequently be received for the referenced Actor.
*/
def unwatch[U](other: ActorRef[U]): ActorRef[U]
/**
* Schedule the sending of a notification in case no other
* message is received during the given period of time. The timeout starts anew
* with each received message. Provide `Duration.Undefined` to switch off this
* mechanism.
*/
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
* the given time period has elapsed. The scheduled action can be cancelled
* by invoking [[akka.actor.Cancellable#cancel]] on the returned
* handle.
*/
def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): akka.actor.Cancellable
/**
* This Actors execution context. It can be used to run asynchronous tasks
* like [[scala.concurrent.Future]] combinators.
*/
implicit def executionContext: ExecutionContextExecutor
/**
* Create a child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*
* The name of the child actor will be composed of a unique identifier
* starting with a dollar sign to which the given `name` argument is
* appended, with an inserted hyphen between these two parts. Therefore
* the given `name` argument does not need to be unique within the scope
* of the parent actor.
*/
def spawnAdapter[U](f: U T, name: String): ActorRef[U]
/**
* Create an anonymous child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*/
def spawnAdapter[U](f: U T): ActorRef[U] = spawnAdapter(f, "")
}
import akka.typed.internal.BehaviorImpl
import akka.typed.internal.TimerSchedulerImpl
@ApiMayChange
object Actor {
import Behavior._
// FIXME check that all behaviors can cope with not getting PreStart as first message
private val _unitFunction = (_: ActorContext[Any], _: Any) ()
private def unitFunction[T] = _unitFunction.asInstanceOf[((ActorContext[T], Signal) Unit)]
final implicit class BehaviorDecorators[T](val behavior: Behavior[T]) extends AnyVal {
/**
@ -159,66 +25,97 @@ object Actor {
* at) and may transform the incoming message to place them into the wrapped
* Behaviors type hierarchy. Signals are not transformed.
*
* see also [[Actor.Widened]]
* Example:
* {{{
* immutable[String] { (ctx, msg) => println(msg); same }.widen[Number] {
* case b: BigDecimal => s"BigDecimal(&dollar;b)"
* case i: BigInteger => s"BigInteger(&dollar;i)"
* // drop all other kinds of Number
* }
* }}}
*/
def widen[U](matcher: PartialFunction[U, T]): Behavior[U] = Widened(behavior, matcher)
}
private val _nullFun = (_: Any) null
private def nullFun[T] = _nullFun.asInstanceOf[Any T]
private implicit class ContextAs[T](val ctx: AC[T]) extends AnyVal {
def as[U] = ctx.asInstanceOf[AC[U]]
def widen[U](matcher: PartialFunction[U, T]): Behavior[U] =
BehaviorImpl.widened(behavior, matcher)
}
/**
* Widen the wrapped Behavior by placing a funnel in front of it: the supplied
* PartialFunction decides which message to pull in (those that it is defined
* at) and may transform the incoming message to place them into the wrapped
* Behaviors type hierarchy. Signals are not transformed.
* `deferred` is a factory for a behavior. Creation of the behavior instance is deferred until
* the actor is started, as opposed to `Actor.immutable` that creates the behavior instance
* immediately before the actor is running. The `factory` function pass the `ActorContext`
* as parameter and that can for example be used for spawning child actors.
*
* Example:
* {{{
* Stateless[String]((ctx, msg) => println(msg)).widen[Number] {
* case b: BigDecimal => s"BigDecimal(&dollar;b)"
* case i: BigInteger => s"BigInteger(&dollar;i)"
* // drop all other kinds of Number
* }
* }}}
* `deferred` is typically used as the outer most behavior when spawning an actor, but it
* can also be returned as the next behavior when processing a message or signal. In that
* case it will be "undeferred" immediately after it is returned, i.e. next message will be
* processed by the undeferred behavior.
*/
final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends Behavior[U] {
private def postProcess(behv: Behavior[T]): Behavior[U] =
if (isUnhandled(behv)) Unhandled
else if (isAlive(behv)) {
val next = canonicalize(behv, behavior)
if (next eq behavior) Same else Widened(next, matcher)
} else Stopped
override def management(ctx: AC[U], msg: Signal): Behavior[U] =
postProcess(behavior.management(ctx.as[T], msg))
override def message(ctx: AC[U], msg: U): Behavior[U] =
matcher.applyOrElse(msg, nullFun) match {
case null Unhandled
case transformed postProcess(behavior.message(ctx.as[T], transformed))
}
override def toString: String = s"${behavior.toString}.widen(${LineNumbers(matcher)})"
}
def deferred[T](factory: ActorContext[T] Behavior[T]): Behavior[T] =
Behavior.DeferredBehavior(factory)
/**
* Wrap a behavior factory so that it runs upon PreStart, i.e. behavior creation
* is deferred to the child actor instead of running within the parent.
* Factory for creating a [[MutableBehavior]] that typically holds mutable state as
* instance variables in the concrete [[MutableBehavior]] implementation class.
*
* Creation of the behavior instance is deferred, i.e. it is created via the `factory`
* function. The reason for the deferred creation is to avoid sharing the same instance in
* multiple actors, and to create a new instance when the actor is restarted.
*
* @param producer
* behavior factory that takes the child actors context as argument
* @return the deferred behavior
*/
final case class Deferred[T](factory: ActorContext[T] Behavior[T]) extends Behavior[T] {
override def management(ctx: AC[T], msg: Signal): Behavior[T] = {
if (msg != PreStart) throw new IllegalStateException(s"Deferred must receive PreStart as first message (got $msg)")
Behavior.preStart(factory(ctx), ctx)
}
def mutable[T](factory: ActorContext[T] MutableBehavior[T]): Behavior[T] =
deferred(factory)
override def message(ctx: AC[T], msg: T): Behavior[T] =
throw new IllegalStateException(s"Deferred must receive PreStart as first message (got $msg)")
/**
* Mutable behavior can be implemented by extending this class and implement the
* abstract method [[MutableBehavior#onMessage]] and optionally override
* [[MutableBehavior#onSignal]].
*
* Instances of this behavior should be created via [[Actor#Mutable]] and if
* the [[ActorContext]] is needed it can be passed as a constructor parameter
* from the factory function.
*
* @see [[Actor#Mutable]]
*/
abstract class MutableBehavior[T] extends ExtensibleBehavior[T] {
@throws(classOf[Exception])
override final def receiveMessage(ctx: akka.typed.ActorContext[T], msg: T): Behavior[T] =
onMessage(msg)
override def toString: String = s"Deferred(${LineNumbers(factory)})"
/**
* Implement this method to process an incoming message and return the next behavior.
*
* The returned behavior can in addition to normal behaviors be one of the canned special objects:
* <ul>
* <li>returning `stopped` will terminate this Behavior</li>
* <li>returning `this` or `same` designates to reuse the current Behavior</li>
* <li>returning `unhandled` keeps the same Behavior and signals that the message was not yet handled</li>
* </ul>
*
*/
@throws(classOf[Exception])
def onMessage(msg: T): Behavior[T]
@throws(classOf[Exception])
override final def receiveSignal(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] =
onSignal.applyOrElse(msg, { case _ Behavior.unhandled }: PartialFunction[Signal, Behavior[T]])
/**
* Override this method to process an incoming [[akka.typed.Signal]] and return the next behavior.
* This means that all lifecycle hooks, ReceiveTimeout, Terminated and Failed messages
* can initiate a behavior change.
*
* The returned behavior can in addition to normal behaviors be one of the canned special objects:
*
* * returning `stopped` will terminate this Behavior
* * returning `this` or `same` designates to reuse the current Behavior
* * returning `unhandled` keeps the same Behavior and signals that the message was not yet handled
*
* By default, partial function is empty and does not handle any signals.
*/
@throws(classOf[Exception])
def onSignal: PartialFunction[Signal, Behavior[T]] = PartialFunction.empty
}
/**
@ -227,7 +124,7 @@ object Actor {
* avoid the allocation overhead of recreating the current behavior where
* that is not necessary.
*/
def Same[T]: Behavior[T] = sameBehavior.asInstanceOf[Behavior[T]]
def same[T]: Behavior[T] = Behavior.same
/**
* Return this behavior from message processing in order to advise the
@ -235,32 +132,39 @@ object Actor {
* message has not been handled. This hint may be used by composite
* behaviors that delegate (partial) handling to other behaviors.
*/
def Unhandled[T]: Behavior[T] = unhandledBehavior.asInstanceOf[Behavior[T]]
/*
* TODO write a Behavior that waits for all child actors to stop and then
* runs some cleanup before stopping. The factory for this behavior should
* stop and watch all children to get the process started.
*/
def unhandled[T]: Behavior[T] = Behavior.unhandled
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure. The PostStop
* signal that results from stopping this actor will NOT be passed to the
* current behavior, it will be effectively ignored.
* these will be stopped as part of the shutdown procedure.
*
* The PostStop signal that results from stopping this actor will be passed to the
* current behavior. All other messages and signals will effectively be
* ignored.
*/
def Stopped[T]: Behavior[T] = stoppedBehavior.asInstanceOf[Behavior[T]]
def stopped[T]: Behavior[T] = Behavior.stopped
/**
* Return this behavior from message processing to signal that this actor
* shall terminate voluntarily. If this actor has created child actors then
* these will be stopped as part of the shutdown procedure.
*
* The PostStop signal that results from stopping this actor will be passed to the
* given `postStop` behavior. All other messages and signals will effectively be
* ignored.
*/
def stopped[T](postStop: Behavior[T]): Behavior[T] = Behavior.stopped(postStop)
/**
* A behavior that treats every incoming message as unhandled.
*/
def Empty[T]: Behavior[T] = emptyBehavior.asInstanceOf[Behavior[T]]
def empty[T]: Behavior[T] = Behavior.empty
/**
* A behavior that ignores every incoming message and returns same.
*/
def Ignore[T]: Behavior[T] = ignoreBehavior.asInstanceOf[Behavior[T]]
def ignore[T]: Behavior[T] = Behavior.ignore
/**
* Construct an actor behavior that can react to both incoming messages and
@ -269,36 +173,20 @@ object Actor {
* [[ActorContext]] that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called stateful because processing the next message
* This constructor is called immutable because the behavior instance doesn't
* have or close over any mutable state. Processing the next message
* results in a new behavior that can potentially be different from this one.
* State is updated by returning a new behavior that holds the new immutable
* state.
*/
final case class Stateful[T](
behavior: (ActorContext[T], T) Behavior[T],
signal: (ActorContext[T], Signal) Behavior[T] = Behavior.unhandledSignal.asInstanceOf[(ActorContext[T], Signal) Behavior[T]])
extends Behavior[T] {
override def management(ctx: AC[T], msg: Signal): Behavior[T] = signal(ctx, msg)
override def message(ctx: AC[T], msg: T) = behavior(ctx, msg)
override def toString = s"Stateful(${LineNumbers(behavior)})"
}
def immutable[T](onMessage: (ActorContext[T], T) Behavior[T]): Immutable[T] =
new Immutable(onMessage)
/**
* Construct an actor behavior that can react to incoming messages but not to
* lifecycle signals. After spawning this actor from another actor (or as the
* guardian of an [[akka.typed.ActorSystem]]) it will be executed within an
* [[ActorContext]] that allows access to the system, spawning and watching
* other actors, etc.
*
* This constructor is called stateless because it cannot be replaced by
* another one after it has been installed. It is most useful for leaf actors
* that do not create child actors themselves.
*/
final case class Stateless[T](behavior: (ActorContext[T], T) Any) extends Behavior[T] {
override def management(ctx: AC[T], msg: Signal): Behavior[T] = Unhandled
override def message(ctx: AC[T], msg: T): Behavior[T] = {
behavior(ctx, msg)
this
}
override def toString = s"Static(${LineNumbers(behavior)})"
final class Immutable[T](onMessage: (ActorContext[T], T) Behavior[T])
extends BehaviorImpl.ImmutableBehavior[T](onMessage) {
def onSignal(onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Behavior[T] =
new BehaviorImpl.ImmutableBehavior(onMessage, onSignal)
}
/**
@ -306,35 +194,20 @@ object Actor {
* some action upon each received message or signal. It is most commonly used
* for logging or tracing what a certain Actor does.
*/
final case class Tap[T](
signal: (ActorContext[T], Signal) _,
mesg: (ActorContext[T], T) _,
behavior: Behavior[T]) extends Behavior[T] {
private def canonical(behv: Behavior[T]): Behavior[T] =
if (isUnhandled(behv)) Unhandled
else if (behv eq sameBehavior) Same
else if (isAlive(behv)) Tap(signal, mesg, behv)
else Stopped
override def management(ctx: AC[T], msg: Signal): Behavior[T] = {
signal(ctx, msg)
canonical(behavior.management(ctx, msg))
}
override def message(ctx: AC[T], msg: T): Behavior[T] = {
mesg(ctx, msg)
canonical(behavior.message(ctx, msg))
}
override def toString = s"Tap(${LineNumbers(signal)},${LineNumbers(mesg)},$behavior)"
}
def tap[T](
onMessage: Function2[ActorContext[T], T, _],
onSignal: Function2[ActorContext[T], Signal, _], // FIXME use partial function here also?
behavior: Behavior[T]): Behavior[T] =
BehaviorImpl.tap(onMessage, onSignal, behavior)
/**
* Behavior decorator that copies all received message to the designated
* monitor [[akka.typed.ActorRef]] before invoking the wrapped behavior. The
* wrapped behavior can evolve (i.e. be stateful) without needing to be
* wrapped behavior can evolve (i.e. return different behavior) without needing to be
* wrapped in a `monitor` call again.
*/
object Monitor {
def apply[T](monitor: ActorRef[T], behavior: Behavior[T]): Tap[T] = Tap(unitFunction, (_, msg) monitor ! msg, behavior)
}
def monitor[T](monitor: ActorRef[T], behavior: Behavior[T]): Behavior[T] =
tap((_, msg) monitor ! msg, unitFunction, behavior)
/**
* Wrap the given behavior such that it is restarted (i.e. reset to its
@ -342,48 +215,32 @@ object Actor {
* subclass thereof. Exceptions that are not subtypes of `Thr` will not be
* caught and thus lead to the termination of the actor.
*
* It is possible to specify that the actor shall not be restarted but
* resumed. This entails keeping the same state as before the exception was
* thrown and is thus less safe. If you use `OnFailure.RESUME` you should at
* least not hold mutable data fields or collections within the actor as those
* might be in an inconsistent state (the exception might have interrupted
* normal processing); avoiding mutable state is possible by returning a fresh
* behavior with the new state after every message.
* It is possible to specify different supervisor strategies, such as restart,
* resume, backoff.
*
* Example:
* {{{
* val dbConnector: Behavior[DbCommand] = ...
* val dbRestarts = Restarter[DbException].wrap(dbConnector)
* val dbRestarts = Restarter[DbException]().wrap(dbConnector)
* }}}
*/
object Restarter {
class Apply[Thr <: Throwable](c: ClassTag[Thr], resume: Boolean) {
def wrap[T](b: Behavior[T]): Behavior[T] = patterns.Restarter(Behavior.validateAsInitial(b), resume)()(c)
def mutableWrap[T](b: Behavior[T]): Behavior[T] = patterns.MutableRestarter(Behavior.validateAsInitial(b), resume)(c)
}
def restarter[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy = SupervisorStrategy.restart): Restarter[Thr] =
new Restarter(implicitly, strategy)
def apply[Thr <: Throwable: ClassTag](resume: Boolean = false): Apply[Thr] = new Apply(implicitly, resume)
final class Restarter[Thr <: Throwable: ClassTag](c: ClassTag[Thr], strategy: SupervisorStrategy) {
def wrap[T](b: Behavior[T]): Behavior[T] = akka.typed.internal.Restarter(Behavior.validateAsInitial(b), strategy)(c)
}
/**
* Support for scheduled `self` messages in an actor.
* It takes care of the lifecycle of the timers such as cancelling them when the actor
* is restarted or stopped.
* @see [[TimerScheduler]]
*/
def withTimers[T](factory: TimerScheduler[T] Behavior[T]): Behavior[T] =
TimerSchedulerImpl.withTimers(factory)
// TODO
// final case class Selective[T](timeout: FiniteDuration, selector: PartialFunction[T, Behavior[T]], onTimeout: () Behavior[T])
/**
* INTERNAL API.
*/
private[akka] val _unhandledFunction = (_: Any) Unhandled[Nothing]
/**
* INTERNAL API.
*/
private[akka] def unhandledFunction[T, U] = _unhandledFunction.asInstanceOf[(T Behavior[U])]
/**
* INTERNAL API.
*/
private[akka] val _unitFunction = (_: ActorContext[Any], _: Any) ()
/**
* INTERNAL API.
*/
private[akka] def unitFunction[T] = _unitFunction.asInstanceOf[((ActorContext[T], Signal) Unit)]
}

View file

@ -0,0 +1,161 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.scaladsl
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration.FiniteDuration
import akka.annotation.ApiMayChange
import akka.annotation.DoNotInherit
import akka.typed.ActorRef
import akka.typed.ActorSystem
import akka.typed.Behavior
import akka.typed.Props
import akka.typed.EmptyProps
/**
* An Actor is given by the combination of a [[Behavior]] and a context in
* which this behavior is executed. As per the Actor Model an Actor can perform
* the following actions when processing a message:
*
* - send a finite number of messages to other Actors it knows
* - create a finite number of Actors
* - designate the behavior for the next message
*
* In Akka the first capability is accessed by using the `!` or `tell` method
* on an [[ActorRef]], the second is provided by [[ActorContext#spawn]]
* and the third is implicit in the signature of [[Behavior]] in that the next
* behavior is always returned from the message processing logic.
*
* An `ActorContext` in addition provides access to the Actors own identity (`self`),
* the [[ActorSystem]] it is part of, methods for querying the list of child Actors it
* created, access to [[Terminated DeathWatch]] and timed message scheduling.
*/
@DoNotInherit
@ApiMayChange
trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T]
/**
* Get the `javadsl` of this `ActorContext`.
*/
def asJava: akka.typed.javadsl.ActorContext[T]
/**
* The identity of this Actor, bound to the lifecycle of this Actor instance.
* An Actor with the same name that lives before or after this instance will
* have a different [[ActorRef]].
*/
def self: ActorRef[T]
/**
* Return the mailbox capacity that was configured by the parent for this actor.
*/
def mailboxCapacity: Int
/**
* The [[ActorSystem]] to which this Actor belongs.
*/
def system: ActorSystem[Nothing]
/**
* The list of child Actors created by this Actor during its lifetime that
* are still alive, in no particular order.
*/
def children: Iterable[ActorRef[Nothing]]
/**
* The named child Actor if it is alive.
*/
def child(name: String): Option[ActorRef[Nothing]]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] under a randomly chosen name.
* It is good practice to name Actors wherever practical.
*/
def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U]
/**
* Create a child Actor from the given [[akka.typed.Behavior]] and with the given name.
*/
def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U]
/**
* Force the child Actor under the given name to terminate after it finishes
* processing its current message. Nothing happens if the ActorRef does not
* refer to a current child actor.
*
* @return whether the passed-in [[ActorRef]] points to a current child Actor
*/
def stop[U](child: ActorRef[U]): Boolean
/**
* Register for [[Terminated]] notification once the Actor identified by the
* given [[ActorRef]] terminates. This message is also sent when the watched actor
* is on a node that has been removed from the cluster when using akka-cluster
* or has been marked unreachable when using akka-remote directly
*/
def watch[U](other: ActorRef[U]): Unit
/**
* Register for termination notification with a custom message once the Actor identified by the
* given [[ActorRef]] terminates. This message is also sent when the watched actor
* is on a node that has been removed from the cluster when using akka-cluster
* or has been marked unreachable when using akka-remote directly.
*/
def watchWith[U](other: ActorRef[U], msg: T): Unit
/**
* Revoke the registration established by `watch`. A [[Terminated]]
* notification will not subsequently be received for the referenced Actor.
*/
def unwatch[U](other: ActorRef[U]): Unit
/**
* Schedule the sending of a notification in case no other
* message is received during the given period of time. The timeout starts anew
* with each received message. Provide `Duration.Undefined` to switch off this
* mechanism.
*/
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
* the given time period has elapsed. The scheduled action can be cancelled
* by invoking [[akka.actor.Cancellable#cancel]] on the returned
* handle.
*/
def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): akka.actor.Cancellable
/**
* This Actors execution context. It can be used to run asynchronous tasks
* like [[scala.concurrent.Future]] combinators.
*/
implicit def executionContext: ExecutionContextExecutor
/**
* Create a child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*
* The name of the child actor will be composed of a unique identifier
* starting with a dollar sign to which the given `name` argument is
* appended, with an inserted hyphen between these two parts. Therefore
* the given `name` argument does not need to be unique within the scope
* of the parent actor.
*/
def spawnAdapter[U](f: U T, name: String): ActorRef[U]
/**
* Create an anonymous child actor that will wrap messages such that other Actors
* protocols can be ingested by this Actor. You are strongly advised to cache
* these ActorRefs or to stop them when no longer needed.
*/
def spawnAdapter[U](f: U T): ActorRef[U]
}

View file

@ -13,7 +13,7 @@ import akka.typed.internal.FunctionRef
import akka.actor.RootActorPath
import akka.actor.Address
import akka.typed.ActorRef
import akka.typed.adapter
import akka.typed.internal.{ adapter adapt }
/**
* The ask-pattern implements the initiator side of a requestreply protocol.
@ -38,9 +38,9 @@ object AskPattern {
implicit class Askable[T](val ref: ActorRef[T]) extends AnyVal {
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)
case a: adapt.ActorRefAdapter[_] askUntyped(ref, a.untyped, timeout, f)
case a: adapt.ActorSystemAdapter[_] askUntyped(ref, a.untyped.guardian, timeout, f)
case _ ask(ref, timeout, scheduler, f)
}
}
@ -50,15 +50,15 @@ object AskPattern {
private[this] val (_ref: ActorRef[U], _future: Future[U], _promiseRef) =
if (untyped.isTerminated)
(
adapter.ActorRefAdapter[U](untyped.provider.deadLetters),
adapt.ActorRefAdapter[U](untyped.provider.deadLetters),
Future.failed[U](new AskTimeoutException(s"Recipient[$target] had already been terminated.")), null)
else if (timeout.duration.length <= 0)
(
adapter.ActorRefAdapter[U](untyped.provider.deadLetters),
adapt.ActorRefAdapter[U](untyped.provider.deadLetters),
Future.failed[U](new IllegalArgumentException(s"Timeout length must be positive, question not sent to [$target]")), null)
else {
val a = PromiseActorRef(untyped.provider, timeout, target, "unknown")
val b = adapter.ActorRefAdapter[U](a)
val b = adapt.ActorRefAdapter[U](a)
(b, a.result.future.asInstanceOf[Future[U]], a)
}

View file

@ -0,0 +1,62 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.typed.scaladsl
import scala.concurrent.duration.FiniteDuration
/**
* Support for scheduled `self` messages in an actor.
* It is used with `Actor.withTimers`.
* Timers are bound to the lifecycle of the actor that owns it,
* and thus are cancelled automatically when it is restarted or stopped.
*
* `TimerScheduler` is not thread-safe, i.e. it must only be used within
* the actor that owns it.
*/
trait TimerScheduler[T] {
/**
* Start a periodic timer that will send `msg` to the `self` actor at
* a fixed `interval`.
*
* Each timer has a key and if a new timer with same key is started
* the previous is cancelled and it's guaranteed that a message from the
* previous timer is not received, even though it might already be enqueued
* in the mailbox when the new timer is started.
*/
def startPeriodicTimer(key: Any, msg: T, interval: FiniteDuration): Unit
/**
* Start a timer that will send `msg` once to the `self` actor after
* the given `timeout`.
*
* Each timer has a key and if a new timer with same key is started
* the previous is cancelled and it's guaranteed that a message from the
* previous timer is not received, even though it might already be enqueued
* in the mailbox when the new timer is started.
*/
def startSingleTimer(key: Any, msg: T, timeout: FiniteDuration): Unit
/**
* Check if a timer with a given `key` is active.
*/
def isTimerActive(key: Any): Boolean
/**
* Cancel a timer with a given `key`.
* If canceling a timer that was already canceled, or key never was used to start a timer
* this operation will do nothing.
*
* It is guaranteed that a message from a canceled timer, including its previous incarnation
* for the same key, will not be received by the actor, even though the message might already
* be enqueued in the mailbox when cancel is called.
*/
def cancel(key: Any): Unit
/**
* Cancel all timers.
*/
def cancelAll(): Unit
}

Some files were not shown because too many files have changed in this diff Show more