Merge pull request #22999 from ktoso/wip-typed-to-master
Merge Akka Typed to master branch
This commit is contained in:
commit
70d69c5fb2
106 changed files with 8303 additions and 3638 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
@@@
|
||||
@@@
|
||||
|
|
|
|||
273
akka-docs/src/main/paradox/java/typed.md
Normal file
273
akka-docs/src/main/paradox/java/typed.md
Normal 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 function’s `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 request–reply
|
||||
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 client’s 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 session’s
|
||||
`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 don’t
|
||||
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.
|
||||
|
|
@ -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 function’s `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 Actor’s 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 client’s 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` Actor’s
|
||||
`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.
|
||||
|
|
|
|||
6
akka-typed-testkit/build.sbt
Normal file
6
akka-typed-testkit/build.sbt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import akka.{ AkkaBuild, Formatting, OSGi }
|
||||
|
||||
AkkaBuild.defaultSettings
|
||||
Formatting.formatSettings
|
||||
|
||||
disablePlugins(MimaPlugin)
|
||||
20
akka-typed-testkit/src/main/resources/reference.conf
Normal file
20
akka-typed-testkit/src/main/resources/reference.conf
Normal 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
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"))
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
8
akka-typed-tests/build.sbt
Normal file
8
akka-typed-tests/build.sbt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import akka.{ AkkaBuild, Formatting }
|
||||
|
||||
AkkaBuild.defaultSettings
|
||||
AkkaBuild.mayChangeSettings
|
||||
Formatting.formatSettings
|
||||
|
||||
disablePlugins(MimaPlugin)
|
||||
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
106
akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java
Normal file
106
akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
207
akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java
Normal file
207
akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
4
akka-typed-tests/src/test/resources/reference.conf
Normal file
4
akka-typed-tests/src/test/resources/reference.conf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
akka.typed {
|
||||
# for the akka.actor.ExtensionSpec
|
||||
library-extensions += "akka.typed.InstanceCountingExtension"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
620
akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
Normal file
620
akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
Normal 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
|
||||
|
||||
}
|
||||
167
akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala
Normal file
167
akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala
Normal 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
|
||||
|
||||
}
|
||||
161
akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala
Normal file
161
akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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(_)))
|
||||
}
|
||||
|
||||
423
akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala
Normal file
423
akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala
Normal 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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
230
akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala
Normal file
230
akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala
Normal 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
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
71
akka-typed-tests/src/test/scala/akka/typed/WatchSpec.scala
Normal file
71
akka-typed-tests/src/test/scala/akka/typed/WatchSpec.scala
Normal 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
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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]]
|
||||
|
||||
|
|
@ -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))
|
||||
|
|
@ -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]
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* Behavior’s type hierarchy. Signals are not transformed.
|
||||
*
|
||||
* <code><pre>
|
||||
* Behavior<String> s = stateless((ctx, msg) -> System.out.println(msg))
|
||||
* Behavior<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 actor’s 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 Actor’s 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 Actor’s 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 Actor’s
|
||||
* 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
|
||||
* Actor’s 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);
|
||||
|
||||
}
|
||||
|
|
@ -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} []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 system’s [[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]] hierarchies—this 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]] hierarchies—this 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]] hierarchies—this 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 actor’s 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
|
||||
* framework—this 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
145
akka-typed/src/main/scala/akka/typed/Extensions.scala
Normal file
145
akka-typed/src/main/scala/akka/typed/Extensions.scala
Normal 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
|
||||
}
|
||||
|
||||
|
|
@ -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 Actor’s 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
|
||||
|
|
|
|||
270
akka-typed/src/main/scala/akka/typed/Props.scala
Normal file
270
akka-typed/src/main/scala/akka/typed/Props.scala
Normal 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 actor’s 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
|
||||
* framework—this 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)
|
||||
}
|
||||
|
|
@ -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
|
||||
* Behavior’s 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 actor’s
|
||||
* 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
|
||||
* Actor’s 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])]
|
||||
|
||||
}
|
||||
119
akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala
Normal file
119
akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
@ -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._
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
179
akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala
Normal file
179
akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala
Normal 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)"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
278
akka-typed/src/main/scala/akka/typed/internal/Restarter.scala
Normal file
278
akka-typed/src/main/scala/akka/typed/internal/Restarter.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()))
|
||||
}
|
||||
|
||||
}
|
||||
280
akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala
Normal file
280
akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala
Normal 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 actor’s 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
|
||||
* Behavior’s type hierarchy. Signals are not transformed.
|
||||
*
|
||||
* Example:
|
||||
* {{{
|
||||
* Behavior<String> s = immutable((ctx, msg) -> {
|
||||
* System.out.println(msg);
|
||||
* return same();
|
||||
* });
|
||||
* Behavior<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]
|
||||
}
|
||||
}
|
||||
173
akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala
Normal file
173
akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala
Normal 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 Actor’s 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 Actor’s 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 Actor’s
|
||||
* 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 Actor’s
|
||||
* 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]
|
||||
|
||||
}
|
||||
115
akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala
Normal file
115
akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala
Normal 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)
|
||||
}
|
||||
14
akka-typed/src/main/scala/akka/typed/javadsl/Ask.scala
Normal file
14
akka-typed/src/main/scala/akka/typed/javadsl/Ask.scala
Normal 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))
|
||||
}
|
||||
|
|
@ -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<String> 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<String> 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<String> 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<String> 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]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String> 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<String> 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Actor’s 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 Actor’s 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 Actor’s
|
||||
* 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 Actor’s
|
||||
* 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
|
||||
* Behavior’s 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($b)"
|
||||
* case i: BigInteger => s"BigInteger($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
|
||||
* Behavior’s 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($b)"
|
||||
* case i: BigInteger => s"BigInteger($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 actor’s 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)]
|
||||
|
||||
}
|
||||
|
|
|
|||
161
akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala
Normal file
161
akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala
Normal 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 Actor’s 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 Actor’s 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 Actor’s
|
||||
* 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 Actor’s
|
||||
* 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]
|
||||
|
||||
}
|
||||
|
|
@ -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 request–reply 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)
|
||||
}
|
||||
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue