From ae467b1528e240a00d75536adcc31606dbc08bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Tue, 4 Apr 2017 13:17:16 +0200 Subject: [PATCH 01/50] Interpreted behaviors in Akka Typed (#22670) --- .../code/docs/akka/typed/IntroSpec.scala | 32 +- .../main/java/akka/typed/javadsl/Actor.java | 33 +- .../src/main/scala/akka/typed/Behavior.scala | 141 +++-- .../src/main/scala/akka/typed/Effects.scala | 13 +- .../main/scala/akka/typed/EventStream.scala | 27 +- .../scala/akka/typed/MessageAndSignals.scala | 9 - .../src/main/scala/akka/typed/ScalaDSL.scala | 494 ------------------ .../akka/typed/adapter/ActorAdapter.scala | 20 +- .../scala/akka/typed/internal/ActorCell.scala | 5 +- .../akka/typed/internal/ActorSystemImpl.scala | 12 +- .../akka/typed/internal/DeathWatch.scala | 2 +- .../akka/typed/internal/EventStreamImpl.scala | 52 +- .../typed/internal/SupervisionMechanics.scala | 9 +- .../akka/typed/patterns/Receptionist.scala | 28 +- .../scala/akka/typed/patterns/Restarter.scala | 75 ++- .../scala/akka/typed/scaladsl/Actor.scala | 64 +-- .../scala/akka/typed/ActorContextSpec.scala | 216 +++----- .../src/test/scala/akka/typed/AskSpec.scala | 11 +- .../test/scala/akka/typed/BehaviorSpec.scala | 333 ++---------- .../scala/akka/typed/PerformanceSpec.scala | 17 +- .../src/test/scala/akka/typed/StepWise.scala | 51 +- .../src/test/scala/akka/typed/TypedSpec.scala | 42 +- .../akka/typed/internal/ActorCellSpec.scala | 39 +- .../akka/typed/internal/ActorSystemSpec.scala | 24 +- .../akka/typed/internal/EventStreamSpec.scala | 18 +- .../typed/patterns/ReceptionistSpec.scala | 7 +- 26 files changed, 531 insertions(+), 1243 deletions(-) delete mode 100644 akka-typed/src/main/scala/akka/typed/ScalaDSL.scala diff --git a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala b/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala index 233f5d403c..4f0b9bc83a 100644 --- a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala +++ b/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala @@ -114,22 +114,24 @@ class IntroSpec extends TypedSpec { //#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 + Deferred { ctx => + val chatRoom = ctx.spawn(ChatRoom.chatRoom(), "chatroom") + val gabblerRef = ctx.spawn(gabbler, "gabbler") + ctx.watch(gabblerRef) + chatRoom ! GetSession("ol’ Gabbler", gabblerRef) + + Stateful( + behavior = (_, _) ⇒ Unhandled, + signal = { (ctx, sig) ⇒ + sig match { + case Terminated(ref) ⇒ + Stopped + case _ ⇒ + Unhandled + } } - }) + ) + } val system = ActorSystem("ChatRoomDemo", main) Await.result(system.whenTerminated, 1.second) diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java index 9bfddb38c5..5b7a49b1ee 100644 --- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java +++ b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java @@ -14,10 +14,7 @@ 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.*; import akka.typed.patterns.Restarter; import akka.typed.scaladsl.Actor.Widened; import scala.reflect.ClassTag; @@ -30,7 +27,7 @@ public abstract class Actor { * same runtime performance (especially concerning allocations for function converters). */ - private static class Deferred extends Behavior { + private static class Deferred extends Behavior.DeferredBehavior { final akka.japi.function.Function, Behavior> producer; public Deferred(akka.japi.function.Function, Behavior> producer) { @@ -38,20 +35,12 @@ public abstract class Actor { } @Override - public Behavior management(akka.typed.ActorContext 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 message(akka.typed.ActorContext ctx, T msg) throws Exception { - throw new IllegalStateException("Deferred behavior must receive PreStart as first signal"); + public Behavior apply(akka.typed.ActorContext ctx) throws Exception { + return producer.apply(ctx); } } - private static class Stateful extends Behavior { + private static class Stateful extends ExtensibleBehavior { final Function2, T, Behavior> message; final Function2, Signal, Behavior> signal; @@ -72,7 +61,7 @@ public abstract class Actor { } } - private static class Stateless extends Behavior { + private static class Stateless extends ExtensibleBehavior { final Procedure2, T> message; public Stateless(Procedure2, T> message) { @@ -91,7 +80,7 @@ public abstract class Actor { } } - private static class Tap extends Behavior { + private static class Tap extends ExtensibleBehavior { final Procedure2, Signal> signal; final Procedure2, T> message; final Behavior behavior; @@ -115,15 +104,15 @@ public abstract class Actor { } @Override - public Behavior management(akka.typed.ActorContext ctx, Signal msg) throws Exception { - signal.apply(ctx, msg); - return canonicalize(behavior.management(ctx, msg)); + public Behavior management(akka.typed.ActorContext ctx, Signal signal) throws Exception { + this.signal.apply(ctx, signal); + return canonicalize(Behavior.interpretSignal(behavior, ctx, signal)); } @Override public Behavior message(akka.typed.ActorContext ctx, T msg) throws Exception { message.apply(ctx, msg); - return canonicalize(behavior.message(ctx, msg)); + return canonicalize(Behavior.interpretMessage(behavior, ctx, msg)); } } diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala index 3821f236e1..7f5497e3b2 100644 --- a/akka-typed/src/main/scala/akka/typed/Behavior.scala +++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala @@ -3,7 +3,9 @@ */ package akka.typed -import akka.annotation.InternalApi +import akka.annotation.{ DoNotInherit, InternalApi } + +import scala.annotation.tailrec /** * The behavior of an actor defines how it reacts to the messages that it @@ -13,14 +15,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 @@ -55,12 +75,6 @@ abstract class Behavior[T] { @throws(classOf[Exception]) def message(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 { @@ -68,30 +82,27 @@ object Behavior { /** * INTERNAL API. */ + @InternalApi @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 + private[akka] object EmptyBehavior extends Behavior[Any] { override def toString = "Empty" } /** * INTERNAL API. */ + @InternalApi @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 + private[akka] object IgnoreBehavior extends Behavior[Any] { override def toString = "Ignore" } /** * INTERNAL API. */ + @InternalApi @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") + private[akka] object UnhandledBehavior extends Behavior[Nothing] { override def toString = "Unhandled" } @@ -99,15 +110,25 @@ object Behavior { * INTERNAL API */ @InternalApi private[akka] val unhandledSignal: (ActorContext[Nothing], Signal) ⇒ Behavior[Nothing] = - (_, _) ⇒ unhandledBehavior + (_, _) ⇒ UnhandledBehavior + + /** + * INTERNAL API. + */ + @InternalApi + @DoNotInherit + @SerialVersionUID(1L) + private[akka] abstract class DeferredBehavior[T] extends Behavior[T] { + /** "undefer" the deferred behavior */ + @throws(classOf[Exception]) + def apply(ctx: ActorContext[T]): Behavior[T] + } /** * 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" } @@ -115,14 +136,7 @@ object Behavior { * 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 Behavior[Nothing] { override def toString = "Stopped" } @@ -132,13 +146,24 @@ object Behavior { * 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] = + 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(undefer(deferred, ctx), deferred, ctx) + case other ⇒ other } + @tailrec + def undefer[T](deferredBehavior: DeferredBehavior[T], ctx: ActorContext[T]): Behavior[T] = { + val behavior = deferredBehavior(ctx) + + behavior match { + case innerDeferred: DeferredBehavior[T] @unchecked ⇒ undefer(innerDeferred, ctx) + case _ ⇒ behavior + } + } + /** * Validate the given behavior as a suitable initial actor behavior; most * notably the behavior can neither be `Same` nor `Unhandled`. Starting @@ -146,27 +171,59 @@ object Behavior { */ 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. + * Validate the given behavior as initial, initialize it if it is deferred. */ 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 + validateAsInitial(behavior) match { + case d: DeferredBehavior[T] @unchecked ⇒ validateAsInitial(undefer(d, ctx)) + case x ⇒ x + } } /** * Returns true if the given behavior is not stopped. */ - def isAlive[T](behavior: Behavior[T]): Boolean = behavior ne stoppedBehavior + def isAlive[T](behavior: Behavior[T]): Boolean = behavior ne StoppedBehavior /** * Returns true if the given behavior is the special `Unhandled` marker. */ - def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq unhandledBehavior + def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq UnhandledBehavior + + /** + * 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 _: DeferredBehavior[_] ⇒ throw new IllegalArgumentException(s"deferred should not be passed to interpreter") + case IgnoreBehavior ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]] + case StoppedBehavior ⇒ StoppedBehavior.asInstanceOf[Behavior[T]] + case EmptyBehavior ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]] + case ext: ExtensibleBehavior[T] @unchecked ⇒ + val possiblyDeferredResult = msg match { + case signal: Signal ⇒ ext.management(ctx, signal) + case msg ⇒ ext.message(ctx, msg.asInstanceOf[T]) + } + possiblyDeferredResult match { + case d: DeferredBehavior[T] @unchecked ⇒ undefer(d, ctx) + case notDeferred ⇒ notDeferred + } + } + } diff --git a/akka-typed/src/main/scala/akka/typed/Effects.scala b/akka-typed/src/main/scala/akka/typed/Effects.scala index 1b13fb9e61..75eb2832b3 100644 --- a/akka-typed/src/main/scala/akka/typed/Effects.scala +++ b/akka-typed/src/main/scala/akka/typed/Effects.scala @@ -49,15 +49,18 @@ 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.preStart(_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) + def run(msg: T): Unit = { + current = Behavior.canonicalize(Behavior.interpretMessage(current, this, msg), current, this) + } + + def signal(signal: Signal): Unit = { + current = Behavior.canonicalize(Behavior.interpretSignal(current, this, signal), current, this) + } override def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = { val ref = super.spawnAnonymous(behavior) diff --git a/akka-typed/src/main/scala/akka/typed/EventStream.scala b/akka-typed/src/main/scala/akka/typed/EventStream.scala index 7ac81dcc6e..b819b7b22d 100644 --- a/akka-typed/src/main/scala/akka/typed/EventStream.scala +++ b/akka-typed/src/main/scala/akka/typed/EventStream.scala @@ -6,6 +6,8 @@ package akka.typed import akka.{ event ⇒ e } import akka.event.Logging.{ LogEvent, LogLevel, StdOutLogger } import akka.testkit.{ EventFilter, TestEvent ⇒ TE } +import akka.typed.Behavior.DeferredBehavior + import scala.annotation.tailrec /** @@ -66,14 +68,15 @@ 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] { () ⇒ + val initialBehavior = { + // TODO avoid depending on dsl here? + import scaladsl.Actor._ + Deferred[Command] { _ ⇒ + scaladsl.Actor.Stateful[Command] { + case (ctx, Initialize(eventStream, replyTo)) ⇒ + val log = ctx.spawn(Deferred[AnyRef] { childCtx ⇒ var filters: List[EventFilter] = Nil def filter(event: LogEvent): Boolean = filters exists (f ⇒ try { f(event) } catch { case e: Exception ⇒ false }) @@ -89,19 +92,23 @@ class DefaultLogger extends Logger with StdOutLogger { 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) + Stateless[AnyRef] { + case (_, TE.Mute(filters)) ⇒ filters foreach addFilter + case (_, TE.UnMute(filters)) ⇒ filters foreach removeFilter + case (_, event: LogEvent) ⇒ if (!filter(event)) print(event) + case _ ⇒ Unhandled } }, "logger") + eventStream.subscribe(log, classOf[TE.Mute]) eventStream.subscribe(log, classOf[TE.UnMute]) ctx.watch(log) // sign death pact replyTo ! log + Empty } } + } } class DefaultLoggingFilter(settings: Settings, eventStream: EventStream) extends e.DefaultLoggingFilter(() ⇒ eventStream.logLevel) diff --git a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala index e5b06d7b93..0b751697bf 100644 --- a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala +++ b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala @@ -27,15 +27,6 @@ final case class DeadLetter(msg: Any) */ 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 -} - /** * Lifecycle signal that is fired upon restart of the Actor before replacing * the behavior with the fresh one (i.e. this signal is received within the diff --git a/akka-typed/src/main/scala/akka/typed/ScalaDSL.scala b/akka-typed/src/main/scala/akka/typed/ScalaDSL.scala deleted file mode 100644 index bb3b46bca2..0000000000 --- a/akka-typed/src/main/scala/akka/typed/ScalaDSL.scala +++ /dev/null @@ -1,494 +0,0 @@ -/** - * Copyright (C) 2014-2017 Lightbend Inc. - */ -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])] - -} diff --git a/akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala b/akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala index bd83b883f4..0695fdb041 100644 --- a/akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala @@ -25,21 +25,22 @@ private[typed] class ActorAdapter[T](_initialBehavior: Behavior[T]) extends a.Ac failures -= ref Terminated(ActorRefAdapter(ref))(ex) } else Terminated(ActorRefAdapter(ref))(null) - next(behavior.management(ctx, msg), msg) + next(Behavior.interpretSignal(behavior, ctx, msg), msg) case a.ReceiveTimeout ⇒ - next(behavior.message(ctx, ctx.receiveTimeoutMsg), ctx.receiveTimeoutMsg) + next(Behavior.interpretMessage(behavior, ctx, ctx.receiveTimeoutMsg), ctx.receiveTimeoutMsg) case msg: T @unchecked ⇒ - next(behavior.message(ctx, msg), msg) + next(Behavior.interpretMessage(behavior, ctx, msg), msg) } private def next(b: Behavior[T], msg: Any): Unit = { if (isUnhandled(b)) unhandled(msg) - behavior = canonicalize(b, behavior) + behavior = canonicalize(b, behavior, ctx) if (!isAlive(behavior)) context.stop(self) } 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) } @@ -51,11 +52,12 @@ private[typed] class ActorAdapter[T](_initialBehavior: Behavior[T]) extends a.Ac } override def preStart(): Unit = - next(behavior.management(ctx, PreStart), PreStart) + behavior = Behavior.preStart(behavior, ctx) override def preRestart(reason: Throwable, message: Option[Any]): Unit = - next(behavior.management(ctx, PreRestart), PreRestart) + next(Behavior.interpretSignal(behavior, 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) + behavior = Behavior.preStart(behavior, ctx) + override def postStop(): Unit = { + next(Behavior.interpretSignal(behavior, ctx, PostStop), PostStop) + } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index 67cb6ab60a..39d74d4e74 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -58,6 +58,7 @@ object ActorCell { final val SuspendedState = 1 final val SuspendedWaitForChildrenState = 2 + /** compile time constant */ final val Debug = false } @@ -323,7 +324,7 @@ 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) + behavior = Behavior.canonicalize(b, behavior, ctx) if (!Behavior.isAlive(behavior)) self.sendSystem(Terminate()) } @@ -351,7 +352,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") } diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index d65d0255c6..688c34d4b0 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -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 - val systemGuardianBehavior: Behavior[SystemCommand] = - ContextAware { ctx ⇒ + val systemGuardianBehavior: Behavior[SystemCommand] = { + // TODO avoid depending on dsl here? + import scaladsl.Actor._ + Deferred { _ ⇒ var i = 1 - Static { - case create: CreateSystemActor[t] ⇒ + Stateless { + case (ctx, create: CreateSystemActor[t]) ⇒ val name = s"$i-${create.name}" i += 1 create.replyTo ! ctx.spawn(create.behavior, name, create.deployment) } } + } } /* diff --git a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala index d5016fb530..c072fa3264 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala @@ -78,7 +78,7 @@ private[typed] trait DeathWatch[T] { } if (maySend) { val t = Terminated(actor)(failure) - next(behavior.management(ctx, t), t) + next(Behavior.interpretSignal(behavior, ctx, t), t) } } if (isTerminating && terminatingMap.isEmpty) { diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala index ccee43cd3d..9814f0ed98 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala @@ -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,31 @@ 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.Stateful[Command]({ + case (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) + 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 (ctx, 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 (ctx, UnregisterIfNoMoreSubscribedChannels(actor)) ⇒ + if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unwatching $actor, since has no subscriptions")) + ctx.unwatch[Nothing](actor) + Actor.Same + }, { + 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 + case (_, _) ⇒ Actor.Unhandled + }) } } @@ -142,9 +146,13 @@ 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.Stateless + Stateless[a.UnhandledMessage] { + case (_, a.UnhandledMessage(msg, sender, rcp)) ⇒ + publish(Debug(rcp.path.toString, rcp.getClass, "unhandled message from " + sender + ": " + msg)) + } } def startStdoutLogger(settings: Settings) { diff --git a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala index 8706565e63..cd00406647 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala @@ -4,10 +4,11 @@ package akka.typed package internal -import scala.annotation.{ tailrec, switch } +import scala.annotation.{ switch, tailrec } import scala.util.control.NonFatal import scala.util.control.Exception.Catcher import akka.event.Logging +import akka.typed.Behavior.DeferredBehavior /** * 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 = Behavior.preStart(behavior, ctx) + if (!Behavior.isAlive(behavior)) self.sendSystem(Terminate()) true } @@ -89,7 +90,7 @@ private[typed] trait SupervisionMechanics[T] { /* * 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) + try if (a ne null) Behavior.canonicalize(Behavior.interpretSignal(a, ctx, PostStop), a, ctx) 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)) diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala index f93db3f075..fed6e0b6d2 100644 --- a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala +++ b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala @@ -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,18 +72,22 @@ 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] = Stateful[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 + } + }, { + case (ctx, Terminated(ref)) ⇒ behavior(map valueRemoved ref) - } + case x ⇒ Unhandled + }) } abstract class Receptionist diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala index 171b5b4875..5ba8872ac0 100644 --- a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala +++ b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala @@ -7,6 +7,7 @@ package patterns import scala.reflect.ClassTag import scala.util.control.NonFatal import akka.event.Logging +import akka.typed.Behavior.{ DeferredBehavior, SameBehavior, StoppedBehavior, UnhandledBehavior } import akka.typed.scaladsl.Actor._ /** @@ -14,43 +15,57 @@ import akka.typed.scaladsl.Actor._ * 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] { + behavior: Behavior[T] = initialBehavior) extends ExtensibleBehavior[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 restart(ctx: ActorContext[T], startedBehavior: Behavior[T]): Behavior[T] = { + try Behavior.interpretSignal(startedBehavior, ctx, PreRestart) catch { case NonFatal(_) ⇒ } + // no need to canonicalize, it's done in the calling methods + Behavior.preStart(initialBehavior, ctx) } - private def canonical(b: Behavior[T]): Behavior[T] = + private def canonical(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] = if (Behavior.isUnhandled(b)) Unhandled - else if (b eq Behavior.sameBehavior) Same + 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) + else { + b match { + case d: DeferredBehavior[_] ⇒ canonical(Behavior.undefer(d.asInstanceOf[DeferredBehavior[T]], ctx), ctx) + case b ⇒ Restarter[T, Thr](initialBehavior, resume)(b) + } + } + + private def preStart(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] = b match { + case d: DeferredBehavior[_] ⇒ Behavior.undefer(d.asInstanceOf[DeferredBehavior[T]], ctx) + case b ⇒ b + } override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = { + val startedBehavior = preStart(behavior, ctx) val b = - try behavior.management(ctx, signal) - catch { + try { + Behavior.interpretSignal(startedBehavior, 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) + if (resume) startedBehavior else restart(ctx, startedBehavior) } - canonical(b) + canonical(b, ctx) } override def message(ctx: ActorContext[T], msg: T): Behavior[T] = { + val startedBehavior = preStart(behavior, ctx) val b = - try behavior.message(ctx, msg) - catch { + try { + Behavior.interpretMessage(startedBehavior, 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) + if (resume) startedBehavior else restart(ctx, startedBehavior) } - canonical(b) + canonical(b, ctx) } } @@ -61,37 +76,41 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav * 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] { +final case class MutableRestarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T], resume: Boolean) extends ExtensibleBehavior[T] { - private[this] var current = initialBehavior + private[this] var current: Behavior[T] = _ + private def startCurrent(ctx: ActorContext[T]) = { + // we need to pre-start it the first time we have access to a context + if (current eq null) current = Behavior.preStart(initialBehavior, ctx) + } private def restart(ctx: ActorContext[T]): Behavior[T] = { - try current.management(ctx, PreRestart) catch { case NonFatal(_) ⇒ } - current = initialBehavior - current.management(ctx, PreStart) + try Behavior.interpretSignal(current, ctx, PreRestart) catch { case NonFatal(_) ⇒ } + Behavior.preStart(initialBehavior, ctx) } override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = { - val b = - try current.management(ctx, signal) - catch { + startCurrent(ctx) + current = + try { + Behavior.interpretSignal(current, 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) + startCurrent(ctx) + current = + try Behavior.interpretMessage(current, 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 } } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index fdd5b47cc5..48d2309ca0 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -150,8 +150,6 @@ trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T] ⇒ object Actor { import Behavior._ - // FIXME check that all behaviors can cope with not getting PreStart as first message - final implicit class BehaviorDecorators[T](val behavior: Behavior[T]) extends AnyVal { /** * Widen the wrapped Behavior by placing a funnel in front of it: the supplied @@ -185,21 +183,21 @@ object Actor { * } * }}} */ - final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends Behavior[U] { - private def postProcess(behv: Behavior[T]): Behavior[U] = + final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends ExtensibleBehavior[U] { + private def postProcess(behv: Behavior[T], ctx: AC[T]): Behavior[U] = if (isUnhandled(behv)) Unhandled else if (isAlive(behv)) { - val next = canonicalize(behv, behavior) + val next = canonicalize(behv, behavior, ctx) 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 management(ctx: AC[U], signal: Signal): Behavior[U] = + postProcess(Behavior.interpretSignal(behavior, ctx.as[T], signal), ctx.as[T]) 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)) + case transformed ⇒ postProcess(Behavior.interpretMessage(behavior, ctx.as[T], transformed), ctx.as[T]) } override def toString: String = s"${behavior.toString}.widen(${LineNumbers(matcher)})" @@ -209,14 +207,9 @@ object Actor { * 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. */ - 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) - } - - override def message(ctx: AC[T], msg: T): Behavior[T] = - throw new IllegalStateException(s"Deferred must receive PreStart as first message (got $msg)") + final case class Deferred[T](factory: ActorContext[T] ⇒ Behavior[T]) extends DeferredBehavior[T] { + /** "undefer" the deferred behavior */ + override def apply(ctx: AC[T]): Behavior[T] = factory(ctx) override def toString: String = s"Deferred(${LineNumbers(factory)})" } @@ -227,7 +220,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] = SameBehavior.asInstanceOf[Behavior[T]] /** * Return this behavior from message processing in order to advise the @@ -235,7 +228,7 @@ 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]] + def Unhandled[T]: Behavior[T] = UnhandledBehavior.asInstanceOf[Behavior[T]] /* * TODO write a Behavior that waits for all child actors to stop and then @@ -250,17 +243,17 @@ object Actor { * signal that results from stopping this actor will NOT be passed to the * current behavior, it will be effectively ignored. */ - def Stopped[T]: Behavior[T] = stoppedBehavior.asInstanceOf[Behavior[T]] + def Stopped[T]: Behavior[T] = StoppedBehavior.asInstanceOf[Behavior[T]] /** * A behavior that treats every incoming message as unhandled. */ - def Empty[T]: Behavior[T] = emptyBehavior.asInstanceOf[Behavior[T]] + 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]] + def Ignore[T]: Behavior[T] = IgnoreBehavior.asInstanceOf[Behavior[T]] /** * Construct an actor behavior that can react to both incoming messages and @@ -275,7 +268,7 @@ object Actor { 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] { + extends ExtensibleBehavior[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)})" @@ -292,7 +285,7 @@ object Actor { * 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] { + final case class Stateless[T](behavior: (ActorContext[T], T) ⇒ Any) extends ExtensibleBehavior[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) @@ -307,23 +300,24 @@ object Actor { * 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] { + onMessage: Function2[ActorContext[T], T, _], + onSignal: Function2[ActorContext[T], Signal, _], + behavior: Behavior[T]) extends ExtensibleBehavior[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 if (behv eq SameBehavior) Same + else if (isAlive(behv)) Tap(onMessage, onSignal, behv) else Stopped - override def management(ctx: AC[T], msg: Signal): Behavior[T] = { - signal(ctx, msg) - canonical(behavior.management(ctx, msg)) + override def management(ctx: AC[T], signal: Signal): Behavior[T] = { + onSignal(ctx, signal) + canonical(Behavior.interpretSignal(behavior, ctx, signal)) } override def message(ctx: AC[T], msg: T): Behavior[T] = { - mesg(ctx, msg) - canonical(behavior.message(ctx, msg)) + onMessage(ctx, msg) + canonical(Behavior.interpretMessage(behavior, ctx, msg)) } - override def toString = s"Tap(${LineNumbers(signal)},${LineNumbers(mesg)},$behavior)" + override def toString = s"Tap(${LineNumbers(onSignal)},${LineNumbers(onMessage)},$behavior)" } /** @@ -333,7 +327,7 @@ object Actor { * 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 apply[T](monitor: ActorRef[T], behavior: Behavior[T]): Tap[T] = Tap((_, msg) ⇒ monitor ! msg, unitFunction, behavior) } /** diff --git a/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala index 93526d555e..9731861657 100644 --- a/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala @@ -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 { @@ -141,14 +140,15 @@ object ActorContextSpec { } 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 - }) + Actor.Stateful[Command]({ + case (_, _) ⇒ Actor.Unhandled + }, { + case (_, Terminated(_)) ⇒ Actor.Unhandled + case (_, sig) ⇒ + monitor ! GotSignal(sig) + Actor.Same + + }) case GetAdapter(replyTo, name) ⇒ replyTo ! Adapter(ctx.spawnAdapter(identity, name)) Actor.Same @@ -156,21 +156,17 @@ object ActorContextSpec { (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 { + Actor.Stateful({ + case (ctx, message) ⇒ message match { case ReceiveTimeout ⇒ monitor ! GotReceiveTimeout - Same + Actor.Same case Ping(replyTo) ⇒ replyTo ! Pong1 - Same + Actor.Same case Miss(replyTo) ⇒ replyTo ! Missed - Unhandled + Actor.Unhandled case Renew(replyTo) ⇒ replyTo ! Renewed subject(monitor) @@ -182,63 +178,71 @@ object ActorContextSpec { case Some(n) ⇒ ctx.spawn(patterns.Restarter[Command, Throwable](subject(mon), false)(), n) } replyTo ! Created(child) - Same + Actor.Same case SetTimeout(d, replyTo) ⇒ d match { case f: FiniteDuration ⇒ ctx.setReceiveTimeout(f, ReceiveTimeout) case _ ⇒ ctx.cancelReceiveTimeout() } replyTo ! TimeoutSet - Same + Actor.Same case Schedule(delay, target, msg, replyTo) ⇒ replyTo ! Scheduled ctx.schedule(delay, target, msg) - Same - case Stop ⇒ Stopped + Actor.Same + case Stop ⇒ Actor.Stopped case Kill(ref, replyTo) ⇒ if (ctx.stop(ref)) replyTo ! Killed else replyTo ! NotKilled - Same + Actor.Same case Watch(ref, replyTo) ⇒ ctx.watch[Nothing](ref) replyTo ! Watched - Same + Actor.Same case Unwatch(ref, replyTo) ⇒ ctx.unwatch[Nothing](ref) replyTo ! Unwatched - Same + Actor.Same case GetInfo(replyTo) ⇒ replyTo ! Info(ctx.self, ctx.system) - Same + Actor.Same case GetChild(name, replyTo) ⇒ replyTo ! Child(ctx.child(name)) - Same + Actor.Same case GetChildren(replyTo) ⇒ replyTo ! Children(ctx.children.toSet) - Same + Actor.Same case BecomeInert(replyTo) ⇒ replyTo ! BecameInert - Full { - case Msg(_, Ping(replyTo)) ⇒ + Actor.Stateful[Command] { + case (_, Ping(replyTo)) ⇒ replyTo ! Pong2 - Same - case Msg(_, Throw(ex)) ⇒ + Actor.Same + case (_, Throw(ex)) ⇒ throw ex - case _ ⇒ Same + case _ ⇒ Actor.Same } case BecomeCareless(replyTo) ⇒ replyTo ! BecameCareless - Full { - case Sig(_, Terminated(_)) ⇒ Unhandled - case Sig(_, sig) ⇒ - monitor ! GotSignal(sig) - Same - } + Actor.Stateful[Command]( + { + case _ ⇒ Actor.Unhandled + }, + { + case (_, Terminated(_)) ⇒ Actor.Unhandled + case (_, sig) ⇒ + monitor ! GotSignal(sig) + Actor.Same + }) case GetAdapter(replyTo, name) ⇒ replyTo ! Adapter(ctx.spawnAdapter(identity, name)) - Same + Actor.Same } - } + }, { + case (_, signal) ⇒ + monitor ! GotSignal(signal) + Actor.Same + }) } } @@ -276,12 +280,8 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( 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 steps = startWith.withKeepTraces(true)(ctx.spawn(props, "subject")) + proc(ctx, steps) }) @@ -299,11 +299,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) } @@ -356,10 +353,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( case (msg, (subj, log)) ⇒ msg should ===(GotSignal(PreRestart)) log.assertDone(expectTimeout) - subj - }.expectMessage(expectTimeout) { (msg, subj) ⇒ - msg should ===(GotSignal(PreStart)) - ctx.stop(subj) + ctx.stop(subj) }.expectMessage(expectTimeout) { (msg, _) ⇒ msg should ===(GotSignal(PostStop)) } @@ -382,11 +376,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) @@ -428,12 +420,7 @@ 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) }) @@ -464,11 +451,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), "A") subj ! Kill(other, ctx.self) child }.expectMessageKeep(expectTimeout) { (msg, _) ⇒ @@ -643,6 +628,14 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( 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]): Behavior[Command] = + Actor.Deferred(_ ⇒ Actor.Deferred(_ ⇒ subject(ctx.self))) + } + 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] = @@ -651,83 +644,4 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( 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 } diff --git a/akka-typed/src/test/scala/akka/typed/AskSpec.scala b/akka-typed/src/test/scala/akka/typed/AskSpec.scala index 4a124e287d..2e70cbed74 100644 --- a/akka-typed/src/test/scala/akka/typed/AskSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/AskSpec.scala @@ -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,11 +29,11 @@ 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] = Stateful[Msg] { + case (_, foo: Foo) ⇒ foo.replyTo ! "foo" Same - case Stop(r) ⇒ + case (_, Stop(r)) ⇒ r ! (()) Stopped } diff --git a/akka-typed/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed/src/test/scala/akka/typed/BehaviorSpec.scala index f371c9461b..f77ebc6ca1 100644 --- a/akka-typed/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/BehaviorSpec.scala @@ -9,6 +9,8 @@ import akka.japi.function.{ Function ⇒ F1e, Function2 ⇒ F2, Procedure2 ⇒ P import akka.japi.pf.{ FI, PFBuilder } import java.util.function.{ Function ⇒ F1 } +import akka.Done + class BehaviorSpec extends TypedSpec { sealed trait Command { @@ -59,11 +61,9 @@ class BehaviorSpec extends TypedSpec { def checkAux(command: Command, aux: Aux): Unit = () case class Init(behv: Behavior[Command], inbox: Inbox[Event], aux: Aux) { - def mkCtx(requirePreStart: Boolean = false): Setup = { + def mkCtx(): Setup = { val ctx = new EffectfulActorContext("ctx", behv, 1000, system) val msgs = inbox.receiveAll() - if (requirePreStart) msgs should ===(GotSignal(PreStart) :: Nil) - checkAux(PreStart, aux) Setup(ctx, inbox, aux) } } @@ -82,7 +82,7 @@ class BehaviorSpec extends TypedSpec { } def mkCtx(requirePreStart: Boolean = false): Setup = - init().mkCtx(requirePreStart) + init().mkCtx() implicit class Check(val setup: Setup) { def check(signal: Signal): Setup = { @@ -180,7 +180,7 @@ class BehaviorSpec extends TypedSpec { trait Unhandled extends Common { def `must return Unhandled`(): Unit = { val Setup(ctx, inbox, aux) = mkCtx() - ctx.currentBehavior.message(ctx, Miss) should be(Behavior.unhandledBehavior) + Behavior.interpretMessage(ctx.currentBehavior, ctx, Miss) should be(Behavior.UnhandledBehavior) inbox.receiveAll() should ===(Missed :: Nil) checkAux(Miss, aux) } @@ -190,7 +190,7 @@ class BehaviorSpec extends TypedSpec { def `must stop`(): Unit = { val Setup(ctx, inbox, aux) = mkCtx() ctx.run(Stop) - ctx.currentBehavior should be(Behavior.stoppedBehavior) + ctx.currentBehavior should be(Behavior.StoppedBehavior) checkAux(Stop, aux) } } @@ -254,31 +254,32 @@ class BehaviorSpec extends TypedSpec { } private def mkFull(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] = { - import ScalaDSL.{ Full, Msg, Sig, Same, Unhandled, Stopped } - Full { - case Sig(ctx, signal) ⇒ - monitor ! GotSignal(signal) - Same - case Msg(ctx, GetSelf) ⇒ + SActor.Stateful[Command]({ + case (ctx, GetSelf) ⇒ monitor ! Self(ctx.self) - Same - case Msg(ctx, Miss) ⇒ + SActor.Same + case (ctx, Miss) ⇒ monitor ! Missed - Unhandled - case Msg(ctx, Ignore) ⇒ + SActor.Unhandled + case (ctx, Ignore) ⇒ monitor ! Ignored - Same - case Msg(ctx, Ping) ⇒ + SActor.Same + case (ctx, Ping) ⇒ monitor ! Pong mkFull(monitor, state) - case Msg(ctx, Swap) ⇒ + case (ctx, Swap) ⇒ monitor ! Swapped mkFull(monitor, state.next) - case Msg(ctx, GetState()) ⇒ + case (ctx, GetState()) ⇒ monitor ! state - Same - case Msg(ctx, Stop) ⇒ Stopped - } + SActor.Same + case (ctx, Stop) ⇒ SActor.Stopped + case (_, _) ⇒ SActor.Unhandled + }, { + case (ctx, signal) ⇒ + monitor ! GotSignal(signal) + SActor.Same + }) } trait FullBehavior extends Messages with BecomeWithLifecycle with Stoppable { @@ -287,256 +288,39 @@ class BehaviorSpec extends TypedSpec { object `A Full Behavior (native)` extends FullBehavior with NativeSystem object `A Full Behavior (adapted)` extends FullBehavior with AdaptedSystem - trait FullTotalBehavior extends Messages with BecomeWithLifecycle with Stoppable { + trait StatefulBehavior 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] = { - import ScalaDSL.{ FullTotal, Msg, Sig, Same, Unhandled, Stopped } - FullTotal { - case Sig(ctx, signal) ⇒ - monitor ! GotSignal(signal) - Same - case Msg(ctx, GetSelf) ⇒ + SActor.Stateful({ + case (ctx, GetSelf) ⇒ monitor ! Self(ctx.self) - Same - case Msg(_, Miss) ⇒ + SActor.Same + case (_, Miss) ⇒ monitor ! Missed - Unhandled - case Msg(_, Ignore) ⇒ + SActor.Unhandled + case (_, Ignore) ⇒ monitor ! Ignored - Same - case Msg(_, Ping) ⇒ + SActor.Same + case (_, Ping) ⇒ monitor ! Pong behv(monitor, state) - case Msg(_, Swap) ⇒ + case (_, Swap) ⇒ monitor ! Swapped behv(monitor, state.next) - case Msg(_, GetState()) ⇒ + case (_, GetState()) ⇒ monitor ! state - Same - case Msg(_, Stop) ⇒ Stopped - case Msg(_, _: AuxPing) ⇒ Unhandled - } - } - } - object `A FullTotal Behavior (native)` extends FullTotalBehavior with NativeSystem - object `A FullTotal Behavior (adapted)` extends FullTotalBehavior with AdaptedSystem - - trait WidenedBehavior extends Messages with BecomeWithLifecycle with Stoppable { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (ScalaDSL.Widened(mkFull(monitor), { case x ⇒ x }), null) - } - object `A Widened Behavior (native)` extends WidenedBehavior with NativeSystem - object `A Widened Behavior (adapted)` extends WidenedBehavior with AdaptedSystem - - trait ContextAwareBehavior extends Messages with BecomeWithLifecycle with Stoppable { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (ScalaDSL.ContextAware(ctx ⇒ mkFull(monitor)), null) - } - object `A ContextAware Behavior (native)` extends ContextAwareBehavior with NativeSystem - object `A ContextAware Behavior (adapted)` extends ContextAwareBehavior with AdaptedSystem - - trait SelfAwareBehavior extends Messages with BecomeWithLifecycle with Stoppable { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (ScalaDSL.SelfAware(self ⇒ mkFull(monitor)), null) - } - object `A SelfAware Behavior (native)` extends SelfAwareBehavior with NativeSystem - object `A SelfAware Behavior (adapted)` extends SelfAwareBehavior with AdaptedSystem - - trait NonMatchingTapBehavior extends Messages with BecomeWithLifecycle with Stoppable { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (ScalaDSL.Tap({ case null ⇒ }, mkFull(monitor)), null) - } - object `A non-matching Tap Behavior (native)` extends NonMatchingTapBehavior with NativeSystem - object `A non-matching Tap Behavior (adapted)` extends NonMatchingTapBehavior with AdaptedSystem - - trait MatchingTapBehavior extends Messages with BecomeWithLifecycle with Stoppable { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (ScalaDSL.Tap({ case _ ⇒ }, mkFull(monitor)), null) - } - object `A matching Tap Behavior (native)` extends MatchingTapBehavior with NativeSystem - object `A matching Tap Behavior (adapted)` extends MatchingTapBehavior with AdaptedSystem - - trait SynchronousSelfBehavior extends Messages with BecomeWithLifecycle with Stoppable { - import ScalaDSL._ - - type Aux = Inbox[Command] - - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (SynchronousSelf(self ⇒ mkFull(monitor)), null) - - private def behavior2(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { - val inbox = Inbox[Command]("syncself") - def first(self: ActorRef[Command]) = Tap.monitor(inbox.ref, Partial[Command] { - case AuxPing(id) ⇒ { self ! AuxPing(0); second(self) } + SActor.Same + case (_, Stop) ⇒ SActor.Stopped + case (_, _: AuxPing) ⇒ SActor.Unhandled + }, { + case (ctx, signal) ⇒ + monitor ! GotSignal(signal) + SActor.Same }) - def second(self: ActorRef[Command]) = Partial[Command] { - case AuxPing(0) ⇒ { self ! AuxPing(1); Same } - case AuxPing(1) ⇒ { self ! AuxPing(2); third(self) } - } - def third(self: ActorRef[Command]) = Partial[Command] { - case AuxPing(2) ⇒ { self ! AuxPing(3); Unhandled } - case AuxPing(3) ⇒ { self ! Ping; Same } - case AuxPing(4) ⇒ { self ! Stop; Stopped } - } - (SynchronousSelf(self ⇒ Or(mkFull(monitor), first(self))), inbox) - } - - override def checkAux(cmd: Command, aux: Aux) = - (cmd, aux) match { - case (AuxPing(42), i: Inbox[_]) ⇒ i.receiveAll() should ===(Seq(42, 0, 1, 2, 3) map AuxPing: Seq[Command]) - case (AuxPing(4), i: Inbox[_]) ⇒ i.receiveAll() should ===(AuxPing(4) :: Nil) - case _ ⇒ // ignore - } - - def `must send messages to itself and stop correctly`(): Unit = { - val Setup(ctx, _, _) = init(behavior2).mkCtx().check(AuxPing(42)) - ctx.run(AuxPing(4)) - ctx.currentBehavior should ===(Stopped[Command]) } } - object `A SynchronousSelf Behavior (native)` extends SynchronousSelfBehavior with NativeSystem - object `A SynchronousSelf Behavior (adapted)` extends SynchronousSelfBehavior with AdaptedSystem - - trait And extends Common { - - private def behavior2(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.And(mkFull(monitor), mkFull(monitor)) → null - - def `must pass message to both parts`(): Unit = { - init(behavior2).mkCtx().check2(Swap).check2(GetState()(StateB)) - } - - def `must half-terminate`(): Unit = { - val Setup(ctx, inbox, _) = mkCtx() - ctx.run(Stop) - ctx.currentBehavior should ===(ScalaDSL.Empty[Command]) - } - } - - trait BehaviorAndLeft extends Messages with BecomeWithLifecycle with And { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.And(mkFull(monitor), ScalaDSL.Empty) → null - } - object `A Behavior combined with And (left, native)` extends BehaviorAndLeft with NativeSystem - object `A Behavior combined with And (left, adapted)` extends BehaviorAndLeft with NativeSystem - - trait BehaviorAndRight extends Messages with BecomeWithLifecycle with And { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.And(ScalaDSL.Empty, mkFull(monitor)) → null - } - object `A Behavior combined with And (right, native)` extends BehaviorAndRight with NativeSystem - object `A Behavior combined with And (right, adapted)` extends BehaviorAndRight with NativeSystem - - trait Or extends Common { - private def strange(monitor: ActorRef[Event]): Behavior[Command] = - ScalaDSL.Full { - case ScalaDSL.Msg(_, Ping | AuxPing(_)) ⇒ - monitor ! Pong - ScalaDSL.Unhandled - } - - private def behavior2(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.Or(mkFull(monitor), strange(monitor)) → null - - private def behavior3(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.Or(strange(monitor), mkFull(monitor)) → null - - def `must pass message only to first interested party`(): Unit = { - init(behavior2).mkCtx().check(Ping).check(AuxPing(0)) - } - - def `must pass message through both if first is uninterested`(): Unit = { - init(behavior3).mkCtx().check2(Ping).check(AuxPing(0)) - } - - def `must half-terminate`(): Unit = { - val Setup(ctx, inbox, _) = mkCtx() - ctx.run(Stop) - ctx.currentBehavior should ===(ScalaDSL.Empty[Command]) - } - } - - trait BehaviorOrLeft extends Messages with BecomeWithLifecycle with Or { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.Or(mkFull(monitor), ScalaDSL.Empty) → null - } - object `A Behavior combined with Or (left, native)` extends BehaviorOrLeft with NativeSystem - object `A Behavior combined with Or (left, adapted)` extends BehaviorOrLeft with NativeSystem - - trait BehaviorOrRight extends Messages with BecomeWithLifecycle with Or { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - ScalaDSL.Or(ScalaDSL.Empty, mkFull(monitor)) → null - } - object `A Behavior combined with Or (right, native)` extends BehaviorOrRight with NativeSystem - object `A Behavior combined with Or (right, adapted)` extends BehaviorOrRight with NativeSystem - - trait PartialBehavior 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] = - ScalaDSL.Partial { - case Ping ⇒ - monitor ! Pong - behv(monitor, state) - case Miss ⇒ - monitor ! Missed - ScalaDSL.Unhandled - case Ignore ⇒ - monitor ! Ignored - ScalaDSL.Same - case Swap ⇒ - monitor ! Swapped - behv(monitor, state.next) - case GetState() ⇒ - monitor ! state - ScalaDSL.Same - case Stop ⇒ ScalaDSL.Stopped - } - } - object `A Partial Behavior (native)` extends PartialBehavior with NativeSystem - object `A Partial Behavior (adapted)` extends PartialBehavior with AdaptedSystem - - trait TotalBehavior 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] = - ScalaDSL.Total { - case Ping ⇒ - monitor ! Pong - behv(monitor, state) - case Miss ⇒ - monitor ! Missed - ScalaDSL.Unhandled - case Ignore ⇒ - monitor ! Ignored - ScalaDSL.Same - case GetSelf ⇒ ScalaDSL.Unhandled - case Swap ⇒ - monitor ! Swapped - behv(monitor, state.next) - case GetState() ⇒ - monitor ! state - ScalaDSL.Same - case Stop ⇒ ScalaDSL.Stopped - case _: AuxPing ⇒ ScalaDSL.Unhandled - } - } - object `A Total Behavior (native)` extends TotalBehavior with NativeSystem - object `A Total Behavior (adapted)` extends TotalBehavior with AdaptedSystem - - trait StaticBehavior extends Messages { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (ScalaDSL.Static { - case Ping ⇒ monitor ! Pong - case Miss ⇒ monitor ! Missed - case Ignore ⇒ monitor ! Ignored - case GetSelf ⇒ - case Swap ⇒ - case GetState() ⇒ - case Stop ⇒ - case _: AuxPing ⇒ - }, null) - } - object `A Static Behavior (native)` extends StaticBehavior with NativeSystem - object `A Static Behavior (adapted)` extends StaticBehavior with AdaptedSystem + object `A Stateful Behavior (native)` extends StatefulBehavior with NativeSystem + object `A Stateful Behavior (adapted)` extends StatefulBehavior with AdaptedSystem trait StatefulWithSignalScalaBehavior extends Messages with BecomeWithLifecycle with Stoppable { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = behv(monitor) → null @@ -575,7 +359,7 @@ class BehaviorSpec extends TypedSpec { trait StatefulScalaBehavior 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.Stateful { (ctx, msg) ⇒ + SActor.Stateful[Command] { (ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.self) @@ -633,21 +417,18 @@ class BehaviorSpec extends TypedSpec { object `A widened Behavior (scala,adapted)` extends WidenedScalaBehavior with AdaptedSystem trait DeferredScalaBehavior extends StatefulWithSignalScalaBehavior { - override type Aux = Inbox[PreStart] + override type Aux = Inbox[Done] override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { - val inbox = Inbox[PreStart]("deferredListener") + val inbox = Inbox[Done]("deferredListener") (SActor.Deferred(ctx ⇒ { - inbox.ref ! PreStart + inbox.ref ! Done super.behavior(monitor)._1 }), inbox) } override def checkAux(signal: Signal, aux: Aux): Unit = - signal match { - case PreStart ⇒ aux.receiveAll() should ===(PreStart :: Nil) - case _ ⇒ aux.receiveAll() should ===(Nil) - } + 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 @@ -655,10 +436,7 @@ class BehaviorSpec extends TypedSpec { trait TapScalaBehavior extends StatefulWithSignalScalaBehavior with Reuse with SignalSiphon { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { val inbox = Inbox[Either[Signal, Command]]("tapListener") - (SActor.Tap( - (_, sig) ⇒ inbox.ref ! Left(sig), - (_, msg) ⇒ inbox.ref ! Right(msg), - super.behavior(monitor)._1), inbox) + (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 @@ -799,21 +577,18 @@ class BehaviorSpec extends TypedSpec { object `A widened Behavior (java,adapted)` extends WidenedJavaBehavior with AdaptedSystem trait DeferredJavaBehavior extends StatefulWithSignalJavaBehavior { - override type Aux = Inbox[PreStart] + override type Aux = Inbox[Done] override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { - val inbox = Inbox[PreStart]("deferredListener") + val inbox = Inbox[Done]("deferredListener") (JActor.deferred(df(ctx ⇒ { - inbox.ref ! PreStart + inbox.ref ! Done super.behavior(monitor)._1 })), inbox) } override def checkAux(signal: Signal, aux: Aux): Unit = - signal match { - case PreStart ⇒ aux.receiveAll() should ===(PreStart :: Nil) - case _ ⇒ aux.receiveAll() should ===(Nil) - } + 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 diff --git a/akka-typed/src/test/scala/akka/typed/PerformanceSpec.scala b/akka-typed/src/test/scala/akka/typed/PerformanceSpec.scala index 8e3b39a02f..a1198f2b3d 100644 --- a/akka-typed/src/test/scala/akka/typed/PerformanceSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/PerformanceSpec.scala @@ -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]) @@ -28,15 +27,15 @@ class PerformanceSpec extends TypedSpec( StepWise[Pong] { (ctx, startWith) ⇒ startWith { - val pinger = SelfAware[Ping](self ⇒ Static { msg ⇒ + val pinger = Stateless[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) + } else msg.pong ! Pong(msg.x - 1, ctx.self, msg.report) + } // FIXME .withDispatcher(executor) - val ponger = SelfAware[Pong](self ⇒ Static { msg ⇒ - msg.ping ! Ping(msg.x, self, msg.report) - }) // FIXME .withDispatcher(executor) + val ponger = Stateless[Pong] { (ctx, msg) ⇒ + msg.ping ! Ping(msg.x, ctx.self, msg.report) + } // FIXME .withDispatcher(executor) val actors = for (i ← 1 to pairs) diff --git a/akka-typed/src/test/scala/akka/typed/StepWise.scala b/akka-typed/src/test/scala/akka/typed/StepWise.scala index dbdaba83d8..43ec05ffca 100644 --- a/akka-typed/src/test/scala/akka/typed/StepWise.scala +++ b/akka-typed/src/test/scala/akka/typed/StepWise.scala @@ -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[ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ()) }.narrow private def throwTimeout(trace: Trace, message: String): Nothing = @@ -133,61 +135,66 @@ 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) ⇒ + Stateful({ + 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") - } + }, { + 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) ⇒ + Stateful({ + 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)") - } + }, { + case (_, other) ⇒ throwIllegalState(trace, s"unexpected $other while waiting for $c messages (got $count valid ones)") + }) } behavior(0, Nil) case Multi(t, c, f, trace) :: tail ⇒ 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) ⇒ + Stateful[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) ⇒ + }, { + case (_, other) ⇒ val nextCount = count + 1 if (nextCount == c) { ctx.cancelReceiveTimeout() run(ctx, tail, f((Left(other) :: acc).reverse, value)) } else behavior(nextCount, Left(other) :: acc) - } + }) } behavior(0, Nil) case Termination(t, f, trace) :: tail ⇒ ctx.setReceiveTimeout(t, ReceiveTimeout) - Full { - case Msg(_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for termination") - case Sig(_, t: Terminated) ⇒ + Stateful[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") + }, { + 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 } } diff --git a/akka-typed/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed/src/test/scala/akka/typed/TypedSpec.scala index 039e09e45d..b7f04c6439 100644 --- a/akka-typed/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/TypedSpec.scala @@ -7,24 +7,27 @@ 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 /** @@ -68,7 +71,7 @@ class TypedSpec(val config: Config) extends TypedSpecSetup { 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(Stateful[Any] { case _ ⇒ Same }, "blackhole")) /** * Run an Actor-based test. The test procedure is most conveniently @@ -130,7 +133,6 @@ class TypedSpec(val config: Config) extends TypedSpecSetup { } object TypedSpec { - import ScalaDSL._ import akka.{ typed ⇒ t } sealed abstract class Start @@ -147,8 +149,20 @@ object TypedSpec { case object Timedout extends Status def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] = - FullTotal { - case Sig(ctx, t @ Terminated(test)) ⇒ + Stateful[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 + }, { + case (ctx, t @ Terminated(test)) ⇒ outstanding get test match { case Some(reply) ⇒ if (t.failure eq null) reply ! Success @@ -156,19 +170,9 @@ object TypedSpec { guardian(outstanding - test) 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 = { val s = (Thread.currentThread.getStackTrace map (_.getClassName) drop 1) diff --git a/akka-typed/src/test/scala/akka/typed/internal/ActorCellSpec.scala b/akka-typed/src/test/scala/akka/typed/internal/ActorCellSpec.scala index 3d2082a059..450d10736d 100644 --- a/akka-typed/src/test/scala/akka/typed/internal/ActorCellSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/internal/ActorCellSpec.scala @@ -4,6 +4,7 @@ package akka.typed package internal +import akka.typed.scaladsl.Actor._ import org.scalactic.ConversionCheckedTripleEquals import org.scalatest.concurrent.ScalaFutures import org.scalatest.exceptions.TestFailedException @@ -13,8 +14,6 @@ import org.junit.runner.RunWith @RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with ScalaFutures with ConversionCheckedTripleEquals { - import ScalaDSL._ - val sys = new ActorSystemStub("ActorCellSpec") def ec = sys.controlledExecutor @@ -22,7 +21,7 @@ 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"; Stateless[String] { case (_, s) ⇒ parent ! s } }), ec, 1000, parent) debugCell(cell) { ec.queueSize should ===(0) cell.sendSystem(Create()) @@ -40,7 +39,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 +59,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 +76,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 +100,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"; Stateful[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 +124,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 +150,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 +181,7 @@ 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 ⇒ Stateless[String] { case (_, s) ⇒ ctx.self ! s; parent ! s } }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) cell.setSelf(ref) debugCell(cell) { @@ -238,7 +238,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); Empty }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) cell.setSelf(ref) debugCell(cell) { @@ -347,8 +347,8 @@ 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") + val cell = new ActorCell(sys, Deferred[String] { ctx ⇒ + ctx.spawn(Deferred[String] { ctx ⇒ parent ! ctx.self; Empty }, "child") Empty }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) @@ -380,8 +380,8 @@ 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")) + val cell = new ActorCell(sys, Deferred[String] { ctx ⇒ + ctx.watch(ctx.spawn(Deferred[String] { ctx ⇒ parent ! ctx.self; Empty }, "child")) Empty }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) @@ -416,7 +416,12 @@ 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, Stateful[String]( + { case _ ⇒ Unhandled }, + { + case (_, PostStop) ⇒ ??? + case _ ⇒ Unhandled + }), ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) cell.setSelf(ref) debugCell(cell) { diff --git a/akka-typed/src/test/scala/akka/typed/internal/ActorSystemSpec.scala b/akka-typed/src/test/scala/akka/typed/internal/ActorSystemSpec.scala index 5466b778de..54c9c0c11d 100644 --- a/akka-typed/src/test/scala/akka/typed/internal/ActorSystemSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/internal/ActorSystemSpec.scala @@ -4,21 +4,20 @@ package akka.typed package internal +import akka.Done +import akka.typed.scaladsl.Actor._ +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 +40,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", Stateful[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,11 +53,14 @@ 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", Stateful[Probe]({ + case (_, _) ⇒ Unhandled + }, { + case (ctx, PostStop) ⇒ inbox.ref ! "done" Same - }) + } + )) sys.terminate().futureValue inbox.receiveAll() should ===("done" :: Nil) } @@ -114,7 +116,7 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca case class Doner(ref: ActorRef[Done]) - val ref1, ref2 = sys.systemActorOf(Static[Doner](_.ref ! Done), "empty").futureValue + val ref1, ref2 = sys.systemActorOf(Stateless[Doner] { case (_, doner) ⇒ doner.ref ! Done }, "empty").futureValue (ref1 ? Doner).futureValue should ===(Done) (ref2 ? Doner).futureValue should ===(Done) val RE = "(\\d+)-empty".r diff --git a/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala b/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala index 4aedbea9b1..3812391312 100644 --- a/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala @@ -4,27 +4,25 @@ 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 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 - } + Stateless { + case (ctx, Logger.Initialize(es, replyTo)) ⇒ + replyTo ! ctx.watch(ctx.spawn(Stateless[LogEvent] { (_, ev: LogEvent) ⇒ logged :+= ev }, "logger")) + Empty } } @@ -262,7 +260,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(Stateful[Done] { case _ ⇒ Stopped }, "tester") futureValue Timeout(1.second) es.subscribe(ref, classOf[Done]) should ===(true) es.subscribe(ref, classOf[Done]) should ===(false) ref ! Done diff --git a/akka-typed/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala b/akka-typed/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala index 93ac31ea51..3010f07845 100644 --- a/akka-typed/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala @@ -4,20 +4,21 @@ 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.Stateless class ReceptionistSpec extends TypedSpec { trait ServiceA case object ServiceKeyA extends ServiceKey[ServiceA] - val behaviorA = Static[ServiceA](msg ⇒ ()) + val behaviorA = Stateless[ServiceA] { case (_, msg) ⇒ () } trait ServiceB case object ServiceKeyB extends ServiceKey[ServiceB] - val behaviorB = Static[ServiceB](msg ⇒ ()) + val behaviorB = Stateless[ServiceB] { case (_, msg) ⇒ () } trait CommonTests { implicit def system: ActorSystem[TypedSpec.Command] From 2b09868f010573d0d93743e374aa8a462a33d7bf Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Tue, 4 Apr 2017 16:51:29 +0200 Subject: [PATCH 02/50] Make 'watch' and 'unwatch' return void (#22664) This indeed makes both the call sites and the implementations a little more elegant. The only non-trivial change was in EventStreamSpec, which while a bit more verbose, seems more readable like this, too. --- .../src/main/java/akka/typed/javadsl/ActorContext.java | 4 ++-- .../src/main/scala/akka/typed/ActorContext.scala | 4 ++-- akka-typed/src/main/scala/akka/typed/Effects.scala | 4 ++-- .../scala/akka/typed/adapter/ActorContextAdapter.scala | 4 ++-- .../main/scala/akka/typed/internal/DeathWatch.scala | 10 ++++------ .../scala/akka/typed/internal/EventStreamImpl.scala | 4 ++-- .../src/main/scala/akka/typed/scaladsl/Actor.scala | 4 ++-- .../src/test/scala/akka/typed/ActorContextSpec.scala | 8 ++++---- .../scala/akka/typed/internal/EventStreamSpec.scala | 4 +++- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java b/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java index 5021e3c1e1..4fba339013 100644 --- a/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java +++ b/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java @@ -104,13 +104,13 @@ public interface ActorContext { * {@link akka.typed.ActorSystem} to which the referenced Actor belongs is declared as failed * (e.g. in reaction to being unreachable). */ - public ActorRef watch(ActorRef other); + public void watch(ActorRef other); /** * Revoke the registration established by {@link #watch}. A {@link akka.typed.Terminated} * notification will not subsequently be received for the referenced Actor. */ - public ActorRef unwatch(ActorRef other); + public void unwatch(ActorRef other); /** * Schedule the sending of a notification in case no other message is received diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index c631ea4802..a52d4655a6 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -105,8 +105,8 @@ class StubbedActorContext[T]( 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 watch(other: ActorRef[_]): Unit = () + override def unwatch(other: ActorRef[_]): Unit = () override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = () override def cancelReceiveTimeout(): Unit = () diff --git a/akka-typed/src/main/scala/akka/typed/Effects.scala b/akka-typed/src/main/scala/akka/typed/Effects.scala index 75eb2832b3..df8c2ac5f4 100644 --- a/akka-typed/src/main/scala/akka/typed/Effects.scala +++ b/akka-typed/src/main/scala/akka/typed/Effects.scala @@ -80,11 +80,11 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma effectQueue.offer(Stopped(child.path.name)) super.stop(child) } - override def watch[U](other: ActorRef[U]): ActorRef[U] = { + override def watch(other: ActorRef[_]): Unit = { effectQueue.offer(Watched(other)) super.watch(other) } - override def unwatch[U](other: ActorRef[U]): ActorRef[U] = { + override def unwatch(other: ActorRef[_]): Unit = { effectQueue.offer(Unwatched(other)) super.unwatch(other) } diff --git a/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala index 129b756a72..b3c8049508 100644 --- a/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala @@ -36,8 +36,8 @@ private[typed] class ActorContextAdapter[T](ctx: a.ActorContext) extends ActorCo 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 } + override def watch(other: ActorRef[_]) = ctx.watch(toUntyped(other)) + override def unwatch(other: ActorRef[_]) = ctx.unwatch(toUntyped(other)) var receiveTimeoutMsg: T = null.asInstanceOf[T] override def setReceiveTimeout(d: FiniteDuration, msg: T) = { receiveTimeoutMsg = msg diff --git a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala index c072fa3264..8efb24a9c0 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala @@ -44,7 +44,7 @@ private[typed] trait DeathWatch[T] { private var watching = Set.empty[ARImpl] private var watchedBy = Set.empty[ARImpl] - final def watch[U](_a: ActorRef[U]): ActorRef[U] = { + final def watch(_a: ActorRef[_]): Unit = { val a = _a.sorry if (a != self && !watching.contains(a)) { maintainAddressTerminatedSubscription(a) { @@ -52,10 +52,9 @@ private[typed] trait DeathWatch[T] { watching += a } } - a } - final def unwatch[U](_a: ActorRef[U]): ActorRef[U] = { + final def unwatch(_a: ActorRef[_]): Unit = { val a = _a.sorry if (a != self && watching.contains(a)) { a.sendSystem(Unwatch(a, self)) @@ -63,7 +62,6 @@ private[typed] trait DeathWatch[T] { watching -= a } } - a } /** @@ -137,7 +135,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 +151,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))) } diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala index 9814f0ed98..8519666ca2 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala @@ -47,7 +47,7 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat Actor.Stateful[Command]({ case (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) + ctx.watch(actor) Actor.Same case (ctx, UnregisterIfNoMoreSubscribedChannels(actor)) if hasSubscriptions(actor) ⇒ Actor.Same @@ -55,7 +55,7 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat case (ctx, UnregisterIfNoMoreSubscribedChannels(actor)) ⇒ if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unwatching $actor, since has no subscriptions")) - ctx.unwatch[Nothing](actor) + ctx.unwatch(actor) Actor.Same }, { case (_, Terminated(actor)) ⇒ diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index 48d2309ca0..308afcc2c5 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -89,13 +89,13 @@ trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T] ⇒ * [[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] + def watch(other: ActorRef[_]): 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]): ActorRef[U] + def unwatch(other: ActorRef[_]): Unit /** * Schedule the sending of a notification in case no other diff --git a/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala index 9731861657..f8adb186ae 100644 --- a/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala @@ -113,11 +113,11 @@ object ActorContextSpec { else replyTo ! NotKilled Actor.Same case Watch(ref, replyTo) ⇒ - ctx.watch[Nothing](ref) + ctx.watch(ref) replyTo ! Watched Actor.Same case Unwatch(ref, replyTo) ⇒ - ctx.unwatch[Nothing](ref) + ctx.unwatch(ref) replyTo ! Unwatched Actor.Same case GetInfo(replyTo) ⇒ @@ -196,11 +196,11 @@ object ActorContextSpec { else replyTo ! NotKilled Actor.Same case Watch(ref, replyTo) ⇒ - ctx.watch[Nothing](ref) + ctx.watch(ref) replyTo ! Watched Actor.Same case Unwatch(ref, replyTo) ⇒ - ctx.unwatch[Nothing](ref) + ctx.unwatch(ref) replyTo ! Unwatched Actor.Same case GetInfo(replyTo) ⇒ diff --git a/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala b/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala index 3812391312..80670fc3ea 100644 --- a/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala +++ b/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala @@ -21,7 +21,9 @@ object EventStreamSpec { def initialBehavior: Behavior[Logger.Command] = Stateless { case (ctx, Logger.Initialize(es, replyTo)) ⇒ - replyTo ! ctx.watch(ctx.spawn(Stateless[LogEvent] { (_, ev: LogEvent) ⇒ logged :+= ev }, "logger")) + val logger = ctx.spawn(Stateless[LogEvent] { (_, ev: LogEvent) ⇒ logged :+= ev }, "logger") + ctx.watch(logger) + replyTo ! logger Empty } } From d4b7b634245fb390a843535de35a0d48bf95e136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Thu, 6 Apr 2017 09:07:17 +0300 Subject: [PATCH 03/50] #21524 Split out typed testkit --- .../typed/testkit/TestEventListener.scala | 62 +++++++++++++++++++ .../java/akka/typed/javadsl/ActorCompile.java | 0 .../src/test/resources/application.conf | 0 .../scala/akka/typed/ActorContextSpec.scala | 1 + .../src/test/scala/akka/typed/AskSpec.scala | 0 .../test/scala/akka/typed/BehaviorSpec.scala | 0 .../akka/typed/DeploymentConfigSpec.scala | 0 .../scala/akka/typed/PerformanceSpec.scala | 0 .../src/test/scala/akka/typed/StepWise.scala | 0 .../src/test/scala/akka/typed/TypedSpec.scala | 0 .../akka/typed/internal/ActorCellSpec.scala | 0 .../akka/typed/internal/ActorSystemSpec.scala | 0 .../akka/typed/internal/ActorSystemStub.scala | 0 .../typed/internal/ControlledExecutor.scala | 0 .../scala/akka/typed/internal/DebugRef.scala | 0 .../akka/typed/internal/EventStreamSpec.scala | 0 .../akka/typed/internal/FunctionRefSpec.scala | 0 .../typed/patterns/ReceptionistSpec.scala | 0 .../main/scala/akka/typed/EventStream.scala | 28 +-------- project/AkkaBuild.scala | 28 ++++++--- 20 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala rename {akka-typed => akka-typed-tests}/src/test/java/akka/typed/javadsl/ActorCompile.java (100%) rename {akka-typed => akka-typed-tests}/src/test/resources/application.conf (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/ActorContextSpec.scala (99%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/AskSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/BehaviorSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/DeploymentConfigSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/PerformanceSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/StepWise.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/TypedSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/ActorCellSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/ActorSystemSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/ActorSystemStub.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/ControlledExecutor.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/DebugRef.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/EventStreamSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/internal/FunctionRefSpec.scala (100%) rename {akka-typed => akka-typed-tests}/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala (100%) diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala new file mode 100644 index 0000000000..81c9e207b8 --- /dev/null +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala @@ -0,0 +1,62 @@ +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 akka.typed.scaladsl.Actor._ + +import scala.annotation.tailrec + +/** + * EventListener for running tests, which allows selectively filtering out + * expected messages. To use it, include something like this into + * your config + * + *

+ * akka.typed {
+ *   loggers = ["akka.typed.testkit.TestEventListener"]
+ * }
+ * 
+ */ +class TestEventListener extends Logger with StdOutLogger { + + val initialBehavior = { + // TODO avoid depending on dsl here? + Deferred[Command] { _ ⇒ + Stateful[Command] { + case (ctx, Initialize(eventStream, replyTo)) ⇒ + val log = ctx.spawn(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) + } + + Stateless[AnyRef] { + case (_, TE.Mute(filters)) ⇒ filters foreach addFilter + case (_, TE.UnMute(filters)) ⇒ filters foreach removeFilter + case (_, event: LogEvent) ⇒ if (!filter(event)) print(event) + case _ ⇒ Unhandled + } + }, "logger") + + eventStream.subscribe(log, classOf[TE.Mute]) + eventStream.subscribe(log, classOf[TE.UnMute]) + ctx.watch(log) // sign death pact + replyTo ! log + + Empty + } + } + } +} diff --git a/akka-typed/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java similarity index 100% rename from akka-typed/src/test/java/akka/typed/javadsl/ActorCompile.java rename to akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java diff --git a/akka-typed/src/test/resources/application.conf b/akka-typed-tests/src/test/resources/application.conf similarity index 100% rename from akka-typed/src/test/resources/application.conf rename to akka-typed-tests/src/test/resources/application.conf diff --git a/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala similarity index 99% rename from akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala index f8adb186ae..981069e473 100644 --- a/akka-typed/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala @@ -254,6 +254,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( | lifecycle = off | autoreceive = off | } + | typed.loggers = ["akka.typed.testkit.TestEventListener"] |}""".stripMargin)) { import ActorContextSpec._ diff --git a/akka-typed/src/test/scala/akka/typed/AskSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/AskSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/BehaviorSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/DeploymentConfigSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/DeploymentConfigSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/DeploymentConfigSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/DeploymentConfigSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/PerformanceSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/PerformanceSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/StepWise.scala b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/StepWise.scala rename to akka-typed-tests/src/test/scala/akka/typed/StepWise.scala diff --git a/akka-typed/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/TypedSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/ActorCellSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/ActorCellSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/ActorSystemSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/ActorSystemSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/ActorSystemStub.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/ActorSystemStub.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/ControlledExecutor.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ControlledExecutor.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/ControlledExecutor.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/ControlledExecutor.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/DebugRef.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/DebugRef.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/DebugRef.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/DebugRef.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/EventStreamSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/internal/FunctionRefSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/FunctionRefSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/internal/FunctionRefSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/internal/FunctionRefSpec.scala diff --git a/akka-typed/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala similarity index 100% rename from akka-typed/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala diff --git a/akka-typed/src/main/scala/akka/typed/EventStream.scala b/akka-typed/src/main/scala/akka/typed/EventStream.scala index b819b7b22d..91bea405f7 100644 --- a/akka-typed/src/main/scala/akka/typed/EventStream.scala +++ b/akka-typed/src/main/scala/akka/typed/EventStream.scala @@ -5,10 +5,6 @@ package akka.typed import akka.{ event ⇒ e } import akka.event.Logging.{ LogEvent, LogLevel, StdOutLogger } -import akka.testkit.{ EventFilter, TestEvent ⇒ TE } -import akka.typed.Behavior.DeferredBehavior - -import scala.annotation.tailrec /** * An EventStream allows local actors to register for certain message types, including @@ -74,34 +70,16 @@ class DefaultLogger extends Logger with StdOutLogger { // TODO avoid depending on dsl here? import scaladsl.Actor._ Deferred[Command] { _ ⇒ - scaladsl.Actor.Stateful[Command] { + Stateful[Command] { case (ctx, Initialize(eventStream, replyTo)) ⇒ val log = ctx.spawn(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) - } Stateless[AnyRef] { - case (_, TE.Mute(filters)) ⇒ filters foreach addFilter - case (_, TE.UnMute(filters)) ⇒ filters foreach removeFilter - case (_, event: LogEvent) ⇒ if (!filter(event)) print(event) - case _ ⇒ Unhandled + case (_, event: LogEvent) ⇒ print(event) + case _ ⇒ Unhandled } }, "logger") - eventStream.subscribe(log, classOf[TE.Mute]) - eventStream.subscribe(log, classOf[TE.UnMute]) ctx.watch(log) // sign death pact replyTo ! log diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 3c4ea4bd76..39762fda56 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -68,7 +68,9 @@ object AkkaBuild extends Build { streamTests, streamTestsTck, testkit, - typed + typed, + typedTests, + typedTestkit ) lazy val root = Project( @@ -95,18 +97,30 @@ object AkkaBuild extends Build { dependencies = Seq(actor) ) - lazy val typed = Project( - id = "akka-typed", - base = file("akka-typed"), - dependencies = Seq(testkit % "compile;test->test") - ) - lazy val actorTests = Project( id = "akka-actor-tests", base = file("akka-actor-tests"), dependencies = Seq(testkit % "compile;test->test") ) + lazy val typed = Project( + id = "akka-typed", + base = file("akka-typed"), + dependencies = Seq(actor) + ) + + lazy val typedTestkit = Project( + id = "akka-typed-testkit", + base = file("akka-typed-testkit"), + dependencies = Seq(typed, testkit % "compile;test->test") + ) + + lazy val typedTests = Project( + id = "akka-typed-tests", + base = file("akka-typed-tests"), + dependencies = Seq(typedTestkit % "compile;test->test") + ) + lazy val benchJmh = Project( id = "akka-bench-jmh", base = file("akka-bench-jmh"), From 0af4b63b78ced3c4c3de967d0113a5943700a410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Thu, 6 Apr 2017 09:48:10 +0300 Subject: [PATCH 04/50] #21524 Add typed tests dependency to docs --- project/AkkaBuild.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 39762fda56..3b842c6e5b 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -275,6 +275,7 @@ object AkkaBuild extends Build { remote % "compile;test->test", persistence % "compile;provided->provided;test->test", typed % "compile;test->test", + typedTests % "compile;test->test", streamTestkit % "compile;test->test" ) ) From 7f63c58e207ff02a7fb9beec099c354c9db1e0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Thu, 6 Apr 2017 10:08:30 +0300 Subject: [PATCH 05/50] #21524 Add missing formatter settings --- akka-typed-testkit/build.sbt | 4 ++++ akka-typed-tests/build.sbt | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 akka-typed-testkit/build.sbt create mode 100644 akka-typed-tests/build.sbt diff --git a/akka-typed-testkit/build.sbt b/akka-typed-testkit/build.sbt new file mode 100644 index 0000000000..aecfae9c9f --- /dev/null +++ b/akka-typed-testkit/build.sbt @@ -0,0 +1,4 @@ +import akka.{ AkkaBuild, Formatting, OSGi } + +AkkaBuild.defaultSettings +Formatting.formatSettings diff --git a/akka-typed-tests/build.sbt b/akka-typed-tests/build.sbt new file mode 100644 index 0000000000..a457754e83 --- /dev/null +++ b/akka-typed-tests/build.sbt @@ -0,0 +1,8 @@ +import akka.{ AkkaBuild, Formatting } + +AkkaBuild.defaultSettings +AkkaBuild.mayChangeSettings +Formatting.formatSettings + +disablePlugins(MimaPlugin) + From b6aea3c61287f8561f94f0c7fd70eb0c8cc864e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Fri, 7 Apr 2017 09:07:27 +0300 Subject: [PATCH 06/50] #21524 Disable MiMa for Typed testkit --- akka-typed-testkit/build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/akka-typed-testkit/build.sbt b/akka-typed-testkit/build.sbt index aecfae9c9f..ea235f557d 100644 --- a/akka-typed-testkit/build.sbt +++ b/akka-typed-testkit/build.sbt @@ -2,3 +2,5 @@ import akka.{ AkkaBuild, Formatting, OSGi } AkkaBuild.defaultSettings Formatting.formatSettings + +disablePlugins(MimaPlugin) From e6dbc865e9170a6237775c2e126f904971b1f6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Fri, 7 Apr 2017 09:13:06 +0300 Subject: [PATCH 07/50] #21524 Moved {Stubbed,Effectful}ActorContext to testkit --- .../scala/akka/typed/testkit}/Effects.scala | 10 +- .../typed/testkit/StubbedActorContext.scala | 100 ++++++++++++++++++ .../test/scala/akka/typed/BehaviorSpec.scala | 1 + .../typed/patterns/ReceptionistSpec.scala | 1 + .../main/scala/akka/typed/ActorContext.scala | 99 +---------------- 5 files changed, 110 insertions(+), 101 deletions(-) rename {akka-typed/src/main/scala/akka/typed => akka-typed-testkit/src/main/scala/akka/typed/testkit}/Effects.scala (94%) create mode 100644 akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala diff --git a/akka-typed/src/main/scala/akka/typed/Effects.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala similarity index 94% rename from akka-typed/src/main/scala/akka/typed/Effects.scala rename to akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala index df8c2ac5f4..d63c85cc36 100644 --- a/akka-typed/src/main/scala/akka/typed/Effects.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala @@ -1,13 +1,15 @@ /** * Copyright (C) 2014-2017 Lightbend Inc. */ -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, DeploymentConfig, EmptyDeploymentConfig, Signal } + import scala.annotation.tailrec import scala.collection.immutable +import scala.concurrent.duration.{ Duration, FiniteDuration } /** * All tracked effects must extend implement this type. It is deliberately @@ -32,8 +34,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 { diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala new file mode 100644 index 0000000000..f368dbaef4 --- /dev/null +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala @@ -0,0 +1,100 @@ +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 + +/** + * 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(other: ActorRef[_]): Unit = () + override def unwatch(other: ActorRef[_]): 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 + + 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)" +} diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala index f77ebc6ca1..90c490666d 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala @@ -10,6 +10,7 @@ import akka.japi.pf.{ FI, PFBuilder } import java.util.function.{ Function ⇒ F1 } import akka.Done +import akka.typed.testkit.EffectfulActorContext class BehaviorSpec extends TypedSpec { diff --git a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala index 3010f07845..ea36f0b4ff 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala @@ -9,6 +9,7 @@ import akka.typed.scaladsl.AskPattern._ import scala.concurrent.duration._ import akka.typed._ import akka.typed.scaladsl.Actor.Stateless +import akka.typed.testkit.{ Effect, EffectfulActorContext } class ReceptionistSpec extends TypedSpec { diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index a52d4655a6..9f0a46986a 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -3,14 +3,10 @@ */ 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 @@ -35,7 +31,7 @@ trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext a } - override def getExecutionContext(): scala.concurrent.ExecutionContextExecutor = + override def getExecutionContext(): ExecutionContextExecutor = executionContext override def getMailboxCapacity(): Int = @@ -59,94 +55,3 @@ trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext override def createAdapter[U](f: java.util.function.Function[U, T], name: String): akka.typed.ActorRef[U] = spawnAdapter(f.apply _, name) } - -/** - * 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(other: ActorRef[_]): Unit = () - override def unwatch(other: ActorRef[_]): 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 - - 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)" -} From ebb5748d6ac499def08beba7aced3e084635f32f Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Wed, 5 Apr 2017 08:08:13 +0200 Subject: [PATCH 08/50] Complete adapter API for coexistence of typed and untyped actors, #22174 * Trying out what is working and what is missing with a test * Add missing API and cleanup (public vs internal) * Note that PropsAdapter will make it possible to use Cluster Sharding with typed entity actors * add javadsl for the adapters, and full java tests for that * Add narrow to ActorRef --- .../main/scala/akka/actor/ActorSystem.scala | 3 +- .../code/docs/akka/typed/IntroSpec.scala | 5 +- .../java/akka/typed/javadsl/AdapterTest.java | 339 ++++++++++++++++++ .../src/test/scala/akka/typed/AskSpec.scala | 5 +- .../src/test/scala/akka/typed/TypedSpec.scala | 25 +- .../typed/scaladsl/adapter/AdapterSpec.scala | 255 +++++++++++++ .../main/scala/akka/typed/ActorContext.scala | 3 + .../src/main/scala/akka/typed/ActorRef.scala | 8 +- .../main/scala/akka/typed/ActorSystem.scala | 11 +- .../typed/adapter/ActorContextAdapter.scala | 61 ---- .../akka/typed/adapter/ActorRefAdapter.scala | 19 - .../akka/typed/adapter/PropsAdapter.scala | 24 -- .../scala/akka/typed/adapter/package.scala | 43 --- .../akka/typed/internal/ActorSystemImpl.scala | 5 +- .../{ => internal}/adapter/ActorAdapter.scala | 8 +- .../adapter/ActorContextAdapter.scala | 105 ++++++ .../internal/adapter/ActorRefAdapter.scala | 47 +++ .../adapter/ActorSystemAdapter.scala | 40 ++- .../adapter/EventStreamAdapter.scala | 16 +- .../typed/internal/adapter/PropsAdapter.scala | 22 ++ .../scala/akka/typed/javadsl/Adapter.scala | 115 ++++++ .../scala/akka/typed/scaladsl/Actor.scala | 16 +- .../main/scala/akka/typed/scaladsl/Ask.scala | 14 +- .../typed/scaladsl/adapter/PropsAdapter.scala | 23 ++ .../akka/typed/scaladsl/adapter/package.scala | 93 +++++ 25 files changed, 1110 insertions(+), 195 deletions(-) create mode 100644 akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java create mode 100644 akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala delete mode 100644 akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala delete mode 100644 akka-typed/src/main/scala/akka/typed/adapter/ActorRefAdapter.scala delete mode 100644 akka-typed/src/main/scala/akka/typed/adapter/PropsAdapter.scala delete mode 100644 akka-typed/src/main/scala/akka/typed/adapter/package.scala rename akka-typed/src/main/scala/akka/typed/{ => internal}/adapter/ActorAdapter.scala (90%) create mode 100644 akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala create mode 100644 akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala rename akka-typed/src/main/scala/akka/typed/{ => internal}/adapter/ActorSystemAdapter.scala (65%) rename akka-typed/src/main/scala/akka/typed/{ => internal}/adapter/EventStreamAdapter.scala (64%) create mode 100644 akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala create mode 100644 akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala create mode 100644 akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 6eae58a480..722dbd15f5 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -702,7 +702,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) diff --git a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala b/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala index 4f0b9bc83a..afdba6b73b 100644 --- a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala +++ b/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala @@ -121,15 +121,14 @@ class IntroSpec extends TypedSpec { chatRoom ! GetSession("ol’ Gabbler", gabblerRef) Stateful( - behavior = (_, _) ⇒ Unhandled, - signal = { (ctx, sig) ⇒ + onMessage = (_, _) ⇒ Unhandled, + onSignal = (ctx, sig) ⇒ sig match { case Terminated(ref) ⇒ Stopped case _ ⇒ Unhandled } - } ) } diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java new file mode 100644 index 0000000000..cd22cb31ee --- /dev/null +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java @@ -0,0 +1,339 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ + +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 create(akka.actor.ActorRef ref, akka.actor.ActorRef probe) { + Typed1 logic = new Typed1(ref, probe); + return stateful( + (ctx, msg) -> logic.onMessage(ctx, msg), + (ctx, sig) -> logic.onSignal(ctx, sig)); + } + + Behavior onMessage(ActorContext 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 onSignal(ActorContext 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 replyTo; + + public Ping(ActorRef 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 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 ref; + private final akka.actor.ActorRef probe; + private final SupervisorStrategy strategy; + + Untyped2(ActorRef ref, akka.actor.ActorRef probe) { + this.ref = ref; + this.probe = probe; + this.strategy = strategy(); + } + + @Override + public Receive createReceive() { + return receiveBuilder() + .matchEquals("send", s -> { + ActorRef replyTo = Adapter.toTyped(getSelf()); + ref.tell(new Ping(replyTo)); + }) + .matchEquals("pong", s -> probe.tell("ok", getSelf())) + .matchEquals("spawn", s -> { + ActorRef 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 child = Adapter.spawnAnonymous(getContext(), typed2()); + Adapter.watch(getContext(), child); + Adapter.stop(getContext(), child); + }) + .build(); + } + + private void testSupervice(ThrowIt t) { + ActorRef 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 typed2() { + return Actor.stateful((ctx, msg) -> { + if (msg instanceof Ping) { + ActorRef 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 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 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 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 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 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 typedRef = Adapter.spawnAnonymous(system, typed2()); + ActorRef 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 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 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 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 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 typedRef = Adapter.spawnAnonymous(system, Typed1.create(ignore, probe.getRef())); + typedRef.tell("stop-child"); + probe.expectMsg("terminated"); + } +} diff --git a/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala index 2e70cbed74..3f48b9a929 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala @@ -65,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") } diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index b7f04c6439..fd5b4fbb17 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -39,7 +39,7 @@ class TypedSpecSetup extends RefSpec with Matchers with BeforeAndAfterAll with S /** * 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._ @@ -48,8 +48,18 @@ class TypedSpec(val config: Config) extends TypedSpecSetup { // 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 = false + sys + } trait NativeSystem { def system = nativeSystem @@ -63,8 +73,10 @@ class TypedSpec(val config: Config) extends TypedSpecSetup { 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): Status + if (adaptedSystemUsed) + Await.result(adaptedSystem ? (Terminate(_)), timeout.duration): Status } // TODO remove after basing on ScalaTest 3 with async support @@ -197,8 +209,7 @@ class TypedSpecSpec extends TypedSpec { sync(runTest("failure")(StepWise[String]((ctx, startWith) ⇒ startWith { fail("expected") - } - ))) + }))) } } } diff --git a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala new file mode 100644 index 0000000000..ba5cc9411f --- /dev/null +++ b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala @@ -0,0 +1,255 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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] = + Stateful( + onMessage = (ctx, msg) ⇒ + msg match { + case "send" ⇒ + val replyTo = ctx.self.toUntyped + ref.tell("ping", replyTo) + Same + case "pong" ⇒ + probe ! "ok" + Same + case "actorOf" ⇒ + val child = ctx.actorOf(untyped1) + child.tell("ping", ctx.self.toUntyped) + Same + case "watch" ⇒ + ctx.watch(ref) + Same + case "supervise-stop" ⇒ + val child = ctx.actorOf(untyped1) + ctx.watch(child) + child ! ThrowIt3 + child.tell("ping", ctx.self.toUntyped) + Same + case "stop-child" ⇒ + val child = ctx.actorOf(untyped1) + ctx.watch(child) + ctx.stop(child) + Same + }, + onSignal = (ctx, sig) ⇒ sig match { + case Terminated(ref) ⇒ + probe ! "terminated" + Same + case _ ⇒ Unhandled + }) + + 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] = + Stateful { (ctx, msg) ⇒ + msg match { + case Ping(replyTo) ⇒ + replyTo ! "pong" + Same + case StopIt ⇒ + 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 ignore = system.spawnAnonymous(Ignore[Ping]) + val untypedRef = system.actorOf(untyped2(ignore, probe.ref)) + untypedRef ! "spawn" + probe.expectMsg("ok") + } + + "actorOf typed child via Props from untyped parent" in { + val probe = TestProbe() + val ignore = system.spawnAnonymous(Ignore[Ping]) + val untypedRef = system.actorOf(untyped2(ignore, 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 ignore = system.spawnAnonymous(Ignore[Ping]) + val untypedRef = system.actorOf(untyped2(ignore, 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(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") + } + } + +} diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index 9f0a46986a..f900cddc02 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -17,6 +17,9 @@ import akka.annotation.ApiMayChange @DoNotInherit @ApiMayChange trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext[T] { + + // FIXME can we simplify this weird hierarchy of contexts, e.g. problem with createAdapter + override def getChild(name: String): Optional[ActorRef[Void]] = child(name) match { case Some(c) ⇒ Optional.of(c.upcast[Void]) diff --git a/akka-typed/src/main/scala/akka/typed/ActorRef.scala b/akka-typed/src/main/scala/akka/typed/ActorRef.scala index 0fbc0b28bf..4a7873c93f 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorRef.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorRef.scala @@ -31,6 +31,11 @@ abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[Act */ def !(msg: T): Unit = tell(msg) + /** + * Narrow the type of this `ActorRef, which is always a safe operation. + */ + final def narrow[U <: T]: ActorRef[U] = this.asInstanceOf[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, @@ -73,7 +78,8 @@ object ActorRef { * 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 diff --git a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala index 9557befad6..914cc2ec1a 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala @@ -11,7 +11,7 @@ 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 @@ -180,10 +180,17 @@ object ActorSystem { 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. + 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, guardianDeployment)), actorSystemSettings) untyped.start() new ActorSystemAdapter(untyped) } diff --git a/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala deleted file mode 100644 index b3c8049508..0000000000 --- a/akka-typed/src/main/scala/akka/typed/adapter/ActorContextAdapter.scala +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2016-2017 Lightbend Inc. - */ -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(other: ActorRef[_]) = ctx.watch(toUntyped(other)) - override def unwatch(other: ActorRef[_]) = ctx.unwatch(toUntyped(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) - } - -} diff --git a/akka-typed/src/main/scala/akka/typed/adapter/ActorRefAdapter.scala b/akka-typed/src/main/scala/akka/typed/adapter/ActorRefAdapter.scala deleted file mode 100644 index 3477beca3f..0000000000 --- a/akka-typed/src/main/scala/akka/typed/adapter/ActorRefAdapter.scala +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (C) 2016-2017 Lightbend Inc. - */ -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]) -} diff --git a/akka-typed/src/main/scala/akka/typed/adapter/PropsAdapter.scala b/akka-typed/src/main/scala/akka/typed/adapter/PropsAdapter.scala deleted file mode 100644 index 3ab936ad61..0000000000 --- a/akka-typed/src/main/scala/akka/typed/adapter/PropsAdapter.scala +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (C) 2016-2017 Lightbend Inc. - */ -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") - } - } - -} diff --git a/akka-typed/src/main/scala/akka/typed/adapter/package.scala b/akka-typed/src/main/scala/akka/typed/adapter/package.scala deleted file mode 100644 index 30222a221d..0000000000 --- a/akka-typed/src/main/scala/akka/typed/adapter/package.scala +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (C) 2016-2017 Lightbend Inc. - */ -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 - } - -} diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index 688c34d4b0..0a8398449c 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -162,7 +162,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) } /** @@ -190,7 +190,8 @@ private[typed] class ActorSystemImpl[-T]( private val terminateTriggered = new AtomicBoolean private val theOneWhoWalksTheBubblesOfSpaceTime: ActorRefImpl[Nothing] = new ActorRef[Nothing](rootPath) with ActorRefImpl[Nothing] { - override def tell(msg: Nothing): Unit = throw new UnsupportedOperationException("cannot send to theOneWhoWalksTheBubblesOfSpaceTime") + override def 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)) diff --git a/akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala similarity index 90% rename from akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala rename to akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala index 0695fdb041..3964c91bb7 100644 --- a/akka-typed/src/main/scala/akka/typed/adapter/ActorAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala @@ -2,12 +2,18 @@ * Copyright (C) 2016-2017 Lightbend Inc. */ package akka.typed +package internal package adapter import akka.{ actor ⇒ a } +import akka.annotation.InternalApi -private[typed] class ActorAdapter[T](_initialBehavior: Behavior[T]) extends a.Actor { +/** + * INTERNAL API + */ +@InternalApi private[typed] class ActorAdapter[T](_initialBehavior: Behavior[T]) extends a.Actor { import Behavior._ + import ActorRefAdapter.toUntyped var behavior: Behavior[T] = _initialBehavior diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala new file mode 100644 index 0000000000..68396fbe19 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2016-2017 Lightbend Inc. + */ +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 ActorContext[T] { + + import ActorRefAdapter.sendSystemMessage + 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], deployment: DeploymentConfig = EmptyDeploymentConfig) = + ActorContextAdapter.spawnAnonymous(untyped, behavior, deployment) + override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig) = + ActorContextAdapter.spawn(untyped, behavior, name, deployment) + override def stop(child: ActorRef[_]) = + 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(other: ActorRef[_]) = { untyped.watch(toUntyped(other)) } + override def unwatch(other: ActorRef[_]) = { 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 def spawnAdapter[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 { + def toUntyped[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 toUntyped[U](ctx: scaladsl.ActorContext[_]): a.ActorContext = + ctx match { + case c: ActorContext[_] ⇒ toUntyped(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[_] ⇒ toUntyped(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], deployment: DeploymentConfig): ActorRef[T] = { + Behavior.validateAsInitial(behavior) + ActorRefAdapter(ctx.actorOf(PropsAdapter(() ⇒ behavior, deployment))) + } + + def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, deployment: DeploymentConfig): ActorRef[T] = { + Behavior.validateAsInitial(behavior) + ActorRefAdapter(ctx.actorOf(PropsAdapter(() ⇒ behavior, deployment), name)) + } +} diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala new file mode 100644 index 0000000000..14199e8616 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2016-2017 Lightbend Inc. + */ +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](untyped.path) with internal.ActorRefImpl[T] { + + 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 + } +} diff --git a/akka-typed/src/main/scala/akka/typed/adapter/ActorSystemAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala similarity index 65% rename from akka-typed/src/main/scala/akka/typed/adapter/ActorSystemAdapter.scala rename to akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala index 1fd87b0a03..b86ab06d34 100644 --- a/akka-typed/src/main/scala/akka/typed/adapter/ActorSystemAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala @@ -2,6 +2,7 @@ * Copyright (C) 2016-2017 Lightbend Inc. */ package akka.typed +package internal package adapter import akka.{ actor ⇒ a, dispatch ⇒ d } @@ -9,19 +10,21 @@ import akka.dispatch.sysmsg import scala.concurrent.ExecutionContextExecutor import akka.util.Timeout import scala.concurrent.Future +import akka.annotation.InternalApi /** - * 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) +@InternalApi 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] { import ActorSystemAdapter._ + import ActorRefAdapter.sendSystemMessage // Members declared in akka.typed.ActorRef override def tell(msg: T): Unit = untyped.guardian ! msg @@ -33,10 +36,12 @@ private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl) 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 } @@ -65,8 +70,8 @@ private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl) 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) + val ref = untyped.systemActorOf(PropsAdapter(() ⇒ behavior, deployment), name) + Future.successful(ActorRefAdapter(ref)) } } @@ -74,9 +79,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, EmptyDeploymentConfig), + "receptionist")) } } diff --git a/akka-typed/src/main/scala/akka/typed/adapter/EventStreamAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/EventStreamAdapter.scala similarity index 64% rename from akka-typed/src/main/scala/akka/typed/adapter/EventStreamAdapter.scala rename to akka-typed/src/main/scala/akka/typed/internal/adapter/EventStreamAdapter.scala index f121f85119..91fb378b06 100644 --- a/akka-typed/src/main/scala/akka/typed/adapter/EventStreamAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/EventStreamAdapter.scala @@ -2,11 +2,16 @@ * Copyright (C) 2016-2017 Lightbend Inc. */ 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") } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala new file mode 100644 index 0000000000..efc275d091 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package akka.typed +package internal +package adapter + +import akka.typed.Behavior +import akka.typed.EmptyDeploymentConfig +import akka.typed.DeploymentConfig +import akka.annotation.InternalApi + +/** + * INTERNAL API + */ +@InternalApi private[akka] object PropsAdapter { + def apply[T](behavior: () ⇒ Behavior[T], deploy: DeploymentConfig = EmptyDeploymentConfig): akka.actor.Props = { + // FIXME use DeploymentConfig, e.g. dispatcher + akka.actor.Props(new ActorAdapter(behavior())) + } + +} diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala new file mode 100644 index 0000000000..86759b2546 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package akka.typed.javadsl + +import akka.typed.Behavior +import akka.typed.DeploymentConfig +import akka.typed.EmptyDeploymentConfig +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, EmptyDeploymentConfig) + + def spawnAnonymous[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], deployment: DeploymentConfig): ActorRef[T] = + sys.spawnAnonymous(behavior, deployment) + + def spawn[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], name: String): ActorRef[T] = + spawn(sys, behavior, name, EmptyDeploymentConfig) + + def spawn[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], name: String, deployment: DeploymentConfig): ActorRef[T] = + sys.spawn(behavior, name, deployment) + + def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T]): ActorRef[T] = + spawnAnonymous(ctx, behavior, EmptyDeploymentConfig) + + def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], deployment: DeploymentConfig): ActorRef[T] = + ctx.spawnAnonymous(behavior, deployment) + + def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String): ActorRef[T] = + spawn(ctx, behavior, name, EmptyDeploymentConfig) + + def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, deployment: DeploymentConfig): ActorRef[T] = + ctx.spawn(behavior, name, deployment) + + 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: DeploymentConfig): 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, EmptyDeploymentConfig) +} diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index 308afcc2c5..dcc50782d0 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -266,12 +266,12 @@ object Actor { * results in a new behavior that can potentially be different from this one. */ 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]]) + onMessage: (ActorContext[T], T) ⇒ Behavior[T], + onSignal: (ActorContext[T], Signal) ⇒ Behavior[T] = Behavior.unhandledSignal.asInstanceOf[(ActorContext[T], Signal) ⇒ Behavior[T]]) extends ExtensibleBehavior[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)})" + override def management(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg) + override def message(ctx: AC[T], msg: T) = onMessage(ctx, msg) + override def toString = s"Stateful(${LineNumbers(onMessage)})" } /** @@ -285,13 +285,13 @@ object Actor { * 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 ExtensibleBehavior[T] { + final case class Stateless[T](onMessage: (ActorContext[T], T) ⇒ Any) extends ExtensibleBehavior[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) + onMessage(ctx, msg) this } - override def toString = s"Static(${LineNumbers(behavior)})" + override def toString = s"Static(${LineNumbers(onMessage)})" } /** diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Ask.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Ask.scala index e45b31ce8d..df49e223b2 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Ask.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Ask.scala @@ -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) } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala new file mode 100644 index 0000000000..b500c207bd --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package akka.typed.scaladsl.adapter + +import akka.typed.Behavior +import akka.typed.EmptyDeploymentConfig +import akka.typed.DeploymentConfig +import akka.typed.internal.adapter.ActorAdapter + +/** + * 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` on 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. + */ +object PropsAdapter { + def apply[T](behavior: ⇒ Behavior[T], deploy: DeploymentConfig = EmptyDeploymentConfig): akka.actor.Props = + akka.typed.internal.adapter.PropsAdapter(() ⇒ behavior, deploy) +} diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala new file mode 100644 index 0000000000..6097195e47 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2016-2017 Lightbend Inc. + */ +package akka.typed +package scaladsl + +import akka.annotation.InternalApi +import akka.typed.internal.adapter._ + +/** + * Scala 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. + * + * Use these adapters with `import akka.typed.scaladsl.adapter._`. + * + * Implicit extension methods are added to untyped and typed `ActorSystem`, + * `ActorContext`. Such 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 is an implicit conversion from untyped [[akka.actor.ActorRef]] to + * typed [[akka.typed.ActorRef]]. + * + * There are also converters (`toTyped`, `toUntyped`) from typed + * [[akka.typed.ActorRef]] to untyped [[akka.actor.ActorRef]], and between untyped + * [[akka.actor.ActorSystem]] and typed [[akka.typed.ActorSystem]]. + */ +package object adapter { + + import language.implicitConversions + + /** + * Extension methods added to [[akka.actor.ActorSystem]]. + */ + implicit class UntypedActorSystemOps(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)) + + def toTyped: ActorSystem[Nothing] = ActorSystemAdapter(sys) + } + + /** + * Extension methods added to [[akka.typed.ActorSystem]]. + */ + implicit class TypedActorSystemOps(val sys: ActorSystem[_]) extends AnyVal { + def toUntyped: akka.actor.ActorSystem = ActorSystemAdapter.toUntyped(sys) + } + + /** + * Extension methods added to [[akka.actor.ActorContext]]. + */ + implicit class UntypedActorContextOps(val ctx: akka.actor.ActorContext) extends AnyVal { + def spawnAnonymous[T](behavior: Behavior[T], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] = + ActorContextAdapter.spawnAnonymous(ctx, behavior, deployment) + def spawn[T](behavior: Behavior[T], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] = + ActorContextAdapter.spawn(ctx, behavior, name, deployment) + + def watch[U](other: ActorRef[U]): Unit = ctx.watch(ActorRefAdapter.toUntyped(other)) + def unwatch[U](other: ActorRef[U]): Unit = ctx.unwatch(ActorRefAdapter.toUntyped(other)) + + def stop(child: ActorRef[_]): Unit = + ctx.stop(ActorRefAdapter.toUntyped(child)) + } + + /** + * Extension methods added to [[akka.typed.scaladsl.ActorContext]]. + */ + implicit class TypedActorContextOps(val ctx: scaladsl.ActorContext[_]) extends AnyVal { + def actorOf(props: akka.actor.Props): akka.actor.ActorRef = + ActorContextAdapter.toUntyped(ctx).actorOf(props) + def actorOf(props: akka.actor.Props, name: String): akka.actor.ActorRef = + ActorContextAdapter.toUntyped(ctx).actorOf(props, name) + + // watch, unwatch and stop not needed here because of the implicit ActorRef conversion + } + + /** + * Extension methods added to [[akka.typed.ActorRef]]. + */ + implicit class TypedActorRefOps(val ref: ActorRef[_]) extends AnyVal { + def toUntyped: akka.actor.ActorRef = ActorRefAdapter.toUntyped(ref) + } + + /** + * Implicit conversion from untyped [[akka.actor.ActorRef]] to typed [[akka.typed.ActorRef]]. + */ + implicit def actorRefAdapter[T](ref: akka.actor.ActorRef): ActorRef[T] = ActorRefAdapter(ref) + +} From e6a99251c272ba909a1fdebd2e675dd7020cc97e Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 19 Apr 2017 00:30:53 +0900 Subject: [PATCH 09/50] +typ #22663 move symbolic ! into implicit class of ActorRef --- .../src/main/scala/akka/typed/ActorRef.scala | 15 +++++++++------ .../scaladsl/{Ask.scala => AskPattern.scala} | 0 2 files changed, 9 insertions(+), 6 deletions(-) rename akka-typed/src/main/scala/akka/typed/scaladsl/{Ask.scala => AskPattern.scala} (100%) diff --git a/akka-typed/src/main/scala/akka/typed/ActorRef.scala b/akka-typed/src/main/scala/akka/typed/ActorRef.scala index 4a7873c93f..5fc333f71b 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorRef.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorRef.scala @@ -25,12 +25,6 @@ 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. - */ - def !(msg: T): Unit = tell(msg) - /** * Narrow the type of this `ActorRef, which is always a safe operation. */ @@ -74,6 +68,15 @@ abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[Act } object ActorRef { + + implicit final class ActorRefScalaTell[-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) + } + /** * Create an ActorRef from a Future, buffering up to the given number of * messages in while the Future is not fulfilled. diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Ask.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/AskPattern.scala similarity index 100% rename from akka-typed/src/main/scala/akka/typed/scaladsl/Ask.scala rename to akka-typed/src/main/scala/akka/typed/scaladsl/AskPattern.scala From 961b4bad2b47406f455611ad08075b405f9d6d8b Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Thu, 20 Apr 2017 09:58:48 +0200 Subject: [PATCH 10/50] typed MutableBehavior API (#22732) * API suggestion of MutableBehavior * cleanup * moved IntroSpec to akka-typed-tests for more efficient dev environment * scaladsl.MutableBehavior * add mutable to BehaviorSpec * return this instead of same --- akka-docs/rst/scala/typed.rst | 14 +- .../test/java/jdocs/akka/typed/IntroTest.java | 132 ++++++++++++++++++ .../jdocs/akka/typed/MutableIntroTest.java | 114 +++++++++++++++ .../test/scala/akka/typed/BehaviorSpec.scala | 38 +++++ .../scala}/docs/akka/typed/IntroSpec.scala | 14 +- .../docs/akka/typed/MutableIntroSpec.scala | 116 +++++++++++++++ .../main/java/akka/typed/javadsl/Actor.java | 112 ++++++++++++--- .../scala/akka/typed/scaladsl/Actor.scala | 69 ++++++++- 8 files changed, 573 insertions(+), 36 deletions(-) create mode 100644 akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java create mode 100644 akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java rename {akka-docs/rst/scala/code => akka-typed-tests/src/test/scala}/docs/akka/typed/IntroSpec.scala (93%) create mode 100644 akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala diff --git a/akka-docs/rst/scala/typed.rst b/akka-docs/rst/scala/typed.rst index 75feb168fb..17808fd5b4 100644 --- a/akka-docs/rst/scala/typed.rst +++ b/akka-docs/rst/scala/typed.rst @@ -15,12 +15,12 @@ As discussed in :ref:`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: -.. includecode:: code/docs/akka/typed/IntroSpec.scala#imports +.. includecode:: ../../../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! -.. includecode:: code/docs/akka/typed/IntroSpec.scala#hello-world-actor +.. includecode:: ../../../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 @@ -54,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: -.. includecode:: code/docs/akka/typed/IntroSpec.scala#hello-world +.. includecode:: ../../../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. @@ -156,7 +156,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: -.. includecode:: code/docs/akka/typed/IntroSpec.scala#chatroom-protocol +.. includecode:: ../../../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 @@ -173,7 +173,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: -.. includecode:: code/docs/akka/typed/IntroSpec.scala#chatroom-behavior +.. includecode:: ../../../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 @@ -215,7 +215,7 @@ Trying it out In order to see this chat room in action we need to write a client Actor that can use it: -.. includecode:: code/docs/akka/typed/IntroSpec.scala#chatroom-gabbler +.. includecode:: ../../../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 @@ -240,7 +240,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: -.. includecode:: code/docs/akka/typed/IntroSpec.scala#chatroom-main +.. includecode:: ../../../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 diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java new file mode 100644 index 0000000000..3cb4b540e2 --- /dev/null +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package jdocs.akka.typed; + +//#imports +import akka.typed.ActorRef; +import akka.typed.Behavior; +import akka.typed.ExtensibleBehavior; +import akka.typed.Signal; +import akka.typed.javadsl.Actor; +import akka.typed.javadsl.ActorContext; +//#imports +import java.util.ArrayList; +import java.util.List; + +public class IntroTest { + + //#hello-world-actor + public static class HelloWorld { + public static final class Greet{ + public final String whom; + public final ActorRef replyTo; + + public Greet(String whom, ActorRef 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 greeter = Actor.stateless((ctx, msg) -> { + System.out.println("Hello " + msg.whom + "!"); + msg.replyTo.tell(new Greeted(msg.whom)); + }); + } + //#hello-world-actor + + //#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 replyTo; + public GetSession(String screenName, ActorRef 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 handle; + public SessionGranted(ActorRef 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 behavior() { + return chatRoom(new ArrayList>()); + } + + private static Behavior chatRoom(List> sessions) { + return Actor.stateful((ctx, msg) -> { + if (msg instanceof GetSession) { + GetSession getSession = (GetSession) msg; + ActorRef wrapper = ctx.createAdapter(p -> + new PostSessionMessage(getSession.screenName, p.message)); + getSession.replyTo.tell(new SessionGranted(wrapper)); + // TODO mutable collection :( + List> newSessions = new ArrayList>(sessions); + newSessions.add(getSession.replyTo); + return chatRoom(newSessions); + } else if (msg instanceof PostSessionMessage) { + PostSessionMessage post = (PostSessionMessage) msg; + MessagePosted mp = new MessagePosted(post.screenName, post.message); + sessions.forEach(s -> s.tell(mp)); + return Actor.same(); + } else { + return Actor.unhandled(); + } + }); + } + //#chatroom-behavior + + } + //#chatroom-actor + +} diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java new file mode 100644 index 0000000000..148542e1b6 --- /dev/null +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package jdocs.akka.typed; + +//#imports +import akka.typed.ActorRef; +import akka.typed.Behavior; +import akka.typed.javadsl.Actor; +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 replyTo; + public GetSession(String screenName, ActorRef 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 handle; + public SessionGranted(ActorRef 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 behavior() { + return Actor.mutable(ChatRoomBehavior::new); + } + + public static class ChatRoomBehavior extends Actor.MutableBehavior { + final ActorContext ctx; + final List> sessions = new ArrayList>(); + + public ChatRoomBehavior(ActorContext ctx) { + this.ctx = ctx; + } + + @Override + public Behavior onMessage(Command msg) { + if (msg instanceof GetSession) { + GetSession getSession = (GetSession) msg; + ActorRef wrapper = ctx.createAdapter(p -> + new PostSessionMessage(getSession.screenName, p.message)); + getSession.replyTo.tell(new SessionGranted(wrapper)); + sessions.add(getSession.replyTo); + return Actor.same(); + } else if (msg instanceof PostSessionMessage) { + PostSessionMessage post = (PostSessionMessage) msg; + MessagePosted mp = new MessagePosted(post.screenName, post.message); + sessions.forEach(s -> s.tell(mp)); + return this; + } else { + return Actor.unhandled(); + } + } + + } + + //#chatroom-behavior + + + + + } + //#chatroom-actor + +} diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala index 90c490666d..03778bcf9b 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala @@ -388,6 +388,44 @@ class BehaviorSpec extends TypedSpec { object `A Stateful Behavior (scala,native)` extends StatefulScalaBehavior with NativeSystem object `A Stateful Behavior (scala,adapted)` extends StatefulScalaBehavior 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 StatelessScalaBehavior extends Messages { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = (SActor.Stateless { (ctx, msg) ⇒ diff --git a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala similarity index 93% rename from akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala rename to akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index afdba6b73b..ba5d44f95b 100644 --- a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -11,8 +11,6 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.concurrent.Await //#imports -import akka.testkit.AkkaSpec -import akka.typed.TypedSpec object IntroSpec { @@ -50,7 +48,10 @@ object IntroSpec { //#chatroom-protocol //#chatroom-behavior - def chatRoom(sessions: List[ActorRef[SessionEvent]] = List.empty): Behavior[Command] = + val behavior: Behavior[Command] = + chatRoom(List.empty) + + private def chatRoom(sessions: List[ActorRef[SessionEvent]]): Behavior[Command] = Stateful[Command] { (ctx, msg) ⇒ msg match { case GetSession(screenName, client) ⇒ @@ -114,8 +115,8 @@ class IntroSpec extends TypedSpec { //#chatroom-main val main: Behavior[akka.NotUsed] = - Deferred { ctx => - val chatRoom = ctx.spawn(ChatRoom.chatRoom(), "chatroom") + Deferred { ctx ⇒ + val chatRoom = ctx.spawn(ChatRoom.behavior, "chatroom") val gabblerRef = ctx.spawn(gabbler, "gabbler") ctx.watch(gabblerRef) chatRoom ! GetSession("ol’ Gabbler", gabblerRef) @@ -128,8 +129,7 @@ class IntroSpec extends TypedSpec { Stopped case _ ⇒ Unhandled - } - ) + }) } val system = ActorSystem("ChatRoomDemo", main) diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala new file mode 100644 index 0000000000..d8626874c1 --- /dev/null +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2014-2017 Lightbend Inc. + */ +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] = + Mutable[Command](ctx ⇒ new ChatRoomBehavior(ctx)) + + class ChatRoomBehavior(ctx: ActorContext[Command]) extends 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 = + Stateful[SessionEvent] { (_, msg) ⇒ + msg match { + case SessionDenied(reason) ⇒ + println(s"cannot start chat room session: $reason") + Stopped + case SessionGranted(handle) ⇒ + handle ! PostMessage("Hello World!") + Same + case MessagePosted(screenName, message) ⇒ + println(s"message has been posted by '$screenName': $message") + Stopped + } + } + //#chatroom-gabbler + + //#chatroom-main + val main: Behavior[akka.NotUsed] = + Deferred { ctx ⇒ + val chatRoom = ctx.spawn(ChatRoom.behavior(), "chatroom") + val gabblerRef = ctx.spawn(gabbler, "gabbler") + ctx.watch(gabblerRef) + chatRoom ! GetSession("ol’ Gabbler", gabblerRef) + + Stateful( + onMessage = (_, _) ⇒ Unhandled, + onSignal = (ctx, sig) ⇒ + sig match { + case Terminated(ref) ⇒ + Stopped + case _ ⇒ + Unhandled + }) + } + + val system = ActorSystem("ChatRoomDemo", main) + Await.result(system.whenTerminated, 1.second) + //#chatroom-main + } + +} diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java index 5b7a49b1ee..2c86bfb93c 100644 --- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java +++ b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java @@ -95,7 +95,7 @@ public abstract class Actor { private Behavior canonicalize(Behavior behv) { if (Behavior.isUnhandled(behv)) return Unhandled(); - else if (behv == Same()) + else if (behv == Same() || behv == this) return Same(); else if (Behavior.isAlive(behv)) return new Tap(signal, message, behv); @@ -120,7 +120,7 @@ public abstract class Actor { * 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 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 @@ -152,11 +152,11 @@ public abstract class Actor { * 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 @@ -165,18 +165,18 @@ public abstract class Actor { static public Behavior stateful(Function2, T, Behavior> message) { return new Stateful(message, unhandledFun()); } - + /** - * Construct an actor behavior that can react to both incoming messages and + * 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 @@ -196,11 +196,11 @@ public abstract class Actor { * 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 @@ -215,7 +215,7 @@ public abstract class Actor { * 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 Behavior same() { @@ -227,7 +227,7 @@ public abstract class Actor { * 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 Behavior unhandled() { @@ -240,7 +240,7 @@ public abstract class Actor { * 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 Behavior stopped() { @@ -249,7 +249,7 @@ public abstract class Actor { /** * A behavior that treats every incoming message as unhandled. - * + * * @return the empty behavior */ static public Behavior empty() { @@ -258,7 +258,7 @@ public abstract class Actor { /** * A behavior that ignores every incoming message and returns “same”. - * + * * @return the inert behavior */ static public Behavior ignore() { @@ -270,7 +270,7 @@ public abstract class Actor { * 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 * tap(...) call again. - * + * * @param signal * procedure to invoke with the {@link ActorContext} and the * {@link akka.typed.Signal} as arguments before delivering the signal to @@ -293,7 +293,7 @@ public abstract class Actor { * 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 monitor(...) call again. - * + * * @param monitor * ActorRef to which to copy all received messages * @param behavior @@ -309,7 +309,7 @@ public abstract class Actor { * 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 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 @@ -317,7 +317,7 @@ public abstract class Actor { * 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 @@ -346,7 +346,7 @@ public abstract class Actor { * // drop all other kinds of Number * ); * - * + * * @param behavior * the behavior that will receive the selected messages * @param selector @@ -362,7 +362,7 @@ public abstract class Actor { * 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 @@ -371,4 +371,74 @@ public abstract class Actor { return new Deferred(producer); } + /** + * 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 producer + * 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 + */ + static public Behavior mutable(akka.japi.function.Function, MutableBehavior> producer) { + return deferred(ctx -> producer.apply(ctx)); + } + + /** + * Mutable behavior can be implemented by extending this class and implement the + * abstract method {@link MutableBehavior#onMessage} and optionally override + * {@link MutableBehavior#onSignal}. + * + * Instances of this behavior should be created via {@link Actor#mutable} and if + * the {@link ActorContext} is needed it can be passed as a constructor parameter + * from the factory function. + * + * @see Actor#mutable + */ + static public abstract class MutableBehavior extends ExtensibleBehavior { + @Override + final public Behavior message(akka.typed.ActorContext ctx, T msg) throws Exception { + return onMessage(msg); + } + + /** + * 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: + *
    + *
  • 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
  • + *
+ * + */ + public abstract Behavior onMessage(T msg) throws Exception; + + @Override + final public Behavior management(akka.typed.ActorContext ctx, Signal msg) throws Exception { + return onSignal(msg); + } + + /** + * Override this method to process an incoming {@link 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, this method returns `unhandled`. + */ + public Behavior onSignal(Signal msg) throws Exception { + return Actor.unhandled(); + } + } + } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index dcc50782d0..97414fc0e0 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -214,6 +214,73 @@ object Actor { override def toString: String = s"Deferred(${LineNumbers(factory)})" } + /** + * 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: 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] { + @throws(classOf[Exception]) + override final def message(ctx: akka.typed.ActorContext[T], msg: T): Behavior[T] = + onMessage(msg) + + /** + * 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: + *
    + *
  • 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
  • + *
+ * + */ + @throws(classOf[Exception]) + def onMessage(msg: T): Behavior[T] + + @throws(classOf[Exception]) + override final def management(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] = + onSignal(msg) + + /** + * 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, this method returns `Unhandled`. + */ + @throws(classOf[Exception]) + def onSignal(msg: Signal): Behavior[T] = + Unhandled + } + /** * Return this behavior from message processing in order to advise the * system to reuse the previous behavior. This is provided in order to @@ -306,7 +373,7 @@ object Actor { private def canonical(behv: Behavior[T]): Behavior[T] = if (isUnhandled(behv)) Unhandled - else if (behv eq SameBehavior) Same + else if ((behv eq SameBehavior) || (behv eq this)) Same else if (isAlive(behv)) Tap(onMessage, onSignal, behv) else Stopped override def management(ctx: AC[T], signal: Signal): Behavior[T] = { From 8b2e4ed33f8c89b8a90844956dd7972e43d63add Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Wed, 19 Apr 2017 15:29:16 +0200 Subject: [PATCH 11/50] remove stateless, rename stateful to immutable, #22325 --- akka-docs/rst/scala/typed.rst | 8 +- .../typed/testkit/TestEventListener.scala | 18 ++-- .../java/akka/typed/javadsl/ActorCompile.java | 14 +-- .../java/akka/typed/javadsl/AdapterTest.java | 4 +- .../test/java/jdocs/akka/typed/IntroTest.java | 5 +- .../scala/akka/typed/ActorContextSpec.scala | 15 +-- .../src/test/scala/akka/typed/AskSpec.scala | 2 +- .../test/scala/akka/typed/BehaviorSpec.scala | 95 ++++++------------- .../scala/akka/typed/PerformanceSpec.scala | 13 ++- .../src/test/scala/akka/typed/StepWise.scala | 8 +- .../src/test/scala/akka/typed/TypedSpec.scala | 4 +- .../akka/typed/internal/ActorCellSpec.scala | 22 ++++- .../akka/typed/internal/ActorSystemSpec.scala | 13 ++- .../akka/typed/internal/EventStreamSpec.scala | 9 +- .../typed/patterns/ReceptionistSpec.scala | 6 +- .../typed/scaladsl/adapter/AdapterSpec.scala | 4 +- .../scala/docs/akka/typed/IntroSpec.scala | 9 +- .../docs/akka/typed/MutableIntroSpec.scala | 4 +- .../main/java/akka/typed/javadsl/Actor.java | 72 ++++---------- .../src/main/scala/akka/typed/Behavior.scala | 2 +- .../main/scala/akka/typed/EventStream.scala | 10 +- .../akka/typed/internal/ActorSystemImpl.scala | 3 +- .../akka/typed/internal/EventStreamImpl.scala | 7 +- .../akka/typed/patterns/Receptionist.scala | 2 +- .../scala/akka/typed/scaladsl/Actor.scala | 33 ++----- 25 files changed, 166 insertions(+), 216 deletions(-) diff --git a/akka-docs/rst/scala/typed.rst b/akka-docs/rst/scala/typed.rst index 17808fd5b4..6eefdadeea 100644 --- a/akka-docs/rst/scala/typed.rst +++ b/akka-docs/rst/scala/typed.rst @@ -30,10 +30,8 @@ supplies so that the :class:`HelloWorld` Actor can send back the confirmation message. The behavior of the Actor is defined as the :meth:`greeter` value with the help -of the :class:`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 :class:`Immutable` behavior constructor—there are several different ways of +formulating behaviors as we shall see in the following. The type of the messages handled by this behavior is declared to be of class :class:`Greet`, which implies that the supplied function’s ``msg`` argument is @@ -248,7 +246,7 @@ 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 :class:`Stateful` behavior decorator. The +particular one using the :class:`Immutable` behavior decorator. The provided ``signal`` function will be invoked for signals (subclasses of :class:`Signal`) or the ``mesg`` function for user messages. diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala index 81c9e207b8..0fe9a27687 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala @@ -24,7 +24,7 @@ class TestEventListener extends Logger with StdOutLogger { val initialBehavior = { // TODO avoid depending on dsl here? Deferred[Command] { _ ⇒ - Stateful[Command] { + Immutable[Command] { case (ctx, Initialize(eventStream, replyTo)) ⇒ val log = ctx.spawn(Deferred[AnyRef] { childCtx ⇒ var filters: List[EventFilter] = Nil @@ -42,11 +42,17 @@ class TestEventListener extends Logger with StdOutLogger { filters = removeFirst(filters) } - Stateless[AnyRef] { - case (_, TE.Mute(filters)) ⇒ filters foreach addFilter - case (_, TE.UnMute(filters)) ⇒ filters foreach removeFilter - case (_, event: LogEvent) ⇒ if (!filter(event)) print(event) - case _ ⇒ Unhandled + Immutable[AnyRef] { + case (_, TE.Mute(filters)) ⇒ + filters foreach addFilter + Same + case (_, TE.UnMute(filters)) ⇒ + filters foreach removeFilter + Same + case (_, event: LogEvent) ⇒ + if (!filter(event)) print(event) + Same + case _ ⇒ Unhandled } }, "logger") diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index f59dc63da1..6ed58916c6 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -7,9 +7,9 @@ import akka.typed.*; import static akka.typed.javadsl.Actor.*; public class ActorCompile { - + interface MyMsg {} - + class MyMsgA implements MyMsg { final ActorRef replyTo; @@ -26,9 +26,8 @@ public class ActorCompile { } } - Behavior actor1 = stateful((ctx, msg) -> stopped(), (ctx, signal) -> same()); - Behavior actor2 = stateful((ctx, msg) -> unhandled()); - Behavior actor3 = stateless((ctx, msg) -> {}); + Behavior actor1 = immutable((ctx, msg) -> stopped(), (ctx, signal) -> same()); + Behavior actor2 = immutable((ctx, msg) -> unhandled()); Behavior actor4 = empty(); Behavior actor5 = ignore(); Behavior actor6 = tap((ctx, signal) -> {}, (ctx, msg) -> {}, actor5); @@ -40,12 +39,13 @@ public class ActorCompile { Behavior actor9 = widened(actor7, pf -> pf.match(MyMsgA.class, x -> x)); { - Actor.stateful((ctx, msg) -> { + Actor.immutable((ctx, msg) -> { if (msg instanceof MyMsgA) { - return stateless((ctx2, msg2) -> { + return immutable((ctx2, msg2) -> { if (msg2 instanceof MyMsgB) { ((MyMsgA) msg).replyTo.tell(((MyMsgB) msg2).greeting); } + return same(); }); } else return unhandled(); }); diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java index cd22cb31ee..7a59034fd2 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/AdapterTest.java @@ -50,7 +50,7 @@ public class AdapterTest extends JUnitSuite { static Behavior create(akka.actor.ActorRef ref, akka.actor.ActorRef probe) { Typed1 logic = new Typed1(ref, probe); - return stateful( + return immutable( (ctx, msg) -> logic.onMessage(ctx, msg), (ctx, sig) -> logic.onSignal(ctx, sig)); } @@ -187,7 +187,7 @@ public class AdapterTest extends JUnitSuite { } static Behavior typed2() { - return Actor.stateful((ctx, msg) -> { + return Actor.immutable((ctx, msg) -> { if (msg instanceof Ping) { ActorRef replyTo = ((Ping) msg).replyTo; replyTo.tell("pong"); diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java index 3cb4b540e2..705514d7f2 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -36,9 +36,10 @@ public class IntroTest { } } - public static final Behavior greeter = Actor.stateless((ctx, msg) -> { + public static final Behavior greeter = Actor.immutable((ctx, msg) -> { System.out.println("Hello " + msg.whom + "!"); msg.replyTo.tell(new Greeted(msg.whom)); + return Actor.same(); }); } //#hello-world-actor @@ -104,7 +105,7 @@ public class IntroTest { } private static Behavior chatRoom(List> sessions) { - return Actor.stateful((ctx, msg) -> { + return Actor.immutable((ctx, msg) -> { if (msg instanceof GetSession) { GetSession getSession = (GetSession) msg; ActorRef wrapper = ctx.createAdapter(p -> diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala index 981069e473..d2c369f29a 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala @@ -73,7 +73,7 @@ object ActorContextSpec { final case class Adapter(a: ActorRef[Command]) extends Event def subject(monitor: ActorRef[Monitor]): Behavior[Command] = - Actor.Stateful( + Actor.Immutable( (ctx, message) ⇒ message match { case ReceiveTimeout ⇒ monitor ! GotReceiveTimeout @@ -131,16 +131,17 @@ object ActorContextSpec { Actor.Same case BecomeInert(replyTo) ⇒ replyTo ! BecameInert - Actor.Stateless { + Actor.Immutable { case (_, Ping(replyTo)) ⇒ replyTo ! Pong2 + Actor.Same case (_, Throw(ex)) ⇒ throw ex - case _ ⇒ () + case _ ⇒ Actor.Unhandled } case BecomeCareless(replyTo) ⇒ replyTo ! BecameCareless - Actor.Stateful[Command]({ + Actor.Immutable[Command]({ case (_, _) ⇒ Actor.Unhandled }, { case (_, Terminated(_)) ⇒ Actor.Unhandled @@ -156,7 +157,7 @@ object ActorContextSpec { (ctx, signal) ⇒ { monitor ! GotSignal(signal); Actor.Same }) def oldSubject(monitor: ActorRef[Monitor]): Behavior[Command] = { - Actor.Stateful({ + Actor.Immutable({ case (ctx, message) ⇒ message match { case ReceiveTimeout ⇒ monitor ! GotReceiveTimeout @@ -214,7 +215,7 @@ object ActorContextSpec { Actor.Same case BecomeInert(replyTo) ⇒ replyTo ! BecameInert - Actor.Stateful[Command] { + Actor.Immutable[Command] { case (_, Ping(replyTo)) ⇒ replyTo ! Pong2 Actor.Same @@ -224,7 +225,7 @@ object ActorContextSpec { } case BecomeCareless(replyTo) ⇒ replyTo ! BecameCareless - Actor.Stateful[Command]( + Actor.Immutable[Command]( { case _ ⇒ Actor.Unhandled }, diff --git a/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala index 3f48b9a929..332fd0159f 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala @@ -29,7 +29,7 @@ class AskSpec extends TypedSpec with ScalaFutures { implicit def executor: ExecutionContext = system.executionContext - val behavior: Behavior[Msg] = Stateful[Msg] { + val behavior: Behavior[Msg] = Immutable[Msg] { case (_, foo: Foo) ⇒ foo.replyTo ! "foo" Same diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala index 03778bcf9b..a58ac4730d 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala @@ -255,7 +255,7 @@ class BehaviorSpec extends TypedSpec { } private def mkFull(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] = { - SActor.Stateful[Command]({ + SActor.Immutable[Command]({ case (ctx, GetSelf) ⇒ monitor ! Self(ctx.self) SActor.Same @@ -289,10 +289,10 @@ class BehaviorSpec extends TypedSpec { object `A Full Behavior (native)` extends FullBehavior with NativeSystem object `A Full Behavior (adapted)` extends FullBehavior with AdaptedSystem - trait StatefulBehavior extends Messages with BecomeWithLifecycle with Stoppable { + 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.Stateful({ + SActor.Immutable({ case (ctx, GetSelf) ⇒ monitor ! Self(ctx.self) SActor.Same @@ -320,13 +320,13 @@ class BehaviorSpec extends TypedSpec { }) } } - object `A Stateful Behavior (native)` extends StatefulBehavior with NativeSystem - object `A Stateful Behavior (adapted)` extends StatefulBehavior with AdaptedSystem + object `A Immutable Behavior (native)` extends ImmutableBehavior with NativeSystem + object `A Immutable Behavior (adapted)` extends ImmutableBehavior with AdaptedSystem - trait StatefulWithSignalScalaBehavior extends Messages with BecomeWithLifecycle with Stoppable { + 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.Stateful( + SActor.Immutable( (ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.self) @@ -354,13 +354,13 @@ class BehaviorSpec extends TypedSpec { SActor.Same }) } - object `A StatefulWithSignal Behavior (scala,native)` extends StatefulWithSignalScalaBehavior with NativeSystem - object `A StatefulWithSignal Behavior (scala,adapted)` extends StatefulWithSignalScalaBehavior with AdaptedSystem + object `A ImmutableWithSignal Behavior (scala,native)` extends ImmutableWithSignalScalaBehavior with NativeSystem + object `A ImmutableWithSignal Behavior (scala,adapted)` extends ImmutableWithSignalScalaBehavior with AdaptedSystem - trait StatefulScalaBehavior extends Messages with Become with Stoppable { + 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.Stateful[Command] { (ctx, msg) ⇒ + SActor.Immutable[Command] { (ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.self) @@ -385,8 +385,8 @@ class BehaviorSpec extends TypedSpec { } } } - object `A Stateful Behavior (scala,native)` extends StatefulScalaBehavior with NativeSystem - object `A Stateful Behavior (scala,adapted)` extends StatefulScalaBehavior with AdaptedSystem + 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 @@ -426,25 +426,7 @@ class BehaviorSpec extends TypedSpec { object `A Mutable Behavior (scala,native)` extends MutableScalaBehavior with NativeSystem object `A Mutable Behavior (scala,adapted)` extends MutableScalaBehavior with AdaptedSystem - trait StatelessScalaBehavior extends Messages { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (SActor.Stateless { (ctx, msg) ⇒ - msg match { - case GetSelf ⇒ monitor ! Self(ctx.self) - case Miss ⇒ monitor ! Missed - case Ignore ⇒ monitor ! Ignored - case Ping ⇒ monitor ! Pong - case Swap ⇒ monitor ! Swapped - case GetState() ⇒ monitor ! StateA - case Stop ⇒ - case _: AuxPing ⇒ - } - }, null) - } - object `A Stateless Behavior (scala,native)` extends StatelessScalaBehavior with NativeSystem - object `A Stateless Behavior (scala,adapted)` extends StatelessScalaBehavior with AdaptedSystem - - trait WidenedScalaBehavior extends StatefulWithSignalScalaBehavior with Reuse with Siphon { + trait WidenedScalaBehavior extends ImmutableWithSignalScalaBehavior with Reuse with Siphon { import SActor.BehaviorDecorators override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { @@ -455,7 +437,7 @@ class BehaviorSpec extends TypedSpec { object `A widened Behavior (scala,native)` extends WidenedScalaBehavior with NativeSystem object `A widened Behavior (scala,adapted)` extends WidenedScalaBehavior with AdaptedSystem - trait DeferredScalaBehavior extends StatefulWithSignalScalaBehavior { + trait DeferredScalaBehavior extends ImmutableWithSignalScalaBehavior { override type Aux = Inbox[Done] override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { @@ -472,7 +454,7 @@ class BehaviorSpec extends TypedSpec { object `A deferred Behavior (scala,native)` extends DeferredScalaBehavior with NativeSystem object `A deferred Behavior (scala,adapted)` extends DeferredScalaBehavior with AdaptedSystem - trait TapScalaBehavior extends StatefulWithSignalScalaBehavior with Reuse with SignalSiphon { + 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) @@ -481,7 +463,7 @@ class BehaviorSpec extends TypedSpec { object `A tap Behavior (scala,native)` extends TapScalaBehavior with NativeSystem object `A tap Behavior (scala,adapted)` extends TapScalaBehavior with AdaptedSystem - trait RestarterScalaBehavior extends StatefulWithSignalScalaBehavior with Reuse { + trait RestarterScalaBehavior extends ImmutableWithSignalScalaBehavior with Reuse { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { SActor.Restarter[Exception]().wrap(super.behavior(monitor)._1) → null } @@ -521,10 +503,10 @@ class BehaviorSpec extends TypedSpec { override def apply(in: JActorContext[Command]) = f(in) } - trait StatefulWithSignalJavaBehavior extends Messages with BecomeWithLifecycle with Stoppable { + 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.stateful( + JActor.immutable( fc((ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.getSelf) @@ -552,13 +534,13 @@ class BehaviorSpec extends TypedSpec { SActor.Same })) } - object `A StatefulWithSignal Behavior (java,native)` extends StatefulWithSignalJavaBehavior with NativeSystem - object `A StatefulWithSignal Behavior (java,adapted)` extends StatefulWithSignalJavaBehavior with AdaptedSystem + object `A ImmutableWithSignal Behavior (java,native)` extends ImmutableWithSignalJavaBehavior with NativeSystem + object `A ImmutableWithSignal Behavior (java,adapted)` extends ImmutableWithSignalJavaBehavior with AdaptedSystem - trait StatefulJavaBehavior extends Messages with Become with Stoppable { + 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.stateful { + JActor.immutable { fc((ctx, msg) ⇒ msg match { case GetSelf ⇒ @@ -584,29 +566,10 @@ class BehaviorSpec extends TypedSpec { }) } } - object `A Stateful Behavior (java,native)` extends StatefulJavaBehavior with NativeSystem - object `A Stateful Behavior (java,adapted)` extends StatefulJavaBehavior with AdaptedSystem + object `A Immutable Behavior (java,native)` extends ImmutableJavaBehavior with NativeSystem + object `A Immutable Behavior (java,adapted)` extends ImmutableJavaBehavior with AdaptedSystem - trait StatelessJavaBehavior extends Messages { - override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = - (JActor.stateless { - pc((ctx, msg) ⇒ - msg match { - case GetSelf ⇒ monitor ! Self(ctx.getSelf) - case Miss ⇒ monitor ! Missed - case Ignore ⇒ monitor ! Ignored - case Ping ⇒ monitor ! Pong - case Swap ⇒ monitor ! Swapped - case GetState() ⇒ monitor ! StateA - case Stop ⇒ - case _: AuxPing ⇒ - }) - }, null) - } - object `A Stateless Behavior (java,native)` extends StatelessJavaBehavior with NativeSystem - object `A Stateless Behavior (java,adapted)` extends StatelessJavaBehavior with AdaptedSystem - - trait WidenedJavaBehavior extends StatefulWithSignalJavaBehavior with Reuse with Siphon { + 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 @@ -615,7 +578,7 @@ class BehaviorSpec extends TypedSpec { object `A widened Behavior (java,native)` extends WidenedJavaBehavior with NativeSystem object `A widened Behavior (java,adapted)` extends WidenedJavaBehavior with AdaptedSystem - trait DeferredJavaBehavior extends StatefulWithSignalJavaBehavior { + trait DeferredJavaBehavior extends ImmutableWithSignalJavaBehavior { override type Aux = Inbox[Done] override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { @@ -632,7 +595,7 @@ class BehaviorSpec extends TypedSpec { object `A deferred Behavior (java,native)` extends DeferredJavaBehavior with NativeSystem object `A deferred Behavior (java,adapted)` extends DeferredJavaBehavior with AdaptedSystem - trait TapJavaBehavior extends StatefulWithSignalJavaBehavior with Reuse with SignalSiphon { + 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( @@ -644,7 +607,7 @@ class BehaviorSpec extends TypedSpec { object `A tap Behavior (java,native)` extends TapJavaBehavior with NativeSystem object `A tap Behavior (java,adapted)` extends TapJavaBehavior with AdaptedSystem - trait RestarterJavaBehavior extends StatefulWithSignalJavaBehavior with Reuse { + trait RestarterJavaBehavior extends ImmutableWithSignalJavaBehavior with Reuse { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { JActor.restarter(classOf[Exception], JActor.OnFailure.RESTART, super.behavior(monitor)._1) → null } diff --git a/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala index a1198f2b3d..11ad566be0 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala @@ -18,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]) @@ -27,14 +27,19 @@ class PerformanceSpec extends TypedSpec( StepWise[Pong] { (ctx, startWith) ⇒ startWith { - val pinger = Stateless[Ping] { (ctx, msg) ⇒ + val pinger = Immutable[Ping] { (ctx, msg) ⇒ if (msg.x == 0) { msg.report ! Pong(0, ctx.self, msg.report) - } else msg.pong ! Pong(msg.x - 1, ctx.self, msg.report) + Same + } else { + msg.pong ! Pong(msg.x - 1, ctx.self, msg.report) + Same + } } // FIXME .withDispatcher(executor) - val ponger = Stateless[Pong] { (ctx, msg) ⇒ + val ponger = Immutable[Pong] { (ctx, msg) ⇒ msg.ping ! Ping(msg.x, ctx.self, msg.report) + Same } // FIXME .withDispatcher(executor) val actors = diff --git a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala index 43ec05ffca..9d54d3fefb 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala @@ -135,7 +135,7 @@ object StepWise { case ThunkV(f) :: tail ⇒ run(ctx, tail, f(value)) case Message(t, f, trace) :: tail ⇒ ctx.setReceiveTimeout(t, ReceiveTimeout) - Stateful({ + Immutable({ case (_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for a message") case (_, msg) ⇒ ctx.cancelReceiveTimeout() @@ -147,7 +147,7 @@ object StepWise { val deadline = Deadline.now + t def behavior(count: Int, acc: List[Any]): Behavior[Any] = { ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout) - Stateful({ + Immutable({ case (_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)") case (_, msg) ⇒ @@ -165,7 +165,7 @@ object StepWise { val deadline = Deadline.now + t def behavior(count: Int, acc: List[Either[Signal, Any]]): Behavior[Any] = { ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout) - Stateful[Any]({ + Immutable[Any]({ case (_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)") case (_, msg) ⇒ @@ -186,7 +186,7 @@ object StepWise { behavior(0, Nil) case Termination(t, f, trace) :: tail ⇒ ctx.setReceiveTimeout(t, ReceiveTimeout) - Stateful[Any]({ + 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") }, { diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index fd5b4fbb17..a0e52febdb 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -83,7 +83,7 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup { import akka.testkit._ def await[T](f: Future[T]): T = Await.result(f, timeout.duration * 1.1) - lazy val blackhole = await(nativeSystem ? Create(Stateful[Any] { case _ ⇒ Same }, "blackhole")) + lazy val blackhole = await(nativeSystem ? Create(Immutable[Any] { case _ ⇒ Same }, "blackhole")) /** * Run an Actor-based test. The test procedure is most conveniently @@ -161,7 +161,7 @@ object TypedSpec { case object Timedout extends Status def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] = - Stateful[Command]({ + Immutable[Command]({ case (ctx, r: RunTest[t]) ⇒ val test = ctx.spawn(r.behavior, r.name) ctx.schedule(r.timeout, r.replyTo, Timedout) diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala index 450d10736d..079dd0d2ea 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala @@ -21,7 +21,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"; Stateless[String] { case (_, 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()) @@ -100,7 +107,7 @@ 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 behavior = Deferred[String](_ ⇒ { parent ! "created"; Stateful[String] { case (s, _) ⇒ throw ex } }) + 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) { @@ -181,7 +188,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, Deferred[String] { ctx ⇒ Stateless[String] { case (_, s) ⇒ ctx.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) { @@ -416,7 +430,7 @@ 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, Stateful[String]( + val cell = new ActorCell(sys, Immutable[String]( { case _ ⇒ Unhandled }, { case (_, PostStop) ⇒ ??? diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala index 54c9c0c11d..7ec4519a45 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala @@ -40,7 +40,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", Stateful[Probe] { case (_, 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) } @@ -53,14 +53,13 @@ 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", Stateful[Probe]({ + val sys = system("terminate", Immutable[Probe]({ case (_, _) ⇒ Unhandled }, { case (ctx, PostStop) ⇒ inbox.ref ! "done" Same - } - )) + })) sys.terminate().futureValue inbox.receiveAll() should ===("done" :: Nil) } @@ -116,7 +115,11 @@ class ActorSystemSpec extends Spec with Matchers with BeforeAndAfterAll with Sca case class Doner(ref: ActorRef[Done]) - val ref1, ref2 = sys.systemActorOf(Stateless[Doner] { case (_, doner) ⇒ 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 diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala index 80670fc3ea..17fe087813 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala @@ -19,9 +19,12 @@ object EventStreamSpec { class MyLogger extends Logger { def initialBehavior: Behavior[Logger.Command] = - Stateless { + Immutable { case (ctx, Logger.Initialize(es, replyTo)) ⇒ - val logger = ctx.spawn(Stateless[LogEvent] { (_, ev: LogEvent) ⇒ logged :+= ev }, "logger") + val logger = ctx.spawn(Immutable[LogEvent] { (_, ev: LogEvent) ⇒ + logged :+= ev + Same + }, "logger") ctx.watch(logger) replyTo ! logger Empty @@ -262,7 +265,7 @@ class EventStreamSpec extends TypedSpec(EventStreamSpec.config) with Eventually } def `must unsubscribe an actor upon termination`(): Unit = { - val ref = nativeSystem ? TypedSpec.Create(Stateful[Done] { case _ ⇒ 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 diff --git a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala index ea36f0b4ff..24b88a741c 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala @@ -8,18 +8,18 @@ import akka.typed.scaladsl.AskPattern._ import scala.concurrent.duration._ import akka.typed._ -import akka.typed.scaladsl.Actor.Stateless +import akka.typed.scaladsl.Actor._ import akka.typed.testkit.{ Effect, EffectfulActorContext } class ReceptionistSpec extends TypedSpec { trait ServiceA case object ServiceKeyA extends ServiceKey[ServiceA] - val behaviorA = Stateless[ServiceA] { case (_, msg) ⇒ () } + val behaviorA = Empty[ServiceA] trait ServiceB case object ServiceKeyB extends ServiceKey[ServiceB] - val behaviorB = Stateless[ServiceB] { case (_, msg) ⇒ () } + val behaviorB = Empty[ServiceB] trait CommonTests { implicit def system: ActorSystem[TypedSpec.Command] diff --git a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala index ba5cc9411f..cea4bba24c 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala @@ -27,7 +27,7 @@ object AdapterSpec { } def typed1(ref: untyped.ActorRef, probe: ActorRef[String]): Behavior[String] = - Stateful( + Immutable( onMessage = (ctx, msg) ⇒ msg match { case "send" ⇒ @@ -126,7 +126,7 @@ object AdapterSpec { } def typed2: Behavior[Typed2Msg] = - Stateful { (ctx, msg) ⇒ + Immutable { (ctx, msg) ⇒ msg match { case Ping(replyTo) ⇒ replyTo ! "pong" diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index ba5d44f95b..c23b0b2246 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -19,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 = Immutable[Greet] { (_, msg) ⇒ println(s"Hello ${msg.whom}!") msg.replyTo ! Greeted(msg.whom) + Same } } //#hello-world-actor @@ -52,7 +53,7 @@ object IntroSpec { chatRoom(List.empty) private def chatRoom(sessions: List[ActorRef[SessionEvent]]): Behavior[Command] = - Stateful[Command] { (ctx, msg) ⇒ + Immutable[Command] { (ctx, msg) ⇒ msg match { case GetSession(screenName, client) ⇒ val wrapper = ctx.spawnAdapter { @@ -98,7 +99,7 @@ class IntroSpec extends TypedSpec { import ChatRoom._ val gabbler = - Stateful[SessionEvent] { (_, msg) ⇒ + Immutable[SessionEvent] { (_, msg) ⇒ msg match { case SessionDenied(reason) ⇒ println(s"cannot start chat room session: $reason") @@ -121,7 +122,7 @@ class IntroSpec extends TypedSpec { ctx.watch(gabblerRef) chatRoom ! GetSession("ol’ Gabbler", gabblerRef) - Stateful( + Immutable( onMessage = (_, _) ⇒ Unhandled, onSignal = (ctx, sig) ⇒ sig match { diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala index d8626874c1..9b93feaa90 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala @@ -74,7 +74,7 @@ class MutableIntroSpec extends TypedSpec { import ChatRoom._ val gabbler = - Stateful[SessionEvent] { (_, msg) ⇒ + Immutable[SessionEvent] { (_, msg) ⇒ msg match { case SessionDenied(reason) ⇒ println(s"cannot start chat room session: $reason") @@ -97,7 +97,7 @@ class MutableIntroSpec extends TypedSpec { ctx.watch(gabblerRef) chatRoom ! GetSession("ol’ Gabbler", gabblerRef) - Stateful( + Immutable( onMessage = (_, _) ⇒ Unhandled, onSignal = (ctx, sig) ⇒ sig match { diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java index 2c86bfb93c..c98363eb93 100644 --- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java +++ b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java @@ -40,11 +40,11 @@ public abstract class Actor { } } - private static class Stateful extends ExtensibleBehavior { + private static class Immutable extends ExtensibleBehavior { final Function2, T, Behavior> message; final Function2, Signal, Behavior> signal; - public Stateful(Function2, T, Behavior> message, + public Immutable(Function2, T, Behavior> message, Function2, Signal, Behavior> signal) { this.signal = signal; this.message = message; @@ -61,25 +61,6 @@ public abstract class Actor { } } - private static class Stateless extends ExtensibleBehavior { - final Procedure2, T> message; - - public Stateless(Procedure2, T> message) { - this.message = message; - } - - @Override - public Behavior management(akka.typed.ActorContext ctx, Signal msg) throws Exception { - return Same(); - } - - @Override - public Behavior message(akka.typed.ActorContext ctx, T msg) throws Exception { - message.apply(ctx, msg); - return Same(); - } - } - private static class Tap extends ExtensibleBehavior { final Procedure2, Signal> signal; final Procedure2, T> message; @@ -153,17 +134,19 @@ public abstract class Actor { * {@link 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. - * If no change is desired, use {@link #same}. + * State is updated by returning a new behavior that holds the new immutable + * state. 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 Behavior stateful(Function2, T, Behavior> message) { - return new Stateful(message, unhandledFun()); + static public Behavior immutable(Function2, T, Behavior> message) { + return new Immutable(message, unhandledFun()); } /** @@ -173,9 +156,11 @@ public abstract class Actor { * {@link 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. - * If no change is desired, use {@link #same}. + * State is updated by returning a new behavior that holds the new immutable + * state. If no change is desired, use {@link #same}. * * @param message * the function that describes how this actor reacts to the next @@ -185,29 +170,9 @@ public abstract class Actor { * signal * @return the behavior */ - static public Behavior stateful(Function2, T, Behavior> message, + static public Behavior immutable(Function2, T, Behavior> message, Function2, Signal, Behavior> signal) { - return new Stateful(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 Behavior stateless(Procedure2, T> message) { - return new Stateless(message); + return new Immutable(message, signal); } /** @@ -268,7 +233,7 @@ public abstract class Actor { /** * 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 + * behavior can evolve (i.e. be immutable) without needing to be wrapped in a * tap(...) call again. * * @param signal @@ -291,7 +256,7 @@ public abstract class Actor { /** * 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 behavior can evolve (i.e. return different behavior) without needing to be * wrapped in a monitor(...) call again. * * @param monitor @@ -339,7 +304,10 @@ public abstract class Actor { * Behavior’s type hierarchy. Signals are not transformed. * *
-   * Behavior<String> s = stateless((ctx, msg) -> System.out.println(msg))
+   * 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 + ")")
diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index 7f5497e3b2..eac5ea2edc 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -212,7 +212,7 @@ object Behavior {
     behavior match {
       case SameBehavior | UnhandledBehavior ⇒ throw new IllegalArgumentException(s"cannot execute with $behavior as behavior")
       case _: DeferredBehavior[_]           ⇒ throw new IllegalArgumentException(s"deferred should not be passed to interpreter")
-      case IgnoreBehavior                   ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]]
+      case IgnoreBehavior                   ⇒ SameBehavior.asInstanceOf[Behavior[T]]
       case StoppedBehavior                  ⇒ StoppedBehavior.asInstanceOf[Behavior[T]]
       case EmptyBehavior                    ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]]
       case ext: ExtensibleBehavior[T] @unchecked ⇒
diff --git a/akka-typed/src/main/scala/akka/typed/EventStream.scala b/akka-typed/src/main/scala/akka/typed/EventStream.scala
index 91bea405f7..0fa37d6a10 100644
--- a/akka-typed/src/main/scala/akka/typed/EventStream.scala
+++ b/akka-typed/src/main/scala/akka/typed/EventStream.scala
@@ -70,13 +70,15 @@ class DefaultLogger extends Logger with StdOutLogger {
     // TODO avoid depending on dsl here?
     import scaladsl.Actor._
     Deferred[Command] { _ ⇒
-      Stateful[Command] {
+      Immutable[Command] {
         case (ctx, Initialize(eventStream, replyTo)) ⇒
           val log = ctx.spawn(Deferred[AnyRef] { childCtx ⇒
 
-            Stateless[AnyRef] {
-              case (_, event: LogEvent) ⇒ print(event)
-              case _                    ⇒ Unhandled
+            Immutable[AnyRef] {
+              case (_, event: LogEvent) ⇒
+                print(event)
+                Same
+              case _ ⇒ Unhandled
             }
           }, "logger")
 
diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala
index 0a8398449c..a52e51660e 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala
@@ -33,11 +33,12 @@ object ActorSystemImpl {
     import scaladsl.Actor._
     Deferred { _ ⇒
       var i = 1
-      Stateless {
+      Immutable {
         case (ctx, create: CreateSystemActor[t]) ⇒
           val name = s"$i-${create.name}"
           i += 1
           create.replyTo ! ctx.spawn(create.behavior, name, create.deployment)
+          Same
       }
     }
   }
diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala
index 8519666ca2..bfe3ddafb0 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala
@@ -44,7 +44,7 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
     import scaladsl.Actor
     Actor.Deferred[Command] { _ ⇒
       if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"registering unsubscriber with $this"))
-      Actor.Stateful[Command]({
+      Actor.Immutable[Command]({
         case (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(actor)
@@ -148,10 +148,11 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
 
   private val UnhandledMessageForwarder = {
     // TODO avoid depending on dsl here?
-    import scaladsl.Actor.Stateless
-    Stateless[a.UnhandledMessage] {
+    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
     }
   }
 
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala
index fed6e0b6d2..1e42b78e39 100644
--- a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala
+++ b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala
@@ -72,7 +72,7 @@ object Receptionist {
 
   private type KV[K <: AbstractServiceKey] = ActorRef[K#Type]
 
-  private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = Stateful[Command]({ (ctx, msg) ⇒
+  private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = Immutable[Command]({ (ctx, msg) ⇒
     msg match {
       case r: Register[t] ⇒
         ctx.watch(r.address)
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index 97414fc0e0..95165a789a 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -176,7 +176,7 @@ object Actor {
    *
    * Example:
    * {{{
-   * Stateless[String]((ctx, msg) => println(msg)).widen[Number] {
+   * 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
@@ -329,36 +329,19 @@ 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](
+  final case class Immutable[T](
     onMessage: (ActorContext[T], T) ⇒ Behavior[T],
     onSignal:  (ActorContext[T], Signal) ⇒ Behavior[T] = Behavior.unhandledSignal.asInstanceOf[(ActorContext[T], Signal) ⇒ Behavior[T]])
     extends ExtensibleBehavior[T] {
     override def management(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg)
     override def message(ctx: AC[T], msg: T) = onMessage(ctx, msg)
-    override def toString = s"Stateful(${LineNumbers(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](onMessage: (ActorContext[T], T) ⇒ Any) extends ExtensibleBehavior[T] {
-    override def management(ctx: AC[T], msg: Signal): Behavior[T] = Unhandled
-    override def message(ctx: AC[T], msg: T): Behavior[T] = {
-      onMessage(ctx, msg)
-      this
-    }
-    override def toString = s"Static(${LineNumbers(onMessage)})"
+    override def toString = s"Immutable(${LineNumbers(onMessage)})"
   }
 
   /**
@@ -390,7 +373,7 @@ object Actor {
   /**
    * 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 {

From 124c374a5fa1abedcfa29698ef0cddc9bc7fc7de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= 
Date: Fri, 14 Apr 2017 16:31:21 +0300
Subject: [PATCH 12/50] #22687 Undefer before terminate

---
 .../src/main/scala/akka/typed/Behavior.scala  | 29 ++++++-------------
 .../typed/internal/SupervisionMechanics.scala |  6 ++--
 .../scala/akka/typed/patterns/Restarter.scala |  8 ++---
 3 files changed, 15 insertions(+), 28 deletions(-)

diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index eac5ea2edc..51e0e42a8b 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -155,13 +155,9 @@ object Behavior {
     }
 
   @tailrec
-  def undefer[T](deferredBehavior: DeferredBehavior[T], ctx: ActorContext[T]): Behavior[T] = {
-    val behavior = deferredBehavior(ctx)
-
-    behavior match {
-      case innerDeferred: DeferredBehavior[T] @unchecked ⇒ undefer(innerDeferred, ctx)
-      case _ ⇒ behavior
-    }
+  def undefer[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] = behavior match {
+    case innerDeferred: DeferredBehavior[T] @unchecked ⇒ undefer(innerDeferred(ctx), ctx)
+    case _ ⇒ behavior
   }
 
   /**
@@ -169,7 +165,7 @@ object Behavior {
    * 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] =
+  def validateAsInitial[T](behavior: BehaSo I get updates every day or so. Bleeding edge, but also works whenever I need it, which is quite rare as well. vior[T]): Behavior[T] =
     behavior match {
       case SameBehavior | UnhandledBehavior ⇒
         throw new IllegalArgumentException(s"cannot use $behavior as initial behavior")
@@ -179,12 +175,8 @@ object Behavior {
   /**
    * Validate the given behavior as initial, initialize it if it is deferred.
    */
-  def preStart[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] = {
-    validateAsInitial(behavior) match {
-      case d: DeferredBehavior[T] @unchecked ⇒ validateAsInitial(undefer(d, ctx))
-      case x                                 ⇒ x
-    }
-  }
+  def preStart[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
+    validateAsInitial(undefer(behavior, ctx))
 
   /**
    * Returns true if the given behavior is not stopped.
@@ -210,8 +202,8 @@ object Behavior {
 
   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 _: DeferredBehavior[_]           ⇒ throw new IllegalArgumentException(s"deferred should not be passed to interpreter")
+      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 StoppedBehavior                  ⇒ StoppedBehavior.asInstanceOf[Behavior[T]]
       case EmptyBehavior                    ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]]
@@ -220,10 +212,7 @@ object Behavior {
           case signal: Signal ⇒ ext.management(ctx, signal)
           case msg            ⇒ ext.message(ctx, msg.asInstanceOf[T])
         }
-        possiblyDeferredResult match {
-          case d: DeferredBehavior[T] @unchecked ⇒ undefer(d, ctx)
-          case notDeferred                       ⇒ notDeferred
-        }
+        undefer(possiblyDeferredResult, ctx)
     }
 
 }
diff --git a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
index cd00406647..b5856cfba7 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
@@ -4,9 +4,7 @@
 package akka.typed
 package internal
 
-import scala.annotation.{ switch, tailrec }
 import scala.util.control.NonFatal
-import scala.util.control.Exception.Catcher
 import akka.event.Logging
 import akka.typed.Behavior.DeferredBehavior
 
@@ -89,8 +87,10 @@ 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.
+     *
+     * Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination.
      */
-    try if (a ne null) Behavior.canonicalize(Behavior.interpretSignal(a, ctx, PostStop), a, ctx)
+    try if ((a ne null) && !a.isInstanceOf[DeferredBehavior[_]]) Behavior.canonicalize(Behavior.interpretSignal(a, ctx, PostStop), a, ctx)
     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))
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
index 5ba8872ac0..6256b65fa1 100644
--- a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
+++ b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
@@ -32,15 +32,13 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav
     else if (b eq behavior) Same
     else {
       b match {
-        case d: DeferredBehavior[_] ⇒ canonical(Behavior.undefer(d.asInstanceOf[DeferredBehavior[T]], ctx), ctx)
+        case d: DeferredBehavior[_] ⇒ canonical(Behavior.undefer(d, ctx), ctx)
         case b                      ⇒ Restarter[T, Thr](initialBehavior, resume)(b)
       }
     }
 
-  private def preStart(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] = b match {
-    case d: DeferredBehavior[_] ⇒ Behavior.undefer(d.asInstanceOf[DeferredBehavior[T]], ctx)
-    case b                      ⇒ b
-  }
+  private def preStart(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
+    Behavior.undefer(b, ctx)
 
   override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
     val startedBehavior = preStart(behavior, ctx)

From f75bb75f3e06d6b832631dd2f4ad6727b27d973c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= 
Date: Thu, 20 Apr 2017 10:15:20 +0300
Subject: [PATCH 13/50] #22744 Remove preStart

---
 .../src/main/scala/akka/typed/testkit/Effects.scala    |  2 +-
 akka-typed/src/main/scala/akka/typed/Behavior.scala    |  8 +-------
 .../akka/typed/internal/SupervisionMechanics.scala     |  4 ++--
 .../akka/typed/internal/adapter/ActorAdapter.scala     |  4 ++--
 .../src/main/scala/akka/typed/patterns/Restarter.scala | 10 +++++-----
 5 files changed, 11 insertions(+), 17 deletions(-)

diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
index d63c85cc36..ed1f1b934f 100644
--- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
@@ -51,7 +51,7 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma
   }
   def hasEffects: Boolean = effectQueue.peek() != null
 
-  private var current = Behavior.preStart(_initialBehavior, this)
+  private var current = Behavior.validateAsInitial(Behavior.undefer(_initialBehavior, this))
 
   def currentBehavior: Behavior[T] = current
   def isAlive: Boolean = Behavior.isAlive(current)
diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index 51e0e42a8b..258fd317c2 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -165,19 +165,13 @@ object Behavior {
    * notably the behavior can neither be `Same` nor `Unhandled`. Starting
    * out with a `Stopped` behavior is allowed, though.
    */
-  def validateAsInitial[T](behavior: BehaSo I get updates every day or so. Bleeding edge, but also works whenever I need it, which is quite rare as well. vior[T]): Behavior[T] =
+  def validateAsInitial[T](behavior: Behavior[T]): Behavior[T] =
     behavior match {
       case SameBehavior | UnhandledBehavior ⇒
         throw new IllegalArgumentException(s"cannot use $behavior as initial behavior")
       case x ⇒ x
     }
 
-  /**
-   * Validate the given behavior as initial, initialize it if it is deferred.
-   */
-  def preStart[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
-    validateAsInitial(undefer(behavior, ctx))
-
   /**
    * Returns true if the given behavior is not stopped.
    */
diff --git a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
index b5856cfba7..b24c8db363 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
@@ -6,7 +6,7 @@ package internal
 
 import scala.util.control.NonFatal
 import akka.event.Logging
-import akka.typed.Behavior.DeferredBehavior
+import akka.typed.Behavior.{ DeferredBehavior, undefer, validateAsInitial }
 
 /**
  * INTERNAL API
@@ -68,7 +68,7 @@ private[typed] trait SupervisionMechanics[T] {
     behavior = initialBehavior
     if (system.settings.untyped.DebugLifecycle)
       publish(Logging.Debug(self.path.toString, clazz(behavior), "started"))
-    behavior = Behavior.preStart(behavior, ctx)
+    behavior = validateAsInitial(undefer(behavior, ctx))
     if (!Behavior.isAlive(behavior)) self.sendSystem(Terminate())
     true
   }
diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala
index 3964c91bb7..272900b979 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala
@@ -58,11 +58,11 @@ import akka.annotation.InternalApi
   }
 
   override def preStart(): Unit =
-    behavior = Behavior.preStart(behavior, ctx)
+    behavior = validateAsInitial(undefer(behavior, ctx))
   override def preRestart(reason: Throwable, message: Option[Any]): Unit =
     next(Behavior.interpretSignal(behavior, ctx, PreRestart), PreRestart)
   override def postRestart(reason: Throwable): Unit =
-    behavior = Behavior.preStart(behavior, ctx)
+    behavior = validateAsInitial(undefer(behavior, ctx))
   override def postStop(): Unit = {
     next(Behavior.interpretSignal(behavior, ctx, PostStop), PostStop)
   }
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
index 6256b65fa1..3afdf29e3c 100644
--- a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
+++ b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
@@ -7,7 +7,7 @@ package patterns
 import scala.reflect.ClassTag
 import scala.util.control.NonFatal
 import akka.event.Logging
-import akka.typed.Behavior.{ DeferredBehavior, SameBehavior, StoppedBehavior, UnhandledBehavior }
+import akka.typed.Behavior._
 import akka.typed.scaladsl.Actor._
 
 /**
@@ -22,7 +22,7 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav
   private def restart(ctx: ActorContext[T], startedBehavior: Behavior[T]): Behavior[T] = {
     try Behavior.interpretSignal(startedBehavior, ctx, PreRestart) catch { case NonFatal(_) ⇒ }
     // no need to canonicalize, it's done in the calling methods
-    Behavior.preStart(initialBehavior, ctx)
+    validateAsInitial(undefer(initialBehavior, ctx))
   }
 
   private def canonical(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
@@ -78,13 +78,13 @@ final case class MutableRestarter[T, Thr <: Throwable: ClassTag](initialBehavior
 
   private[this] var current: Behavior[T] = _
   private def startCurrent(ctx: ActorContext[T]) = {
-    // we need to pre-start it the first time we have access to a context
-    if (current eq null) current = Behavior.preStart(initialBehavior, ctx)
+    // we need to undefer it if needed the first time we have access to a context
+    if (current eq null) current = validateAsInitial(undefer(initialBehavior, ctx))
   }
 
   private def restart(ctx: ActorContext[T]): Behavior[T] = {
     try Behavior.interpretSignal(current, ctx, PreRestart) catch { case NonFatal(_) ⇒ }
-    Behavior.preStart(initialBehavior, ctx)
+    validateAsInitial(undefer(initialBehavior, ctx))
   }
 
   override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {

From 2065edb3d16b8e50873d9c0d0cc0c03ef63167ad Mon Sep 17 00:00:00 2001
From: Arnout Engelen 
Date: Fri, 21 Apr 2017 16:26:26 +0200
Subject: [PATCH 14/50] Add generic parameter to watch/unwatch (#22696)

@ktoso mentioned in https://github.com/akka/akka/pull/22683#discussion_r110620041
this makes the methods more usable in the Java API. Added a test showing monitoring (watching) from the Java API
---
 .../scala/akka/typed/testkit/Effects.scala    |  4 +-
 .../typed/testkit/StubbedActorContext.scala   |  4 +-
 .../akka/typed/javadsl/MonitoringTest.java    | 67 +++++++++++++++++++
 .../java/akka/typed/javadsl/ActorContext.java |  4 +-
 .../main/scala/akka/typed/ActorSystem.scala   | 12 ++++
 .../akka/typed/internal/DeathWatch.scala      |  4 +-
 .../adapter/ActorContextAdapter.scala         |  4 +-
 .../main/scala/akka/typed/javadsl/Ask.scala   | 14 ++++
 .../scala/akka/typed/scaladsl/Actor.scala     |  4 +-
 9 files changed, 105 insertions(+), 12 deletions(-)
 create mode 100644 akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java
 create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/Ask.scala

diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
index ed1f1b934f..56076032a2 100644
--- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
@@ -82,11 +82,11 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma
     effectQueue.offer(Stopped(child.path.name))
     super.stop(child)
   }
-  override def watch(other: ActorRef[_]): Unit = {
+  override def watch[U](other: ActorRef[U]): Unit = {
     effectQueue.offer(Watched(other))
     super.watch(other)
   }
-  override def unwatch(other: ActorRef[_]): Unit = {
+  override def unwatch[U](other: ActorRef[U]): Unit = {
     effectQueue.offer(Unwatched(other))
     super.unwatch(other)
   }
diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala
index f368dbaef4..8a9daed51f 100644
--- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala
@@ -53,8 +53,8 @@ class StubbedActorContext[T](
       case Some(inbox) ⇒ inbox.ref == child
     }
   }
-  override def watch(other: ActorRef[_]): Unit = ()
-  override def unwatch(other: ActorRef[_]): Unit = ()
+  override def watch[U](other: ActorRef[U]): Unit = ()
+  override def unwatch[U](other: ActorRef[U]): Unit = ()
   override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = ()
   override def cancelReceiveTimeout(): Unit = ()
 
diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java
new file mode 100644
index 0000000000..f0f9eb001a
--- /dev/null
+++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java
@@ -0,0 +1,67 @@
+package akka.typed.javadsl;
+
+import java.util.concurrent.CompletionStage;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import akka.Done;
+import org.scalatest.junit.JUnitSuite;
+import scala.concurrent.Future;
+import scala.concurrent.duration.Duration;
+import akka.util.Timeout;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import akka.typed.*;
+import static akka.typed.javadsl.Actor.*;
+import static akka.typed.javadsl.AskPattern.*;
+
+import akka.testkit.AkkaSpec;
+
+public class MonitoringTest extends JUnitSuite {
+
+    static final class RunTest {
+        private final ActorRef replyTo;
+        public RunTest(ActorRef replyTo) {
+            this.replyTo = replyTo;
+        }
+    }
+    static final class Stop {}
+
+    // final FiniteDuration fiveSeconds = FiniteDuration.create(5, TimeUnit.SECONDS);
+    final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
+
+    final Behavior exitingActor = stateful((ctx, msg) -> {
+        System.out.println("Stopping!");
+        return stopped();
+    });
+
+    private Behavior> waitingForTermination(ActorRef replyWhenTerminated) {
+        return stateful(
+            (ctx, msg) -> unhandled(),
+            (ctx, sig) -> {
+                if (sig instanceof Terminated) {
+                    replyWhenTerminated.tell(Done.getInstance());
+                }
+                return same();
+            }
+        );
+    }
+
+
+    @Test
+    public void shouldWatchTerminatingActor() throws Exception {
+        Behavior> root = stateful((ctx, msg) -> {
+            ActorRef watched = ctx.spawn(exitingActor, "exitingActor");
+            ctx.watch(watched);
+            watched.tell(new Stop());
+            return waitingForTermination(msg.replyTo);
+        });
+        ActorSystem> system = ActorSystem$.MODULE$.create("sysname", root, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
+
+        // Not sure why this does not compile without an explicit cast?
+        // system.tell(new RunTest());
+        CompletionStage result = AskPattern.ask((ActorRef>)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler());
+        result.toCompletableFuture().get(3, TimeUnit.SECONDS);
+    }
+}
\ No newline at end of file
diff --git a/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java b/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java
index 4fba339013..dc92b15f73 100644
--- a/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java
+++ b/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java
@@ -104,13 +104,13 @@ public interface ActorContext {
    * {@link akka.typed.ActorSystem} to which the referenced Actor belongs is declared as failed
    * (e.g. in reaction to being unreachable).
    */
-  public void watch(ActorRef other);
+  public  void watch(ActorRef other);
 
   /**
    * Revoke the registration established by {@link #watch}. A {@link akka.typed.Terminated}
    * notification will not subsequently be received for the referenced Actor.
    */
-  public void unwatch(ActorRef other);
+  public  void unwatch(ActorRef other);
 
   /**
    * Schedule the sending of a notification in case no other message is received
diff --git a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala
index 914cc2ec1a..1ec0786a18 100644
--- a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala
+++ b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala
@@ -169,6 +169,18 @@ object ActorSystem {
     new ActorSystemImpl(name, appConfig, cl, executionContext, guardianBehavior, guardianDeployment)
   }
 
+  /**
+   * Java API
+   */
+  def create[T](name: String, guardianBehavior: Behavior[T],
+                guardianDeployment: java.util.Optional[DeploymentConfig],
+                config:             java.util.Optional[Config],
+                classLoader:        java.util.Optional[ClassLoader],
+                executionContext:   java.util.Optional[ExecutionContext]): ActorSystem[T] = {
+    import scala.compat.java8.OptionConverters._
+    apply(name, guardianBehavior, guardianDeployment.asScala.getOrElse(EmptyDeploymentConfig), config.asScala, classLoader.asScala, executionContext.asScala)
+  }
+
   /**
    * Create an ActorSystem based on the untyped [[akka.actor.ActorSystem]]
    * which runs Akka Typed [[Behavior]] on an emulation layer. In this
diff --git a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala
index 8efb24a9c0..fc91798448 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala
@@ -44,7 +44,7 @@ private[typed] trait DeathWatch[T] {
   private var watching = Set.empty[ARImpl]
   private var watchedBy = Set.empty[ARImpl]
 
-  final def watch(_a: ActorRef[_]): Unit = {
+  final def watch[U](_a: ActorRef[U]): Unit = {
     val a = _a.sorry
     if (a != self && !watching.contains(a)) {
       maintainAddressTerminatedSubscription(a) {
@@ -54,7 +54,7 @@ private[typed] trait DeathWatch[T] {
     }
   }
 
-  final def unwatch(_a: ActorRef[_]): Unit = {
+  final def unwatch[U](_a: ActorRef[U]): Unit = {
     val a = _a.sorry
     if (a != self && watching.contains(a)) {
       a.sendSystem(Unwatch(a, self))
diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala
index 68396fbe19..7000a685a2 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala
@@ -41,8 +41,8 @@ import akka.annotation.InternalApi
             false // none of our business
         }
     }
-  override def watch(other: ActorRef[_]) = { untyped.watch(toUntyped(other)) }
-  override def unwatch(other: ActorRef[_]) = { untyped.unwatch(toUntyped(other)) }
+  override def watch[U](other: ActorRef[U]) = { untyped.watch(toUntyped(other)) }
+  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
diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Ask.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Ask.scala
new file mode 100644
index 0000000000..d615b072b3
--- /dev/null
+++ b/akka-typed/src/main/scala/akka/typed/javadsl/Ask.scala
@@ -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))
+}
\ No newline at end of file
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index 95165a789a..8be3134c64 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -89,13 +89,13 @@ trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T] ⇒
    * [[ActorSystem]] to which the referenced Actor belongs is declared as
    * failed (e.g. in reaction to being unreachable).
    */
-  def watch(other: ActorRef[_]): Unit
+  def watch[U](other: ActorRef[U]): Unit
 
   /**
    * Revoke the registration established by `watch`. A [[Terminated]]
    * notification will not subsequently be received for the referenced Actor.
    */
-  def unwatch(other: ActorRef[_]): Unit
+  def unwatch[U](other: ActorRef[U]): Unit
 
   /**
    * Schedule the sending of a notification in case no other

From 6c40da71ddbb1033b1fa4eb94f0fad56423cfd10 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski 
Date: Fri, 21 Apr 2017 20:05:43 +0900
Subject: [PATCH 15/50] =typ #22736 expose Behavior.Same and friends

---
 .../main/java/akka/typed/javadsl/Actor.java   |  4 +--
 .../src/main/scala/akka/typed/Behavior.scala  | 33 +++++++++++++++++++
 .../scala/akka/typed/patterns/Restarter.scala | 12 +++----
 .../scala/akka/typed/scaladsl/Actor.scala     | 16 +++------
 4 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
index c98363eb93..fb0f84013b 100644
--- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
+++ b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
@@ -192,7 +192,7 @@ public abstract class Actor {
    * 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  Behavior unhandled() {
@@ -405,7 +405,7 @@ public abstract class Actor {
      * By default, this method returns `unhandled`.
      */
     public Behavior onSignal(Signal msg) throws Exception {
-      return Actor.unhandled();
+      return unhandled();
     }
   }
 
diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index 258fd317c2..0a7b377e12 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -79,6 +79,39 @@ abstract class ExtensibleBehavior[T] extends Behavior[T] {
 
 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 NOT be passed to the
+   * current behavior, it will be effectively ignored.
+   */
+  def stopped[T]: Behavior[T] = StoppedBehavior.asInstanceOf[Behavior[T]]
+
+  /**
+   * 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.
    */
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
index 3afdf29e3c..7e44bb718e 100644
--- a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
+++ b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
@@ -7,7 +7,7 @@ package patterns
 import scala.reflect.ClassTag
 import scala.util.control.NonFatal
 import akka.event.Logging
-import akka.typed.Behavior._
+import akka.typed.scaladsl.Actor
 import akka.typed.scaladsl.Actor._
 
 /**
@@ -22,7 +22,7 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav
   private def restart(ctx: ActorContext[T], startedBehavior: Behavior[T]): Behavior[T] = {
     try Behavior.interpretSignal(startedBehavior, ctx, PreRestart) catch { case NonFatal(_) ⇒ }
     // no need to canonicalize, it's done in the calling methods
-    validateAsInitial(undefer(initialBehavior, ctx))
+    Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
   }
 
   private def canonical(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
@@ -32,8 +32,8 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav
     else if (b eq behavior) Same
     else {
       b match {
-        case d: DeferredBehavior[_] ⇒ canonical(Behavior.undefer(d, ctx), ctx)
-        case b                      ⇒ Restarter[T, Thr](initialBehavior, resume)(b)
+        case d: Behavior.DeferredBehavior[_] ⇒ canonical(Behavior.undefer(d, ctx), ctx)
+        case b                               ⇒ Restarter[T, Thr](initialBehavior, resume)(b)
       }
     }
 
@@ -79,12 +79,12 @@ final case class MutableRestarter[T, Thr <: Throwable: ClassTag](initialBehavior
   private[this] var current: Behavior[T] = _
   private def startCurrent(ctx: ActorContext[T]) = {
     // we need to undefer it if needed the first time we have access to a context
-    if (current eq null) current = validateAsInitial(undefer(initialBehavior, ctx))
+    if (current eq null) current = Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
   }
 
   private def restart(ctx: ActorContext[T]): Behavior[T] = {
     try Behavior.interpretSignal(current, ctx, PreRestart) catch { case NonFatal(_) ⇒ }
-    validateAsInitial(undefer(initialBehavior, ctx))
+    Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
   }
 
   override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index 95165a789a..9015d450ae 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -287,7 +287,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
@@ -295,13 +295,7 @@ 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
@@ -310,17 +304,17 @@ object Actor {
    * signal that results from stopping this actor will NOT be passed to the
    * current behavior, it will be effectively ignored.
    */
-  def Stopped[T]: Behavior[T] = StoppedBehavior.asInstanceOf[Behavior[T]]
+  def Stopped[T]: Behavior[T] = Behavior.stopped
 
   /**
    * 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

From cccadc6ec7ffdd86c0ac0e57a69ff76b4e53dc94 Mon Sep 17 00:00:00 2001
From: Arnout Engelen 
Date: Wed, 26 Apr 2017 02:04:45 -0700
Subject: [PATCH 16/50] Rename 'stateful' to 'immutable' in MonitoringTest too
 (#22793)

---
 .../src/test/java/akka/typed/javadsl/MonitoringTest.java    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java
index f0f9eb001a..eca93bd4a8 100644
--- a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java
+++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java
@@ -31,13 +31,13 @@ public class MonitoringTest extends JUnitSuite {
     // final FiniteDuration fiveSeconds = FiniteDuration.create(5, TimeUnit.SECONDS);
     final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
 
-    final Behavior exitingActor = stateful((ctx, msg) -> {
+    final Behavior exitingActor = immutable((ctx, msg) -> {
         System.out.println("Stopping!");
         return stopped();
     });
 
     private Behavior> waitingForTermination(ActorRef replyWhenTerminated) {
-        return stateful(
+        return immutable(
             (ctx, msg) -> unhandled(),
             (ctx, sig) -> {
                 if (sig instanceof Terminated) {
@@ -51,7 +51,7 @@ public class MonitoringTest extends JUnitSuite {
 
     @Test
     public void shouldWatchTerminatingActor() throws Exception {
-        Behavior> root = stateful((ctx, msg) -> {
+        Behavior> root = immutable((ctx, msg) -> {
             ActorRef watched = ctx.spawn(exitingActor, "exitingActor");
             ctx.watch(watched);
             watched.tell(new Stop());

From 993a2041437549868b5718ea65a405efd097fc80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johan=20Andr=C3=A9n?= 
Date: Wed, 26 Apr 2017 14:14:45 +0200
Subject: [PATCH 17/50] Don't schedule messages on a scheduler that might be
 shut down already #22127

---
 akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
index a0e52febdb..550253639f 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
@@ -74,9 +74,9 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
 
   override def afterAll(): Unit = {
     if (nativeSystemUsed)
-      Await.result(nativeSystem ? (Terminate(_)), timeout.duration): Status
+      Await.result(nativeSystem.terminate, timeout.duration)
     if (adaptedSystemUsed)
-      Await.result(adaptedSystem ? (Terminate(_)), timeout.duration): Status
+      Await.result(adaptedSystem.terminate, timeout.duration)
   }
 
   // TODO remove after basing on ScalaTest 3 with async support

From 3d031c0262851d573d480dd3d5054ec50918c930 Mon Sep 17 00:00:00 2001
From: stanislav 
Date: Wed, 26 Apr 2017 17:13:00 +0300
Subject: [PATCH 18/50] Rename `management` to `receiveSignal` and `message` to
 `receiveMessage` #22718

---
 .../src/main/java/akka/typed/javadsl/Actor.java  | 12 ++++++------
 .../src/main/scala/akka/typed/Behavior.scala     |  8 ++++----
 .../scala/akka/typed/patterns/Restarter.scala    |  8 ++++----
 .../main/scala/akka/typed/scaladsl/Actor.scala   | 16 ++++++++--------
 4 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
index c98363eb93..aa43424668 100644
--- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
+++ b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
@@ -51,12 +51,12 @@ public abstract class Actor {
     }
 
     @Override
-    public Behavior management(akka.typed.ActorContext ctx, Signal msg) throws Exception {
+    public Behavior receiveSignal(akka.typed.ActorContext ctx, Signal msg) throws Exception {
       return signal.apply(ctx, msg);
     }
 
     @Override
-    public Behavior message(akka.typed.ActorContext ctx, T msg) throws Exception {
+    public Behavior receiveMessage(akka.typed.ActorContext ctx, T msg) throws Exception {
       return message.apply(ctx, msg);
     }
   }
@@ -85,13 +85,13 @@ public abstract class Actor {
     }
 
     @Override
-    public Behavior management(akka.typed.ActorContext ctx, Signal signal) throws Exception {
+    public Behavior receiveSignal(akka.typed.ActorContext ctx, Signal signal) throws Exception {
       this.signal.apply(ctx, signal);
       return canonicalize(Behavior.interpretSignal(behavior, ctx, signal));
     }
 
     @Override
-    public Behavior message(akka.typed.ActorContext ctx, T msg) throws Exception {
+    public Behavior receiveMessage(akka.typed.ActorContext ctx, T msg) throws Exception {
       message.apply(ctx, msg);
       return canonicalize(Behavior.interpretMessage(behavior, ctx, msg));
     }
@@ -368,7 +368,7 @@ public abstract class Actor {
    */
   static public abstract class MutableBehavior extends ExtensibleBehavior {
     @Override
-    final public Behavior message(akka.typed.ActorContext ctx, T msg) throws Exception {
+    final public Behavior receiveMessage(akka.typed.ActorContext ctx, T msg) throws Exception {
       return onMessage(msg);
     }
 
@@ -386,7 +386,7 @@ public abstract class Actor {
     public abstract Behavior onMessage(T msg) throws Exception;
 
     @Override
-    final public Behavior management(akka.typed.ActorContext ctx, Signal msg) throws Exception {
+    final public Behavior receiveSignal(akka.typed.ActorContext ctx, Signal msg) throws Exception {
       return onSignal(msg);
     }
 
diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index 258fd317c2..be4135175e 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -57,7 +57,7 @@ abstract class ExtensibleBehavior[T] extends Behavior[T] {
    * 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.
@@ -73,7 +73,7 @@ abstract class ExtensibleBehavior[T] extends Behavior[T] {
    * 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]
 
 }
 
@@ -203,8 +203,8 @@ object Behavior {
       case EmptyBehavior                    ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]]
       case ext: ExtensibleBehavior[T] @unchecked ⇒
         val possiblyDeferredResult = msg match {
-          case signal: Signal ⇒ ext.management(ctx, signal)
-          case msg            ⇒ ext.message(ctx, msg.asInstanceOf[T])
+          case signal: Signal ⇒ ext.receiveSignal(ctx, signal)
+          case msg            ⇒ ext.receiveMessage(ctx, msg.asInstanceOf[T])
         }
         undefer(possiblyDeferredResult, ctx)
     }
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
index 3afdf29e3c..900b3853d4 100644
--- a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
+++ b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
@@ -40,7 +40,7 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav
   private def preStart(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
     Behavior.undefer(b, ctx)
 
-  override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
+  override def receiveSignal(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
     val startedBehavior = preStart(behavior, ctx)
     val b =
       try {
@@ -53,7 +53,7 @@ final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behav
     canonical(b, ctx)
   }
 
-  override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
+  override def receiveMessage(ctx: ActorContext[T], msg: T): Behavior[T] = {
     val startedBehavior = preStart(behavior, ctx)
     val b =
       try {
@@ -87,7 +87,7 @@ final case class MutableRestarter[T, Thr <: Throwable: ClassTag](initialBehavior
     validateAsInitial(undefer(initialBehavior, ctx))
   }
 
-  override def management(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
+  override def receiveSignal(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
     startCurrent(ctx)
     current =
       try {
@@ -100,7 +100,7 @@ final case class MutableRestarter[T, Thr <: Throwable: ClassTag](initialBehavior
     if (Behavior.isAlive(current)) this else Stopped
   }
 
-  override def message(ctx: ActorContext[T], msg: T): Behavior[T] = {
+  override def receiveMessage(ctx: ActorContext[T], msg: T): Behavior[T] = {
     startCurrent(ctx)
     current =
       try Behavior.interpretMessage(current, ctx, msg)
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index 8be3134c64..6f4effe6da 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -191,10 +191,10 @@ object Actor {
         if (next eq behavior) Same else Widened(next, matcher)
       } else Stopped
 
-    override def management(ctx: AC[U], signal: Signal): Behavior[U] =
+    override def receiveSignal(ctx: AC[U], signal: Signal): Behavior[U] =
       postProcess(Behavior.interpretSignal(behavior, ctx.as[T], signal), ctx.as[T])
 
-    override def message(ctx: AC[U], msg: U): Behavior[U] =
+    override def receiveMessage(ctx: AC[U], msg: U): Behavior[U] =
       matcher.applyOrElse(msg, nullFun) match {
         case null        ⇒ Unhandled
         case transformed ⇒ postProcess(Behavior.interpretMessage(behavior, ctx.as[T], transformed), ctx.as[T])
@@ -242,7 +242,7 @@ object Actor {
    */
   abstract class MutableBehavior[T] extends ExtensibleBehavior[T] {
     @throws(classOf[Exception])
-    override final def message(ctx: akka.typed.ActorContext[T], msg: T): Behavior[T] =
+    override final def receiveMessage(ctx: akka.typed.ActorContext[T], msg: T): Behavior[T] =
       onMessage(msg)
 
     /**
@@ -260,7 +260,7 @@ object Actor {
     def onMessage(msg: T): Behavior[T]
 
     @throws(classOf[Exception])
-    override final def management(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] =
+    override final def receiveSignal(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] =
       onSignal(msg)
 
     /**
@@ -339,8 +339,8 @@ object Actor {
     onMessage: (ActorContext[T], T) ⇒ Behavior[T],
     onSignal:  (ActorContext[T], Signal) ⇒ Behavior[T] = Behavior.unhandledSignal.asInstanceOf[(ActorContext[T], Signal) ⇒ Behavior[T]])
     extends ExtensibleBehavior[T] {
-    override def management(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg)
-    override def message(ctx: AC[T], msg: T) = onMessage(ctx, msg)
+    override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg)
+    override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg)
     override def toString = s"Immutable(${LineNumbers(onMessage)})"
   }
 
@@ -359,11 +359,11 @@ object Actor {
       else if ((behv eq SameBehavior) || (behv eq this)) Same
       else if (isAlive(behv)) Tap(onMessage, onSignal, behv)
       else Stopped
-    override def management(ctx: AC[T], signal: Signal): Behavior[T] = {
+    override def receiveSignal(ctx: AC[T], signal: Signal): Behavior[T] = {
       onSignal(ctx, signal)
       canonical(Behavior.interpretSignal(behavior, ctx, signal))
     }
-    override def message(ctx: AC[T], msg: T): Behavior[T] = {
+    override def receiveMessage(ctx: AC[T], msg: T): Behavior[T] = {
       onMessage(ctx, msg)
       canonical(Behavior.interpretMessage(behavior, ctx, msg))
     }

From eb7ba7fc4851e8503c1ea83329d5b46cd49554f2 Mon Sep 17 00:00:00 2001
From: Arnout Engelen 
Date: Wed, 26 Apr 2017 07:20:19 -0700
Subject: [PATCH 19/50] Remove reference to Total from akka-typed docs #22610
 (#22797)

---
 akka-docs/rst/scala/typed.rst                 | 28 +++++++++----------
 .../scala/docs/akka/typed/IntroSpec.scala     |  3 ++
 2 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/akka-docs/rst/scala/typed.rst b/akka-docs/rst/scala/typed.rst
index 6eefdadeea..042726bad1 100644
--- a/akka-docs/rst/scala/typed.rst
+++ b/akka-docs/rst/scala/typed.rst
@@ -30,8 +30,12 @@ supplies so that the :class:`HelloWorld` Actor can send back the confirmation
 message.
 
 The behavior of the Actor is defined as the :meth:`greeter` value with the help
-of the :class:`Immutable` behavior constructor—there are several different ways of
-formulating behaviors as we shall see in the following.
+of the :class:`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 :class:`Same`.
 
 The type of the messages handled by this behavior is declared to be of class
 :class:`Greet`, which implies that the supplied function’s ``msg`` argument is
@@ -176,7 +180,7 @@ as the following:
 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 :class:`GetSession` command comes in we add that client to the 
+is not needed. When a new :class:`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
 :class:`ActorRef` that will be used to post messages. In this case we want to
 create a very simple Actor that just repackages the :class:`PostMessage`
@@ -218,18 +222,12 @@ In order to see this chat room in action we need to write a client Actor that ca
 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 :class:`Total`. This constructor takes as
-argument a function from the handled message type, in this case
-:class:`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 :class:`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
-:class:`SessionGranted` we may also receive a :class:`SessionDenied` event.
+running behavior into the terminated state. This is why here we do not return
+:class:`Same`, as above, but another special value :class:`Stopped`.
+Since :class:`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 :class:`SessionGranted` we may also receive a
+:class:`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
diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
index c23b0b2246..0ec9489a48 100644
--- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
+++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
@@ -101,9 +101,12 @@ class IntroSpec extends TypedSpec {
     val gabbler =
       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
+          //#chatroom-gabbler
           case SessionGranted(handle) ⇒
             handle ! PostMessage("Hello World!")
             Same

From e63239e3372a2f696170fb0514acea53a10e5b5d Mon Sep 17 00:00:00 2001
From: Arnout Engelen 
Date: Wed, 26 Apr 2017 17:05:12 +0200
Subject: [PATCH 20/50] Stop adapted actorsystem after running TypedSpec
 (#22763)

---
 .../src/test/scala/akka/typed/TypedSpec.scala             | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
index a0e52febdb..aff7c3435a 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
@@ -57,7 +57,7 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
   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 = false
+    adaptedSystemUsed = true
     sys
   }
 
@@ -202,13 +202,15 @@ class TypedSpecSpec extends TypedSpec {
   object `A TypedSpec` {
 
     trait CommonTests {
+      class MyException(message: String) extends Exception(message)
+
       implicit def system: ActorSystem[TypedSpec.Command]
 
       def `must report failures`(): Unit = {
-        a[TestFailedException] must be thrownBy {
+        a[MyException] must be thrownBy {
           sync(runTest("failure")(StepWise[String]((ctx, startWith) ⇒
             startWith {
-              fail("expected")
+              throw new MyException("expected")
             })))
         }
       }

From e3e2f325927dd65e9640081b99954b6e46be028b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= 
Date: Wed, 26 Apr 2017 16:44:37 +0300
Subject: [PATCH 21/50] #22662 Refactor to onSignal builder method instead of
 constructor parameter

---
 .../scala/akka/typed/ActorContextSpec.scala   | 189 +++++++++---------
 .../test/scala/akka/typed/BehaviorSpec.scala  |  68 ++++---
 .../src/test/scala/akka/typed/StepWise.scala  |  24 +--
 .../src/test/scala/akka/typed/TypedSpec.scala |   7 +-
 .../akka/typed/internal/ActorCellSpec.scala   |  12 +-
 .../akka/typed/internal/ActorSystemSpec.scala |   6 +-
 .../typed/scaladsl/adapter/AdapterSpec.scala  |  70 +++----
 .../scala/docs/akka/typed/IntroSpec.scala     |   9 +-
 .../docs/akka/typed/MutableIntroSpec.scala    |   9 +-
 .../akka/typed/internal/EventStreamImpl.scala |   6 +-
 .../akka/typed/patterns/Receptionist.scala    |   6 +-
 .../scala/akka/typed/scaladsl/Actor.scala     |  10 +-
 12 files changed, 216 insertions(+), 200 deletions(-)

diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
index d2c369f29a..b73232c615 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
@@ -73,91 +73,94 @@ object ActorContextSpec {
   final case class Adapter(a: ActorRef[Command]) extends Event
 
   def subject(monitor: ActorRef[Monitor]): Behavior[Command] =
-    Actor.Immutable(
-      (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)
-        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)
-          }
-          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
-          }, {
-            case (_, Terminated(_)) ⇒ Actor.Unhandled
-            case (_, sig) ⇒
-              monitor ! GotSignal(sig)
-              Actor.Same
+    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)
+          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)
+            }
+            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 (_, 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 })
+            }
+          case GetAdapter(replyTo, name) ⇒
+            replyTo ! Adapter(ctx.spawnAdapter(identity, name))
+            Actor.Same
+        }
+    } onSignal {
+      (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.Same
+    }
 
   def oldSubject(monitor: ActorRef[Monitor]): Behavior[Command] = {
-    Actor.Immutable({
+    Actor.Immutable[Command] {
       case (ctx, message) ⇒ message match {
         case ReceiveTimeout ⇒
           monitor ! GotReceiveTimeout
@@ -225,25 +228,23 @@ object ActorContextSpec {
           }
         case BecomeCareless(replyTo) ⇒
           replyTo ! BecameCareless
-          Actor.Immutable[Command](
-            {
-              case _ ⇒ Actor.Unhandled
-            },
-            {
-              case (_, Terminated(_)) ⇒ Actor.Unhandled
-              case (_, sig) ⇒
-                monitor ! GotSignal(sig)
-                Actor.Same
-            })
+          Actor.Immutable[Command] {
+            case _ ⇒ Actor.Unhandled
+          } onSignal {
+            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 (_, signal) ⇒
         monitor ! GotSignal(signal)
         Actor.Same
-    })
+    }
   }
 
 }
diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
index a58ac4730d..2ce88e1302 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
@@ -255,7 +255,7 @@ class BehaviorSpec extends TypedSpec {
   }
 
   private def mkFull(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] = {
-    SActor.Immutable[Command]({
+    SActor.Immutable[Command] {
       case (ctx, GetSelf) ⇒
         monitor ! Self(ctx.self)
         SActor.Same
@@ -276,11 +276,11 @@ class BehaviorSpec extends TypedSpec {
         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 {
@@ -292,7 +292,7 @@ class BehaviorSpec extends TypedSpec {
   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({
+      SActor.Immutable[Command] {
         case (ctx, GetSelf) ⇒
           monitor ! Self(ctx.self)
           SActor.Same
@@ -313,11 +313,11 @@ class BehaviorSpec extends TypedSpec {
           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
@@ -326,33 +326,37 @@ class BehaviorSpec extends TypedSpec {
   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(
-        (ctx, msg) ⇒ msg match {
-          case GetSelf ⇒
-            monitor ! Self(ctx.self)
+      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 {
+        (ctx, sig) ⇒
+          {
+            monitor ! GotSignal(sig)
             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
-        },
-        (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
diff --git a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala
index 9d54d3fefb..93ca0cc267 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala
@@ -135,19 +135,19 @@ object StepWise {
       case ThunkV(f) :: tail ⇒ run(ctx, tail, f(value))
       case Message(t, f, trace) :: tail ⇒
         ctx.setReceiveTimeout(t, ReceiveTimeout)
-        Immutable({
+        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))
-        }, {
+        } onSignal {
           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)
-          Immutable({
+          Immutable[Any] {
             case (_, ReceiveTimeout) ⇒
               throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
             case (_, msg) ⇒
@@ -156,16 +156,16 @@ object StepWise {
                 ctx.cancelReceiveTimeout()
                 run(ctx, tail, f((msg :: acc).reverse, value))
               } else behavior(nextCount, msg :: acc)
-          }, {
+          } onSignal {
             case (_, other) ⇒ throwIllegalState(trace, s"unexpected $other while waiting for $c messages (got $count valid ones)")
-          })
+          }
         }
         behavior(0, Nil)
       case Multi(t, c, f, trace) :: tail ⇒
         val deadline = Deadline.now + t
         def behavior(count: Int, acc: List[Either[Signal, Any]]): Behavior[Any] = {
           ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout)
-          Immutable[Any]({
+          Immutable[Any] {
             case (_, ReceiveTimeout) ⇒
               throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)")
             case (_, msg) ⇒
@@ -174,27 +174,27 @@ object StepWise {
                 ctx.cancelReceiveTimeout()
                 run(ctx, tail, f((Right(msg) :: acc).reverse, value))
               } else behavior(nextCount, Right(msg) :: acc)
-          }, {
+          } onSignal {
             case (_, other) ⇒
               val nextCount = count + 1
               if (nextCount == c) {
                 ctx.cancelReceiveTimeout()
                 run(ctx, tail, f((Left(other) :: acc).reverse, value))
               } else behavior(nextCount, Left(other) :: acc)
-          })
+          }
         }
         behavior(0, Nil)
       case Termination(t, f, trace) :: tail ⇒
         ctx.setReceiveTimeout(t, ReceiveTimeout)
-        Immutable[Any]({
+        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 (_, t: Terminated) ⇒
             ctx.cancelReceiveTimeout()
             run(ctx, tail, f(t, value))
           case other ⇒ throwIllegalState(trace, s"unexpected $other while waiting for termination")
-        })
+        }
       case Nil ⇒ Stopped
     }
 }
diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
index aff7c3435a..18d6960070 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala
@@ -161,7 +161,7 @@ object TypedSpec {
   case object Timedout extends Status
 
   def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] =
-    Immutable[Command]({
+    Immutable[Command] {
       case (ctx, r: RunTest[t]) ⇒
         val test = ctx.spawn(r.behavior, r.name)
         ctx.schedule(r.timeout, r.replyTo, Timedout)
@@ -173,7 +173,7 @@ object TypedSpec {
       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) ⇒
@@ -183,8 +183,7 @@ object TypedSpec {
           case None ⇒ Same
         }
       case _ ⇒ Same
-
-    })
+    }
 
   def getCallerName(clazz: Class[_]): String = {
     val s = (Thread.currentThread.getStackTrace map (_.getClassName) drop 1)
diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala
index 079dd0d2ea..8edfc8637c 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala
@@ -430,12 +430,12 @@ 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, Immutable[String](
-        { case _ ⇒ Unhandled },
-        {
-          case (_, PostStop) ⇒ ???
-          case _             ⇒ Unhandled
-        }), ec, 1000, parent)
+      val cell = new ActorCell(sys, Immutable[String] {
+        case _ ⇒ Unhandled
+      } onSignal {
+        case (_, PostStop) ⇒ ???
+        case _             ⇒ Unhandled
+      }, ec, 1000, parent)
       val ref = new LocalActorRef(parent.path / "child", cell)
       cell.setSelf(ref)
       debugCell(cell) {
diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala
index 7ec4519a45..1caa4c8289 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala
@@ -53,13 +53,13 @@ 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", Immutable[Probe]({
+      val sys = system("terminate", Immutable[Probe] {
         case (_, _) ⇒ Unhandled
-      }, {
+      } onSignal {
         case (ctx, PostStop) ⇒
           inbox.ref ! "done"
           Same
-      }))
+      })
       sys.terminate().futureValue
       inbox.receiveAll() should ===("done" :: Nil)
     }
diff --git a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala
index cea4bba24c..89363b2a2c 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala
@@ -27,41 +27,43 @@ object AdapterSpec {
   }
 
   def typed1(ref: untyped.ActorRef, probe: ActorRef[String]): Behavior[String] =
-    Immutable(
-      onMessage = (ctx, msg) ⇒
-      msg match {
-        case "send" ⇒
-          val replyTo = ctx.self.toUntyped
-          ref.tell("ping", replyTo)
+    Immutable[String] {
+      (ctx, msg) ⇒
+        msg match {
+          case "send" ⇒
+            val replyTo = ctx.self.toUntyped
+            ref.tell("ping", replyTo)
+            Same
+          case "pong" ⇒
+            probe ! "ok"
+            Same
+          case "actorOf" ⇒
+            val child = ctx.actorOf(untyped1)
+            child.tell("ping", ctx.self.toUntyped)
+            Same
+          case "watch" ⇒
+            ctx.watch(ref)
+            Same
+          case "supervise-stop" ⇒
+            val child = ctx.actorOf(untyped1)
+            ctx.watch(child)
+            child ! ThrowIt3
+            child.tell("ping", ctx.self.toUntyped)
+            Same
+          case "stop-child" ⇒
+            val child = ctx.actorOf(untyped1)
+            ctx.watch(child)
+            ctx.stop(child)
+            Same
+        }
+    } onSignal { (ctx, sig) ⇒
+      sig match {
+        case Terminated(ref) ⇒
+          probe ! "terminated"
           Same
-        case "pong" ⇒
-          probe ! "ok"
-          Same
-        case "actorOf" ⇒
-          val child = ctx.actorOf(untyped1)
-          child.tell("ping", ctx.self.toUntyped)
-          Same
-        case "watch" ⇒
-          ctx.watch(ref)
-          Same
-        case "supervise-stop" ⇒
-          val child = ctx.actorOf(untyped1)
-          ctx.watch(child)
-          child ! ThrowIt3
-          child.tell("ping", ctx.self.toUntyped)
-          Same
-        case "stop-child" ⇒
-          val child = ctx.actorOf(untyped1)
-          ctx.watch(child)
-          ctx.stop(child)
-          Same
-      },
-      onSignal = (ctx, sig) ⇒ sig match {
-      case Terminated(ref) ⇒
-        probe ! "terminated"
-        Same
-      case _ ⇒ Unhandled
-    })
+        case _ ⇒ Unhandled
+      }
+    }
 
   sealed trait Typed2Msg
   final case class Ping(replyTo: ActorRef[String]) extends Typed2Msg
diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
index 0ec9489a48..419f943f9d 100644
--- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
+++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
@@ -125,15 +125,16 @@ class IntroSpec extends TypedSpec {
         ctx.watch(gabblerRef)
         chatRoom ! GetSession("ol’ Gabbler", gabblerRef)
 
-        Immutable(
-          onMessage = (_, _) ⇒ Unhandled,
-          onSignal = (ctx, sig) ⇒
+        Immutable[akka.NotUsed] {
+          (_, _) ⇒ Unhandled
+        } onSignal { (ctx, sig) ⇒
           sig match {
             case Terminated(ref) ⇒
               Stopped
             case _ ⇒
               Unhandled
-          })
+          }
+        }
       }
 
     val system = ActorSystem("ChatRoomDemo", main)
diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala
index 9b93feaa90..3c4a06f7f3 100644
--- a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala
+++ b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala
@@ -97,15 +97,16 @@ class MutableIntroSpec extends TypedSpec {
         ctx.watch(gabblerRef)
         chatRoom ! GetSession("ol’ Gabbler", gabblerRef)
 
-        Immutable(
-          onMessage = (_, _) ⇒ Unhandled,
-          onSignal = (ctx, sig) ⇒
+        Immutable[akka.NotUsed] {
+          (_, _) ⇒ Unhandled
+        } onSignal { (ctx, sig) ⇒
           sig match {
             case Terminated(ref) ⇒
               Stopped
             case _ ⇒
               Unhandled
-          })
+          }
+        }
       }
 
     val system = ActorSystem("ChatRoomDemo", main)
diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala
index bfe3ddafb0..9292c5e0a2 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala
@@ -44,7 +44,7 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
     import scaladsl.Actor
     Actor.Deferred[Command] { _ ⇒
       if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"registering unsubscriber with $this"))
-      Actor.Immutable[Command]({
+      Actor.Immutable[Command] {
         case (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(actor)
@@ -57,13 +57,13 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat
           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
         case (_, _) ⇒ Actor.Unhandled
-      })
+      }
     }
   }
 
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala
index 1e42b78e39..9e43793997 100644
--- a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala
+++ b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala
@@ -72,7 +72,7 @@ object Receptionist {
 
   private type KV[K <: AbstractServiceKey] = ActorRef[K#Type]
 
-  private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = Immutable[Command]({ (ctx, msg) ⇒
+  private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = Immutable[Command] { (ctx, msg) ⇒
     msg match {
       case r: Register[t] ⇒
         ctx.watch(r.address)
@@ -83,11 +83,11 @@ object Receptionist {
         f.replyTo ! Listing(f.key, set)
         Same
     }
-  }, {
+  } onSignal {
     case (ctx, Terminated(ref)) ⇒
       behavior(map valueRemoved ref)
     case x ⇒ Unhandled
-  })
+  }
 }
 
 abstract class Receptionist
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index a6d7da4489..9ad6eafe6e 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -329,13 +329,21 @@ object Actor {
    * State is updated by returning a new behavior that holds the new immutable
    * state.
    */
-  final case class Immutable[T](
+  final class Immutable[T] private (
     onMessage: (ActorContext[T], T) ⇒ Behavior[T],
     onSignal:  (ActorContext[T], Signal) ⇒ Behavior[T] = Behavior.unhandledSignal.asInstanceOf[(ActorContext[T], Signal) ⇒ Behavior[T]])
     extends ExtensibleBehavior[T] {
     override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg)
     override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg)
     override def toString = s"Immutable(${LineNumbers(onMessage)})"
+
+    def onSignal(onSignal: (ActorContext[T], Signal) ⇒ Behavior[T]): Immutable[T] =
+      new Immutable(onMessage, onSignal)
+  }
+
+  object Immutable {
+    def apply[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]) =
+      new Immutable(onMessage)
   }
 
   /**

From 6cbd80e86e3ff3617e1da0579624cb7585bb02c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= 
Date: Wed, 26 Apr 2017 17:01:47 +0300
Subject: [PATCH 22/50] #22662 Convert onSignal to partial function

---
 .../test/scala/akka/typed/ActorContextSpec.scala  |  2 +-
 .../src/test/scala/akka/typed/BehaviorSpec.scala  |  8 +++-----
 .../akka/typed/scaladsl/adapter/AdapterSpec.scala |  4 ++--
 .../test/scala/docs/akka/typed/IntroSpec.scala    | 15 ++++++++-------
 .../scala/docs/akka/typed/MutableIntroSpec.scala  | 15 ++++++++-------
 .../src/main/scala/akka/typed/Behavior.scala      |  5 +++--
 .../main/scala/akka/typed/scaladsl/Actor.scala    |  4 ++--
 7 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
index b73232c615..e812d1c8c0 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
@@ -156,7 +156,7 @@ object ActorContextSpec {
             Actor.Same
         }
     } onSignal {
-      (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.Same
+      case (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.Same
     }
 
   def oldSubject(monitor: ActorRef[Monitor]): Behavior[Command] = {
diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
index 2ce88e1302..60e6a33cf7 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
@@ -351,11 +351,9 @@ class BehaviorSpec extends TypedSpec {
             case _: AuxPing ⇒ SActor.Unhandled
           }
       } onSignal {
-        (ctx, sig) ⇒
-          {
-            monitor ! GotSignal(sig)
-            SActor.Same
-          }
+        case (ctx, sig) ⇒
+          monitor ! GotSignal(sig)
+          SActor.Same
       }
   }
   object `A ImmutableWithSignal Behavior (scala,native)` extends ImmutableWithSignalScalaBehavior with NativeSystem
diff --git a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala
index 89363b2a2c..bdfc09f891 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala
@@ -56,8 +56,8 @@ object AdapterSpec {
             ctx.stop(child)
             Same
         }
-    } onSignal { (ctx, sig) ⇒
-      sig match {
+    } onSignal {
+      case (ctx, sig) ⇒ sig match {
         case Terminated(ref) ⇒
           probe ! "terminated"
           Same
diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
index 419f943f9d..fe091ebbbd 100644
--- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
+++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala
@@ -127,13 +127,14 @@ class IntroSpec extends TypedSpec {
 
         Immutable[akka.NotUsed] {
           (_, _) ⇒ Unhandled
-        } onSignal { (ctx, sig) ⇒
-          sig match {
-            case Terminated(ref) ⇒
-              Stopped
-            case _ ⇒
-              Unhandled
-          }
+        } onSignal {
+          case (ctx, sig) ⇒
+            sig match {
+              case Terminated(ref) ⇒
+                Stopped
+              case _ ⇒
+                Unhandled
+            }
         }
       }
 
diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala
index 3c4a06f7f3..515a07de7f 100644
--- a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala
+++ b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala
@@ -99,13 +99,14 @@ class MutableIntroSpec extends TypedSpec {
 
         Immutable[akka.NotUsed] {
           (_, _) ⇒ Unhandled
-        } onSignal { (ctx, sig) ⇒
-          sig match {
-            case Terminated(ref) ⇒
-              Stopped
-            case _ ⇒
-              Unhandled
-          }
+        } onSignal {
+          case (ctx, sig) ⇒
+            sig match {
+              case Terminated(ref) ⇒
+                Stopped
+              case _ ⇒
+                Unhandled
+            }
         }
       }
 
diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index ff840eb436..67fe352772 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -142,8 +142,9 @@ object Behavior {
   /**
    * 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.
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index 9ad6eafe6e..be63b7e7e0 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -331,13 +331,13 @@ object Actor {
    */
   final class Immutable[T] private (
     onMessage: (ActorContext[T], T) ⇒ Behavior[T],
-    onSignal:  (ActorContext[T], Signal) ⇒ Behavior[T] = Behavior.unhandledSignal.asInstanceOf[(ActorContext[T], Signal) ⇒ Behavior[T]])
+    onSignal:  PartialFunction[(ActorContext[T], Signal), Behavior[T]] = Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]])
     extends ExtensibleBehavior[T] {
     override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg)
     override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg)
     override def toString = s"Immutable(${LineNumbers(onMessage)})"
 
-    def onSignal(onSignal: (ActorContext[T], Signal) ⇒ Behavior[T]): Immutable[T] =
+    def onSignal(onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Immutable[T] =
       new Immutable(onMessage, onSignal)
   }
 

From 8c8683c80dcea2fafc0680ee9b2fce11780bd0f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= 
Date: Fri, 28 Apr 2017 08:29:31 +0300
Subject: [PATCH 23/50] #22662 Partial application for signal management

---
 akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index be63b7e7e0..527e1cdc78 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -274,11 +274,10 @@ object Actor {
      *  * 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, this method returns `Unhandled`.
+     * By default, partial function is empty and does not handle any signals.
      */
     @throws(classOf[Exception])
-    def onSignal(msg: Signal): Behavior[T] =
-      Unhandled
+    def onSignal: PartialFunction[Signal, Behavior[T]] = PartialFunction.empty
   }
 
   /**
@@ -333,7 +332,7 @@ object Actor {
     onMessage: (ActorContext[T], T) ⇒ Behavior[T],
     onSignal:  PartialFunction[(ActorContext[T], Signal), Behavior[T]] = Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]])
     extends ExtensibleBehavior[T] {
-    override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = onSignal(ctx, msg)
+    override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = onSignal.applyOrElse((ctx, msg), Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]])
     override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg)
     override def toString = s"Immutable(${LineNumbers(onMessage)})"
 

From 946f2ffd4e4ec9cfd5d325122caa1fbb43f3e304 Mon Sep 17 00:00:00 2001
From: Patrik Nordwall 
Date: Fri, 21 Apr 2017 09:20:03 +0200
Subject: [PATCH 24/50] add testkit for real asynchronous testing, #22764

---
 .../src/main/resources/reference.conf         |  20 ++
 .../scala/akka/typed/testkit/Effects.scala    |  18 +-
 .../akka/typed/testkit/TestKitSettings.scala  |  34 +++
 .../typed/testkit/scaladsl/TestProbe.scala    | 229 ++++++++++++++++++
 .../akka/typed/testkit/scaladsl/package.scala |  34 +++
 5 files changed, 333 insertions(+), 2 deletions(-)
 create mode 100644 akka-typed-testkit/src/main/resources/reference.conf
 create mode 100644 akka-typed-testkit/src/main/scala/akka/typed/testkit/TestKitSettings.scala
 create mode 100644 akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala
 create mode 100644 akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/package.scala

diff --git a/akka-typed-testkit/src/main/resources/reference.conf b/akka-typed-testkit/src/main/resources/reference.conf
new file mode 100644
index 0000000000..1230df0591
--- /dev/null
+++ b/akka-typed-testkit/src/main/resources/reference.conf
@@ -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
+
+}
diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
index 56076032a2..165d8f8506 100644
--- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
@@ -9,7 +9,10 @@ import akka.typed.{ ActorContext, ActorRef, ActorSystem, Behavior, DeploymentCon
 
 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 }
+import akka.typed.PostStop
 
 /**
  * All tracked effects must extend implement this type. It is deliberately
@@ -56,12 +59,23 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma
   def currentBehavior: Behavior[T] = current
   def isAlive: Boolean = Behavior.isAlive(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
+  }
+
   def run(msg: T): Unit = {
-    current = Behavior.canonicalize(Behavior.interpretMessage(current, this, msg), current, this)
+    try {
+      current = Behavior.canonicalize(Behavior.interpretMessage(current, this, msg), current, this)
+    } catch handleException
   }
 
   def signal(signal: Signal): Unit = {
-    current = Behavior.canonicalize(Behavior.interpretSignal(current, this, signal), current, this)
+    try {
+      current = Behavior.canonicalize(Behavior.interpretSignal(current, this, signal), current, this)
+    } catch handleException
   }
 
   override def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = {
diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestKitSettings.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestKitSettings.scala
new file mode 100644
index 0000000000..3cfa588d65
--- /dev/null
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestKitSettings.scala
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2017 Lightbend Inc. 
+ */
+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"))
+}
diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala
new file mode 100644
index 0000000000..a1850a1c57
--- /dev/null
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala
@@ -0,0 +1,229 @@
+/**
+ * Copyright (C) 2017 Lightbend Inc. 
+ */
+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] = Immutable { (ctx, msg) ⇒
+    queue.offerLast(msg)
+    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]
+  }
+
+}
diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/package.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/package.scala
new file mode 100644
index 0000000000..81053e8c94
--- /dev/null
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/package.scala
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2009-2017 Lightbend Inc. 
+ */
+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]
+  }
+
+}

From aceca4cc242ff791514a34d7a958784e42444de0 Mon Sep 17 00:00:00 2001
From: Patrik Nordwall 
Date: Fri, 21 Apr 2017 09:24:19 +0200
Subject: [PATCH 25/50] complete and test Restarter, #21217

* supervisor strategies API
* limited restarts
* backoff restarts
* tests
* eager creation (not on first message) of underlying behavior of Restarter
---
 .../scala/akka/typed/ActorContextSpec.scala   |  26 +-
 .../test/scala/akka/typed/BehaviorSpec.scala  |   2 +-
 .../test/scala/akka/typed/DeferredSpec.scala  | 107 +++++
 .../test/scala/akka/typed/RestarterSpec.scala | 428 ++++++++++++++++++
 .../main/java/akka/typed/javadsl/Actor.java   |  34 +-
 .../src/main/scala/akka/typed/Behavior.scala  |  20 +-
 .../scala/akka/typed/SupervisorStrategy.scala | 119 +++++
 .../scala/akka/typed/internal/Restarter.scala | 279 ++++++++++++
 .../typed/internal/SupervisionMechanics.scala |   2 +-
 .../scala/akka/typed/patterns/Restarter.scala | 114 -----
 .../scala/akka/typed/scaladsl/Actor.scala     |  19 +-
 11 files changed, 971 insertions(+), 179 deletions(-)
 create mode 100644 akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala
 create mode 100644 akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala
 create mode 100644 akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala
 create mode 100644 akka-typed/src/main/scala/akka/typed/internal/Restarter.scala
 delete mode 100644 akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala

diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
index e812d1c8c0..82b6b382af 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
@@ -178,8 +178,8 @@ object ActorContextSpec {
           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)
+            case None    ⇒ ctx.spawnAnonymous(Actor.Restarter[Throwable]().wrap(subject(mon)))
+            case Some(n) ⇒ ctx.spawn(Actor.Restarter[Throwable]().wrap(subject(mon)), n)
           }
           replyTo ! Created(child)
           Actor.Same
@@ -428,16 +428,18 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
         .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) ⇒
       val self = ctx.self
diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
index 60e6a33cf7..5d1a345950 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
@@ -611,7 +611,7 @@ class BehaviorSpec extends TypedSpec {
 
   trait RestarterJavaBehavior extends ImmutableWithSignalJavaBehavior with Reuse {
     override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
-      JActor.restarter(classOf[Exception], JActor.OnFailure.RESTART, super.behavior(monitor)._1) → null
+      JActor.restarter(classOf[Exception], SupervisorStrategy.restart, super.behavior(monitor)._1) → null
     }
   }
   object `A restarter Behavior (java,native)` extends RestarterJavaBehavior with NativeSystem
diff --git a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala
new file mode 100644
index 0000000000..9947cf2b28
--- /dev/null
+++ b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2017 Lightbend Inc. 
+ */
+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.AskPattern._
+import akka.typed.testkit.EffectfulActorContext
+import akka.typed.testkit.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] =
+    Immutable((ctx, cmd) ⇒ cmd match {
+      case Ping ⇒
+        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 create underlying deferred behavior immediately`(): Unit = {
+      val inbox = Inbox[Event]("evt")
+      val behv = 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 = Deferred[Command] { _ ⇒
+        inbox.ref ! Started
+        throw exc
+      }
+      intercept[RuntimeException] {
+        mkCtx(behv)
+      } should ===(exc)
+      inbox.receiveMsg() should ===(Started)
+    }
+
+  }
+
+  trait RealTests {
+    implicit def system: ActorSystem[TypedSpec.Command]
+    implicit val testSettings = TestKitSettings(system)
+
+    val nameCounter = Iterator.from(0)
+    def nextName(): String = s"a-${nameCounter.next()}"
+
+    def start(behv: Behavior[Command]): ActorRef[Command] =
+      Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated)
+
+    def `must create underlying`(): Unit = {
+      val probe = TestProbe[Event]("evt")
+      val behv = 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 = Deferred[Command] { _ ⇒
+        probe.ref ! Started
+        throw new RuntimeException("simulated exc from factory") with NoStackTrace
+      }
+      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
+
+}
diff --git a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala
new file mode 100644
index 0000000000..edc4085ec7
--- /dev/null
+++ b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala
@@ -0,0 +1,428 @@
+/**
+ * Copyright (C) 2017 Lightbend Inc. 
+ */
+package akka.typed
+
+import scala.concurrent.duration._
+import akka.typed.scaladsl.Actor._
+import akka.typed.testkit.EffectfulActorContext
+import scala.util.control.NoStackTrace
+import akka.typed.testkit.TestKitSettings
+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 {
+    import akka.typed.scaladsl.adapter._
+    implicit def system: ActorSystem[TypedSpec.Command]
+    implicit val testSettings = TestKitSettings(system)
+
+    val nameCounter = Iterator.from(0)
+    def nextName(): String = s"a-${nameCounter.next()}"
+
+    def start(behv: Behavior[Command]): ActorRef[Command] =
+      Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated)
+
+    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
+
+}
diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
index 2e85eda1a1..ef87d79445 100644
--- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
+++ b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
@@ -15,7 +15,7 @@ import akka.japi.function.Function2;
 import akka.japi.function.Procedure2;
 import akka.japi.pf.PFBuilder;
 import akka.typed.*;
-import akka.typed.patterns.Restarter;
+import akka.typed.internal.Restarter;
 import akka.typed.scaladsl.Actor.Widened;
 import scala.reflect.ClassTag;
 
@@ -97,21 +97,6 @@ public abstract class Actor {
     }
   }
 
-  /**
-   * 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 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.
-   */
-  public enum OnFailure {
-    RESUME, RESTART;
-  }
-
   private static Function2 _unhandledFun = (ctx, msg) -> Unhandled();
 
   @SuppressWarnings("unchecked")
@@ -275,26 +260,21 @@ public abstract class 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.
    *
    * @param clazz
    *          the type of exceptions that shall be caught
-   * @param mode
-   *          whether to restart or resume the actor upon a caught failure
+   * @param strategy
+   *          whether to restart, resume, or backoff the actor upon a caught failure
    * @param initialBehavior
    *          the initial behavior, that is also restored during a restart
    * @return the wrapped behavior
    */
-  static public  Behavior restarter(Class clazz, OnFailure mode,
+  static public  Behavior restarter(Class clazz, SupervisorStrategy strategy,
       Behavior initialBehavior) {
     final ClassTag catcher = akka.japi.Util.classTag(clazz);
-    return new Restarter(initialBehavior, mode == OnFailure.RESUME, initialBehavior, catcher);
+    return Restarter.apply(Behavior.validateAsInitial(initialBehavior), strategy, catcher);
   }
 
   /**
diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala
index 67fe352772..77180872f2 100644
--- a/akka-typed/src/main/scala/akka/typed/Behavior.scala
+++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala
@@ -116,7 +116,6 @@ object Behavior {
    * INTERNAL API.
    */
   @InternalApi
-  @SerialVersionUID(1L)
   private[akka] object EmptyBehavior extends Behavior[Any] {
     override def toString = "Empty"
   }
@@ -125,7 +124,6 @@ object Behavior {
    * INTERNAL API.
    */
   @InternalApi
-  @SerialVersionUID(1L)
   private[akka] object IgnoreBehavior extends Behavior[Any] {
     override def toString = "Ignore"
   }
@@ -134,7 +132,6 @@ object Behavior {
    * INTERNAL API.
    */
   @InternalApi
-  @SerialVersionUID(1L)
   private[akka] object UnhandledBehavior extends Behavior[Nothing] {
     override def toString = "Unhandled"
   }
@@ -150,8 +147,6 @@ object Behavior {
    * INTERNAL API.
    */
   @InternalApi
-  @DoNotInherit
-  @SerialVersionUID(1L)
   private[akka] abstract class DeferredBehavior[T] extends Behavior[T] {
     /** "undefer" the deferred behavior */
     @throws(classOf[Exception])
@@ -161,7 +156,6 @@ object Behavior {
   /**
    * INTERNAL API.
    */
-  @SerialVersionUID(1L)
   private[akka] object SameBehavior extends Behavior[Nothing] {
     override def toString = "Same"
   }
@@ -169,7 +163,6 @@ object Behavior {
   /**
    * INTERNAL API.
    */
-  @SerialVersionUID(1L)
   private[akka] object StoppedBehavior extends Behavior[Nothing] {
     override def toString = "Stopped"
   }
@@ -180,18 +173,21 @@ object Behavior {
    * behavior) this method computes the next behavior, suitable for passing a
    * message or signal.
    */
+  @tailrec
   def canonicalize[T](behavior: Behavior[T], current: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
     behavior match {
       case SameBehavior                  ⇒ current
       case UnhandledBehavior             ⇒ current
-      case deferred: DeferredBehavior[T] ⇒ canonicalize(undefer(deferred, ctx), deferred, ctx)
+      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] @unchecked ⇒ undefer(innerDeferred(ctx), ctx)
-    case _ ⇒ behavior
+  def undefer[T](behavior: Behavior[T], ctx: ActorContext[T]): Behavior[T] = {
+    behavior match {
+      case innerDeferred: DeferredBehavior[T] ⇒ undefer(innerDeferred(ctx), ctx)
+      case _                                  ⇒ behavior
+    }
   }
 
   /**
@@ -235,7 +231,7 @@ object Behavior {
       case IgnoreBehavior                   ⇒ SameBehavior.asInstanceOf[Behavior[T]]
       case StoppedBehavior                  ⇒ StoppedBehavior.asInstanceOf[Behavior[T]]
       case EmptyBehavior                    ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]]
-      case ext: ExtensibleBehavior[T] @unchecked ⇒
+      case ext: ExtensibleBehavior[T] ⇒
         val possiblyDeferredResult = msg match {
           case signal: Signal ⇒ ext.receiveSignal(ctx, signal)
           case msg            ⇒ ext.receiveMessage(ctx, msg.asInstanceOf[T])
diff --git a/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala b/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala
new file mode 100644
index 0000000000..be7a5e1e77
--- /dev/null
+++ b/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala
@@ -0,0 +1,119 @@
+/**
+ * Copyright (C) 2017 Lightbend Inc. 
+ */
+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 trait SupervisorStrategy {
+  def loggingEnabled: Boolean
+
+  def withLoggingEnabled(on: Boolean): SupervisorStrategy
+}
+
+sealed trait 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
+}
diff --git a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala
new file mode 100644
index 0000000000..c0efe5df5f
--- /dev/null
+++ b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala
@@ -0,0 +1,279 @@
+/**
+ * Copyright (C) 2016-2017 Lightbend Inc. 
+ */
+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(ctx.asInstanceOf[akka.typed.ActorContext[T]], 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.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)) Behavior.stopped
+    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.self.toString, behavior.getClass, ex.getMessage))
+  }
+
+  protected final def publish(ctx: ActorContext[T], e: Logging.LogEvent): Unit =
+    try ctx.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.
+  //       Something to consider is that timer messages should typically not be part of the
+  //       ordinary public message protocol and therefore those should perhaps be signals.
+  //       https://github.com/akka/akka/issues/16742
+
+  import BackoffRestarter._
+
+  override def loggingEnabled: Boolean = strategy.loggingEnabled
+
+  override def receiveSignal(ctx: ActorContext[Any], signal: Signal): Behavior[Any] = {
+    if (blackhole) {
+      ctx.system.eventStream.publish(Dropped(signal, ctx.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.schedule(strategy.resetBackoffAfter, ctx.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.system.eventStream.publish(Dropped(msg, ctx.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.self.path.toString, behavior.getClass, "failure during PreRestart"))
+      }
+      val restartDelay = calculateDelay(restartCount, strategy.minBackoff, strategy.maxBackoff, strategy.randomFactor)
+      ctx.schedule(restartDelay, ctx.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)
+  }
+}
+
diff --git a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
index b24c8db363..c8f33b3e6f 100644
--- a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
+++ b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala
@@ -90,7 +90,7 @@ private[typed] trait SupervisionMechanics[T] {
      *
      * Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination.
      */
-    try if ((a ne null) && !a.isInstanceOf[DeferredBehavior[_]]) Behavior.canonicalize(Behavior.interpretSignal(a, ctx, PostStop), a, ctx)
+    try if ((a ne null) && !a.isInstanceOf[DeferredBehavior[_]]) 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))
diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala b/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
deleted file mode 100644
index 2766a8ba22..0000000000
--- a/akka-typed/src/main/scala/akka/typed/patterns/Restarter.scala
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * Copyright (C) 2016-2017 Lightbend Inc. 
- */
-package akka.typed
-package patterns
-
-import scala.reflect.ClassTag
-import scala.util.control.NonFatal
-import akka.event.Logging
-import akka.typed.scaladsl.Actor
-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)
- */
-final case class Restarter[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T], resume: Boolean)(
-  behavior: Behavior[T] = initialBehavior) extends ExtensibleBehavior[T] {
-
-  private def restart(ctx: ActorContext[T], startedBehavior: Behavior[T]): Behavior[T] = {
-    try Behavior.interpretSignal(startedBehavior, ctx, PreRestart) catch { case NonFatal(_) ⇒ }
-    // no need to canonicalize, it's done in the calling methods
-    Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
-  }
-
-  private def canonical(b: Behavior[T], ctx: ActorContext[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 {
-      b match {
-        case d: Behavior.DeferredBehavior[_] ⇒ canonical(Behavior.undefer(d, ctx), ctx)
-        case b                               ⇒ Restarter[T, Thr](initialBehavior, resume)(b)
-      }
-    }
-
-  private def preStart(b: Behavior[T], ctx: ActorContext[T]): Behavior[T] =
-    Behavior.undefer(b, ctx)
-
-  override def receiveSignal(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
-    val startedBehavior = preStart(behavior, ctx)
-    val b =
-      try {
-        Behavior.interpretSignal(startedBehavior, ctx, signal)
-      } catch {
-        case ex: Thr ⇒
-          ctx.system.eventStream.publish(Logging.Error(ex, ctx.self.toString, behavior.getClass, ex.getMessage))
-          if (resume) startedBehavior else restart(ctx, startedBehavior)
-      }
-    canonical(b, ctx)
-  }
-
-  override def receiveMessage(ctx: ActorContext[T], msg: T): Behavior[T] = {
-    val startedBehavior = preStart(behavior, ctx)
-    val b =
-      try {
-        Behavior.interpretMessage(startedBehavior, ctx, msg)
-      } catch {
-        case ex: Thr ⇒
-          ctx.system.eventStream.publish(Logging.Error(ex, ctx.self.toString, behavior.getClass, ex.getMessage))
-          if (resume) startedBehavior else restart(ctx, startedBehavior)
-      }
-    canonical(b, ctx)
-  }
-}
-
-/**
- * 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 ExtensibleBehavior[T] {
-
-  private[this] var current: Behavior[T] = _
-  private def startCurrent(ctx: ActorContext[T]) = {
-    // we need to undefer it if needed the first time we have access to a context
-    if (current eq null) current = Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
-  }
-
-  private def restart(ctx: ActorContext[T]): Behavior[T] = {
-    try Behavior.interpretSignal(current, ctx, PreRestart) catch { case NonFatal(_) ⇒ }
-    Behavior.validateAsInitial(Behavior.undefer(initialBehavior, ctx))
-  }
-
-  override def receiveSignal(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
-    startCurrent(ctx)
-    current =
-      try {
-        Behavior.interpretSignal(current, 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)
-      }
-    if (Behavior.isAlive(current)) this else Stopped
-  }
-
-  override def receiveMessage(ctx: ActorContext[T], msg: T): Behavior[T] = {
-    startCurrent(ctx)
-    current =
-      try Behavior.interpretMessage(current, 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)
-      }
-    if (Behavior.isAlive(current)) this else Stopped
-  }
-}
diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
index 527e1cdc78..297be00742 100644
--- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
+++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala
@@ -387,27 +387,22 @@ 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)
+    class Apply[Thr <: Throwable](c: ClassTag[Thr], strategy: SupervisorStrategy) {
+      def wrap[T](b: Behavior[T]): Behavior[T] = akka.typed.internal.Restarter(Behavior.validateAsInitial(b), strategy)(c)
     }
 
-    def apply[Thr <: Throwable: ClassTag](resume: Boolean = false): Apply[Thr] = new Apply(implicitly, resume)
+    def apply[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy = SupervisorStrategy.restart): Apply[Thr] =
+      new Apply(implicitly, strategy)
   }
 
   // TODO

From 2eb41b910aa3095f5be6f490d8ff3830893183b8 Mon Sep 17 00:00:00 2001
From: Patrik Nordwall 
Date: Thu, 27 Apr 2017 08:14:42 +0200
Subject: [PATCH 26/50] implement javadsl in Scala, #22749

* and separate scaladsl api and impl, #22749
---
 .../scala/akka/typed/testkit/Effects.scala    |   2 +-
 .../typed/testkit/StubbedActorContext.scala   |   2 +-
 .../scala/akka/typed/ActorContextSpec.scala   |   2 +-
 .../test/scala/akka/typed/BehaviorSpec.scala  |   2 +-
 .../main/java/akka/typed/javadsl/Actor.java   | 392 ------------------
 .../java/akka/typed/javadsl/ActorContext.java | 159 -------
 .../main/scala/akka/typed/ActorContext.scala  |  10 +-
 .../src/main/scala/akka/typed/ActorRef.scala  |   2 +-
 .../src/main/scala/akka/typed/Behavior.scala  |   9 +-
 .../scala/akka/typed/internal/ActorCell.scala |   2 +-
 .../akka/typed/internal/BehaviorImpl.scala    |  77 ++++
 .../akka/typed/internal/EventStreamImpl.scala |  25 +-
 .../adapter/ActorContextAdapter.scala         |   2 +-
 .../main/scala/akka/typed/javadsl/Actor.scala | 243 +++++++++++
 .../akka/typed/javadsl/ActorContext.scala     | 160 +++++++
 .../scala/akka/typed/scaladsl/Actor.scala     | 277 ++-----------
 .../akka/typed/scaladsl/ActorContext.scala    | 148 +++++++
 17 files changed, 692 insertions(+), 822 deletions(-)
 delete mode 100644 akka-typed/src/main/java/akka/typed/javadsl/Actor.java
 delete mode 100644 akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java
 create mode 100644 akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala
 create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala
 create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala
 create mode 100644 akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala

diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
index 165d8f8506..03d06fd72f 100644
--- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala
@@ -92,7 +92,7 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma
     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)
   }
diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala
index 8a9daed51f..952d0b5521 100644
--- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala
+++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala
@@ -47,7 +47,7 @@ class StubbedActorContext[T](
    * 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 = {
+  override def stop[U](child: ActorRef[U]): Boolean = {
     _children.get(child.path.name) match {
       case None        ⇒ false
       case Some(inbox) ⇒ inbox.ref == child
diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
index 82b6b382af..3275fbcde3 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala
@@ -279,7 +279,7 @@ 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)(
       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))
diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
index 5d1a345950..a144bdb630 100644
--- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
+++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala
@@ -601,8 +601,8 @@ class BehaviorSpec extends TypedSpec {
     override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
       val inbox = Inbox[Either[Signal, Command]]("tapListener")
       (JActor.tap(
-        ps((_, sig) ⇒ inbox.ref ! Left(sig)),
         pc((_, msg) ⇒ inbox.ref ! Right(msg)),
+        ps((_, sig) ⇒ inbox.ref ! Left(sig)),
         super.behavior(monitor)._1), inbox)
     }
   }
diff --git a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java b/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
deleted file mode 100644
index ef87d79445..0000000000
--- a/akka-typed/src/main/java/akka/typed/javadsl/Actor.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/**
- * Copyright (C) 2017 Lightbend Inc. 
- */
-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.*;
-import akka.typed.internal.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 extends Behavior.DeferredBehavior {
-    final akka.japi.function.Function, Behavior> producer;
-
-    public Deferred(akka.japi.function.Function, Behavior> producer) {
-      this.producer = producer;
-    }
-
-    @Override
-    public Behavior apply(akka.typed.ActorContext ctx) throws Exception {
-      return producer.apply(ctx);
-    }
-  }
-
-  private static class Immutable extends ExtensibleBehavior {
-    final Function2, T, Behavior> message;
-    final Function2, Signal, Behavior> signal;
-
-    public Immutable(Function2, T, Behavior> message,
-        Function2, Signal, Behavior> signal) {
-      this.signal = signal;
-      this.message = message;
-    }
-
-    @Override
-    public Behavior receiveSignal(akka.typed.ActorContext ctx, Signal msg) throws Exception {
-      return signal.apply(ctx, msg);
-    }
-
-    @Override
-    public Behavior receiveMessage(akka.typed.ActorContext ctx, T msg) throws Exception {
-      return message.apply(ctx, msg);
-    }
-  }
-
-  private static class Tap extends ExtensibleBehavior {
-    final Procedure2, Signal> signal;
-    final Procedure2, T> message;
-    final Behavior behavior;
-
-    public Tap(Procedure2, Signal> signal, Procedure2, T> message,
-        Behavior behavior) {
-      this.signal = signal;
-      this.message = message;
-      this.behavior = behavior;
-    }
-
-    private Behavior canonicalize(Behavior behv) {
-      if (Behavior.isUnhandled(behv))
-        return Unhandled();
-      else if (behv == Same() || behv == this)
-        return Same();
-      else if (Behavior.isAlive(behv))
-        return new Tap(signal, message, behv);
-      else
-        return Stopped();
-    }
-
-    @Override
-    public Behavior receiveSignal(akka.typed.ActorContext ctx, Signal signal) throws Exception {
-      this.signal.apply(ctx, signal);
-      return canonicalize(Behavior.interpretSignal(behavior, ctx, signal));
-    }
-
-    @Override
-    public Behavior receiveMessage(akka.typed.ActorContext ctx, T msg) throws Exception {
-      message.apply(ctx, msg);
-      return canonicalize(Behavior.interpretMessage(behavior, ctx, msg));
-    }
-  }
-
-  private static Function2 _unhandledFun = (ctx, msg) -> Unhandled();
-
-  @SuppressWarnings("unchecked")
-  private static  Function2, Signal, Behavior> unhandledFun() {
-    return (Function2, Signal, Behavior>) (Object) _unhandledFun;
-  }
-
-  private static Procedure2 _doNothing = (ctx, msg) -> {
-  };
-
-  @SuppressWarnings("unchecked")
-  private static  Procedure2, Signal> doNothing() {
-    return (Procedure2, 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 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 message
-   *          the function that describes how this actor reacts to the next
-   *          message
-   * @return the behavior
-   */
-  static public  Behavior immutable(Function2, T, Behavior> message) {
-    return new Immutable(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 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 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  Behavior immutable(Function2, T, Behavior> message,
-      Function2, Signal, Behavior> signal) {
-    return new Immutable(message, signal);
-  }
-
-  /**
-   * 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  Behavior 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  Behavior 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  Behavior stopped() {
-    return Stopped();
-  }
-
-  /**
-   * A behavior that treats every incoming message as unhandled.
-   *
-   * @return the empty behavior
-   */
-  static public  Behavior empty() {
-    return Empty();
-  }
-
-  /**
-   * A behavior that ignores every incoming message and returns “same”.
-   *
-   * @return the inert behavior
-   */
-  static public  Behavior 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 immutable) without needing to be wrapped in a
-   * tap(...) 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  Behavior tap(Procedure2, Signal> signal, Procedure2, T> message,
-      Behavior behavior) {
-    return new Tap(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. return different behavior) without needing to be
-   * wrapped in a monitor(...) 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  Behavior monitor(ActorRef monitor, Behavior behavior) {
-    return new Tap(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 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.
-   *
-   * @param clazz
-   *          the type of exceptions that shall be caught
-   * @param strategy
-   *          whether to restart, resume, or backoff the actor upon a caught failure
-   * @param initialBehavior
-   *          the initial behavior, that is also restored during a restart
-   * @return the wrapped behavior
-   */
-  static public  Behavior restarter(Class clazz, SupervisorStrategy strategy,
-      Behavior initialBehavior) {
-    final ClassTag catcher = akka.japi.Util.classTag(clazz);
-    return Restarter.apply(Behavior.validateAsInitial(initialBehavior), strategy, 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.
-   *
-   * 
-   * 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 - */ - static public Behavior widened(Behavior behavior, Function, PFBuilder> selector) { - return new Widened(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 Behavior deferred(akka.japi.function.Function, Behavior> producer) { - return new Deferred(producer); - } - - /** - * 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 producer - * 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 - */ - static public Behavior mutable(akka.japi.function.Function, MutableBehavior> producer) { - return deferred(ctx -> producer.apply(ctx)); - } - - /** - * Mutable behavior can be implemented by extending this class and implement the - * abstract method {@link MutableBehavior#onMessage} and optionally override - * {@link MutableBehavior#onSignal}. - * - * Instances of this behavior should be created via {@link Actor#mutable} and if - * the {@link ActorContext} is needed it can be passed as a constructor parameter - * from the factory function. - * - * @see Actor#mutable - */ - static public abstract class MutableBehavior extends ExtensibleBehavior { - @Override - final public Behavior receiveMessage(akka.typed.ActorContext ctx, T msg) throws Exception { - return onMessage(msg); - } - - /** - * 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: - *
    - *
  • 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
  • - *
- * - */ - public abstract Behavior onMessage(T msg) throws Exception; - - @Override - final public Behavior receiveSignal(akka.typed.ActorContext ctx, Signal msg) throws Exception { - return onSignal(msg); - } - - /** - * Override this method to process an incoming {@link 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, this method returns `unhandled`. - */ - public Behavior onSignal(Signal msg) throws Exception { - return unhandled(); - } - } - -} diff --git a/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java b/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java deleted file mode 100644 index dc92b15f73..0000000000 --- a/akka-typed/src/main/java/akka/typed/javadsl/ActorContext.java +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (C) 2017 Lightbend Inc. - */ -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: - * - *
    - *
  • 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 {@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 ActorContext 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 { - - /** - * 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 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 getSystem(); - - /** - * The list of child Actors created by this Actor during its lifetime that are - * still alive, in no particular order. - */ - public List> getChildren(); - - /** - * The named child Actor if it is alive. - */ - public Optional> 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 ActorRef spawnAnonymous(Behavior 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 ActorRef spawnAnonymous(Behavior behavior, DeploymentConfig deployment); - - /** - * Create a child Actor from the given {@link akka.typed.Behavior} and with the given name. - */ - public ActorRef spawn(Behavior behavior, String name); - - /** - * Create a child Actor from the given {@link akka.typed.Behavior} and with the given name. - */ - public ActorRef spawn(Behavior 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 void watch(ActorRef other); - - /** - * Revoke the registration established by {@link #watch}. A {@link akka.typed.Terminated} - * notification will not subsequently be received for the referenced Actor. - */ - public void unwatch(ActorRef 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 scala.concurrent.duration.Duration.Undefined 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 Cancellable schedule(FiniteDuration delay, ActorRef target, U msg); - - /** - * This Actor’s execution context. It can be used to run asynchronous tasks - * like scala.concurrent.Future 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 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. - */ - public ActorRef createAdapter(Function 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 ActorRef createAdapter(Function f); - -} diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index f900cddc02..3565d8f29e 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -26,7 +26,7 @@ trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext case None ⇒ Optional.empty() } - override def getChildren(): java.util.List[akka.typed.ActorRef[Void]] = { + 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 @@ -34,16 +34,16 @@ trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext a } - override def getExecutionContext(): ExecutionContextExecutor = + override def getExecutionContext: ExecutionContextExecutor = executionContext - override def getMailboxCapacity(): Int = + override def getMailboxCapacity: Int = mailboxCapacity - override def getSelf(): akka.typed.ActorRef[T] = + override def getSelf: akka.typed.ActorRef[T] = self - override def getSystem(): akka.typed.ActorSystem[Void] = + 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] = diff --git a/akka-typed/src/main/scala/akka/typed/ActorRef.scala b/akka-typed/src/main/scala/akka/typed/ActorRef.scala index 5fc333f71b..13d2cdbc2e 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorRef.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorRef.scala @@ -69,7 +69,7 @@ abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[Act object ActorRef { - implicit final class ActorRefScalaTell[-T](val ref: ActorRef[T]) extends AnyVal { + 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. diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala index 77180872f2..8e8d8ba257 100644 --- a/akka-typed/src/main/scala/akka/typed/Behavior.scala +++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala @@ -3,6 +3,7 @@ */ package akka.typed +import akka.util.LineNumbers import akka.annotation.{ DoNotInherit, InternalApi } import scala.annotation.tailrec @@ -145,12 +146,16 @@ object Behavior { /** * INTERNAL API. + * Not placed in internal.BehaviorImpl because Behavior is sealed. */ @InternalApi - private[akka] abstract class DeferredBehavior[T] extends Behavior[T] { + private[akka] final case class DeferredBehavior[T](factory: ActorContext[T] ⇒ Behavior[T]) extends Behavior[T] { + /** "undefer" the deferred behavior */ @throws(classOf[Exception]) - def apply(ctx: ActorContext[T]): Behavior[T] + def apply(ctx: ActorContext[T]): Behavior[T] = factory(ctx) + + override def toString: String = s"Deferred(${LineNumbers(factory)})" } /** diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index 39d74d4e74..bba4660148 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -122,7 +122,7 @@ private[typed] class ActorCell[T]( spawn(behavior, name, deployment) } - 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 diff --git a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala new file mode 100644 index 0000000000..073372c6cd --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package akka.typed +package internal + +import akka.util.LineNumbers +import akka.annotation.InternalApi +import akka.typed.{ ActorContext ⇒ AC } +import akka.typed.scaladsl.Actor + +/** + * 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]] + } + + final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends ExtensibleBehavior[U] { + private def postProcess(behv: Behavior[T], ctx: AC[T]): Behavior[U] = + if (isUnhandled(behv)) unhandled + else if (isAlive(behv)) { + val next = canonicalize(behv, behavior, ctx) + if (next eq behavior) same else Widened(next, matcher) + } else stopped + + override def receiveSignal(ctx: AC[U], signal: Signal): Behavior[U] = + postProcess(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 ⇒ postProcess(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: (ActorContext[T], T) ⇒ Behavior[T], + onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]] = Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]]) + extends ExtensibleBehavior[T] { + + override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = + onSignal.applyOrElse((ctx, msg), Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]]) + override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg) + override def toString = s"Immutable(${LineNumbers(onMessage)})" + } + + final case class Tap[T]( + onMessage: Function2[ActorContext[T], T, _], + onSignal: Function2[ActorContext[T], Signal, _], + behavior: Behavior[T]) extends ExtensibleBehavior[T] { + + private def canonical(behv: Behavior[T]): Behavior[T] = + if (isUnhandled(behv)) unhandled + else if ((behv eq SameBehavior) || (behv eq this)) same + else if (isAlive(behv)) Tap(onMessage, onSignal, behv) + else stopped + override def receiveSignal(ctx: AC[T], signal: Signal): Behavior[T] = { + onSignal(ctx, signal) + canonical(Behavior.interpretSignal(behavior, ctx, signal)) + } + override def receiveMessage(ctx: AC[T], msg: T): Behavior[T] = { + onMessage(ctx, msg) + canonical(Behavior.interpretMessage(behavior, ctx, msg)) + } + override def toString = s"Tap(${LineNumbers(onSignal)},${LineNumbers(onMessage)},$behavior)" + } + +} diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala index 9292c5e0a2..2c4c1023b5 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala @@ -44,25 +44,26 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat import scaladsl.Actor Actor.Deferred[Command] { _ ⇒ if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"registering unsubscriber with $this")) - Actor.Immutable[Command] { - case (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(actor) - Actor.Same + 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 (ctx, 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 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 (ctx, UnregisterIfNoMoreSubscribedChannels(actor)) ⇒ - if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unwatching $actor, since has no subscriptions")) - ctx.unwatch(actor) - 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 - case (_, _) ⇒ Actor.Unhandled } } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala index 7000a685a2..e9c35e5d2c 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala @@ -27,7 +27,7 @@ import akka.annotation.InternalApi ActorContextAdapter.spawnAnonymous(untyped, behavior, deployment) override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig) = ActorContextAdapter.spawn(untyped, behavior, name, deployment) - override def stop(child: ActorRef[_]) = + override def stop[U](child: ActorRef[U]) = toUntyped(child) match { case f: akka.actor.FunctionRef ⇒ val cell = untyped.asInstanceOf[akka.actor.ActorCell] diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala new file mode 100644 index 0000000000..c707d3e968 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -0,0 +1,243 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package akka.typed.javadsl + +import java.util.function.{ Function ⇒ JFunction } +import akka.japi.function.{ Function2 ⇒ JapiFunction2 } +import akka.japi.function.Procedure2 +import akka.typed.Behavior +import akka.typed.ExtensibleBehavior +import akka.typed.Signal +import akka.typed.internal.BehaviorImpl +import akka.typed.ActorRef +import akka.typed.SupervisorStrategy +import scala.reflect.ClassTag +import akka.typed.internal.Restarter +import akka.japi.pf.PFBuilder + +object Actor { + + private val _unitFunction = (_: ActorContext[Any], _: Any) ⇒ () + private def unitFunction[T] = _unitFunction.asInstanceOf[((ActorContext[T], Signal) ⇒ Unit)] + + /** + * 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. + */ + def deferred[T](factory: akka.japi.function.Function[ActorContext[T], Behavior[T]]): Behavior[T] = + Behavior.DeferredBehavior(ctx ⇒ factory.apply(ctx)) + + /** + * 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] { + @throws(classOf[Exception]) + override final def receiveMessage(ctx: akka.typed.ActorContext[T], msg: T): Behavior[T] = + onMessage(msg) + + /** + * 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: + *
    + *
  • 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
  • + *
+ * + */ + @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(msg) + + /** + * 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, this method returns `unhandled`. + */ + @throws(classOf[Exception]) + def onSignal(msg: Signal): Behavior[T] = + unhandled + } + + /** + * 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 NOT be passed to the + * current behavior, it will be effectively ignored. + */ + def stopped[T]: Behavior[T] = Behavior.stopped + + /** + * 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, 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, msg), + { case (ctx, sig) ⇒ onSignal.apply(ctx, sig) }) + } + + /** + * 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, msg), + (ctx, sig) ⇒ onSignal.apply(ctx, 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()) + +} diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala new file mode 100644 index 0000000000..b7559216c7 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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.DeploymentConfig +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 + + /** + * 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], deployment: DeploymentConfig): 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, deployment: DeploymentConfig): 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 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]): 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 createAdapter[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 createAdapter[U](f: JFunction[U, T]): ActorRef[U] + +} diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index 297be00742..6952c24d04 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -4,152 +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]): 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] = spawnAdapter(f, "") - -} +import akka.typed.internal.BehaviorImpl @ApiMayChange object Actor { import Behavior._ + 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 { /** * Widen the wrapped Behavior by placing a funnel in front of it: the supplied @@ -157,62 +24,25 @@ 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]] - } - - /** - * 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: - * {{{ - * 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 - * } - * }}} - */ - final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends ExtensibleBehavior[U] { - private def postProcess(behv: Behavior[T], ctx: AC[T]): Behavior[U] = - if (isUnhandled(behv)) Unhandled - else if (isAlive(behv)) { - val next = canonicalize(behv, behavior, ctx) - if (next eq behavior) Same else Widened(next, matcher) - } else Stopped - - override def receiveSignal(ctx: AC[U], signal: Signal): Behavior[U] = - postProcess(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 ⇒ postProcess(Behavior.interpretMessage(behavior, ctx.as[T], transformed), ctx.as[T]) - } - - override def toString: String = s"${behavior.toString}.widen(${LineNumbers(matcher)})" + def widen[U](matcher: PartialFunction[U, T]): Behavior[U] = + BehaviorImpl.Widened(behavior, 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. */ - final case class Deferred[T](factory: ActorContext[T] ⇒ Behavior[T]) extends DeferredBehavior[T] { - /** "undefer" the deferred behavior */ - override def apply(ctx: AC[T]): Behavior[T] = factory(ctx) - - override def toString: String = s"Deferred(${LineNumbers(factory)})" - } + def Deferred[T](factory: ActorContext[T] ⇒ Behavior[T]): Behavior[T] = + Behavior.DeferredBehavior(factory) /** * Factory for creating a [[MutableBehavior]] that typically holds mutable state as @@ -328,21 +158,14 @@ object Actor { * State is updated by returning a new behavior that holds the new immutable * state. */ - final class Immutable[T] private ( - onMessage: (ActorContext[T], T) ⇒ Behavior[T], - onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]] = Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]]) - extends ExtensibleBehavior[T] { - override def receiveSignal(ctx: AC[T], msg: Signal): Behavior[T] = onSignal.applyOrElse((ctx, msg), Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]]) - override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg) - override def toString = s"Immutable(${LineNumbers(onMessage)})" + def Immutable[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]): Immutable[T] = + new Immutable(onMessage) - def onSignal(onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Immutable[T] = - new Immutable(onMessage, onSignal) - } + final class Immutable[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]) + extends BehaviorImpl.ImmutableBehavior[T](onMessage) { - object Immutable { - def apply[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]) = - new Immutable(onMessage) + def onSignal(onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Behavior[T] = + new BehaviorImpl.ImmutableBehavior(onMessage, onSignal) } /** @@ -350,26 +173,11 @@ 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]( + def Tap[T]( onMessage: Function2[ActorContext[T], T, _], onSignal: Function2[ActorContext[T], Signal, _], - behavior: Behavior[T]) extends ExtensibleBehavior[T] { - - private def canonical(behv: Behavior[T]): Behavior[T] = - if (isUnhandled(behv)) Unhandled - else if ((behv eq SameBehavior) || (behv eq this)) Same - else if (isAlive(behv)) Tap(onMessage, onSignal, behv) - else Stopped - override def receiveSignal(ctx: AC[T], signal: Signal): Behavior[T] = { - onSignal(ctx, signal) - canonical(Behavior.interpretSignal(behavior, ctx, signal)) - } - override def receiveMessage(ctx: AC[T], msg: T): Behavior[T] = { - onMessage(ctx, msg) - canonical(Behavior.interpretMessage(behavior, ctx, msg)) - } - override def toString = s"Tap(${LineNumbers(onSignal)},${LineNumbers(onMessage)},$behavior)" - } + behavior: Behavior[T]): Behavior[T] = + BehaviorImpl.Tap(onMessage, onSignal, behavior) /** * Behavior decorator that copies all received message to the designated @@ -377,9 +185,8 @@ object Actor { * 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((_, msg) ⇒ monitor ! msg, unitFunction, 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 @@ -396,34 +203,14 @@ object Actor { * val dbRestarts = Restarter[DbException]().wrap(dbConnector) * }}} */ - object Restarter { - class Apply[Thr <: Throwable](c: ClassTag[Thr], strategy: SupervisorStrategy) { - def wrap[T](b: Behavior[T]): Behavior[T] = akka.typed.internal.Restarter(Behavior.validateAsInitial(b), strategy)(c) - } + def Restarter[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy = SupervisorStrategy.restart): Restarter[Thr] = + new Restarter(implicitly, strategy) - def apply[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy = SupervisorStrategy.restart): Apply[Thr] = - new Apply(implicitly, strategy) + 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) } // 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)] - } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala new file mode 100644 index 0000000000..90dc824178 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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.DeploymentConfig +import akka.typed.EmptyDeploymentConfig + +/** + * 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[U](child: ActorRef[U]): 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]): 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] = spawnAdapter(f, "") + +} From cae2f57abf5465dc8eb465c85b9f74dcd51c685b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Tue, 2 May 2017 15:01:18 +0200 Subject: [PATCH 27/50] Rename DeploymentConfig to Props #22660 --- .../scala/akka/typed/testkit/Effects.scala | 7 +- .../typed/testkit/StubbedActorContext.scala | 4 +- .../akka/typed/internal/ActorSystemStub.scala | 2 +- .../main/scala/akka/typed/ActorContext.scala | 4 +- .../main/scala/akka/typed/ActorSystem.scala | 28 +-- .../main/scala/akka/typed/Deployment.scala | 185 -------------- .../src/main/scala/akka/typed/Props.scala | 233 ++++++++++++++++++ .../scala/akka/typed/internal/ActorCell.scala | 10 +- .../akka/typed/internal/ActorSystemImpl.scala | 30 +-- .../adapter/ActorContextAdapter.scala | 16 +- .../internal/adapter/ActorSystemAdapter.scala | 6 +- .../typed/internal/adapter/PropsAdapter.scala | 8 +- .../akka/typed/javadsl/ActorContext.scala | 6 +- .../scala/akka/typed/javadsl/Adapter.scala | 32 +-- .../akka/typed/scaladsl/ActorContext.scala | 8 +- .../typed/scaladsl/adapter/PropsAdapter.scala | 6 +- .../akka/typed/scaladsl/adapter/package.scala | 16 +- 17 files changed, 324 insertions(+), 277 deletions(-) delete mode 100644 akka-typed/src/main/scala/akka/typed/Deployment.scala create mode 100644 akka-typed/src/main/scala/akka/typed/Props.scala diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala index 03d06fd72f..2a0be055f2 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Effects.scala @@ -5,14 +5,13 @@ package akka.typed.testkit import java.util.concurrent.ConcurrentLinkedQueue -import akka.typed.{ ActorContext, ActorRef, ActorSystem, Behavior, DeploymentConfig, EmptyDeploymentConfig, Signal } +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 } -import akka.typed.PostStop /** * All tracked effects must extend implement this type. It is deliberately @@ -78,7 +77,7 @@ class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _ma } catch handleException } - override def spawnAnonymous[U](behavior: Behavior[U], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = { + 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 @@ -88,7 +87,7 @@ 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) } diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala index 952d0b5521..6d7d0d2314 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala @@ -28,12 +28,12 @@ class StubbedActorContext[T]( 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] = { + 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, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] = + 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 ⇒ diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala index b05c424bbf..038c6714b4 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala @@ -57,7 +57,7 @@ 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")) } diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index 3565d8f29e..b6346bee13 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -47,10 +47,10 @@ trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext system.asInstanceOf[ActorSystem[Void]] override def spawn[U](behavior: akka.typed.Behavior[U], name: String): akka.typed.ActorRef[U] = - spawn(behavior, name, EmptyDeploymentConfig) + spawn(behavior, name, EmptyProps) override def spawnAnonymous[U](behavior: akka.typed.Behavior[U]): akka.typed.ActorRef[U] = - spawnAnonymous(behavior, EmptyDeploymentConfig) + spawnAnonymous(behavior, EmptyProps) override def createAdapter[U](f: java.util.function.Function[U, T]): akka.typed.ActorRef[U] = spawnAdapter(f.apply _) diff --git a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala index 1ec0786a18..1a8d383955 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala @@ -133,7 +133,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 +142,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$]]. @@ -159,26 +159,26 @@ object ActorSystem { * [[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 */ def create[T](name: String, guardianBehavior: Behavior[T], - guardianDeployment: java.util.Optional[DeploymentConfig], - config: java.util.Optional[Config], - classLoader: java.util.Optional[ClassLoader], - executionContext: java.util.Optional[ExecutionContext]): ActorSystem[T] = { + guardianProps: java.util.Optional[Props], + config: java.util.Optional[Config], + classLoader: java.util.Optional[ClassLoader], + executionContext: java.util.Optional[ExecutionContext]): ActorSystem[T] = { import scala.compat.java8.OptionConverters._ - apply(name, guardianBehavior, guardianDeployment.asScala.getOrElse(EmptyDeploymentConfig), config.asScala, classLoader.asScala, executionContext.asScala) + apply(name, guardianBehavior, guardianProps.asScala.getOrElse(EmptyProps), config.asScala, classLoader.asScala, executionContext.asScala) } /** @@ -187,7 +187,7 @@ object ActorSystem { * 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, @@ -202,7 +202,7 @@ object ActorSystem { 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) + Some(PropsAdapter(() ⇒ guardianBehavior, guardianProps)), actorSystemSettings) untyped.start() new ActorSystemAdapter(untyped) } diff --git a/akka-typed/src/main/scala/akka/typed/Deployment.scala b/akka-typed/src/main/scala/akka/typed/Deployment.scala deleted file mode 100644 index acfa98c249..0000000000 --- a/akka-typed/src/main/scala/akka/typed/Deployment.scala +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (C) 2016-2017 Lightbend Inc. - */ -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. This does NOT append the given list - * of configuration nodes to the current list! - */ - 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) -} diff --git a/akka-typed/src/main/scala/akka/typed/Props.scala b/akka-typed/src/main/scala/akka/typed/Props.scala new file mode 100644 index 0000000000..a0eff74353 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/Props.scala @@ -0,0 +1,233 @@ +/** + * Copyright (C) 2016-2017 Lightbend Inc. + */ +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. This does NOT append the given list + * of configuration nodes to the current list! + * + * 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 +} + +/** + * 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. + * + * INTERNAL API + */ +@DoNotInherit +@InternalApi +private[akka] sealed trait DispatcherSelector extends Props + +/** + * 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) +} diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index bba4660148..a8a016881b 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -101,11 +101,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) @@ -116,10 +116,10 @@ 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[U](child: ActorRef[U]): Boolean = { diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index a52e51660e..724c370251 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -26,7 +26,7 @@ import akka.typed.scaladsl.AskPattern object ActorSystemImpl { 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] = { // TODO avoid depending on dsl here? @@ -37,7 +37,7 @@ object ActorSystemImpl { 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) Same } } @@ -71,12 +71,12 @@ Distributed Data: */ private[typed] class ActorSystemImpl[-T]( - override val name: String, - _config: Config, - _cl: ClassLoader, - _ec: Option[ExecutionContext], - _userGuardianBehavior: Behavior[T], - _userGuardianDeployment: DeploymentConfig) + override val name: String, + _config: Config, + _cl: ClassLoader, + _ec: Option[ExecutionContext], + _userGuardianBehavior: Behavior[T], + _userGuardianProps: Props) extends ActorRef[T](a.RootActorPath(a.Address("akka", name)) / "user") with ActorSystem[T] with ActorRefImpl[T] { import ActorSystemImpl._ @@ -212,9 +212,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) @@ -223,12 +223,12 @@ 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) @@ -257,10 +257,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 = { diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala index e9c35e5d2c..2a43a89119 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala @@ -23,10 +23,10 @@ import akka.annotation.InternalApi 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], deployment: DeploymentConfig = EmptyDeploymentConfig) = - ActorContextAdapter.spawnAnonymous(untyped, behavior, deployment) - override def spawn[U](behavior: Behavior[U], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig) = - ActorContextAdapter.spawn(untyped, behavior, name, deployment) + 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 ⇒ @@ -93,13 +93,13 @@ import akka.annotation.InternalApi s"($ctx of class ${ctx.getClass.getName})") } - def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], deployment: DeploymentConfig): ActorRef[T] = { + def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], props: Props): ActorRef[T] = { Behavior.validateAsInitial(behavior) - ActorRefAdapter(ctx.actorOf(PropsAdapter(() ⇒ behavior, deployment))) + ActorRefAdapter(ctx.actorOf(PropsAdapter(() ⇒ behavior, props))) } - def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, deployment: DeploymentConfig): ActorRef[T] = { + def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, props: Props): ActorRef[T] = { Behavior.validateAsInitial(behavior) - ActorRefAdapter(ctx.actorOf(PropsAdapter(() ⇒ behavior, deployment), name)) + ActorRefAdapter(ctx.actorOf(PropsAdapter(() ⇒ behavior, props), name)) } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala index b86ab06d34..a01ca6af19 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala @@ -69,8 +69,8 @@ import akka.annotation.InternalApi 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) + 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)) } @@ -96,7 +96,7 @@ private[typed] object ActorSystemAdapter { class ReceptionistExtension(system: a.ExtendedActorSystem) extends a.Extension { val receptionist: ActorRef[patterns.Receptionist.Command] = ActorRefAdapter(system.systemActorOf( - PropsAdapter(() ⇒ patterns.Receptionist.behavior, EmptyDeploymentConfig), + PropsAdapter(() ⇒ patterns.Receptionist.behavior, EmptyProps), "receptionist")) } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala index efc275d091..a3a4d60f9d 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/PropsAdapter.scala @@ -6,16 +6,16 @@ package internal package adapter import akka.typed.Behavior -import akka.typed.EmptyDeploymentConfig -import akka.typed.DeploymentConfig +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: DeploymentConfig = EmptyDeploymentConfig): akka.actor.Props = { - // FIXME use DeploymentConfig, e.g. dispatcher + 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())) } diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala index b7559216c7..b3816a3491 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala @@ -10,7 +10,7 @@ import akka.typed.ActorRef import akka.typed.ActorSystem import java.util.Optional import akka.typed.Behavior -import akka.typed.DeploymentConfig +import akka.typed.Props import scala.concurrent.duration.FiniteDuration import scala.concurrent.ExecutionContextExecutor @@ -75,7 +75,7 @@ trait ActorContext[T] { * 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): ActorRef[U] + 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. @@ -85,7 +85,7 @@ trait ActorContext[T] { /** * 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): ActorRef[U] + 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 diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala index 86759b2546..40f6a780ab 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Adapter.scala @@ -4,8 +4,8 @@ package akka.typed.javadsl import akka.typed.Behavior -import akka.typed.DeploymentConfig -import akka.typed.EmptyDeploymentConfig +import akka.typed.Props +import akka.typed.EmptyProps import akka.typed.ActorRef import akka.typed.internal.adapter.ActorRefAdapter import akka.typed.scaladsl.adapter._ @@ -30,28 +30,28 @@ import akka.japi.Creator object Adapter { def spawnAnonymous[T](sys: akka.actor.ActorSystem, behavior: Behavior[T]): ActorRef[T] = - spawnAnonymous(sys, behavior, EmptyDeploymentConfig) + spawnAnonymous(sys, behavior, EmptyProps) - def spawnAnonymous[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], deployment: DeploymentConfig): ActorRef[T] = - sys.spawnAnonymous(behavior, deployment) + 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, EmptyDeploymentConfig) + spawn(sys, behavior, name, EmptyProps) - def spawn[T](sys: akka.actor.ActorSystem, behavior: Behavior[T], name: String, deployment: DeploymentConfig): ActorRef[T] = - sys.spawn(behavior, name, deployment) + 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, EmptyDeploymentConfig) + spawnAnonymous(ctx, behavior, EmptyProps) - def spawnAnonymous[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], deployment: DeploymentConfig): ActorRef[T] = - ctx.spawnAnonymous(behavior, deployment) + 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, EmptyDeploymentConfig) + spawn(ctx, behavior, name, EmptyProps) - def spawn[T](ctx: akka.actor.ActorContext, behavior: Behavior[T], name: String, deployment: DeploymentConfig): ActorRef[T] = - ctx.spawn(behavior, name, deployment) + 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]] @@ -98,7 +98,7 @@ object Adapter { * takes an untyped [[akka.actor.Props]] parameter. Cluster Sharding is an * example of that. */ - def props[T](behavior: Creator[Behavior[T]], deploy: DeploymentConfig): akka.actor.Props = + def props[T](behavior: Creator[Behavior[T]], deploy: Props): akka.actor.Props = akka.typed.internal.adapter.PropsAdapter(() ⇒ behavior.create(), deploy) /** @@ -111,5 +111,5 @@ object Adapter { * example of that. */ def props[T](behavior: Creator[Behavior[T]]): akka.actor.Props = - props(behavior, EmptyDeploymentConfig) + props(behavior, EmptyProps) } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala index 90dc824178..1d959b6d37 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala @@ -11,8 +11,8 @@ import akka.annotation.DoNotInherit import akka.typed.ActorRef import akka.typed.ActorSystem import akka.typed.Behavior -import akka.typed.DeploymentConfig -import akka.typed.EmptyDeploymentConfig +import akka.typed.Props +import akka.typed.EmptyProps /** * An Actor is given by the combination of a [[Behavior]] and a context in @@ -68,12 +68,12 @@ trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T] ⇒ * 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] + 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, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[U] + 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 diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala index b500c207bd..2e0e07b5ec 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/PropsAdapter.scala @@ -4,8 +4,8 @@ package akka.typed.scaladsl.adapter import akka.typed.Behavior -import akka.typed.EmptyDeploymentConfig -import akka.typed.DeploymentConfig +import akka.typed.EmptyProps +import akka.typed.Props import akka.typed.internal.adapter.ActorAdapter /** @@ -18,6 +18,6 @@ import akka.typed.internal.adapter.ActorAdapter * example of that. */ object PropsAdapter { - def apply[T](behavior: ⇒ Behavior[T], deploy: DeploymentConfig = EmptyDeploymentConfig): akka.actor.Props = + def apply[T](behavior: ⇒ Behavior[T], deploy: Props = Props.empty): akka.actor.Props = akka.typed.internal.adapter.PropsAdapter(() ⇒ behavior, deploy) } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala index 6097195e47..73c698ff6b 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/adapter/package.scala @@ -35,10 +35,10 @@ package object adapter { * Extension methods added to [[akka.actor.ActorSystem]]. */ implicit class UntypedActorSystemOps(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)) + def spawnAnonymous[T](behavior: Behavior[T], props: Props = Props.empty): ActorRef[T] = + ActorRefAdapter(sys.actorOf(PropsAdapter(Behavior.validateAsInitial(behavior), props))) + def spawn[T](behavior: Behavior[T], name: String, props: Props = Props.empty): ActorRef[T] = + ActorRefAdapter(sys.actorOf(PropsAdapter(Behavior.validateAsInitial(behavior), props), name)) def toTyped: ActorSystem[Nothing] = ActorSystemAdapter(sys) } @@ -54,10 +54,10 @@ package object adapter { * Extension methods added to [[akka.actor.ActorContext]]. */ implicit class UntypedActorContextOps(val ctx: akka.actor.ActorContext) extends AnyVal { - def spawnAnonymous[T](behavior: Behavior[T], deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] = - ActorContextAdapter.spawnAnonymous(ctx, behavior, deployment) - def spawn[T](behavior: Behavior[T], name: String, deployment: DeploymentConfig = EmptyDeploymentConfig): ActorRef[T] = - ActorContextAdapter.spawn(ctx, behavior, name, deployment) + def spawnAnonymous[T](behavior: Behavior[T], props: Props = Props.empty): ActorRef[T] = + ActorContextAdapter.spawnAnonymous(ctx, behavior, props) + def spawn[T](behavior: Behavior[T], name: String, props: Props = Props.empty): ActorRef[T] = + ActorContextAdapter.spawn(ctx, behavior, name, props) def watch[U](other: ActorRef[U]): Unit = ctx.watch(ActorRefAdapter.toUntyped(other)) def unwatch[U](other: ActorRef[U]): Unit = ctx.unwatch(ActorRefAdapter.toUntyped(other)) From 202c2340da5b3785c46478c7ee77d7bed1230ef2 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Thu, 27 Apr 2017 15:47:10 +0200 Subject: [PATCH 28/50] rename createAdapter to spawnAdapter (again), #22804 --- .../typed/testkit/StubbedActorContext.scala | 6 +++- .../java/akka/typed/javadsl/ActorCompile.java | 29 +++++++++++++++++-- .../test/java/jdocs/akka/typed/IntroTest.java | 2 +- .../jdocs/akka/typed/MutableIntroTest.java | 2 +- .../main/scala/akka/typed/ActorContext.scala | 23 +++++++++++---- .../scala/akka/typed/internal/ActorCell.scala | 6 ++-- .../adapter/ActorContextAdapter.scala | 5 ++-- .../akka/typed/javadsl/ActorContext.scala | 4 +-- .../akka/typed/scaladsl/ActorContext.scala | 2 +- 9 files changed, 60 insertions(+), 19 deletions(-) diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala index 6d7d0d2314..5c82f2a8d9 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala @@ -7,6 +7,7 @@ import akka.util.Helpers import scala.collection.immutable.TreeMap import scala.concurrent.ExecutionContextExecutor import scala.concurrent.duration.FiniteDuration +import akka.annotation.InternalApi /** * An [[ActorContext]] for synchronous execution of a [[Behavior]] that @@ -65,7 +66,10 @@ class StubbedActorContext[T]( override def executionContext: ExecutionContextExecutor = system.executionContext - override def spawnAdapter[U](f: U ⇒ T, name: String = ""): ActorRef[U] = { + /** + * 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 diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index 6ed58916c6..3cc84886df 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -4,13 +4,15 @@ package akka.typed.javadsl; import akka.typed.*; +import akka.typed.ActorContext; + import static akka.typed.javadsl.Actor.*; public class ActorCompile { interface MyMsg {} - class MyMsgA implements MyMsg { + static class MyMsgA implements MyMsg { final ActorRef replyTo; public MyMsgA(ActorRef replyTo) { @@ -18,7 +20,7 @@ public class ActorCompile { } } - class MyMsgB implements MyMsg { + static class MyMsgB implements MyMsg { final String greeting; public MyMsgB(String greeting) { @@ -44,10 +46,33 @@ public class ActorCompile { return immutable((ctx2, msg2) -> { if (msg2 instanceof MyMsgB) { ((MyMsgA) msg).replyTo.tell(((MyMsgB) msg2).greeting); + + @SuppressWarnings("unused") + ActorRef adapter = ctx2.spawnAdapter(s -> new MyMsgB(s.toUpperCase())); } return same(); }); } else return unhandled(); }); } + + + static class MyBehavior extends ExtensibleBehavior { + + @Override + public Behavior receiveSignal(ActorContext ctx, Signal msg) throws Exception { + return this; + } + + @Override + public Behavior receiveMessage(ActorContext ctx, MyMsg msg) throws Exception { + // FIXME this doesn't work with Scala 2.12 "reference to spawnAdapter is ambiguous" + @SuppressWarnings("unused") + ActorRef adapter = ctx.spawnAdapter(s -> new MyMsgB(s.toUpperCase())); + return this; + } + + } + + } diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java index 705514d7f2..14508eced3 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -108,7 +108,7 @@ public class IntroTest { return Actor.immutable((ctx, msg) -> { if (msg instanceof GetSession) { GetSession getSession = (GetSession) msg; - ActorRef wrapper = ctx.createAdapter(p -> + ActorRef wrapper = ctx.spawnAdapter(p -> new PostSessionMessage(getSession.screenName, p.message)); getSession.replyTo.tell(new SessionGranted(wrapper)); // TODO mutable collection :( diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java index 148542e1b6..5bb74b553f 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java @@ -86,7 +86,7 @@ public class MutableIntroTest { public Behavior onMessage(Command msg) { if (msg instanceof GetSession) { GetSession getSession = (GetSession) msg; - ActorRef wrapper = ctx.createAdapter(p -> + ActorRef wrapper = ctx.spawnAdapter(p -> new PostSessionMessage(getSession.screenName, p.message)); getSession.replyTo.tell(new SessionGranted(wrapper)); sessions.add(getSession.replyTo); diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index b6346bee13..e87017483e 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -9,6 +9,7 @@ import java.util.ArrayList import akka.annotation.DoNotInherit import akka.annotation.ApiMayChange +import akka.annotation.InternalApi /** * This trait is not meant to be extended by user code. If you do so, you may @@ -18,7 +19,7 @@ import akka.annotation.ApiMayChange @ApiMayChange trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext[T] { - // FIXME can we simplify this weird hierarchy of contexts, e.g. problem with createAdapter + // FIXME can we simplify this weird hierarchy of contexts, e.g. problem with spawnAdapter override def getChild(name: String): Optional[ActorRef[Void]] = child(name) match { @@ -52,9 +53,21 @@ trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext override def spawnAnonymous[U](behavior: akka.typed.Behavior[U]): akka.typed.ActorRef[U] = spawnAnonymous(behavior, EmptyProps) - override def createAdapter[U](f: java.util.function.Function[U, T]): akka.typed.ActorRef[U] = - spawnAdapter(f.apply _) + override def spawnAdapter[U](f: U ⇒ T, name: String): ActorRef[U] = + internalSpawnAdapter(f, name) - override def createAdapter[U](f: java.util.function.Function[U, T], name: String): akka.typed.ActorRef[U] = - spawnAdapter(f.apply _, 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] } diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index a8a016881b..5f6e8ae137 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -6,14 +6,14 @@ 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 @@ -146,7 +146,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 diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala index 2a43a89119..fee1cb6177 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala @@ -15,7 +15,6 @@ import akka.annotation.InternalApi */ @InternalApi private[typed] class ActorContextAdapter[T](val untyped: a.ActorContext) extends ActorContext[T] { - import ActorRefAdapter.sendSystemMessage import ActorRefAdapter.toUntyped override def self = ActorRefAdapter(untyped.self) @@ -57,9 +56,9 @@ import akka.annotation.InternalApi import untyped.dispatcher untyped.system.scheduler.scheduleOnce(delay, toUntyped(target), msg) } - 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 cell = untyped.asInstanceOf[akka.actor.ActorCell] - val ref = cell.addFunctionRef((_, msg) ⇒ untyped.self ! f(msg.asInstanceOf[U]), name) + val ref = cell.addFunctionRef((_, msg) ⇒ untyped.self ! f(msg.asInstanceOf[U]), _name) ActorRefAdapter[U](ref) } diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala index b3816a3491..b9c2ab1c19 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala @@ -148,13 +148,13 @@ trait ActorContext[T] { * the given `name` argument does not need to be unique within the scope * of the parent actor. */ - def createAdapter[U](f: JFunction[U, T], name: String): ActorRef[U] + 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 createAdapter[U](f: JFunction[U, T]): ActorRef[U] + def spawnAdapter[U](f: JFunction[U, T]): ActorRef[U] } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala index 1d959b6d37..492e0d9271 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala @@ -143,6 +143,6 @@ trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T] ⇒ * 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, "") + def spawnAdapter[U](f: U ⇒ T): ActorRef[U] } From 163248198c52666766836b01b0399bd6181c11b1 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Thu, 27 Apr 2017 18:29:53 +0200 Subject: [PATCH 29/50] refactoring of ActorContext hierarchy, #22804 * by making akka.typed.ActorContext empty, only providing asScala and asJava the mixed scaladsl and javadsl is never exposed to user * thereby no ambigious spawnAdapter overload * the only time akka.typed.ActorContext is exposed is when implementing an ExtensibleBehavior, but that should be rare and it's good to explicity choose asJava or asScala in that case * no overhead since all of them are still the same instance --- .../typed/testkit/StubbedActorContext.scala | 3 +- .../java/akka/typed/javadsl/ActorCompile.java | 3 +- .../test/scala/akka/typed/BehaviorSpec.scala | 2 +- .../src/test/scala/akka/typed/StepWise.scala | 2 +- .../main/scala/akka/typed/ActorContext.scala | 65 +++-------------- .../src/main/scala/akka/typed/Behavior.scala | 8 +-- .../scala/akka/typed/internal/ActorCell.scala | 2 +- .../typed/internal/ActorContextImpl.scala | 71 +++++++++++++++++++ .../akka/typed/internal/BehaviorImpl.scala | 17 ++--- .../scala/akka/typed/internal/Restarter.scala | 18 ++--- .../adapter/ActorContextAdapter.scala | 11 +-- .../main/scala/akka/typed/javadsl/Actor.scala | 17 ++--- .../akka/typed/javadsl/ActorContext.scala | 5 ++ .../akka/typed/scaladsl/ActorContext.scala | 5 ++ 14 files changed, 136 insertions(+), 93 deletions(-) create mode 100644 akka-typed/src/main/scala/akka/typed/internal/ActorContextImpl.scala diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala index 5c82f2a8d9..102d5d1fa9 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala @@ -8,6 +8,7 @@ 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 @@ -19,7 +20,7 @@ import akka.annotation.InternalApi class StubbedActorContext[T]( val name: String, override val mailboxCapacity: Int, - override val system: ActorSystem[Nothing]) extends ActorContext[T] { + override val system: ActorSystem[Nothing]) extends ActorContextImpl[T] { val selfInbox = Inbox[T](name) override val self = selfInbox.ref diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index 3cc84886df..7bae597a9e 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -66,9 +66,8 @@ public class ActorCompile { @Override public Behavior receiveMessage(ActorContext ctx, MyMsg msg) throws Exception { - // FIXME this doesn't work with Scala 2.12 "reference to spawnAdapter is ambiguous" @SuppressWarnings("unused") - ActorRef adapter = ctx.spawnAdapter(s -> new MyMsgB(s.toUpperCase())); + ActorRef adapter = ctx.asJava().spawnAdapter(s -> new MyMsgB(s.toUpperCase())); return this; } diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala index a144bdb630..7349e05f90 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala @@ -18,7 +18,7 @@ class BehaviorSpec extends TypedSpec { def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Nil } case object GetSelf extends Command { - override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Self(ctx.self) :: Nil + override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Self(ctx.asScala.self) :: Nil } // Behavior under test must return Unhandled case object Miss extends Command { diff --git a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala index 93ca0cc267..ea2999ca78 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala @@ -110,7 +110,7 @@ object StepWise { def apply[T](f: (scaladsl.ActorContext[T], StartWith[T]) ⇒ Steps[T, _]): Behavior[T] = Deferred[Any] { ctx ⇒ - run(ctx, f(ctx.asInstanceOf[ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ()) + run(ctx, f(ctx.asInstanceOf[scaladsl.ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ()) }.narrow private def throwTimeout(trace: Trace, message: String): Nothing = diff --git a/akka-typed/src/main/scala/akka/typed/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/ActorContext.scala index e87017483e..5f69d92a70 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorContext.scala @@ -3,13 +3,8 @@ */ package akka.typed -import scala.concurrent.ExecutionContextExecutor -import java.util.Optional -import java.util.ArrayList - import akka.annotation.DoNotInherit import akka.annotation.ApiMayChange -import akka.annotation.InternalApi /** * This trait is not meant to be extended by user code. If you do so, you may @@ -17,57 +12,17 @@ import akka.annotation.InternalApi */ @DoNotInherit @ApiMayChange -trait ActorContext[T] extends javadsl.ActorContext[T] with scaladsl.ActorContext[T] { - - // FIXME can we simplify this weird hierarchy of contexts, e.g. problem with spawnAdapter - - 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, EmptyProps) - - override def spawnAnonymous[U](behavior: akka.typed.Behavior[U]): akka.typed.ActorRef[U] = - spawnAnonymous(behavior, EmptyProps) - - 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) +trait ActorContext[T] { + // this should be a pure interface, i.e. only abstract methods /** - * INTERNAL API: Needed to make Scala 2.12 compiler happy. - * Otherwise "ambiguous reference to overloaded definition" because Function is lambda. + * Get the `javadsl` of this `ActorContext`. */ - @InternalApi private[akka] def internalSpawnAdapter[U](f: U ⇒ T, _name: String): ActorRef[U] + def asJava: javadsl.ActorContext[T] + + /** + * Get the `scaladsl` of this `ActorContext`. + */ + def asScala: scaladsl.ActorContext[T] } + diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala index 8e8d8ba257..90d4eeaa12 100644 --- a/akka-typed/src/main/scala/akka/typed/Behavior.scala +++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala @@ -3,10 +3,10 @@ */ package akka.typed +import scala.annotation.tailrec import akka.util.LineNumbers import akka.annotation.{ DoNotInherit, InternalApi } - -import scala.annotation.tailrec +import akka.typed.scaladsl.{ ActorContext ⇒ SAC } /** * The behavior of an actor defines how it reacts to the messages that it @@ -149,11 +149,11 @@ object Behavior { * Not placed in internal.BehaviorImpl because Behavior is sealed. */ @InternalApi - private[akka] final case class DeferredBehavior[T](factory: ActorContext[T] ⇒ Behavior[T]) extends Behavior[T] { + 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) + def apply(ctx: ActorContext[T]): Behavior[T] = factory(ctx.asScala) override def toString: String = s"Deferred(${LineNumbers(factory)})" } diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index 5f6e8ae137..f3a6af6bd8 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -71,7 +71,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._ /* diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorContextImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorContextImpl.scala new file mode 100644 index 0000000000..ff03f7bd6a --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorContextImpl.scala @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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] +} + diff --git a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala index 073372c6cd..2a435e31d9 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala @@ -7,6 +7,7 @@ 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 /** @@ -43,19 +44,19 @@ import akka.typed.scaladsl.Actor } class ImmutableBehavior[T]( - val onMessage: (ActorContext[T], T) ⇒ Behavior[T], - onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]] = Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[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, msg), Behavior.unhandledSignal.asInstanceOf[PartialFunction[(ActorContext[T], Signal), Behavior[T]]]) - override def receiveMessage(ctx: AC[T], msg: T) = onMessage(ctx, msg) + 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)})" } final case class Tap[T]( - onMessage: Function2[ActorContext[T], T, _], - onSignal: Function2[ActorContext[T], Signal, _], + onMessage: Function2[SAC[T], T, _], + onSignal: Function2[SAC[T], Signal, _], behavior: Behavior[T]) extends ExtensibleBehavior[T] { private def canonical(behv: Behavior[T]): Behavior[T] = @@ -64,11 +65,11 @@ import akka.typed.scaladsl.Actor else if (isAlive(behv)) Tap(onMessage, onSignal, behv) else stopped override def receiveSignal(ctx: AC[T], signal: Signal): Behavior[T] = { - onSignal(ctx, signal) + onSignal(ctx.asScala, signal) canonical(Behavior.interpretSignal(behavior, ctx, signal)) } override def receiveMessage(ctx: AC[T], msg: T): Behavior[T] = { - onMessage(ctx, msg) + onMessage(ctx.asScala, msg) canonical(Behavior.interpretMessage(behavior, ctx, msg)) } override def toString = s"Tap(${LineNumbers(onSignal)},${LineNumbers(onMessage)},$behavior)" diff --git a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala index c0efe5df5f..0ac1288b21 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala @@ -76,7 +76,8 @@ import akka.typed.scaladsl.Actor 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.self.path.toString, behavior.getClass, "failure during PreRestart")) + 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) @@ -110,11 +111,11 @@ import akka.typed.scaladsl.Actor protected def log(ctx: ActorContext[T], ex: Thr): Unit = { if (loggingEnabled) - publish(ctx, Logging.Error(ex, ctx.self.toString, behavior.getClass, ex.getMessage)) + 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.system.eventStream.publish(e) catch { case NonFatal(_) ⇒ } + try ctx.asScala.system.eventStream.publish(e) catch { case NonFatal(_) ⇒ } } /** @@ -229,7 +230,7 @@ import akka.typed.scaladsl.Actor override def receiveSignal(ctx: ActorContext[Any], signal: Signal): Behavior[Any] = { if (blackhole) { - ctx.system.eventStream.publish(Dropped(signal, ctx.self)) + ctx.asScala.system.eventStream.publish(Dropped(signal, ctx.asScala.self)) Behavior.same } else super.receiveSignal(ctx, signal) @@ -241,7 +242,7 @@ import akka.typed.scaladsl.Actor case ScheduledRestart ⇒ // actual restart after scheduled backoff delay val restartedBehavior = Restarter.initialUndefer(ctx, initialBehavior) - ctx.schedule(strategy.resetBackoffAfter, ctx.self, ResetRestartCount(restartCount)) + 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) @@ -250,7 +251,7 @@ import akka.typed.scaladsl.Actor Behavior.same case _ ⇒ if (blackhole) { - ctx.system.eventStream.publish(Dropped(msg, ctx.self)) + ctx.asScala.system.eventStream.publish(Dropped(msg, ctx.asScala.self)) Behavior.same } else super.receiveMessage(ctx, msg) @@ -262,10 +263,11 @@ import akka.typed.scaladsl.Actor 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.self.path.toString, behavior.getClass, "failure during PreRestart")) + 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.schedule(restartDelay, ctx.self, ScheduledRestart) + ctx.asScala.schedule(restartDelay, ctx.asScala.self, ScheduledRestart) new BackoffRestarter[T, Thr](initialBehavior, startedBehavior, strategy, restartCount + 1, blackhole = true) } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala index fee1cb6177..a94b7081e8 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala @@ -13,7 +13,7 @@ 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 ActorContext[T] { +@InternalApi private[typed] class ActorContextAdapter[T](val untyped: a.ActorContext) extends ActorContextImpl[T] { import ActorRefAdapter.toUntyped @@ -68,7 +68,8 @@ import akka.annotation.InternalApi * INTERNAL API */ @InternalApi private[typed] object ActorContextAdapter { - def toUntyped[U](ctx: ActorContext[_]): a.ActorContext = + + private def toUntypedImp[U](ctx: ActorContext[_]): a.ActorContext = ctx match { case adapter: ActorContextAdapter[_] ⇒ adapter.untyped case _ ⇒ @@ -76,9 +77,11 @@ import akka.annotation.InternalApi 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[_] ⇒ toUntyped(c) + case c: ActorContext[_] ⇒ toUntypedImp(c) case _ ⇒ throw new UnsupportedOperationException("unknown ActorContext type " + s"($ctx of class ${ctx.getClass.getName})") @@ -86,7 +89,7 @@ import akka.annotation.InternalApi def toUntyped[U](ctx: javadsl.ActorContext[_]): a.ActorContext = ctx match { - case c: ActorContext[_] ⇒ toUntyped(c) + case c: ActorContext[_] ⇒ toUntypedImp(c) case _ ⇒ throw new UnsupportedOperationException("unknown ActorContext type " + s"($ctx of class ${ctx.getClass.getName})") diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala index c707d3e968..5b305e90d0 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -6,6 +6,7 @@ package akka.typed.javadsl import java.util.function.{ Function ⇒ JFunction } import akka.japi.function.{ Function2 ⇒ JapiFunction2 } import akka.japi.function.Procedure2 +import akka.typed.scaladsl.{ ActorContext ⇒ SAC } import akka.typed.Behavior import akka.typed.ExtensibleBehavior import akka.typed.Signal @@ -18,15 +19,15 @@ import akka.japi.pf.PFBuilder object Actor { - private val _unitFunction = (_: ActorContext[Any], _: Any) ⇒ () - private def unitFunction[T] = _unitFunction.asInstanceOf[((ActorContext[T], Signal) ⇒ Unit)] + private val _unitFunction = (_: SAC[Any], _: Any) ⇒ () + private def unitFunction[T] = _unitFunction.asInstanceOf[((SAC[T], Signal) ⇒ Unit)] /** * 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. */ def deferred[T](factory: akka.japi.function.Function[ActorContext[T], Behavior[T]]): Behavior[T] = - Behavior.DeferredBehavior(ctx ⇒ factory.apply(ctx)) + Behavior.DeferredBehavior(ctx ⇒ factory.apply(ctx.asJava)) /** * Factory for creating a [[MutableBehavior]] that typically holds mutable state as @@ -144,7 +145,7 @@ object Actor { * state. */ def immutable[T](onMessage: JapiFunction2[ActorContext[T], T, Behavior[T]]): Behavior[T] = - new BehaviorImpl.ImmutableBehavior((ctx, msg) ⇒ onMessage.apply(ctx, msg)) + new BehaviorImpl.ImmutableBehavior((ctx, msg) ⇒ onMessage.apply(ctx.asJava, msg)) /** * Construct an actor behavior that can react to both incoming messages and @@ -163,8 +164,8 @@ object Actor { onMessage: JapiFunction2[ActorContext[T], T, Behavior[T]], onSignal: JapiFunction2[ActorContext[T], Signal, Behavior[T]]): Behavior[T] = { new BehaviorImpl.ImmutableBehavior( - (ctx, msg) ⇒ onMessage.apply(ctx, msg), - { case (ctx, sig) ⇒ onSignal.apply(ctx, sig) }) + (ctx, msg) ⇒ onMessage.apply(ctx.asJava, msg), + { case (ctx, sig) ⇒ onSignal.apply(ctx.asJava, sig) }) } /** @@ -177,8 +178,8 @@ object Actor { onSignal: Procedure2[ActorContext[T], Signal], behavior: Behavior[T]): Behavior[T] = { BehaviorImpl.Tap( - (ctx, msg) ⇒ onMessage.apply(ctx, msg), - (ctx, sig) ⇒ onSignal.apply(ctx, sig), + (ctx, msg) ⇒ onMessage.apply(ctx.asJava, msg), + (ctx, sig) ⇒ onSignal.apply(ctx.asJava, sig), behavior) } diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala index b9c2ab1c19..514913658e 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala @@ -37,6 +37,11 @@ import scala.concurrent.ExecutionContextExecutor 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 diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala index 492e0d9271..9c6f97bd58 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala @@ -36,6 +36,11 @@ import akka.typed.EmptyProps @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 From 39ad32601a4dc4c4a45df9005f0d2f28fd3826b8 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 2 May 2017 16:42:33 +0200 Subject: [PATCH 30/50] rename Actor behavior factory methods to lower case, #22805 --- .../typed/testkit/TestEventListener.scala | 24 ++-- .../typed/testkit/scaladsl/TestProbe.scala | 6 +- .../akka/typed/javadsl/MonitoringTest.java | 4 - .../test/java/jdocs/akka/typed/IntroTest.java | 3 - .../scala/akka/typed/ActorContextSpec.scala | 112 ++++++++-------- .../src/test/scala/akka/typed/AskSpec.scala | 6 +- .../test/scala/akka/typed/BehaviorSpec.scala | 122 +++++++++--------- .../test/scala/akka/typed/DeferredSpec.scala | 14 +- .../scala/akka/typed/PerformanceSpec.scala | 10 +- .../test/scala/akka/typed/RestarterSpec.scala | 60 ++++----- .../src/test/scala/akka/typed/StepWise.scala | 12 +- .../src/test/scala/akka/typed/TypedSpec.scala | 12 +- .../akka/typed/internal/ActorCellSpec.scala | 57 ++++---- .../akka/typed/internal/ActorSystemSpec.scala | 25 ++-- .../akka/typed/internal/EventStreamSpec.scala | 12 +- .../typed/patterns/ReceptionistSpec.scala | 5 +- .../typed/scaladsl/adapter/AdapterSpec.scala | 40 +++--- .../scala/docs/akka/typed/IntroSpec.scala | 28 ++-- .../docs/akka/typed/MutableIntroSpec.scala | 24 ++-- .../src/main/scala/akka/typed/Behavior.scala | 18 +-- .../main/scala/akka/typed/EventStream.scala | 14 +- .../akka/typed/internal/ActorSystemImpl.scala | 9 +- .../akka/typed/internal/EventStreamImpl.scala | 18 +-- .../scala/akka/typed/internal/Restarter.scala | 2 +- .../main/scala/akka/typed/javadsl/Actor.scala | 2 +- .../akka/typed/patterns/Receptionist.scala | 6 +- .../scala/akka/typed/scaladsl/Actor.scala | 34 ++--- 27 files changed, 337 insertions(+), 342 deletions(-) diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala index 0fe9a27687..faebf639d9 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/TestEventListener.scala @@ -4,9 +4,10 @@ import akka.event.Logging.{ LogEvent, StdOutLogger } import akka.testkit.{ EventFilter, TestEvent ⇒ TE } import akka.typed.Logger import akka.typed.Logger.{ Command, Initialize } -import akka.typed.scaladsl.Actor._ import scala.annotation.tailrec +import akka.typed.scaladsl.Actor +import akka.typed.Behavior /** * EventListener for running tests, which allows selectively filtering out @@ -21,12 +22,11 @@ import scala.annotation.tailrec */ class TestEventListener extends Logger with StdOutLogger { - val initialBehavior = { - // TODO avoid depending on dsl here? - Deferred[Command] { _ ⇒ - Immutable[Command] { + override val initialBehavior: Behavior[Command] = { + Actor.deferred[Command] { _ ⇒ + Actor.immutable[Command] { case (ctx, Initialize(eventStream, replyTo)) ⇒ - val log = ctx.spawn(Deferred[AnyRef] { childCtx ⇒ + 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 }) @@ -42,17 +42,17 @@ class TestEventListener extends Logger with StdOutLogger { filters = removeFirst(filters) } - Immutable[AnyRef] { + Actor.immutable[AnyRef] { case (_, TE.Mute(filters)) ⇒ filters foreach addFilter - Same + Actor.same case (_, TE.UnMute(filters)) ⇒ filters foreach removeFilter - Same + Actor.same case (_, event: LogEvent) ⇒ if (!filter(event)) print(event) - Same - case _ ⇒ Unhandled + Actor.same + case _ ⇒ Actor.unhandled } }, "logger") @@ -61,7 +61,7 @@ class TestEventListener extends Logger with StdOutLogger { ctx.watch(log) // sign death pact replyTo ! log - Empty + Actor.empty } } } diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala index a1850a1c57..d6be133da0 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/scaladsl/TestProbe.scala @@ -6,7 +6,7 @@ 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.scaladsl.Actor import akka.typed.ActorSystem import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.atomic.AtomicInteger @@ -28,9 +28,9 @@ object 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] = Immutable { (ctx, msg) ⇒ + private def testActor[M](queue: BlockingDeque[M]): Behavior[M] = Actor.immutable { (ctx, msg) ⇒ queue.offerLast(msg) - Same + Actor.same } } diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java index eca93bd4a8..ec5a918b97 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java @@ -6,18 +6,14 @@ import java.util.concurrent.TimeUnit; import akka.Done; import org.scalatest.junit.JUnitSuite; -import scala.concurrent.Future; import scala.concurrent.duration.Duration; import akka.util.Timeout; -import org.junit.ClassRule; import org.junit.Test; import akka.typed.*; import static akka.typed.javadsl.Actor.*; import static akka.typed.javadsl.AskPattern.*; -import akka.testkit.AkkaSpec; - public class MonitoringTest extends JUnitSuite { static final class RunTest { diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java index 14508eced3..0f23f89ecb 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -6,10 +6,7 @@ package jdocs.akka.typed; //#imports import akka.typed.ActorRef; import akka.typed.Behavior; -import akka.typed.ExtensibleBehavior; -import akka.typed.Signal; import akka.typed.javadsl.Actor; -import akka.typed.javadsl.ActorContext; //#imports import java.util.ArrayList; import java.util.List; diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala index 3275fbcde3..b2e2fd74b2 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala @@ -73,18 +73,18 @@ object ActorContextSpec { final case class Adapter(a: ActorRef[Command]) extends Event def subject(monitor: ActorRef[Monitor]): Behavior[Command] = - Actor.Immutable[Command] { + Actor.immutable[Command] { (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) @@ -92,85 +92,85 @@ object ActorContextSpec { 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))) + case Some(n) ⇒ ctx.spawn(Actor.restarter[Throwable]().wrap(subject(mon)), 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(ref) replyTo ! Watched - Actor.Same + Actor.same case Unwatch(ref, replyTo) ⇒ 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.Immutable { + Actor.immutable { case (_, Ping(replyTo)) ⇒ replyTo ! Pong2 - Actor.Same + Actor.same case (_, Throw(ex)) ⇒ throw ex - case _ ⇒ Actor.Unhandled + case _ ⇒ Actor.unhandled } case BecomeCareless(replyTo) ⇒ replyTo ! BecameCareless - Actor.Immutable[Command] { - case (_, _) ⇒ Actor.Unhandled + Actor.immutable[Command] { + case (_, _) ⇒ Actor.unhandled } onSignal { - case (_, Terminated(_)) ⇒ Actor.Unhandled + case (_, Terminated(_)) ⇒ Actor.unhandled case (_, sig) ⇒ monitor ! GotSignal(sig) - Actor.Same + Actor.same } case GetAdapter(replyTo, name) ⇒ replyTo ! Adapter(ctx.spawnAdapter(identity, name)) - Actor.Same + Actor.same } } onSignal { - case (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.Same + case (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.same } def oldSubject(monitor: ActorRef[Monitor]): Behavior[Command] = { - Actor.Immutable[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) @@ -178,72 +178,72 @@ object ActorContextSpec { 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))) + case Some(n) ⇒ ctx.spawn(Actor.restarter[Throwable]().wrap(subject(mon)), 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(ref) replyTo ! Watched - Actor.Same + Actor.same case Unwatch(ref, replyTo) ⇒ 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.Immutable[Command] { + Actor.immutable[Command] { case (_, Ping(replyTo)) ⇒ replyTo ! Pong2 - Actor.Same + Actor.same case (_, Throw(ex)) ⇒ throw ex - case _ ⇒ Actor.Same + case _ ⇒ Actor.same } case BecomeCareless(replyTo) ⇒ replyTo ! BecameCareless - Actor.Immutable[Command] { - case _ ⇒ Actor.Unhandled + Actor.immutable[Command] { + case _ ⇒ Actor.unhandled } onSignal { - case (_, Terminated(_)) ⇒ Actor.Unhandled + case (_, Terminated(_)) ⇒ Actor.unhandled case (_, sig) ⇒ monitor ! GotSignal(sig) - Actor.Same + Actor.same } case GetAdapter(replyTo, name) ⇒ replyTo ! Adapter(ctx.spawnAdapter(identity, name)) - Actor.Same + Actor.same } } onSignal { case (_, signal) ⇒ monitor ! GotSignal(signal) - Actor.Same + Actor.same } } @@ -345,7 +345,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( } }) - def `01 must correctly wire the lifecycle hooks`(): Unit = sync(setup("ctx01", Some(Actor.Restarter[Throwable]())) { (ctx, startWith) ⇒ + 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 ⇒ @@ -415,7 +415,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 @@ -430,7 +430,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( def `06 must not reset behavior upon Resume`(): Unit = sync(setup( "ctx06", - Some(Actor.Restarter[Exception](SupervisorStrategy.resume))) { (ctx, startWith) ⇒ + Some(Actor.restarter[Exception](SupervisorStrategy.resume))) { (ctx, startWith) ⇒ val self = ctx.self val ex = new Exception("KABOOM06") startWith @@ -628,7 +628,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( trait Deferred extends Tests { override def suite = "deferred" override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] = - Actor.Deferred(_ ⇒ subject(ctx.self)) + Actor.deferred(_ ⇒ subject(ctx.self)) } object `An ActorContext with deferred Behavior (native)` extends Deferred with NativeSystem object `An ActorContext with deferred Behavior (adapted)` extends Deferred with AdaptedSystem @@ -636,7 +636,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( trait NestedDeferred extends Tests { override def suite = "deferred" override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] = - Actor.Deferred(_ ⇒ Actor.Deferred(_ ⇒ subject(ctx.self))) + Actor.deferred(_ ⇒ Actor.deferred(_ ⇒ subject(ctx.self))) } object `An ActorContext with nested deferred Behavior (native)` extends NestedDeferred with NativeSystem object `An ActorContext with nested deferred Behavior (adapted)` extends NestedDeferred with AdaptedSystem @@ -644,7 +644,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( trait Tap extends Tests { override def suite = "tap" override def behavior(ctx: scaladsl.ActorContext[Event]): Behavior[Command] = - Actor.Tap((_, _) ⇒ (), (_, _) ⇒ (), subject(ctx.self)) + Actor.tap((_, _) ⇒ (), (_, _) ⇒ (), subject(ctx.self)) } object `An ActorContext with Tap (old-native)` extends Tap with NativeSystem object `An ActorContext with Tap (old-adapted)` extends Tap with AdaptedSystem diff --git a/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala index 332fd0159f..9233495585 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/AskSpec.scala @@ -29,13 +29,13 @@ class AskSpec extends TypedSpec with ScalaFutures { implicit def executor: ExecutionContext = system.executionContext - val behavior: Behavior[Msg] = Immutable[Msg] { + val behavior: Behavior[Msg] = immutable[Msg] { case (_, foo: Foo) ⇒ foo.replyTo ! "foo" - Same + same case (_, Stop(r)) ⇒ r ! (()) - Stopped + stopped } def `must fail the future if the actor is already terminated`(): Unit = { diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala index 7349e05f90..c136736d6a 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala @@ -24,7 +24,7 @@ class BehaviorSpec extends TypedSpec { case object Miss extends Command { override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Missed :: Nil } - // Behavior under test must return Same + // Behavior under test must return same case object Ignore extends Command { override def expectedResponse(ctx: ActorContext[Command]): Seq[Event] = Ignored :: Nil } @@ -255,16 +255,16 @@ class BehaviorSpec extends TypedSpec { } private def mkFull(monitor: ActorRef[Event], state: State = StateA): Behavior[Command] = { - SActor.Immutable[Command] { + SActor.immutable[Command] { case (ctx, GetSelf) ⇒ monitor ! Self(ctx.self) - SActor.Same + SActor.same case (ctx, Miss) ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case (ctx, Ignore) ⇒ monitor ! Ignored - SActor.Same + SActor.same case (ctx, Ping) ⇒ monitor ! Pong mkFull(monitor, state) @@ -273,13 +273,13 @@ class BehaviorSpec extends TypedSpec { mkFull(monitor, state.next) case (ctx, GetState()) ⇒ monitor ! state - SActor.Same - case (ctx, Stop) ⇒ SActor.Stopped - case (_, _) ⇒ SActor.Unhandled + SActor.same + case (ctx, Stop) ⇒ SActor.stopped + case (_, _) ⇒ SActor.unhandled } onSignal { case (ctx, signal) ⇒ monitor ! GotSignal(signal) - SActor.Same + SActor.same } } @@ -292,16 +292,16 @@ class BehaviorSpec extends TypedSpec { 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] { + SActor.immutable[Command] { case (ctx, GetSelf) ⇒ monitor ! Self(ctx.self) - SActor.Same + SActor.same case (_, Miss) ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case (_, Ignore) ⇒ monitor ! Ignored - SActor.Same + SActor.same case (_, Ping) ⇒ monitor ! Pong behv(monitor, state) @@ -310,34 +310,34 @@ class BehaviorSpec extends TypedSpec { behv(monitor, state.next) case (_, GetState()) ⇒ monitor ! state - SActor.Same - case (_, Stop) ⇒ SActor.Stopped - case (_, _: AuxPing) ⇒ SActor.Unhandled + SActor.same + case (_, Stop) ⇒ SActor.stopped + case (_, _: AuxPing) ⇒ SActor.unhandled } onSignal { case (ctx, signal) ⇒ monitor ! GotSignal(signal) - SActor.Same + SActor.same } } } - object `A Immutable Behavior (native)` extends ImmutableBehavior with NativeSystem - object `A Immutable Behavior (adapted)` extends ImmutableBehavior with AdaptedSystem + 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] { + SActor.immutable[Command] { (ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.self) - SActor.Same + SActor.same case Miss ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case Ignore ⇒ monitor ! Ignored - SActor.Same + SActor.same case Ping ⇒ monitor ! Pong behv(monitor, state) @@ -346,14 +346,14 @@ class BehaviorSpec extends TypedSpec { behv(monitor, state.next) case GetState() ⇒ monitor ! state - SActor.Same - case Stop ⇒ SActor.Stopped - case _: AuxPing ⇒ SActor.Unhandled + SActor.same + case Stop ⇒ SActor.stopped + case _: AuxPing ⇒ SActor.unhandled } } onSignal { case (ctx, sig) ⇒ monitor ! GotSignal(sig) - SActor.Same + SActor.same } } object `A ImmutableWithSignal Behavior (scala,native)` extends ImmutableWithSignalScalaBehavior with NativeSystem @@ -362,17 +362,17 @@ class BehaviorSpec extends TypedSpec { 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) ⇒ + SActor.immutable[Command] { (ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.self) - SActor.Same + SActor.same case Miss ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case Ignore ⇒ monitor ! Ignored - SActor.Same + SActor.same case Ping ⇒ monitor ! Pong behv(monitor, state) @@ -381,19 +381,19 @@ class BehaviorSpec extends TypedSpec { behv(monitor, state.next) case GetState() ⇒ monitor ! state - SActor.Same - case Stop ⇒ SActor.Stopped - case _: AuxPing ⇒ SActor.Unhandled + 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 + 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 ⇒ + SActor.mutable[Command] { ctx ⇒ new SActor.MutableBehavior[Command] { private var state: State = StateA @@ -404,10 +404,10 @@ class BehaviorSpec extends TypedSpec { this case Miss ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case Ignore ⇒ monitor ! Ignored - SActor.Same // this or Same works the same way + SActor.same // this or same works the same way case Ping ⇒ monitor ! Pong this @@ -418,15 +418,15 @@ class BehaviorSpec extends TypedSpec { case GetState() ⇒ monitor ! state this - case Stop ⇒ SActor.Stopped - case _: AuxPing ⇒ SActor.Unhandled + 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 + 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 @@ -444,7 +444,7 @@ class BehaviorSpec extends TypedSpec { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { val inbox = Inbox[Done]("deferredListener") - (SActor.Deferred(ctx ⇒ { + (SActor.deferred(ctx ⇒ { inbox.ref ! Done super.behavior(monitor)._1 }), inbox) @@ -459,7 +459,7 @@ class BehaviorSpec extends TypedSpec { 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) + (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 @@ -467,7 +467,7 @@ class BehaviorSpec extends TypedSpec { trait RestarterScalaBehavior extends ImmutableWithSignalScalaBehavior with Reuse { override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = { - SActor.Restarter[Exception]().wrap(super.behavior(monitor)._1) → null + SActor.restarter[Exception]().wrap(super.behavior(monitor)._1) → null } } object `A restarter Behavior (scala,native)` extends RestarterScalaBehavior with NativeSystem @@ -512,13 +512,13 @@ class BehaviorSpec extends TypedSpec { fc((ctx, msg) ⇒ msg match { case GetSelf ⇒ monitor ! Self(ctx.getSelf) - SActor.Same + SActor.same case Miss ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case Ignore ⇒ monitor ! Ignored - SActor.Same + SActor.same case Ping ⇒ monitor ! Pong behv(monitor, state) @@ -527,13 +527,13 @@ class BehaviorSpec extends TypedSpec { behv(monitor, state.next) case GetState() ⇒ monitor ! state - SActor.Same - case Stop ⇒ SActor.Stopped - case _: AuxPing ⇒ SActor.Unhandled + SActor.same + case Stop ⇒ SActor.stopped + case _: AuxPing ⇒ SActor.unhandled }), fs((ctx, sig) ⇒ { monitor ! GotSignal(sig) - SActor.Same + SActor.same })) } object `A ImmutableWithSignal Behavior (java,native)` extends ImmutableWithSignalJavaBehavior with NativeSystem @@ -547,13 +547,13 @@ class BehaviorSpec extends TypedSpec { msg match { case GetSelf ⇒ monitor ! Self(ctx.getSelf) - SActor.Same + SActor.same case Miss ⇒ monitor ! Missed - SActor.Unhandled + SActor.unhandled case Ignore ⇒ monitor ! Ignored - SActor.Same + SActor.same case Ping ⇒ monitor ! Pong behv(monitor, state) @@ -562,14 +562,14 @@ class BehaviorSpec extends TypedSpec { behv(monitor, state.next) case GetState() ⇒ monitor ! state - SActor.Same - case Stop ⇒ SActor.Stopped - case _: AuxPing ⇒ SActor.Unhandled + 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 + 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) = { diff --git a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala index 9947cf2b28..9be2432718 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala @@ -7,7 +7,7 @@ import scala.concurrent.Await import scala.concurrent.duration._ import scala.util.control.NoStackTrace -import akka.typed.scaladsl.Actor._ +import akka.typed.scaladsl.Actor import akka.typed.scaladsl.AskPattern._ import akka.typed.testkit.EffectfulActorContext import akka.typed.testkit.TestKitSettings @@ -24,10 +24,10 @@ class DeferredSpec extends TypedSpec { case object Started extends Event def target(monitor: ActorRef[Event]): Behavior[Command] = - Immutable((ctx, cmd) ⇒ cmd match { + Actor.immutable((ctx, cmd) ⇒ cmd match { case Ping ⇒ monitor ! Pong - Same + Actor.same }) trait StubbedTests { @@ -38,7 +38,7 @@ class DeferredSpec extends TypedSpec { def `must create underlying deferred behavior immediately`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Deferred[Command] { _ ⇒ + val behv = Actor.deferred[Command] { _ ⇒ inbox.ref ! Started target(inbox.ref) } @@ -50,7 +50,7 @@ class DeferredSpec extends TypedSpec { 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 = Deferred[Command] { _ ⇒ + val behv = Actor.deferred[Command] { _ ⇒ inbox.ref ! Started throw exc } @@ -74,7 +74,7 @@ class DeferredSpec extends TypedSpec { def `must create underlying`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Deferred[Command] { _ ⇒ + val behv = Actor.deferred[Command] { _ ⇒ probe.ref ! Started target(probe.ref) } @@ -86,7 +86,7 @@ class DeferredSpec extends TypedSpec { def `must stop when exception from factory`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Deferred[Command] { _ ⇒ + val behv = Actor.deferred[Command] { _ ⇒ probe.ref ! Started throw new RuntimeException("simulated exc from factory") with NoStackTrace } diff --git a/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala index 11ad566be0..cafef74c55 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/PerformanceSpec.scala @@ -27,19 +27,19 @@ class PerformanceSpec extends TypedSpec( StepWise[Pong] { (ctx, startWith) ⇒ startWith { - val pinger = Immutable[Ping] { (ctx, msg) ⇒ + val pinger = immutable[Ping] { (ctx, msg) ⇒ if (msg.x == 0) { msg.report ! Pong(0, ctx.self, msg.report) - Same + same } else { msg.pong ! Pong(msg.x - 1, ctx.self, msg.report) - Same + same } } // FIXME .withDispatcher(executor) - val ponger = Immutable[Pong] { (ctx, msg) ⇒ + val ponger = immutable[Pong] { (ctx, msg) ⇒ msg.ping ! Ping(msg.x, ctx.self, msg.report) - Same + same } // FIXME .withDispatcher(executor) val actors = diff --git a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala index edc4085ec7..d2e48efa78 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala @@ -33,27 +33,27 @@ class RestarterSpec extends TypedSpec { 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) ⇒ + immutable[Command] { (ctx, cmd) ⇒ cmd match { case Ping ⇒ monitor ! Pong - Same + 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 + same case CreateChild(childBehv, childName) ⇒ ctx.spawn(childBehv, childName) - Same + same case Throw(e) ⇒ throw e } }.onSignal { case (ctx, sig) ⇒ monitor ! GotSignal(sig) - Same + same } class FailingConstructor(monitor: ActorRef[Event]) extends MutableBehavior[Command] { @@ -62,7 +62,7 @@ class RestarterSpec extends TypedSpec { override def onMessage(msg: Command): Behavior[Command] = { monitor ! Pong - Same + same } } @@ -74,7 +74,7 @@ class RestarterSpec extends TypedSpec { def `must receive message`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Restarter[Throwable]().wrap(target(inbox.ref)) + val behv = restarter[Throwable]().wrap(target(inbox.ref)) val ctx = mkCtx(behv) ctx.run(Ping) inbox.receiveMsg() should ===(Pong) @@ -92,7 +92,7 @@ class RestarterSpec extends TypedSpec { def `must stop when unhandled exception`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Restarter[Exc1]().wrap(target(inbox.ref)) + val behv = restarter[Exc1]().wrap(target(inbox.ref)) val ctx = mkCtx(behv) intercept[Exc3] { ctx.run(Throw(new Exc3)) @@ -102,7 +102,7 @@ class RestarterSpec extends TypedSpec { def `must restart when handled exception`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Restarter[Exc1]().wrap(target(inbox.ref)) + val behv = restarter[Exc1]().wrap(target(inbox.ref)) val ctx = mkCtx(behv) ctx.run(NextState) ctx.run(GetState) @@ -116,7 +116,7 @@ class RestarterSpec extends TypedSpec { def `must resume when handled exception`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Restarter[Exc1](SupervisorStrategy.resume).wrap(target(inbox.ref)) + val behv = restarter[Exc1](SupervisorStrategy.resume).wrap(target(inbox.ref)) val ctx = mkCtx(behv) ctx.run(NextState) ctx.run(GetState) @@ -129,7 +129,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exc3]().wrap(restarter[Exc2](SupervisorStrategy.resume).wrap(target(inbox.ref))) val ctx = mkCtx(behv) ctx.run(NextState) ctx.run(GetState) @@ -155,7 +155,7 @@ class RestarterSpec extends TypedSpec { def `must not catch fatal error`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Restarter[Throwable]().wrap(target(inbox.ref)) + val behv = restarter[Throwable]().wrap(target(inbox.ref)) val ctx = mkCtx(behv) intercept[StackOverflowError] { ctx.run(Throw(new StackOverflowError)) @@ -166,7 +166,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exc1](strategy).wrap(target(inbox.ref)) val ctx = mkCtx(behv) ctx.run(Throw(new Exc1)) inbox.receiveMsg() should ===(GotSignal(PreRestart)) @@ -182,7 +182,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exc1](strategy).wrap(target(inbox.ref)) val ctx = mkCtx(behv) ctx.run(Throw(new Exc1)) inbox.receiveMsg() should ===(GotSignal(PreRestart)) @@ -203,7 +203,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exc1](strategy).wrap(target(inbox.ref)) val ctx = mkCtx(behv) intercept[Exc1] { ctx.run(Throw(new Exc1)) @@ -213,7 +213,7 @@ class RestarterSpec extends TypedSpec { def `must create underlying deferred behavior immediately`(): Unit = { val inbox = Inbox[Event]("evt") - val behv = Restarter[Exception]().wrap(Deferred[Command] { _ ⇒ + val behv = restarter[Exception]().wrap(deferred[Command] { _ ⇒ inbox.ref ! Started target(inbox.ref) }) @@ -236,7 +236,7 @@ class RestarterSpec extends TypedSpec { def `must receive message`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Restarter[Throwable]().wrap(target(probe.ref)) + val behv = restarter[Throwable]().wrap(target(probe.ref)) val ref = start(behv) ref ! Ping probe.expectMsg(Pong) @@ -253,7 +253,7 @@ class RestarterSpec extends TypedSpec { def `must stop when unhandled exception`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Restarter[Exc1]().wrap(target(probe.ref)) + val behv = restarter[Exc1]().wrap(target(probe.ref)) val ref = start(behv) ref ! Throw(new Exc3) probe.expectMsg(GotSignal(PostStop)) @@ -261,7 +261,7 @@ class RestarterSpec extends TypedSpec { def `must restart when handled exception`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Restarter[Exc1]().wrap(target(probe.ref)) + val behv = restarter[Exc1]().wrap(target(probe.ref)) val ref = start(behv) ref ! NextState ref ! GetState @@ -275,7 +275,7 @@ class RestarterSpec extends TypedSpec { def `must NOT stop children when restarting`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Restarter[Exc1]().wrap(target(probe.ref)) + val behv = restarter[Exc1]().wrap(target(probe.ref)) val ref = start(behv) val childProbe = TestProbe[Event]("childEvt") @@ -294,7 +294,7 @@ class RestarterSpec extends TypedSpec { def `must resume when handled exception`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Restarter[Exc1](SupervisorStrategy.resume).wrap(target(probe.ref)) + val behv = restarter[Exc1](SupervisorStrategy.resume).wrap(target(probe.ref)) val ref = start(behv) ref ! NextState ref ! GetState @@ -307,7 +307,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exc3]().wrap(restarter[Exc2](SupervisorStrategy.resume).wrap(target(probe.ref))) val ref = start(behv) ref ! NextState ref ! GetState @@ -335,7 +335,7 @@ class RestarterSpec extends TypedSpec { val minBackoff = 1.seconds val strategy = SupervisorStrategy.restartWithBackoff(minBackoff, 10.seconds, 0.0) .withResetBackoffAfter(10.seconds) - val behv = Restarter[Exc1](strategy).wrap(Deferred[Command] { _ ⇒ + val behv = restarter[Exc1](strategy).wrap(deferred[Command] { _ ⇒ startedProbe.ref ! Started target(probe.ref) }) @@ -371,7 +371,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exc1](strategy).wrap(target(probe.ref)) val ref = start(behv) ref ! NextState @@ -398,7 +398,7 @@ class RestarterSpec extends TypedSpec { def `must create underlying deferred behavior immediately`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Restarter[Exception]().wrap(Deferred[Command] { _ ⇒ + val behv = restarter[Exception]().wrap(deferred[Command] { _ ⇒ probe.ref ! Started target(probe.ref) }) @@ -410,7 +410,7 @@ class RestarterSpec extends TypedSpec { 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 behv = restarter[Exception]().wrap(mutable[Command](_ ⇒ new FailingConstructor(probe.ref))) val ref = start(behv) probe.expectMsg(Started) ref ! Ping @@ -419,10 +419,10 @@ class RestarterSpec extends TypedSpec { } - object `A Restarter (stubbed, native)` extends StubbedTests with NativeSystem - object `A Restarter (stubbed, adapted)` extends StubbedTests with AdaptedSystem + 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 + object `A restarter (real, native)` extends RealTests with NativeSystem + object `A restarter (real, adapted)` extends RealTests with AdaptedSystem } diff --git a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala index ea2999ca78..30321069eb 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala @@ -109,7 +109,7 @@ object StepWise { } def apply[T](f: (scaladsl.ActorContext[T], StartWith[T]) ⇒ Steps[T, _]): Behavior[T] = - Deferred[Any] { ctx ⇒ + deferred[Any] { ctx ⇒ run(ctx, f(ctx.asInstanceOf[scaladsl.ActorContext[T]], new StartWith(keepTraces = false)).ops.reverse, ()) }.narrow @@ -135,7 +135,7 @@ object StepWise { case ThunkV(f) :: tail ⇒ run(ctx, tail, f(value)) case Message(t, f, trace) :: tail ⇒ ctx.setReceiveTimeout(t, ReceiveTimeout) - Immutable[Any] { + immutable[Any] { case (_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for a message") case (_, msg) ⇒ ctx.cancelReceiveTimeout() @@ -147,7 +147,7 @@ object StepWise { val deadline = Deadline.now + t def behavior(count: Int, acc: List[Any]): Behavior[Any] = { ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout) - Immutable[Any] { + immutable[Any] { case (_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)") case (_, msg) ⇒ @@ -165,7 +165,7 @@ object StepWise { val deadline = Deadline.now + t def behavior(count: Int, acc: List[Either[Signal, Any]]): Behavior[Any] = { ctx.setReceiveTimeout(deadline.timeLeft, ReceiveTimeout) - Immutable[Any] { + immutable[Any] { case (_, ReceiveTimeout) ⇒ throwTimeout(trace, s"timeout of $t expired while waiting for $c messages (got only $count)") case (_, msg) ⇒ @@ -186,7 +186,7 @@ object StepWise { behavior(0, Nil) case Termination(t, f, trace) :: tail ⇒ ctx.setReceiveTimeout(t, ReceiveTimeout) - Immutable[Any] { + 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 { @@ -195,7 +195,7 @@ object StepWise { run(ctx, tail, f(t, value)) case other ⇒ throwIllegalState(trace, s"unexpected $other while waiting for termination") } - case Nil ⇒ Stopped + case Nil ⇒ stopped } } diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index 18d6960070..4fe0d32bf3 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -83,7 +83,7 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup { import akka.testkit._ def await[T](f: Future[T]): T = Await.result(f, timeout.duration * 1.1) - lazy val blackhole = await(nativeSystem ? Create(Immutable[Any] { case _ ⇒ Same }, "blackhole")) + lazy val blackhole = await(nativeSystem ? Create(immutable[Any] { case _ ⇒ same }, "blackhole")) /** * Run an Actor-based test. The test procedure is most conveniently @@ -161,7 +161,7 @@ object TypedSpec { case object Timedout extends Status def guardian(outstanding: Map[ActorRef[_], ActorRef[Status]] = Map.empty): Behavior[Command] = - Immutable[Command] { + immutable[Command] { case (ctx, r: RunTest[t]) ⇒ val test = ctx.spawn(r.behavior, r.name) ctx.schedule(r.timeout, r.replyTo, Timedout) @@ -169,10 +169,10 @@ object TypedSpec { guardian(outstanding + ((test, r.replyTo))) case (_, Terminate(reply)) ⇒ reply ! Success - Stopped + stopped case (ctx, c: Create[t]) ⇒ c.replyTo ! ctx.spawn(c.behavior, c.name) - Same + same } onSignal { case (ctx, t @ Terminated(test)) ⇒ outstanding get test match { @@ -180,9 +180,9 @@ object TypedSpec { if (t.failure eq null) reply ! Success else reply ! Failed(t.failure) guardian(outstanding - test) - case None ⇒ Same + case None ⇒ same } - case _ ⇒ Same + case _ ⇒ same } def getCallerName(clazz: Class[_]): String = { diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala index 8edfc8637c..7cf49d7fce 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala @@ -4,12 +4,13 @@ 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 { @@ -21,12 +22,12 @@ 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](_ ⇒ { + val cell = new ActorCell(sys, deferred[String](_ ⇒ { parent ! "created" - Immutable[String] { + immutable[String] { case (_, s) ⇒ parent ! s - Same + same } }), ec, 1000, parent) debugCell(cell) { @@ -46,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) @@ -66,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) @@ -86,7 +87,7 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala 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[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) @@ -107,7 +108,7 @@ 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 behavior = Deferred[String](_ ⇒ { parent ! "created"; Immutable[String] { case (s, _) ⇒ throw ex } }) + 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) { @@ -131,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[String](_ ⇒ { 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) @@ -157,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[String](_ ⇒ { 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) @@ -188,12 +189,12 @@ 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, Deferred[String] { ctx ⇒ - Immutable[String] { + val cell = new ActorCell(sys, deferred[String] { ctx ⇒ + immutable[String] { case (_, s) ⇒ ctx.self ! s parent ! s - Same + same } }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) @@ -228,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) { @@ -252,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, Deferred[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) { @@ -284,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) { @@ -303,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) { @@ -323,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) { @@ -340,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) { @@ -361,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, Deferred[String] { ctx ⇒ - ctx.spawn(Deferred[String] { ctx ⇒ parent ! ctx.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) @@ -394,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, Deferred[String] { ctx ⇒ - ctx.watch(ctx.spawn(Deferred[String] { ctx ⇒ parent ! ctx.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) @@ -430,11 +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, Immutable[String] { - case _ ⇒ Unhandled + val cell = new ActorCell(sys, immutable[String] { + case _ ⇒ unhandled } onSignal { case (_, PostStop) ⇒ ??? - case _ ⇒ Unhandled + case _ ⇒ unhandled }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) cell.setSelf(ref) diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala index 1caa4c8289..4f2c0120b9 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala @@ -5,6 +5,7 @@ package akka.typed package internal import akka.Done +import akka.typed.scaladsl.Actor import akka.typed.scaladsl.Actor._ import akka.util.Timeout import org.junit.runner.RunWith @@ -40,7 +41,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", Immutable[Probe] { case (_, 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) } @@ -53,12 +54,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", Immutable[Probe] { - case (_, _) ⇒ Unhandled + 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) @@ -67,19 +68,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) @@ -88,7 +89,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) } @@ -101,24 +102,24 @@ 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(Immutable[Doner] { + val ref1, ref2 = sys.systemActorOf(immutable[Doner] { case (_, doner) ⇒ doner.ref ! Done - Same + same }, "empty").futureValue (ref1 ? Doner).futureValue should ===(Done) (ref2 ? Doner).futureValue should ===(Done) diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala index 17fe087813..9f40b3e7a1 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala @@ -19,15 +19,15 @@ object EventStreamSpec { class MyLogger extends Logger { def initialBehavior: Behavior[Logger.Command] = - Immutable { + immutable { case (ctx, Logger.Initialize(es, replyTo)) ⇒ - val logger = ctx.spawn(Immutable[LogEvent] { (_, ev: LogEvent) ⇒ + val logger = ctx.spawn(immutable[LogEvent] { (_, ev: LogEvent) ⇒ logged :+= ev - Same + same }, "logger") ctx.watch(logger) replyTo ! logger - Empty + empty } } @@ -265,7 +265,7 @@ class EventStreamSpec extends TypedSpec(EventStreamSpec.config) with Eventually } def `must unsubscribe an actor upon termination`(): Unit = { - val ref = nativeSystem ? TypedSpec.Create(Immutable[Done] { case _ ⇒ 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 @@ -273,7 +273,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)) diff --git a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala index 24b88a741c..ad80d1c603 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala @@ -8,6 +8,7 @@ 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 } @@ -15,11 +16,11 @@ class ReceptionistSpec extends TypedSpec { trait ServiceA case object ServiceKeyA extends ServiceKey[ServiceA] - val behaviorA = Empty[ServiceA] + val behaviorA = Actor.empty[ServiceA] trait ServiceB case object ServiceKeyB extends ServiceKey[ServiceB] - val behaviorB = Empty[ServiceB] + val behaviorB = Actor.empty[ServiceB] trait CommonTests { implicit def system: ActorSystem[TypedSpec.Command] diff --git a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala index bdfc09f891..320a4bca52 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala @@ -9,7 +9,7 @@ import akka.{ actor ⇒ untyped } import akka.typed.ActorRef import akka.typed.Behavior import akka.typed.Terminated -import akka.typed.scaladsl.Actor._ +import akka.typed.scaladsl.Actor import akka.{ actor ⇒ untyped } import akka.annotation.ApiMayChange import akka.annotation.DoNotInherit @@ -27,41 +27,41 @@ object AdapterSpec { } def typed1(ref: untyped.ActorRef, probe: ActorRef[String]): Behavior[String] = - Immutable[String] { + Actor.immutable[String] { (ctx, msg) ⇒ msg match { case "send" ⇒ val replyTo = ctx.self.toUntyped ref.tell("ping", replyTo) - Same + Actor.same case "pong" ⇒ probe ! "ok" - Same + Actor.same case "actorOf" ⇒ val child = ctx.actorOf(untyped1) child.tell("ping", ctx.self.toUntyped) - Same + Actor.same case "watch" ⇒ ctx.watch(ref) - Same + Actor.same case "supervise-stop" ⇒ val child = ctx.actorOf(untyped1) ctx.watch(child) child ! ThrowIt3 child.tell("ping", ctx.self.toUntyped) - Same + Actor.same case "stop-child" ⇒ val child = ctx.actorOf(untyped1) ctx.watch(child) ctx.stop(child) - Same + Actor.same } } onSignal { case (ctx, sig) ⇒ sig match { case Terminated(ref) ⇒ probe ! "terminated" - Same - case _ ⇒ Unhandled + Actor.same + case _ ⇒ Actor.unhandled } } @@ -128,13 +128,13 @@ object AdapterSpec { } def typed2: Behavior[Typed2Msg] = - Immutable { (ctx, msg) ⇒ + Actor.immutable { (ctx, msg) ⇒ msg match { case Ping(replyTo) ⇒ replyTo ! "pong" - Same + Actor.same case StopIt ⇒ - Stopped + Actor.stopped case t: ThrowIt ⇒ throw t } @@ -167,16 +167,16 @@ class AdapterSpec extends AkkaSpec { "spawn typed child from untyped parent" in { val probe = TestProbe() - val ignore = system.spawnAnonymous(Ignore[Ping]) - val untypedRef = system.actorOf(untyped2(ignore, probe.ref)) + 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 ignore = system.spawnAnonymous(Ignore[Ping]) - val untypedRef = system.actorOf(untyped2(ignore, probe.ref)) + val ign = system.spawnAnonymous(Actor.ignore[Ping]) + val untypedRef = system.actorOf(untyped2(ign, probe.ref)) untypedRef ! "actorOf-props" probe.expectMsg("ok") } @@ -209,8 +209,8 @@ class AdapterSpec extends AkkaSpec { "supervise typed child from untyped parent" in { val probe = TestProbe() - val ignore = system.spawnAnonymous(Ignore[Ping]) - val untypedRef = system.actorOf(untyped2(ignore, probe.ref)) + val ign = system.spawnAnonymous(Actor.ignore[Ping]) + val untypedRef = system.actorOf(untyped2(ign, probe.ref)) untypedRef ! "supervise-stop" probe.expectMsg("thrown-stop") @@ -239,7 +239,7 @@ class AdapterSpec extends AkkaSpec { "stop typed child from untyped parent" in { val probe = TestProbe() - val ignore = system.spawnAnonymous(Ignore[Ping]) + val ignore = system.spawnAnonymous(Actor.ignore[Ping]) val untypedRef = system.actorOf(untyped2(ignore, probe.ref)) untypedRef ! "stop-child" probe.expectMsg("terminated") diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index fe091ebbbd..967122e4dc 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -5,7 +5,7 @@ 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._ @@ -19,10 +19,10 @@ object IntroSpec { final case class Greet(whom: String, replyTo: ActorRef[Greeted]) final case class Greeted(whom: String) - val greeter = Immutable[Greet] { (_, msg) ⇒ + val greeter = Actor.immutable[Greet] { (_, msg) ⇒ println(s"Hello ${msg.whom}!") msg.replyTo ! Greeted(msg.whom) - Same + Actor.same } } //#hello-world-actor @@ -53,7 +53,7 @@ object IntroSpec { chatRoom(List.empty) private def chatRoom(sessions: List[ActorRef[SessionEvent]]): Behavior[Command] = - Immutable[Command] { (ctx, msg) ⇒ + Actor.immutable[Command] { (ctx, msg) ⇒ msg match { case GetSession(screenName, client) ⇒ val wrapper = ctx.spawnAdapter { @@ -64,7 +64,7 @@ object IntroSpec { case PostSessionMessage(screenName, message) ⇒ val mp = MessagePosted(screenName, message) sessions foreach (_ ! mp) - Same + Actor.same } } //#chatroom-behavior @@ -99,41 +99,41 @@ class IntroSpec extends TypedSpec { import ChatRoom._ val gabbler = - Immutable[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] = - Deferred { ctx ⇒ + Actor.deferred { ctx ⇒ val chatRoom = ctx.spawn(ChatRoom.behavior, "chatroom") val gabblerRef = ctx.spawn(gabbler, "gabbler") ctx.watch(gabblerRef) chatRoom ! GetSession("ol’ Gabbler", gabblerRef) - Immutable[akka.NotUsed] { - (_, _) ⇒ Unhandled + Actor.immutable[akka.NotUsed] { + (_, _) ⇒ Actor.unhandled } onSignal { case (ctx, sig) ⇒ sig match { case Terminated(ref) ⇒ - Stopped + Actor.stopped case _ ⇒ - Unhandled + Actor.unhandled } } } diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala index 515a07de7f..eee2bd57ee 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala @@ -5,7 +5,7 @@ package docs.akka.typed //#imports import akka.typed._ -import akka.typed.scaladsl.Actor._ +import akka.typed.scaladsl.Actor import akka.typed.scaladsl.ActorContext import akka.typed.scaladsl.AskPattern._ import scala.concurrent.Future @@ -38,9 +38,9 @@ object MutableIntroSpec { //#chatroom-behavior def behavior(): Behavior[Command] = - Mutable[Command](ctx ⇒ new ChatRoomBehavior(ctx)) + Actor.mutable[Command](ctx ⇒ new ChatRoomBehavior(ctx)) - class ChatRoomBehavior(ctx: ActorContext[Command]) extends MutableBehavior[Command] { + class ChatRoomBehavior(ctx: ActorContext[Command]) extends Actor.MutableBehavior[Command] { private var sessions: List[ActorRef[SessionEvent]] = List.empty override def onMessage(msg: Command): Behavior[Command] = { @@ -74,38 +74,38 @@ class MutableIntroSpec extends TypedSpec { import ChatRoom._ val gabbler = - Immutable[SessionEvent] { (_, msg) ⇒ + Actor.immutable[SessionEvent] { (_, msg) ⇒ msg match { case SessionDenied(reason) ⇒ println(s"cannot start chat room session: $reason") - Stopped + Actor.stopped 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] = - Deferred { ctx ⇒ + Actor.deferred { ctx ⇒ val chatRoom = ctx.spawn(ChatRoom.behavior(), "chatroom") val gabblerRef = ctx.spawn(gabbler, "gabbler") ctx.watch(gabblerRef) chatRoom ! GetSession("ol’ Gabbler", gabblerRef) - Immutable[akka.NotUsed] { - (_, _) ⇒ Unhandled + Actor.immutable[akka.NotUsed] { + (_, _) ⇒ Actor.unhandled } onSignal { case (ctx, sig) ⇒ sig match { case Terminated(ref) ⇒ - Stopped + Actor.stopped case _ ⇒ - Unhandled + Actor.unhandled } } } diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala index 90d4eeaa12..10d4bdd6e1 100644 --- a/akka-typed/src/main/scala/akka/typed/Behavior.scala +++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala @@ -50,9 +50,9 @@ abstract class ExtensibleBehavior[T] extends 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. @@ -66,9 +66,9 @@ abstract class ExtensibleBehavior[T] extends 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. @@ -174,7 +174,7 @@ object Behavior { /** * 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. */ @@ -197,7 +197,7 @@ object 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] = @@ -213,7 +213,7 @@ object Behavior { def isAlive[T](behavior: Behavior[T]): Boolean = behavior ne StoppedBehavior /** - * Returns true if the given behavior is the special `Unhandled` marker. + * Returns true if the given behavior is the special `unhandled` marker. */ def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq UnhandledBehavior diff --git a/akka-typed/src/main/scala/akka/typed/EventStream.scala b/akka-typed/src/main/scala/akka/typed/EventStream.scala index 0fa37d6a10..5a1f816399 100644 --- a/akka-typed/src/main/scala/akka/typed/EventStream.scala +++ b/akka-typed/src/main/scala/akka/typed/EventStream.scala @@ -69,23 +69,23 @@ class DefaultLogger extends Logger with StdOutLogger { val initialBehavior = { // TODO avoid depending on dsl here? import scaladsl.Actor._ - Deferred[Command] { _ ⇒ - Immutable[Command] { + deferred[Command] { _ ⇒ + immutable[Command] { case (ctx, Initialize(eventStream, replyTo)) ⇒ - val log = ctx.spawn(Deferred[AnyRef] { childCtx ⇒ + val log = ctx.spawn(deferred[AnyRef] { childCtx ⇒ - Immutable[AnyRef] { + immutable[AnyRef] { case (_, event: LogEvent) ⇒ print(event) - Same - case _ ⇒ Unhandled + same + case _ ⇒ unhandled } }, "logger") ctx.watch(log) // sign death pact replyTo ! log - Empty + empty } } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index 724c370251..1ad50a8ed9 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -29,16 +29,15 @@ object ActorSystemImpl { case class CreateSystemActor[T](behavior: Behavior[T], name: String, props: Props)(val replyTo: ActorRef[ActorRef[T]]) extends SystemCommand val systemGuardianBehavior: Behavior[SystemCommand] = { - // TODO avoid depending on dsl here? - import scaladsl.Actor._ - Deferred { _ ⇒ + import scaladsl.Actor + Actor.deferred { _ ⇒ var i = 1 - Immutable { + Actor.immutable { case (ctx, create: CreateSystemActor[t]) ⇒ val name = s"$i-${create.name}" i += 1 create.replyTo ! ctx.spawn(create.behavior, name, create.props) - Same + Actor.same } } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala index 2c4c1023b5..db46fa559a 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala @@ -42,28 +42,28 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat private val unsubscriberBehavior = { // TODO avoid depending on dsl here? import scaladsl.Actor - Actor.Deferred[Command] { _ ⇒ + Actor.deferred[Command] { _ ⇒ if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"registering unsubscriber with $this")) - Actor.Immutable[Command] { (ctx, msg) ⇒ + 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 + Actor.same - case UnregisterIfNoMoreSubscribedChannels(actor) if hasSubscriptions(actor) ⇒ Actor.Same + 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 UnregisterIfNoMoreSubscribedChannels(actor) ⇒ if (debug) publish(e.Logging.Debug(simpleName(getClass), getClass, s"unwatching $actor, since has no subscriptions")) ctx.unwatch(actor) - Actor.Same + 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 + Actor.same } } } @@ -149,11 +149,11 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat private val UnhandledMessageForwarder = { // TODO avoid depending on dsl here? - import scaladsl.Actor.{ Same, Immutable } - Immutable[a.UnhandledMessage] { + 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 + same } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala index 0ac1288b21..14ead33c7f 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala @@ -32,7 +32,7 @@ import akka.typed.scaladsl.Actor */ @InternalApi private[akka] object Restarter { def apply[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T], strategy: SupervisorStrategy): Behavior[T] = - Actor.Deferred[T] { ctx ⇒ + Actor.deferred[T] { ctx ⇒ val c = ctx.asInstanceOf[akka.typed.ActorContext[T]] val startedBehavior = initialUndefer(ctx.asInstanceOf[akka.typed.ActorContext[T]], initialBehavior) strategy match { diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala index 5b305e90d0..4394795d5a 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -86,7 +86,7 @@ object Actor { * 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 `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, this method returns `unhandled`. diff --git a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala index 9e43793997..029dd487ee 100644 --- a/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala +++ b/akka-typed/src/main/scala/akka/typed/patterns/Receptionist.scala @@ -72,7 +72,7 @@ object Receptionist { private type KV[K <: AbstractServiceKey] = ActorRef[K#Type] - private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = Immutable[Command] { (ctx, msg) ⇒ + private def behavior(map: TypedMultiMap[AbstractServiceKey, KV]): Behavior[Command] = immutable[Command] { (ctx, msg) ⇒ msg match { case r: Register[t] ⇒ ctx.watch(r.address) @@ -81,12 +81,12 @@ object Receptionist { case f: Find[t] ⇒ val set = map get f.key f.replyTo ! Listing(f.key, set) - Same + same } } onSignal { case (ctx, Terminated(ref)) ⇒ behavior(map valueRemoved ref) - case x ⇒ Unhandled + case x ⇒ unhandled } } diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index 6952c24d04..e467ff779d 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -26,7 +26,7 @@ object Actor { * * Example: * {{{ - * Immutable[String] { (ctx, msg) => println(msg); Same }.widen[Number] { + * 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 @@ -41,7 +41,7 @@ object Actor { * 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. */ - def Deferred[T](factory: ActorContext[T] ⇒ Behavior[T]): Behavior[T] = + def deferred[T](factory: ActorContext[T] ⇒ Behavior[T]): Behavior[T] = Behavior.DeferredBehavior(factory) /** @@ -56,8 +56,8 @@ object Actor { * behavior factory that takes the child actor’s context as argument * @return the deferred behavior */ - def Mutable[T](factory: ActorContext[T] ⇒ MutableBehavior[T]): Behavior[T] = - Deferred(factory) + def mutable[T](factory: ActorContext[T] ⇒ MutableBehavior[T]): Behavior[T] = + deferred(factory) /** * Mutable behavior can be implemented by extending this class and implement the @@ -100,9 +100,9 @@ object Actor { * * 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 + * * 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. */ @@ -116,7 +116,7 @@ object Actor { * avoid the allocation overhead of recreating the current behavior where * that is not necessary. */ - def Same[T]: Behavior[T] = Behavior.same + def same[T]: Behavior[T] = Behavior.same /** * Return this behavior from message processing in order to advise the @@ -124,7 +124,7 @@ 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] = Behavior.unhandled + def unhandled[T]: Behavior[T] = Behavior.unhandled /** * Return this behavior from message processing to signal that this actor @@ -133,17 +133,17 @@ object Actor { * signal that results from stopping this actor will NOT be passed to the * current behavior, it will be effectively ignored. */ - def Stopped[T]: Behavior[T] = Behavior.stopped + def stopped[T]: Behavior[T] = Behavior.stopped /** * A behavior that treats every incoming message as unhandled. */ - def Empty[T]: Behavior[T] = Behavior.empty + def empty[T]: Behavior[T] = Behavior.empty /** * A behavior that ignores every incoming message and returns “same”. */ - def Ignore[T]: Behavior[T] = Behavior.ignore + def ignore[T]: Behavior[T] = Behavior.ignore /** * Construct an actor behavior that can react to both incoming messages and @@ -158,7 +158,7 @@ object Actor { * State is updated by returning a new behavior that holds the new immutable * state. */ - def Immutable[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]): Immutable[T] = + def immutable[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]): Immutable[T] = new Immutable(onMessage) final class Immutable[T](onMessage: (ActorContext[T], T) ⇒ Behavior[T]) @@ -173,7 +173,7 @@ object Actor { * 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]( + def tap[T]( onMessage: Function2[ActorContext[T], T, _], onSignal: Function2[ActorContext[T], Signal, _], behavior: Behavior[T]): Behavior[T] = @@ -185,8 +185,8 @@ object Actor { * 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] = - Tap((_, msg) ⇒ monitor ! msg, unitFunction, 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 @@ -203,7 +203,7 @@ object Actor { * val dbRestarts = Restarter[DbException]().wrap(dbConnector) * }}} */ - def Restarter[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy = SupervisorStrategy.restart): Restarter[Thr] = + def restarter[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy = SupervisorStrategy.restart): Restarter[Thr] = new Restarter(implicitly, strategy) final class Restarter[Thr <: Throwable: ClassTag](c: ClassTag[Thr], strategy: SupervisorStrategy) { From a2bf46f901b328f88c176675468797dc659dab9d Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 2 May 2017 20:56:47 +0200 Subject: [PATCH 31/50] use "akka" as protocol also for typed --- .../src/main/scala/akka/typed/internal/ActorSystemImpl.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index 724c370251..aaf674e6f3 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -185,7 +185,7 @@ 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 From bc01d936f1e15fc73883526db23495a0b95dab08 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 2 May 2017 21:11:12 +0200 Subject: [PATCH 32/50] silence scary printTree in TypedSpecSpec, #22763 --- .../src/test/scala/akka/typed/TypedSpec.scala | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index 18d6960070..e771de7b17 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -29,6 +29,7 @@ import org.junit.runner.RunWith import scala.util.control.NonFatal import org.scalatest.exceptions.TestFailedException import akka.typed.scaladsl.AskPattern +import scala.util.control.NoStackTrace /** * Helper class for writing tests for typed Actors with ScalaTest. @@ -102,12 +103,19 @@ abstract 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 @@ -160,6 +168,8 @@ 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] = Immutable[Command] { case (ctx, r: RunTest[t]) ⇒ @@ -201,15 +211,13 @@ class TypedSpecSpec extends TypedSpec { object `A TypedSpec` { trait CommonTests { - class MyException(message: String) extends Exception(message) - implicit def system: ActorSystem[TypedSpec.Command] def `must report failures`(): Unit = { - a[MyException] must be thrownBy { + a[TypedSpec.SimulatedException] must be thrownBy { sync(runTest("failure")(StepWise[String]((ctx, startWith) ⇒ startWith { - throw new MyException("expected") + throw new TypedSpec.SimulatedException("expected") }))) } } From db0e170d32413d91edd8a36d2bfe7251e9debd4b Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 28 Apr 2017 11:37:38 +0200 Subject: [PATCH 33/50] support for safe timers, periodic scheduling, #16742 * implemented an Intercept behavior that can handle the scheduled TimerMsg, even though it's of a different type * Intercept is similar to Tap but more powerful, implemented Tap with Intercept. Intercept is internal API. * When wrapping a behavior, e.g. Tap, the outer behavior must also be Deferred if the wrapped behavior is Deferred * PostStop not signaled when stopped voluntarily, intercept messages to cancel timers when stopped is returned --- .../java/akka/typed/javadsl/ActorCompile.java | 10 + .../test/scala/akka/typed/DeferredSpec.scala | 54 +++- .../test/scala/akka/typed/RestarterSpec.scala | 8 +- .../src/test/scala/akka/typed/TimerSpec.scala | 231 ++++++++++++++++++ .../src/test/scala/akka/typed/TypedSpec.scala | 24 +- .../src/main/scala/akka/typed/Behavior.scala | 9 + .../akka/typed/internal/BehaviorImpl.scala | 143 +++++++++-- .../scala/akka/typed/internal/Restarter.scala | 5 +- .../typed/internal/TimerSchedulerImpl.scala | 152 ++++++++++++ .../main/scala/akka/typed/javadsl/Actor.scala | 16 +- .../akka/typed/javadsl/TimerScheduler.scala | 62 +++++ .../scala/akka/typed/scaladsl/Actor.scala | 16 +- .../akka/typed/scaladsl/TimerScheduler.scala | 62 +++++ 13 files changed, 744 insertions(+), 48 deletions(-) create mode 100644 akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala create mode 100644 akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/TimerScheduler.scala create mode 100644 akka-typed/src/main/scala/akka/typed/scaladsl/TimerScheduler.scala diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index 7bae597a9e..9830562f2e 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -8,6 +8,9 @@ import akka.typed.ActorContext; import static akka.typed.javadsl.Actor.*; +import java.util.concurrent.TimeUnit; +import scala.concurrent.duration.Duration; + public class ActorCompile { interface MyMsg {} @@ -56,6 +59,13 @@ public class ActorCompile { }); } + { + Behavior b = Actor.withTimers(timers -> { + timers.startPeriodicTimer("key", new MyMsgB("tick"), Duration.create(1, TimeUnit.SECONDS)); + return Actor.ignore(); + }); + } + static class MyBehavior extends ExtensibleBehavior { diff --git a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala index 9be2432718..b61b975302 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala @@ -8,6 +8,7 @@ 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 import akka.typed.testkit.TestKitSettings @@ -62,16 +63,10 @@ class DeferredSpec extends TypedSpec { } - trait RealTests { + trait RealTests extends StartSupport { implicit def system: ActorSystem[TypedSpec.Command] implicit val testSettings = TestKitSettings(system) - val nameCounter = Iterator.from(0) - def nextName(): String = s"a-${nameCounter.next()}" - - def start(behv: Behavior[Command]): ActorRef[Command] = - Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated) - def `must create underlying`(): Unit = { val probe = TestProbe[Event]("evt") val behv = Actor.deferred[Command] { _ ⇒ @@ -96,6 +91,51 @@ class DeferredSpec extends TypedSpec { probe.expectNoMsg(100.millis) } + 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 Restarter (stubbed, native)` extends StubbedTests with NativeSystem diff --git a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala index d2e48efa78..fdcb4412f5 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala @@ -223,17 +223,11 @@ class RestarterSpec extends TypedSpec { } } - trait RealTests { + trait RealTests extends StartSupport { import akka.typed.scaladsl.adapter._ implicit def system: ActorSystem[TypedSpec.Command] implicit val testSettings = TestKitSettings(system) - val nameCounter = Iterator.from(0) - def nextName(): String = s"a-${nameCounter.next()}" - - def start(behv: Behavior[Command]): ActorRef[Command] = - Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated) - def `must receive message`(): Unit = { val probe = TestProbe[Event]("evt") val behv = restarter[Throwable]().wrap(target(probe.ref)) diff --git a/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala new file mode 100644 index 0000000000..09707eebae --- /dev/null +++ b/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala @@ -0,0 +1,231 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +package akka.typed + +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference + +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._ +import org.scalatest.concurrent.Eventually.eventually + +@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 + } + + 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 + } + + 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 + } + + 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 + } + + 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 + } + + 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 + } + + 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 timerRef = new AtomicReference[TimerScheduler[Command]] + val behv = Actor.withTimers[Command] { timer ⇒ + timerRef.set(timer) + timer.startPeriodicTimer("T", Tick(1), interval) + target(probe.ref, timer, 1) + } + val ref = start(behv) + ref ! End + // PostStop is not signalled when stopped voluntarily + eventually { + timerRef.get().isTimerActive("T") should ===(false) + } + } + } + + object `A Restarter (real, native)` extends RealTests with NativeSystem + object `A Restarter (real, adapted)` extends RealTests with AdaptedSystem + +} diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index 5bd3860a25..7757711392 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -30,6 +30,7 @@ import scala.util.control.NonFatal import org.scalatest.exceptions.TestFailedException import akka.typed.scaladsl.AskPattern import scala.util.control.NoStackTrace +import akka.typed.testkit.TestKitSettings /** * Helper class for writing tests for typed Actors with ScalaTest. @@ -46,6 +47,8 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup { def this() = this(ConfigFactory.empty) + def this(config: String) = this(ConfigFactory.parseString(config)) + // extension point def setTimeout: Timeout = Timeout(1.minute) @@ -62,11 +65,26 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup { sys } - trait NativeSystem { - def system = nativeSystem + 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: ActorSystem[TypedSpec.Command] = nativeSystem + } + trait AdaptedSystem { - def system = adaptedSystem + def system: ActorSystem[TypedSpec.Command] = adaptedSystem } implicit val timeout = setTimeout diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala index 10d4bdd6e1..bd1576964c 100644 --- a/akka-typed/src/main/scala/akka/typed/Behavior.scala +++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala @@ -7,6 +7,7 @@ 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 @@ -217,6 +218,14 @@ object Behavior { */ def isUnhandled[T](behavior: Behavior[T]): Boolean = behavior eq UnhandledBehavior + /** + * Returns true if the given behavior is the special `Unhandled` marker. + */ + def isDeferred[T](behavior: Behavior[T]): Boolean = behavior match { + case _: DeferredBehavior[T] ⇒ true + case _ ⇒ false + } + /** * Execute the behavior with the given message */ diff --git a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala index 2a435e31d9..eceff42b7a 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala @@ -9,6 +9,8 @@ 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 @@ -23,21 +25,40 @@ import akka.typed.scaladsl.Actor def as[U] = ctx.asInstanceOf[AC[U]] } - final case class Widened[T, U](behavior: Behavior[T], matcher: PartialFunction[U, T]) extends ExtensibleBehavior[U] { - private def postProcess(behv: Behavior[T], ctx: AC[T]): Behavior[U] = - if (isUnhandled(behv)) unhandled - else if (isAlive(behv)) { - val next = canonicalize(behv, behavior, ctx) - if (next eq behavior) same else Widened(next, matcher) - } else stopped + 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] = - postProcess(Behavior.interpretSignal(behavior, ctx.as[T], signal), ctx.as[T]) + 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 ⇒ postProcess(Behavior.interpretMessage(behavior, ctx.as[T], transformed), ctx.as[T]) + case transformed ⇒ canonical(Behavior.interpretMessage(behavior, ctx.as[T], transformed), ctx.as[T]) } override def toString: String = s"${behavior.toString}.widen(${LineNumbers(matcher)})" @@ -54,25 +75,105 @@ import akka.typed.scaladsl.Actor override def toString = s"Immutable(${LineNumbers(onMessage)})" } - final case class Tap[T]( + def tap[T]( onMessage: Function2[SAC[T], T, _], onSignal: Function2[SAC[T], Signal, _], - behavior: Behavior[T]) extends ExtensibleBehavior[T] { + 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)) Behavior.stopped + else { + b match { + case d: DeferredBehavior[T] ⇒ canonical(Behavior.undefer(d, ctx), ctx) + case _ ⇒ Intercept(beforeOnMessage, beforeOnSignal, afterMessage, afterSignal, b) + } + } + } - private def canonical(behv: Behavior[T]): Behavior[T] = - if (isUnhandled(behv)) unhandled - else if ((behv eq SameBehavior) || (behv eq this)) same - else if (isAlive(behv)) Tap(onMessage, onSignal, behv) - else stopped override def receiveSignal(ctx: AC[T], signal: Signal): Behavior[T] = { - onSignal(ctx.asScala, signal) - canonical(Behavior.interpretSignal(behavior, ctx, signal)) + 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] = { - onMessage(ctx.asScala, msg) - canonical(Behavior.interpretMessage(behavior, ctx, msg)) + 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"Tap(${LineNumbers(onSignal)},${LineNumbers(onMessage)},$behavior)" + + override def toString = s"$toStringPrefix(${LineNumbers(beforeOnMessage)},${LineNumbers(beforeOnSignal)},$behavior)" } } diff --git a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala index 14ead33c7f..b2634e8e0e 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala @@ -34,7 +34,7 @@ import akka.typed.scaladsl.Actor 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(ctx.asInstanceOf[akka.typed.ActorContext[T]], initialBehavior) + val startedBehavior = initialUndefer(c, initialBehavior) strategy match { case Restart(-1, _, loggingEnabled) ⇒ new Restarter(initialBehavior, startedBehavior, loggingEnabled) @@ -220,9 +220,6 @@ import akka.typed.scaladsl.Actor strategy: Backoff, restartCount: Int, blackhole: Boolean) extends Supervisor[Any, Thr] { // TODO using Any here because the scheduled messages can't be of type T. - // Something to consider is that timer messages should typically not be part of the - // ordinary public message protocol and therefore those should perhaps be signals. - // https://github.com/akka/akka/issues/16742 import BackoffRestarter._ diff --git a/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala new file mode 100644 index 0000000000..8b3bf4cf7c --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala @@ -0,0 +1,152 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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) ⇒ { + // PostStop is not signaled when voluntarily stopped + if (!Behavior.isAlive(b)) + cancelAll() + b + }, + afterSignal = (ctx, sig, b) ⇒ b, // TODO optimize by using more ConstantFun + behavior)(ClassTag(classOf[TimerSchedulerImpl.TimerMsg])) + } + +} diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala index 4394795d5a..baf095b22f 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -16,6 +16,7 @@ import akka.typed.SupervisorStrategy import scala.reflect.ClassTag import akka.typed.internal.Restarter import akka.japi.pf.PFBuilder +import akka.typed.internal.TimerSchedulerImpl object Actor { @@ -177,7 +178,7 @@ object Actor { onMessage: Procedure2[ActorContext[T], T], onSignal: Procedure2[ActorContext[T], Signal], behavior: Behavior[T]): Behavior[T] = { - BehaviorImpl.Tap( + BehaviorImpl.tap( (ctx, msg) ⇒ onMessage.apply(ctx.asJava, msg), (ctx, sig) ⇒ onSignal.apply(ctx.asJava, sig), behavior) @@ -190,7 +191,7 @@ object Actor { * wrapped in a `monitor` call again. */ def monitor[T](monitor: ActorRef[T], behavior: Behavior[T]): Behavior[T] = { - BehaviorImpl.Tap( + BehaviorImpl.tap( (ctx, msg) ⇒ monitor ! msg, unitFunction, behavior) @@ -239,6 +240,15 @@ object Actor { * @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()) + 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)) } diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/TimerScheduler.scala b/akka-typed/src/main/scala/akka/typed/javadsl/TimerScheduler.scala new file mode 100644 index 0000000000..1412043a29 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/javadsl/TimerScheduler.scala @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 + +} diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index e467ff779d..50efc0c41c 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -9,6 +9,7 @@ import scala.reflect.ClassTag import akka.annotation.ApiMayChange import akka.annotation.DoNotInherit import akka.typed.internal.BehaviorImpl +import akka.typed.internal.TimerSchedulerImpl @ApiMayChange object Actor { @@ -34,7 +35,7 @@ object Actor { * }}} */ def widen[U](matcher: PartialFunction[U, T]): Behavior[U] = - BehaviorImpl.Widened(behavior, matcher) + BehaviorImpl.widened(behavior, matcher) } /** @@ -175,9 +176,9 @@ object Actor { */ def tap[T]( onMessage: Function2[ActorContext[T], T, _], - onSignal: Function2[ActorContext[T], Signal, _], + onSignal: Function2[ActorContext[T], Signal, _], // FIXME use partial function here also? behavior: Behavior[T]): Behavior[T] = - BehaviorImpl.Tap(onMessage, onSignal, behavior) + BehaviorImpl.tap(onMessage, onSignal, behavior) /** * Behavior decorator that copies all received message to the designated @@ -210,6 +211,15 @@ object Actor { 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]) diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/TimerScheduler.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/TimerScheduler.scala new file mode 100644 index 0000000000..9189d66b59 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/TimerScheduler.scala @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 + +} From d53c6c3d410aa3f6155f277bfbb27e0249c91294 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 2 May 2017 20:47:05 +0200 Subject: [PATCH 34/50] call onSignal as a PartiaFunction, #22662 * some missing pieces of the singal PF --- .../src/test/scala/akka/typed/ActorContextSpec.scala | 1 - .../src/test/scala/akka/typed/RestarterSpec.scala | 2 +- .../test/scala/akka/typed/internal/ActorCellSpec.scala | 1 - .../scala/akka/typed/scaladsl/adapter/AdapterSpec.scala | 9 +++------ .../src/test/scala/docs/akka/typed/IntroSpec.scala | 9 ++------- .../test/scala/docs/akka/typed/MutableIntroSpec.scala | 9 ++------- .../src/main/scala/akka/typed/MessageAndSignals.scala | 2 +- .../src/main/scala/akka/typed/scaladsl/Actor.scala | 2 +- 8 files changed, 10 insertions(+), 25 deletions(-) diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala index b2e2fd74b2..e2072bec83 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala @@ -149,7 +149,6 @@ object ActorContextSpec { case (_, sig) ⇒ monitor ! GotSignal(sig) Actor.same - } case GetAdapter(replyTo, name) ⇒ replyTo ! Adapter(ctx.spawnAdapter(identity, name)) diff --git a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala index fdcb4412f5..dbb9845196 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala @@ -50,7 +50,7 @@ class RestarterSpec extends TypedSpec { case Throw(e) ⇒ throw e } - }.onSignal { + } onSignal { case (ctx, sig) ⇒ monitor ! GotSignal(sig) same diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala index 7cf49d7fce..60005cf684 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorCellSpec.scala @@ -435,7 +435,6 @@ class ActorCellSpec extends Spec with Matchers with BeforeAndAfterAll with Scala case _ ⇒ unhandled } onSignal { case (_, PostStop) ⇒ ??? - case _ ⇒ unhandled }, ec, 1000, parent) val ref = new LocalActorRef(parent.path / "child", cell) cell.setSelf(ref) diff --git a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala index 320a4bca52..4ad7bf0b50 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/scaladsl/adapter/AdapterSpec.scala @@ -57,12 +57,9 @@ object AdapterSpec { Actor.same } } onSignal { - case (ctx, sig) ⇒ sig match { - case Terminated(ref) ⇒ - probe ! "terminated" - Actor.same - case _ ⇒ Actor.unhandled - } + case (ctx, Terminated(ref)) ⇒ + probe ! "terminated" + Actor.same } sealed trait Typed2Msg diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index 967122e4dc..eeb01e6ce5 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -128,13 +128,8 @@ class IntroSpec extends TypedSpec { Actor.immutable[akka.NotUsed] { (_, _) ⇒ Actor.unhandled } onSignal { - case (ctx, sig) ⇒ - sig match { - case Terminated(ref) ⇒ - Actor.stopped - case _ ⇒ - Actor.unhandled - } + case (ctx, Terminated(ref)) ⇒ + Actor.stopped } } diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala index eee2bd57ee..8b86e518a7 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/MutableIntroSpec.scala @@ -100,13 +100,8 @@ class MutableIntroSpec extends TypedSpec { Actor.immutable[akka.NotUsed] { (_, _) ⇒ Actor.unhandled } onSignal { - case (ctx, sig) ⇒ - sig match { - case Terminated(ref) ⇒ - Actor.stopped - case _ ⇒ - Actor.unhandled - } + case (ctx, Terminated(ref)) ⇒ + Actor.stopped } } diff --git a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala index 0b751697bf..e94ddc0c40 100644 --- a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala +++ b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala @@ -25,7 +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 +trait Signal /** * Lifecycle signal that is fired upon restart of the Actor before replacing diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index 50efc0c41c..00bef454ec 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -92,7 +92,7 @@ object Actor { @throws(classOf[Exception]) override final def receiveSignal(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] = - onSignal(msg) + 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. From 01cd05f6a99ab73870c98b5905cb8a717ffe3d9f Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 4 May 2017 10:07:12 -0700 Subject: [PATCH 35/50] javadsl BehaviorBuilder, #22748 Partially based on https://github.com/akka/akka/compare/master...johanandren:play-around-with-typed-pre-2.5-rc1?expand=1 * remove build(), add methods, predicates * akka.japi.function, docs, construction * Javadocs, stand-alone entrypoints * No case class to pollute the javadocs less * Make BehaviorChain a proper BehaviorBuilder again Separating these steps will keep us more flexible to introduce optimizations later, at the expense of not being able to use the BehaviorBuilder as a Behavior directly * Less awkward generic when constructing the builder from Java * Shorten/DRYer implementation for adding message/signal handlers * Introduce 'mutable' javadsl builder API * Use onMessage/onSignal in the builder as well * Return unhandled if no case matches * Using builder but extend MutableBehavior * Use 'receive' terminology * Use 'onMessage'/'onSignal' for receive builder, too * 'unhandled' when no case matches * Receive interface, promote 'this' for mutable behaviors * Move ReceiveBuilder tests to its own file * Remove need for StateHolder * Test for and fix initialization issue * Avoid lazy val during mutable actor initialization --- .../typed/javadsl/BehaviorBuilderTest.java | 84 ++++++ .../typed/javadsl/ReceiveBuilderTest.java | 63 ++++ .../jdocs/akka/typed/MutableIntroTest.java | 37 ++- .../main/scala/akka/typed/javadsl/Actor.scala | 78 ++--- .../akka/typed/javadsl/BehaviorBuilder.scala | 268 ++++++++++++++++++ .../akka/typed/javadsl/ReceiveBuilder.scala | 169 +++++++++++ 6 files changed, 642 insertions(+), 57 deletions(-) create mode 100644 akka-typed-tests/src/test/java/akka/typed/javadsl/BehaviorBuilderTest.java create mode 100644 akka-typed-tests/src/test/java/akka/typed/javadsl/ReceiveBuilderTest.java create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/BehaviorBuilder.scala create mode 100644 akka-typed/src/main/scala/akka/typed/javadsl/ReceiveBuilder.scala diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/BehaviorBuilderTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/BehaviorBuilderTest.java new file mode 100644 index 0000000000..b9294ab5f0 --- /dev/null +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/BehaviorBuilderTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 extends ArrayList implements Message { + }; + + @Test + public void shouldCompile() { + Behavior 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 ctx, MyList l) -> { + String first = l.get(0); + return Actor.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 sender; + public Get(ActorRef sender) { + this.sender = sender; + } + }; + static final class Got { + final int n; + public Got(int n) { + this.n = n; + } + } + + public Behavior 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 immutable = immutableCounter(0); + } + +} diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ReceiveBuilderTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ReceiveBuilderTest.java new file mode 100644 index 0000000000..78a792c0d9 --- /dev/null +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ReceiveBuilderTest.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 mutable = Actor.mutable(ctx -> new Actor.MutableBehavior() { + int currentValue = 0; + + private Behavior receiveIncrease(BehaviorBuilderTest.Increase msg) { + currentValue++; + return this; + } + + private Behavior receiveGet(BehaviorBuilderTest.Get get) { + get.sender.tell(new BehaviorBuilderTest.Got(currentValue)); + return this; + } + + @Override + public Actor.Receive createReceive() { + return receiveBuilder() + .onMessage(BehaviorBuilderTest.Increase.class, this::receiveIncrease) + .onMessage(BehaviorBuilderTest.Get.class, this::receiveGet) + .build(); + } + }); + } + + private static class MyMutableBehavior extends Actor.MutableBehavior { + private int value; + + public MyMutableBehavior(int initialValue) { + super(); + this.value = initialValue; + } + + @Override + public Actor.Receive 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())); + } +} diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java index 5bb74b553f..a2bb502506 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/MutableIntroTest.java @@ -7,6 +7,7 @@ package jdocs.akka.typed; 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; @@ -83,31 +84,25 @@ public class MutableIntroTest { } @Override - public Behavior onMessage(Command msg) { - if (msg instanceof GetSession) { - GetSession getSession = (GetSession) msg; - ActorRef wrapper = ctx.spawnAdapter(p -> - new PostSessionMessage(getSession.screenName, p.message)); - getSession.replyTo.tell(new SessionGranted(wrapper)); - sessions.add(getSession.replyTo); - return Actor.same(); - } else if (msg instanceof PostSessionMessage) { - PostSessionMessage post = (PostSessionMessage) msg; - MessagePosted mp = new MessagePosted(post.screenName, post.message); - sessions.forEach(s -> s.tell(mp)); - return this; - } else { - return Actor.unhandled(); - } + public Receive createReceive() { + return receiveBuilder() + .onMessage(GetSession.class, getSession -> { + ActorRef 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 diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala index baf095b22f..d03a7c62b1 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -4,18 +4,23 @@ 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.typed.scaladsl.{ ActorContext ⇒ SAC } +import akka.japi.pf.PFBuilder + import akka.typed.Behavior import akka.typed.ExtensibleBehavior import akka.typed.Signal -import akka.typed.internal.BehaviorImpl import akka.typed.ActorRef import akka.typed.SupervisorStrategy -import scala.reflect.ClassTag +import akka.typed.scaladsl.{ ActorContext ⇒ SAC } + +import akka.typed.internal.BehaviorImpl import akka.typed.internal.Restarter -import akka.japi.pf.PFBuilder import akka.typed.internal.TimerSchedulerImpl object Actor { @@ -57,44 +62,26 @@ object Actor { * @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] = - onMessage(msg) - - /** - * 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: - *
    - *
  • 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
  • - *
- * - */ - @throws(classOf[Exception]) - def onMessage(msg: T): Behavior[T] + receive.receiveMessage(msg) @throws(classOf[Exception]) override final def receiveSignal(ctx: akka.typed.ActorContext[T], msg: Signal): Behavior[T] = - onSignal(msg) + receive.receiveSignal(msg) - /** - * 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, this method returns `unhandled`. - */ - @throws(classOf[Exception]) - def onSignal(msg: Signal): Behavior[T] = - unhandled + def createReceive: Receive[T] + + def receiveBuilder: ReceiveBuilder[T] = ReceiveBuilder.create } /** @@ -169,6 +156,21 @@ object Actor { { 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 @@ -251,4 +253,8 @@ object Actor { 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] + } } diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/BehaviorBuilder.scala b/akka-typed/src/main/scala/akka/typed/javadsl/BehaviorBuilder.scala new file mode 100644 index 0000000000..3c2d22d79c --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/javadsl/BehaviorBuilder.scala @@ -0,0 +1,268 @@ +/** + * Copyright (C) 2009-2017 Lightbend Inc. + */ + +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. List.class and (List<String> list) -> {...} + * + * @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. GenMsg.class and (ActorContext ctx, GenMsg<String> list) -> {...} + * + * @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. List.class and (List<String> list) -> {...} + * + * @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. GenMsg.class and (ActorContext ctx, GenMsg<String> list) -> {...} + * + * @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] + } + +} \ No newline at end of file diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/ReceiveBuilder.scala b/akka-typed/src/main/scala/akka/typed/javadsl/ReceiveBuilder.scala new file mode 100644 index 0000000000..7a3beee314 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/javadsl/ReceiveBuilder.scala @@ -0,0 +1,169 @@ +/** + * Copyright (C) 2009-2017 Lightbend Inc. + */ + +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. List.class and (List<String> list) -> {...} + * + * @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. GenMsg.class and (ActorContext ctx, GenMsg<String> list) -> {...} + * + * @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 + } + +} \ No newline at end of file From 9393a2412544a7dfad7a2ba909a8e0298a6b393a Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 5 May 2017 08:05:48 +0200 Subject: [PATCH 36/50] Java API for ActorSystem.create, #22847 * because it's a trait the static forwarders are missing when compiled with Scala 2.11 * changed ActorRef to a pure interface (trait) instead of abstract class and then ActorSystem can be an abstract class instead * also, improved docs of deferred --- .../java/akka/typed/javadsl/ActorCompile.java | 2 ++ .../akka/typed/internal/ActorSystemStub.scala | 5 +-- .../scala/akka/typed/internal/DebugRef.scala | 4 +-- akka-typed/build.sbt | 2 +- .../src/main/scala/akka/typed/ActorRef.scala | 31 ++++------------ .../main/scala/akka/typed/ActorSystem.scala | 26 ++++++++++---- .../akka/typed/internal/ActorRefImpl.scala | 35 ++++++++++++++++--- .../akka/typed/internal/ActorSystemImpl.scala | 10 ++++-- .../akka/typed/internal/EventStreamImpl.scala | 5 ++- .../internal/adapter/ActorRefAdapter.scala | 3 +- .../internal/adapter/ActorSystemAdapter.scala | 7 ++-- .../main/scala/akka/typed/javadsl/Actor.scala | 11 ++++-- .../scala/akka/typed/scaladsl/Actor.scala | 11 ++++-- 13 files changed, 101 insertions(+), 51 deletions(-) diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index 9830562f2e..e14f1950db 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -43,6 +43,8 @@ public class ActorCompile { }); Behavior actor9 = widened(actor7, pf -> pf.match(MyMsgA.class, x -> x)); + ActorSystem system = ActorSystem.create("Sys", actor1); + { Actor.immutable((ctx, msg) -> { if (msg instanceof MyMsgA) { diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala index 038c6714b4..6c54fb7543 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala @@ -11,8 +11,9 @@ import java.util.concurrent.ThreadFactory 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) diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/DebugRef.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/DebugRef.scala index 464c040d70..24223c10ec 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/DebugRef.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/DebugRef.scala @@ -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]] diff --git a/akka-typed/build.sbt b/akka-typed/build.sbt index 2ee6ecd446..77aabbf672 100644 --- a/akka-typed/build.sbt +++ b/akka-typed/build.sbt @@ -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 diff --git a/akka-typed/src/main/scala/akka/typed/ActorRef.scala b/akka-typed/src/main/scala/akka/typed/ActorRef.scala index 13d2cdbc2e..7eb3c1ec82 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorRef.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorRef.scala @@ -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* @@ -28,14 +29,14 @@ abstract class ActorRef[-T](_path: a.ActorPath) extends java.lang.Comparable[Act /** * Narrow the type of this `ActorRef, which is always a safe operation. */ - final def narrow[U <: T]: ActorRef[U] = this.asInstanceOf[ActorRef[U]] + 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 @@ -43,28 +44,8 @@ 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 { @@ -77,6 +58,8 @@ object ActorRef { 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. diff --git a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala index 1a8d383955..5c1e7ff738 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala @@ -15,6 +15,7 @@ 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] { this: internal.ActorRefImpl[T] ⇒ /** * The name of this actor system, used to distinguish multiple ones within @@ -154,7 +155,7 @@ 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. */ @@ -170,17 +171,27 @@ object ActorSystem { } /** - * Java API + * 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: java.util.Optional[Props], - config: java.util.Optional[Config], - classLoader: java.util.Optional[ClassLoader], - executionContext: java.util.Optional[ExecutionContext]): ActorSystem[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 @@ -197,6 +208,7 @@ object ActorSystem { // 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()) diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorRefImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorRefImpl.scala index 0176727070..5f5766925f 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorRefImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorRefImpl.scala @@ -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._ /** diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index ce4752cbb8..ac559394eb 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -76,7 +76,7 @@ private[typed] class ActorSystemImpl[-T]( _ec: Option[ExecutionContext], _userGuardianBehavior: Behavior[T], _userGuardianProps: Props) - extends ActorRef[T](a.RootActorPath(a.Address("akka", name)) / "user") with ActorSystem[T] with ActorRefImpl[T] { + extends ActorSystem[T] with ActorRef[T] with ActorRefImpl[T] { import ActorSystemImpl._ @@ -85,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) @@ -189,7 +191,8 @@ private[typed] class ActorSystemImpl[-T]( private val topLevelActors = new ConcurrentSkipListSet[ActorRefImpl[Nothing]] private val terminateTriggered = new AtomicBoolean private val theOneWhoWalksTheBubblesOfSpaceTime: ActorRefImpl[Nothing] = - new ActorRef[Nothing](rootPath) with ActorRefImpl[Nothing] { + 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 { @@ -240,7 +243,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 { diff --git a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala index db46fa559a..248b6f216e 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/EventStreamImpl.scala @@ -124,7 +124,10 @@ private[typed] class EventStreamImpl(private val debug: Boolean)(implicit privat * started. Its log level can be defined by configuration setting * akka.stdout-loglevel. */ - 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) diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala index 14199e8616..fd151b05de 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorRefAdapter.scala @@ -13,8 +13,9 @@ import akka.dispatch.sysmsg * INTERNAL API */ @InternalApi private[typed] class ActorRefAdapter[-T](val untyped: a.InternalActorRef) - extends ActorRef[T](untyped.path) with internal.ActorRefImpl[T] { + 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 = diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala index a01ca6af19..b5acfd07fb 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala @@ -11,6 +11,7 @@ import scala.concurrent.ExecutionContextExecutor import akka.util.Timeout import scala.concurrent.Future import akka.annotation.InternalApi +import scala.annotation.unchecked.uncheckedVariance /** * INTERNAL API. Lightweight wrapper for presenting an untyped ActorSystem to a Behavior (via the context). @@ -20,8 +21,7 @@ import akka.annotation.InternalApi * most circumstances. */ @InternalApi 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] { + extends ActorSystem[T] with ActorRef[T] with internal.ActorRefImpl[T] { import ActorSystemAdapter._ import ActorRefAdapter.sendSystemMessage @@ -30,6 +30,9 @@ import akka.annotation.InternalApi 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) diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala index d03a7c62b1..f9f9a9cfb5 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -29,8 +29,15 @@ object Actor { private def unitFunction[T] = _unitFunction.asInstanceOf[((SAC[T], Signal) ⇒ Unit)] /** - * 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. + * `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)) diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index 00bef454ec..ef81ec29fb 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -39,8 +39,15 @@ object Actor { } /** - * 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. + * `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: ActorContext[T] ⇒ Behavior[T]): Behavior[T] = Behavior.DeferredBehavior(factory) From 090893179fadb8b794f320f93931f59d58e13c9a Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Fri, 5 May 2017 00:13:32 -0700 Subject: [PATCH 37/50] Implement watchWith to customize the termination msg, #18778 * Add test with both normal and adapted AS And add watch method on Actor, fix comment * Formatting * Avoid 'unreachable' in javadocs as it could be confusing * Fix unwatching * Exclude added methods from MiMa * Some comments and variable names * Fix compilation errors after rebase * More specific docs on when Terminated is sent --- .../src/main/scala/akka/actor/ActorCell.scala | 8 +++ .../scala/akka/actor/dungeon/DeathWatch.scala | 63 ++++++++++++---- .../typed/testkit/StubbedActorContext.scala | 1 + .../akka/typed/javadsl/MonitoringTest.java | 36 +++++++++- .../scala/akka/typed/MonitoringSpec.scala | 71 +++++++++++++++++++ .../scala/akka/typed/MessageAndSignals.scala | 5 +- .../akka/typed/internal/DeathWatch.scala | 53 +++++++++----- .../adapter/ActorContextAdapter.scala | 1 + .../akka/typed/javadsl/ActorContext.scala | 14 +++- .../akka/typed/scaladsl/ActorContext.scala | 14 +++- project/MiMa.scala | 7 ++ 11 files changed, 233 insertions(+), 40 deletions(-) create mode 100644 akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 480f84b993..7dbcf41d45 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -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 diff --git a/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala b/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala index 756d45520e..1a953907aa 100644 --- a/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala +++ b/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala @@ -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,10 @@ 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)) } } finally { - watching = ActorCell.emptyActorRefSet + watching = Map.empty terminatedQueued = ActorCell.emptyActorRefSet } } @@ -166,7 +199,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 +218,7 @@ private[akka] trait DeathWatch { this: ActorCell ⇒ } if (isNonLocal(change)) { - def hasNonLocalAddress: Boolean = ((watching exists isNonLocal) || (watchedBy exists isNonLocal)) + def hasNonLocalAddress: Boolean = ((watching.keys exists isNonLocal) || (watchedBy exists isNonLocal)) val had = hasNonLocalAddress val result = block val has = hasNonLocalAddress diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala index 102d5d1fa9..dc03597faf 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala @@ -56,6 +56,7 @@ class StubbedActorContext[T]( } } 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 = () diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java index ec5a918b97..7e9f1f84cf 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java @@ -16,13 +16,15 @@ import static akka.typed.javadsl.AskPattern.*; public class MonitoringTest extends JUnitSuite { - static final class RunTest { + static interface Message {} + static final class RunTest implements Message { private final ActorRef replyTo; public RunTest(ActorRef 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)); @@ -44,6 +46,18 @@ public class MonitoringTest extends JUnitSuite { ); } + private Behavior waitingForMessage(ActorRef replyWhenReceived) { + return immutable( + (ctx, msg) -> { + if (msg instanceof CustomTerminationMessage) { + replyWhenReceived.tell(Done.getInstance()); + return same(); + } else { + return unhandled(); + } + } + ); + } @Test public void shouldWatchTerminatingActor() throws Exception { @@ -60,4 +74,24 @@ public class MonitoringTest extends JUnitSuite { CompletionStage result = AskPattern.ask((ActorRef>)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler()); result.toCompletableFuture().get(3, TimeUnit.SECONDS); } + + @Test + public void shouldWatchWithCustomMessage() throws Exception { + Behavior root = immutable((ctx, msg) -> { + if (msg instanceof RunTest) { + ActorRef watched = ctx.spawn(exitingActor, "exitingActor"); + ctx.watchWith(watched, new CustomTerminationMessage()); + watched.tell(new Stop()); + return waitingForMessage(((RunTest) msg).replyTo); + } else { + return unhandled(); + } + }); + ActorSystem system = ActorSystem$.MODULE$.create("sysname", root, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); + + // Not sure why this does not compile without an explicit cast? + // system.tell(new RunTest()); + CompletionStage result = AskPattern.ask((ActorRef)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler()); + result.toCompletableFuture().get(3, TimeUnit.SECONDS); + } } \ No newline at end of file diff --git a/akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala new file mode 100644 index 0000000000..41c2e1e38c --- /dev/null +++ b/akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 MonitoringSpec 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[_]) + + val terminator = Await.result(system ? TypedSpec.Create(immutable[Stop.type] { + case (ctx, `Stop`) ⇒ stopped + }, "t"), 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 + }, "w"), 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[_], msg: CustomTerminationMessage.type) extends Message + + val terminator = Await.result(system ? TypedSpec.Create(immutable[Stop.type] { + case (ctx, `Stop`) ⇒ stopped + }, "t"), 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 + }, "w"), 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 +} diff --git a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala index e94ddc0c40..c5a82a8c9e 100644 --- a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala +++ b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala @@ -59,8 +59,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 diff --git a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala index fc91798448..e44af7d32d 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala @@ -41,15 +41,29 @@ 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]): Unit = { - 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) + } + } + } + + 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)) } } } @@ -70,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.interpretSignal(behavior, 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() @@ -118,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 } } } @@ -163,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)) } } @@ -181,7 +202,7 @@ private[typed] trait DeathWatch[T] { } if (isNonLocal(change)) { - def hasNonLocalAddress: Boolean = ((watching exists isNonLocal) || (watchedBy exists isNonLocal)) + def hasNonLocalAddress: Boolean = ((watching.keys exists isNonLocal) || (watchedBy exists isNonLocal)) val had = hasNonLocalAddress val result = block val has = hasNonLocalAddress diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala index a94b7081e8..4fb98a9734 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorContextAdapter.scala @@ -41,6 +41,7 @@ import akka.annotation.InternalApi } } 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) = { diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala index 514913658e..f774f85876 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/ActorContext.scala @@ -103,12 +103,20 @@ trait ActorContext[T] { /** * 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). + * 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. diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala index 9c6f97bd58..d4a7f13d5b 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/ActorContext.scala @@ -91,12 +91,20 @@ trait ActorContext[T] { this: akka.typed.javadsl.ActorContext[T] ⇒ /** * 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). + * 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. diff --git a/project/MiMa.scala b/project/MiMa.scala index ec87eec54d..99b262eddc 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -1147,6 +1147,13 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasConfigSerializerId"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getScopeSerializerId"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getScopeManifest") + ), + "2.5.1" -> Seq( + // #22794 watchWith + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.ActorContext.watchWith"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.DeathWatch.watchWith"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.DeathWatch.akka$actor$dungeon$DeathWatch$$watching"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.DeathWatch.akka$actor$dungeon$DeathWatch$$watching_=") ) // make sure that // * this list ends with the latest released version number From 9dce4b9065bad69443a2a5e498e03e4fbc822ec7 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 5 May 2017 12:02:22 +0200 Subject: [PATCH 38/50] minor follow up on PR #22794 (#22851) * and MonitoringTest didn't terminate actor system --- .../scala/akka/actor/dungeon/DeathWatch.scala | 5 ++- .../{MonitoringTest.java => WatchTest.java} | 36 +++++++++++-------- .../{MonitoringSpec.scala => WatchSpec.scala} | 14 ++++---- .../akka/typed/internal/DeathWatch.scala | 2 +- 4 files changed, 33 insertions(+), 24 deletions(-) rename akka-typed-tests/src/test/java/akka/typed/javadsl/{MonitoringTest.java => WatchTest.java} (68%) rename akka-typed-tests/src/test/scala/akka/typed/{MonitoringSpec.scala => WatchSpec.scala} (84%) diff --git a/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala b/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala index 1a953907aa..dfd9271f67 100644 --- a/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala +++ b/akka-actor/src/main/scala/akka/actor/dungeon/DeathWatch.scala @@ -147,6 +147,9 @@ private[akka] trait DeathWatch { this: ActorCell ⇒ try { watching foreach { // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ 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 = Map.empty @@ -218,7 +221,7 @@ private[akka] trait DeathWatch { this: ActorCell ⇒ } if (isNonLocal(change)) { - def hasNonLocalAddress: Boolean = ((watching.keys exists isNonLocal) || (watchedBy exists isNonLocal)) + def hasNonLocalAddress: Boolean = ((watching.keysIterator exists isNonLocal) || (watchedBy exists isNonLocal)) val had = hasNonLocalAddress val result = block val has = hasNonLocalAddress diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java similarity index 68% rename from akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java rename to akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java index 7e9f1f84cf..710660d19f 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/MonitoringTest.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java @@ -1,11 +1,11 @@ package akka.typed.javadsl; import java.util.concurrent.CompletionStage; -import java.util.Optional; 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; @@ -14,7 +14,7 @@ import akka.typed.*; import static akka.typed.javadsl.Actor.*; import static akka.typed.javadsl.AskPattern.*; -public class MonitoringTest extends JUnitSuite { +public class WatchTest extends JUnitSuite { static interface Message {} static final class RunTest implements Message { @@ -67,12 +67,15 @@ public class MonitoringTest extends JUnitSuite { watched.tell(new Stop()); return waitingForTermination(msg.replyTo); }); - ActorSystem> system = ActorSystem$.MODULE$.create("sysname", root, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); - - // Not sure why this does not compile without an explicit cast? - // system.tell(new RunTest()); - CompletionStage result = AskPattern.ask((ActorRef>)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler()); - result.toCompletableFuture().get(3, TimeUnit.SECONDS); + ActorSystem> system = ActorSystem.create("sysname", root); + try { + // Not sure why this does not compile without an explicit cast? + // system.tell(new RunTest()); + CompletionStage result = AskPattern.ask((ActorRef>)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler()); + result.toCompletableFuture().get(3, TimeUnit.SECONDS); + } finally { + Await.ready(system.terminate(), Duration.create(10, TimeUnit.SECONDS)); + } } @Test @@ -82,16 +85,19 @@ public class MonitoringTest extends JUnitSuite { ActorRef watched = ctx.spawn(exitingActor, "exitingActor"); ctx.watchWith(watched, new CustomTerminationMessage()); watched.tell(new Stop()); - return waitingForMessage(((RunTest) msg).replyTo); + return waitingForMessage(((RunTest) msg).replyTo); } else { return unhandled(); } }); - ActorSystem system = ActorSystem$.MODULE$.create("sysname", root, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); - - // Not sure why this does not compile without an explicit cast? - // system.tell(new RunTest()); - CompletionStage result = AskPattern.ask((ActorRef)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler()); - result.toCompletableFuture().get(3, TimeUnit.SECONDS); + ActorSystem system = ActorSystem.create("sysname", root); + try { + // Not sure why this does not compile without an explicit cast? + // system.tell(new RunTest()); + CompletionStage result = AskPattern.ask((ActorRef)system, (ActorRef ref) -> new RunTest(ref), timeout, system.scheduler()); + result.toCompletableFuture().get(3, TimeUnit.SECONDS); + } finally { + Await.ready(system.terminate(), Duration.create(10, TimeUnit.SECONDS)); + } } } \ No newline at end of file diff --git a/akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/WatchSpec.scala similarity index 84% rename from akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/WatchSpec.scala index 41c2e1e38c..b836942518 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/MonitoringSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/WatchSpec.scala @@ -10,18 +10,18 @@ import akka.typed.scaladsl.AskPattern._ import akka.testkit._ @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class MonitoringSpec extends TypedSpec { +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[_]) + case class StartWatching(watchee: ActorRef[Stop.type]) val terminator = Await.result(system ? TypedSpec.Create(immutable[Stop.type] { case (ctx, `Stop`) ⇒ stopped - }, "t"), 3.seconds /*.dilated*/ ) + }, "t1"), 3.seconds /*.dilated*/ ) val receivedTerminationSignal: Promise[Unit] = Promise() @@ -29,7 +29,7 @@ class MonitoringSpec extends TypedSpec { case (ctx, StartWatching(watchee)) ⇒ ctx.watch(watchee); same }.onSignal { case (ctx, Terminated(_)) ⇒ receivedTerminationSignal.success(()); stopped - }, "w"), 3.seconds /*.dilated*/ ) + }, "w1"), 3.seconds /*.dilated*/ ) watcher ! StartWatching(terminator) terminator ! Stop @@ -42,11 +42,11 @@ class MonitoringSpec extends TypedSpec { sealed trait Message case object CustomTerminationMessage extends Message - case class StartWatchingWith(watchee: ActorRef[_], msg: CustomTerminationMessage.type) 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 - }, "t"), 3.seconds /*.dilated*/ ) + }, "t2"), 3.seconds /*.dilated*/ ) val receivedTerminationSignal: Promise[Unit] = Promise() @@ -57,7 +57,7 @@ class MonitoringSpec extends TypedSpec { case (ctx, `CustomTerminationMessage`) ⇒ receivedTerminationSignal.success(()) stopped - }, "w"), 3.seconds /*.dilated*/ ) + }, "w2"), 3.seconds /*.dilated*/ ) watcher ! StartWatchingWith(terminator, CustomTerminationMessage) terminator ! Stop diff --git a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala index e44af7d32d..90fcfd865e 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/DeathWatch.scala @@ -202,7 +202,7 @@ private[typed] trait DeathWatch[T] { } if (isNonLocal(change)) { - def hasNonLocalAddress: Boolean = ((watching.keys exists isNonLocal) || (watchedBy exists isNonLocal)) + def hasNonLocalAddress: Boolean = ((watching.keysIterator exists isNonLocal) || (watchedBy exists isNonLocal)) val had = hasNonLocalAddress val result = block val has = hasNonLocalAddress From 57a502641042f9925fdfd49b9a21eebfefff580b Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 5 May 2017 13:06:15 +0200 Subject: [PATCH 39/50] signal PostStop also when voluntarily stopped, #22840 * possibility to use another behavior for the PostStop signal, but default is to use current behavior * it was difficult to fix the ActorContextSpec, mostly because StepWise is an unknown beast, at some point we will replace that --- .../java/akka/typed/javadsl/ActorCompile.java | 1 + .../scala/akka/typed/ActorContextSpec.scala | 110 ++++++++++-------- .../src/test/scala/akka/typed/StepWise.scala | 15 ++- .../src/test/scala/akka/typed/TimerSpec.scala | 15 ++- .../src/main/scala/akka/typed/Behavior.scala | 36 +++++- .../scala/akka/typed/MessageAndSignals.scala | 4 - .../scala/akka/typed/internal/ActorCell.scala | 23 +++- .../akka/typed/internal/BehaviorImpl.scala | 2 +- .../scala/akka/typed/internal/Restarter.scala | 2 +- .../typed/internal/SupervisionMechanics.scala | 16 ++- .../typed/internal/TimerSchedulerImpl.scala | 9 +- .../typed/internal/adapter/ActorAdapter.scala | 45 ++++++- .../main/scala/akka/typed/javadsl/Actor.scala | 19 ++- .../scala/akka/typed/scaladsl/Actor.scala | 19 ++- 14 files changed, 221 insertions(+), 95 deletions(-) diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index e14f1950db..d81a5e7d42 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -42,6 +42,7 @@ public class ActorCompile { return monitor(self, ignore()); }); Behavior actor9 = widened(actor7, pf -> pf.match(MyMsgA.class, x -> x)); + Behavior actor10 = immutable((ctx, msg) -> stopped(actor4), (ctx, signal) -> same()); ActorSystem system = ActorSystem.create("Sys", actor1); diff --git a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala index e2072bec83..b5b2667f4f 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/ActorContextSpec.scala @@ -72,7 +72,7 @@ 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] = + def subject(monitor: ActorRef[Monitor], ignorePostStop: Boolean): Behavior[Command] = Actor.immutable[Command] { (ctx, message) ⇒ message match { @@ -87,13 +87,13 @@ object ActorContextSpec { 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 @@ -108,7 +108,8 @@ object ActorContextSpec { replyTo ! Scheduled ctx.schedule(delay, target, msg) Actor.same - case Stop ⇒ Actor.stopped + case Stop ⇒ + Actor.stopped case Kill(ref, replyTo) ⇒ if (ctx.stop(ref)) replyTo ! Killed else replyTo ! NotKilled @@ -145,7 +146,8 @@ object ActorContextSpec { Actor.immutable[Command] { case (_, _) ⇒ Actor.unhandled } onSignal { - case (_, Terminated(_)) ⇒ Actor.unhandled + case (_, PostStop) if ignorePostStop ⇒ Actor.same // ignore PostStop here + case (_, Terminated(_)) ⇒ Actor.unhandled case (_, sig) ⇒ monitor ! GotSignal(sig) Actor.same @@ -155,10 +157,11 @@ object ActorContextSpec { Actor.same } } onSignal { - case (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.same + case (_, PostStop) if ignorePostStop ⇒ Actor.same // ignore PostStop here + case (ctx, signal) ⇒ monitor ! GotSignal(signal); Actor.same } - def oldSubject(monitor: ActorRef[Monitor]): Behavior[Command] = { + def oldSubject(monitor: ActorRef[Monitor], ignorePostStop: Boolean): Behavior[Command] = { Actor.immutable[Command] { case (ctx, message) ⇒ message match { case ReceiveTimeout ⇒ @@ -172,13 +175,13 @@ object ActorContextSpec { 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 @@ -193,7 +196,8 @@ object ActorContextSpec { replyTo ! Scheduled ctx.schedule(delay, target, msg) Actor.same - case Stop ⇒ Actor.stopped + case Stop ⇒ + Actor.stopped case Kill(ref, replyTo) ⇒ if (ctx.stop(ref)) replyTo ! Killed else replyTo ! NotKilled @@ -230,7 +234,8 @@ object ActorContextSpec { Actor.immutable[Command] { case _ ⇒ Actor.unhandled } onSignal { - case (_, Terminated(_)) ⇒ Actor.unhandled + case (_, PostStop) if ignorePostStop ⇒ Actor.same // ignore PostStop here + case (_, Terminated(_)) ⇒ Actor.unhandled case (_, sig) ⇒ monitor ! GotSignal(sig) Actor.same @@ -240,6 +245,7 @@ object ActorContextSpec { Actor.same } } onSignal { + case (_, PostStop) if ignorePostStop ⇒ Actor.same // ignore PostStop here case (_, signal) ⇒ monitor ! GotSignal(signal) Actor.same @@ -270,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] @@ -278,10 +284,10 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( if (system eq nativeSystem) suite + "Native" else suite + "Adapted" - def setup(name: String, wrapper: Option[Actor.Restarter[_]] = 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 props = wrapper.map(_.wrap(behavior(ctx, ignorePostStop))).getOrElse(behavior(ctx, ignorePostStop)) val steps = startWith.withKeepTraces(true)(ctx.spawn(props, "subject")) proc(ctx, steps) @@ -344,29 +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) - 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)) } }) @@ -440,7 +447,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( }.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 @@ -457,7 +464,7 @@ class ActorContextSpec extends TypedSpec(ConfigFactory.parseString( val self = ctx.self startWith.mkChild(Some("A"), ctx.spawnAdapter(ChildEvent), self) { case (subj, child) ⇒ - val other = ctx.spawn(behavior(ctx), "A") + val other = ctx.spawn(behavior(ctx, ignorePostStop = true), "A") subj ! Kill(other, ctx.self) child }.expectMessageKeep(expectTimeout) { (msg, _) ⇒ @@ -515,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) ⇒ @@ -531,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)) } }) @@ -579,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) ⇒ @@ -609,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 @@ -618,32 +628,32 @@ 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]): Behavior[Command] = - Actor.deferred(_ ⇒ Actor.deferred(_ ⇒ subject(ctx.self))) + 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 diff --git a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala index 30321069eb..b4d4d61511 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/StepWise.scala @@ -141,6 +141,9 @@ object StepWise { ctx.cancelReceiveTimeout() run(ctx, tail, f(msg, value)) } 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 ⇒ @@ -157,6 +160,9 @@ object StepWise { run(ctx, tail, f((msg :: acc).reverse, value)) } else behavior(nextCount, msg :: acc) } 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)") } } @@ -175,6 +181,9 @@ object StepWise { run(ctx, tail, f((Right(msg) :: acc).reverse, value)) } else behavior(nextCount, Right(msg) :: acc) } onSignal { + case (_, PostStop) ⇒ + // ignore PostStop here + run(ctx, ops, value) case (_, other) ⇒ val nextCount = count + 1 if (nextCount == c) { @@ -190,12 +199,16 @@ object StepWise { 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 } } diff --git a/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala index 09707eebae..7bbb3b2f5d 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TimerSpec.scala @@ -6,7 +6,6 @@ package akka.typed import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicReference import scala.concurrent.duration._ import scala.util.control.NoStackTrace @@ -16,7 +15,6 @@ import akka.typed.scaladsl.AskPattern._ import akka.typed.scaladsl.TimerScheduler import akka.typed.testkit.TestKitSettings import akka.typed.testkit.scaladsl._ -import org.scalatest.concurrent.Eventually.eventually @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TimerSpec extends TypedSpec(""" @@ -96,6 +94,7 @@ class TimerSpec extends TypedSpec(""" probe.expectNoMsg(100.millis) ref ! End + probe.expectMsg(GotPostStop(false)) } def `02 must schedule repeated ticks`(): Unit = { @@ -113,6 +112,7 @@ class TimerSpec extends TypedSpec(""" } ref ! End + probe.expectMsg(GotPostStop(false)) } def `03 must replace timer`(): Unit = { @@ -132,6 +132,7 @@ class TimerSpec extends TypedSpec(""" probe.expectMsg(Tock(2)) ref ! End + probe.expectMsg(GotPostStop(false)) } def `04 must cancel timer`(): Unit = { @@ -147,6 +148,7 @@ class TimerSpec extends TypedSpec(""" probe.expectNoMsg(dilatedInterval + 100.millis) ref ! End + probe.expectMsg(GotPostStop(false)) } def `05 must discard timers from old incarnation after restart, alt 1`(): Unit = { @@ -170,6 +172,7 @@ class TimerSpec extends TypedSpec(""" probe.expectMsg(Tock(2)) ref ! End + probe.expectMsg(GotPostStop(false)) } def `06 must discard timers from old incarnation after restart, alt 2`(): Unit = { @@ -195,6 +198,7 @@ class TimerSpec extends TypedSpec(""" probe.expectMsg(Tock(1)) ref ! End + probe.expectMsg(GotPostStop(false)) } def `07 must cancel timers when stopped from exception`(): Unit = { @@ -210,18 +214,13 @@ class TimerSpec extends TypedSpec(""" def `08 must cancel timers when stopped voluntarily`(): Unit = { val probe = TestProbe[Event]("evt") - val timerRef = new AtomicReference[TimerScheduler[Command]] val behv = Actor.withTimers[Command] { timer ⇒ - timerRef.set(timer) timer.startPeriodicTimer("T", Tick(1), interval) target(probe.ref, timer, 1) } val ref = start(behv) ref ! End - // PostStop is not signalled when stopped voluntarily - eventually { - timerRef.get().isTimerActive("T") should ===(false) - } + probe.expectMsg(GotPostStop(false)) } } diff --git a/akka-typed/src/main/scala/akka/typed/Behavior.scala b/akka-typed/src/main/scala/akka/typed/Behavior.scala index bd1576964c..777248fa59 100644 --- a/akka-typed/src/main/scala/akka/typed/Behavior.scala +++ b/akka-typed/src/main/scala/akka/typed/Behavior.scala @@ -95,15 +95,30 @@ object Behavior { * 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 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]] + /** + * 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. */ @@ -169,7 +184,13 @@ object Behavior { /** * INTERNAL API. */ - private[akka] object StoppedBehavior extends Behavior[Nothing] { + 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" } @@ -211,7 +232,10 @@ object Behavior { /** * Returns true if the given behavior is not stopped. */ - def isAlive[T](behavior: Behavior[T]): Boolean = behavior ne StoppedBehavior + def isAlive[T](behavior: Behavior[T]): Boolean = behavior match { + case _: StoppedBehavior[_] ⇒ false + case _ ⇒ true + } /** * Returns true if the given behavior is the special `unhandled` marker. @@ -243,7 +267,7 @@ object Behavior { 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 StoppedBehavior ⇒ StoppedBehavior.asInstanceOf[Behavior[T]] + case s: StoppedBehavior[T] ⇒ s case EmptyBehavior ⇒ UnhandledBehavior.asInstanceOf[Behavior[T]] case ext: ExtensibleBehavior[T] ⇒ val possiblyDeferredResult = msg match { diff --git a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala index c5a82a8c9e..421b5a676d 100644 --- a/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala +++ b/akka-typed/src/main/scala/akka/typed/MessageAndSignals.scala @@ -42,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. - * - * IMPORTANT NOTE: 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 { diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index f3a6af6bd8..8f1d2dacb4 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -18,6 +18,8 @@ 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 @@ -324,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, ctx) - 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 { diff --git a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala index eceff42b7a..2e0d9d4812 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/BehaviorImpl.scala @@ -139,7 +139,7 @@ import scala.annotation.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)) Behavior.stopped + else if (!Behavior.isAlive(b)) b else { b match { case d: DeferredBehavior[T] ⇒ canonical(Behavior.undefer(d, ctx), ctx) diff --git a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala index b2634e8e0e..f233ffbc5d 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/Restarter.scala @@ -87,7 +87,7 @@ import akka.typed.scaladsl.Actor 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)) Behavior.stopped + else if (!Behavior.isAlive(b)) b else { b match { case d: DeferredBehavior[T] ⇒ canonical(Behavior.undefer(d, ctx), ctx, afterException) diff --git a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala index c8f33b3e6f..fb9d72df37 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/SupervisionMechanics.scala @@ -7,6 +7,8 @@ package internal import scala.util.control.NonFatal import akka.event.Logging import akka.typed.Behavior.{ DeferredBehavior, undefer, validateAsInitial } +import akka.typed.Behavior.StoppedBehavior +import akka.util.OptionVal /** * INTERNAL API @@ -88,10 +90,18 @@ private[typed] trait SupervisionMechanics[T] { /* * The following order is crucial for things to work properly. Only change this if you're very confident and lucky. * - * Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination. + * */ - try if ((a ne null) && !a.isInstanceOf[DeferredBehavior[_]]) Behavior.interpretSignal(a, 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 { diff --git a/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala index 8b3bf4cf7c..a123f5acfd 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/TimerSchedulerImpl.scala @@ -139,13 +139,8 @@ import scala.reflect.ClassTag } true }, - afterMessage = (ctx, msg, b) ⇒ { - // PostStop is not signaled when voluntarily stopped - if (!Behavior.isAlive(b)) - cancelAll() - b - }, - afterSignal = (ctx, sig, b) ⇒ b, // TODO optimize by using more ConstantFun + afterMessage = (ctx, msg, b) ⇒ b, // TODO optimize by using more ConstantFun + afterSignal = (ctx, sig, b) ⇒ b, behavior)(ClassTag(classOf[TimerSchedulerImpl.TimerMsg])) } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala index 272900b979..81e6170473 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala @@ -7,6 +7,7 @@ package adapter import akka.{ actor ⇒ a } import akka.annotation.InternalApi +import akka.util.OptionVal /** * INTERNAL API @@ -39,9 +40,26 @@ import akka.annotation.InternalApi } private def next(b: Behavior[T], msg: Any): Unit = { - if (isUnhandled(b)) unhandled(msg) - behavior = canonicalize(b, behavior, ctx) - if (!isAlive(behavior)) context.stop(self) + 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 { @@ -59,11 +77,26 @@ import akka.annotation.InternalApi override def preStart(): Unit = behavior = validateAsInitial(undefer(behavior, ctx)) - override def preRestart(reason: Throwable, message: Option[Any]): Unit = - next(Behavior.interpretSignal(behavior, ctx, PreRestart), PreRestart) + + 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)) + override def postStop(): Unit = { - next(Behavior.interpretSignal(behavior, ctx, PostStop), PostStop) + 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 } } diff --git a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala index f9f9a9cfb5..ba921ed9d6 100644 --- a/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/javadsl/Actor.scala @@ -110,12 +110,25 @@ object Actor { /** * 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] = 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. */ diff --git a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala index ef81ec29fb..d9c3481677 100644 --- a/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala +++ b/akka-typed/src/main/scala/akka/typed/scaladsl/Actor.scala @@ -137,12 +137,25 @@ object Actor { /** * 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] = 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. */ From f963ed4ae42f44f4892288f1b083c7ebe6d50cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Fri, 5 May 2017 13:22:01 +0200 Subject: [PATCH 40/50] Extensions for Akka typed, #16738 --- .../test/java/akka/typed/ExtensionsTest.java | 81 +++++++++ .../java/akka/typed/javadsl/WatchTest.java | 3 + .../src/test/resources/reference.conf | 4 + .../scala/akka/typed/ExtensionsSpec.scala | 161 ++++++++++++++++++ .../akka/typed/internal/ActorSystemStub.scala | 8 + akka-typed/src/main/resources/reference.conf | 16 +- .../main/scala/akka/typed/ActorSystem.scala | 2 +- .../main/scala/akka/typed/Extensions.scala | 145 ++++++++++++++++ .../akka/typed/internal/ActorSystemImpl.scala | 4 +- .../akka/typed/internal/ExtensionsImpl.scala | 120 +++++++++++++ .../internal/adapter/ActorSystemAdapter.scala | 2 +- 11 files changed, 542 insertions(+), 4 deletions(-) create mode 100644 akka-typed-tests/src/test/java/akka/typed/ExtensionsTest.java create mode 100644 akka-typed-tests/src/test/resources/reference.conf create mode 100644 akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala create mode 100644 akka-typed/src/main/scala/akka/typed/Extensions.scala create mode 100644 akka-typed/src/main/scala/akka/typed/internal/ExtensionsImpl.scala diff --git a/akka-typed-tests/src/test/java/akka/typed/ExtensionsTest.java b/akka-typed-tests/src/test/java/akka/typed/ExtensionsTest.java new file mode 100644 index 0000000000..86b5d78d68 --- /dev/null +++ b/akka-typed-tests/src/test/java/akka/typed/ExtensionsTest.java @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 { + + 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 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 system = ActorSystem.create("loadScalaExtension", Behavior.empty()); + try { + DummyExtension1 instance1 = DummyExtension1.get(system); + DummyExtension1 instance2 = DummyExtension1.get(system); + + assertSame(instance1, instance2); + } finally { + system.terminate(); + } + } + + +} diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java index 710660d19f..c8da9ddfd0 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/WatchTest.java @@ -1,3 +1,6 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ package akka.typed.javadsl; import java.util.concurrent.CompletionStage; diff --git a/akka-typed-tests/src/test/resources/reference.conf b/akka-typed-tests/src/test/resources/reference.conf new file mode 100644 index 0000000000..fd19764c9e --- /dev/null +++ b/akka-typed-tests/src/test/resources/reference.conf @@ -0,0 +1,4 @@ +akka.typed { + # for the akka.actor.ExtensionSpec + library-extensions += "akka.typed.InstanceCountingExtension" +} \ No newline at end of file diff --git a/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala new file mode 100644 index 0000000000..fd56e45bbd --- /dev/null +++ b/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala @@ -0,0 +1,161 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 with ScalaFutures { + + 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 + } + + } +} diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala index 6c54fb7543..2060f5898c 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala @@ -62,4 +62,12 @@ private[typed] class ActorSystemStub(val name: String) 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") } diff --git a/akka-typed/src/main/resources/reference.conf b/akka-typed/src/main/resources/reference.conf index f40d9d7e3e..1d6a4dd975 100644 --- a/akka-typed/src/main/resources/reference.conf +++ b/akka-typed/src/main/resources/reference.conf @@ -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} [] } diff --git a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala index 5c1e7ff738..b0f9092d06 100644 --- a/akka-typed/src/main/scala/akka/typed/ActorSystem.scala +++ b/akka-typed/src/main/scala/akka/typed/ActorSystem.scala @@ -26,7 +26,7 @@ import java.util.Optional */ @DoNotInherit @ApiMayChange -abstract class 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 diff --git a/akka-typed/src/main/scala/akka/typed/Extensions.scala b/akka-typed/src/main/scala/akka/typed/Extensions.scala new file mode 100644 index 0000000000..2fdc2914db --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/Extensions.scala @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 { + * // 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 +} + diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala index ac559394eb..ee425d16c0 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorSystemImpl.scala @@ -76,7 +76,7 @@ private[typed] class ActorSystemImpl[-T]( _ec: Option[ExecutionContext], _userGuardianBehavior: Behavior[T], _userGuardianProps: Props) - extends ActorSystem[T] with ActorRef[T] with ActorRefImpl[T] { + extends ActorSystem[T] with ActorRef[T] with ActorRefImpl[T] with ExtensionsImpl { import ActorSystemImpl._ @@ -236,6 +236,8 @@ private[typed] class ActorSystemImpl[-T]( eventStream.startUnsubscriber(this) eventStream.startDefaultLoggers(this) + loadExtensions() + override def terminate(): Future[Terminated] = { theOneWhoWalksTheBubblesOfSpaceTime.sendSystem(Terminate()) terminationPromise.future diff --git a/akka-typed/src/main/scala/akka/typed/internal/ExtensionsImpl.scala b/akka-typed/src/main/scala/akka/typed/internal/ExtensionsImpl.scala new file mode 100644 index 0000000000..9688b2d222 --- /dev/null +++ b/akka-typed/src/main/scala/akka/typed/internal/ExtensionsImpl.scala @@ -0,0 +1,120 @@ +/** + * Copyright (C) 2017 Lightbend Inc. + */ +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 + } +} \ No newline at end of file diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala index b5acfd07fb..0aa621f49d 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorSystemAdapter.scala @@ -21,7 +21,7 @@ import scala.annotation.unchecked.uncheckedVariance * most circumstances. */ @InternalApi private[typed] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl) - extends ActorSystem[T] with ActorRef[T] with internal.ActorRefImpl[T] { + extends ActorSystem[T] with ActorRef[T] with internal.ActorRefImpl[T] with ExtensionsImpl { import ActorSystemAdapter._ import ActorRefAdapter.sendSystemMessage From cf7dd8b75297dd9e14704d7382f883a1b248770c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Fri, 5 May 2017 13:23:57 +0200 Subject: [PATCH 41/50] Factory methods for dispatcher selector props #22828 --- .../akka/typed/DispatcherSelectorTest.java | 16 ++++++ ...oymentConfigSpec.scala => PropsSpec.scala} | 6 +-- .../src/main/scala/akka/typed/Props.scala | 51 ++++++++++++++++--- 3 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 akka-typed-tests/src/test/java/akka/typed/DispatcherSelectorTest.java rename akka-typed-tests/src/test/scala/akka/typed/{DeploymentConfigSpec.scala => PropsSpec.scala} (90%) diff --git a/akka-typed-tests/src/test/java/akka/typed/DispatcherSelectorTest.java b/akka-typed-tests/src/test/java/akka/typed/DispatcherSelectorTest.java new file mode 100644 index 0000000000..83bcb69381 --- /dev/null +++ b/akka-typed-tests/src/test/java/akka/typed/DispatcherSelectorTest.java @@ -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); + +} diff --git a/akka-typed-tests/src/test/scala/akka/typed/DeploymentConfigSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/PropsSpec.scala similarity index 90% rename from akka-typed-tests/src/test/scala/akka/typed/DeploymentConfigSpec.scala rename to akka-typed-tests/src/test/scala/akka/typed/PropsSpec.scala index 36ddf1dccc..72abfd4d61 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/DeploymentConfigSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/PropsSpec.scala @@ -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(_))) } diff --git a/akka-typed/src/main/scala/akka/typed/Props.scala b/akka-typed/src/main/scala/akka/typed/Props.scala index a0eff74353..fcaa9bba97 100644 --- a/akka-typed/src/main/scala/akka/typed/Props.scala +++ b/akka-typed/src/main/scala/akka/typed/Props.scala @@ -164,17 +164,54 @@ private[akka] case object EmptyProps extends Props { } /** - * Subclasses of this type describe which thread pool shall be used to run - * the actor to which this configuration is applied. + * 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. - * - * INTERNAL API */ -@DoNotInherit -@InternalApi -private[akka] sealed trait DispatcherSelector extends Props +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. From e56ac943a2fd504ef720c23478d718ce218e8228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Fri, 5 May 2017 14:04:32 +0200 Subject: [PATCH 42/50] Moved typed inbox to testkit #22753 --- .../main/scala/akka/typed/testkit}/Inbox.scala | 15 +++++++-------- .../src/test/scala/akka/typed/BehaviorSpec.scala | 2 +- .../src/test/scala/akka/typed/DeferredSpec.scala | 4 +--- .../src/test/scala/akka/typed/RestarterSpec.scala | 5 +++-- .../src/test/scala/akka/typed/TypedSpec.scala | 3 ++- .../akka/typed/internal/ActorSystemSpec.scala | 1 + .../akka/typed/internal/ActorSystemStub.scala | 3 +++ .../akka/typed/internal/EventStreamSpec.scala | 1 + .../akka/typed/patterns/ReceptionistSpec.scala | 2 +- 9 files changed, 20 insertions(+), 16 deletions(-) rename {akka-typed/src/main/scala/akka/typed => akka-typed-testkit/src/main/scala/akka/typed/testkit}/Inbox.scala (84%) diff --git a/akka-typed/src/main/scala/akka/typed/Inbox.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Inbox.scala similarity index 84% rename from akka-typed/src/main/scala/akka/typed/Inbox.scala rename to akka-typed-testkit/src/main/scala/akka/typed/testkit/Inbox.scala index 233a12713e..42cd305b50 100644 --- a/akka-typed/src/main/scala/akka/typed/Inbox.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/Inbox.scala @@ -1,16 +1,15 @@ /** * Copyright (C) 2014-2017 Lightbend Inc. */ -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 diff --git a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala index c136736d6a..3b2adaf8a9 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/BehaviorSpec.scala @@ -10,7 +10,7 @@ import akka.japi.pf.{ FI, PFBuilder } import java.util.function.{ Function ⇒ F1 } import akka.Done -import akka.typed.testkit.EffectfulActorContext +import akka.typed.testkit.{ EffectfulActorContext, Inbox } class BehaviorSpec extends TypedSpec { diff --git a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala index b61b975302..1fc798c28f 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala @@ -6,12 +6,10 @@ 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 -import akka.typed.testkit.TestKitSettings +import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings } import akka.typed.testkit.scaladsl._ @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) diff --git a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala index dbb9845196..526a1996f5 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/RestarterSpec.scala @@ -5,11 +5,12 @@ package akka.typed import scala.concurrent.duration._ import akka.typed.scaladsl.Actor._ -import akka.typed.testkit.EffectfulActorContext +import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings } + import scala.util.control.NoStackTrace -import akka.typed.testkit.TestKitSettings import akka.typed.testkit.scaladsl._ import akka.typed.scaladsl.AskPattern._ + import scala.concurrent.Await @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index 5fc3c091e0..79cdf1c3fe 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -29,8 +29,9 @@ import org.junit.runner.RunWith import scala.util.control.NonFatal import org.scalatest.exceptions.TestFailedException import akka.typed.scaladsl.AskPattern + import scala.util.control.NoStackTrace -import akka.typed.testkit.TestKitSettings +import akka.typed.testkit.{ Inbox, TestKitSettings } /** * Helper class for writing tests for typed Actors with ScalaTest. diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala index 4f2c0120b9..003358eec1 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemSpec.scala @@ -7,6 +7,7 @@ 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 diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala index 2060f5898c..ae080526c5 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/ActorSystemStub.scala @@ -5,9 +5,12 @@ 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) diff --git a/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala index 9f40b3e7a1..3b8dc64acc 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/internal/EventStreamSpec.scala @@ -8,6 +8,7 @@ import akka.Done import akka.event.Logging._ import akka.typed.scaladsl.Actor._ import akka.typed.scaladsl.AskPattern._ +import akka.typed.testkit.Inbox import com.typesafe.config.ConfigFactory import org.scalatest.concurrent.Eventually import org.scalatest.concurrent.PatienceConfiguration.Timeout diff --git a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala index ad80d1c603..e7d54af006 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/patterns/ReceptionistSpec.scala @@ -10,7 +10,7 @@ import scala.concurrent.duration._ import akka.typed._ import akka.typed.scaladsl.Actor import akka.typed.scaladsl.Actor._ -import akka.typed.testkit.{ Effect, EffectfulActorContext } +import akka.typed.testkit.{ Effect, EffectfulActorContext, Inbox } class ReceptionistSpec extends TypedSpec { From 0426154ad96c2a0e5ee4f0b69a383fc2c4025bf6 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 5 May 2017 14:40:10 +0200 Subject: [PATCH 43/50] add Java docs for Akka Typed * still a few more things to be ported from the scala docs, good enough for today --- akka-docs/rst/java/index-actors.rst | 1 + akka-docs/rst/java/typed.rst | 248 ++++++++++++++++++ akka-docs/rst/scala/typed.rst | 29 +- .../test/java/jdocs/akka/typed/IntroTest.java | 30 ++- 4 files changed, 293 insertions(+), 15 deletions(-) create mode 100644 akka-docs/rst/java/typed.rst diff --git a/akka-docs/rst/java/index-actors.rst b/akka-docs/rst/java/index-actors.rst index 6f1f0a6895..ab68fc7e5c 100644 --- a/akka-docs/rst/java/index-actors.rst +++ b/akka-docs/rst/java/index-actors.rst @@ -5,6 +5,7 @@ Actors :maxdepth: 2 actors + typed fault-tolerance dispatchers mailboxes diff --git a/akka-docs/rst/java/typed.rst b/akka-docs/rst/java/typed.rst new file mode 100644 index 0000000000..105a6916ad --- /dev/null +++ b/akka-docs/rst/java/typed.rst @@ -0,0 +1,248 @@ +.. _typed-java: + +########## +Akka Typed +########## + +.. warning:: + + This module is currently marked as :ref:`may change ` 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 :ref:`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: + +.. includecode:: ../../../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! + +.. includecode:: ../../../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 :class:`Greet` type contains not only the information of whom to +greet, it also holds an :class:`ActorRef` that the sender of the message +supplies so that the :class:`HelloWorld` Actor can send back the confirmation +message. + +The behavior of the Actor is defined as the :meth:`greeter` value with the help +of the :meth:`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 :class:`Same`. + +The type of the messages handled by this behavior is declared to be of class +: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 :class:`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`` 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: + +.. includecode:: ../../../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 :class:`CompletionStage`. + +Note that the :class:`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 :class:`ActorRef`, which is the +``replyTo`` parameter of the ``Greet`` message, which means that when sending +the reply message to that :class:`ActorRef` the message that fulfills the +:class:`CompletionStage` can only be of type :class:`Greeted`. + +We use this here to send the :class:`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`_ 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: + +.. _`Actor Model`: http://en.wikipedia.org/wiki/Actor_model + + 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: + +.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#chatroom-protocol + +Initially the client Actors only get access to an ``ActorRef`` +which allows them to make the first step. Once a client’s session has been +established it gets a :class:`SessionGranted` message that contains a ``handle`` to +unlock the next protocol step, posting messages. The :class:`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 +:class:`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: + +.. includecode:: ../../../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 :class:`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 +:class:`ActorRef` that will be used to post messages. In this case we want to +create a very simple Actor that just repackages the :class:`PostMessage` +command into a :class:`PostSessionMessage` command which also includes the +screen name. Such a wrapper Actor can be created by using the +:meth:`spawnAdapter` method on the :class:`ActorContext`, so that we can then +go on to reply to the client with the :class:`SessionGranted` result. + +The behavior that we declare here can handle both subtypes of :class:`Command`. +:class:`GetSession` has been explained already and the +:class:`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 +:class:`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 :class:`GetSession` protocol to include +authentication information to further secure this). Therefore :class:`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 :class:`PostMessage` is +removed and all clients just get an :class:`ActorRef` 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 +:class:`ActorRef` is contravariant in its type parameter, meaning that we +can use a :class:`ActorRef` wherever an +:class:`ActorRef` is needed—this makes sense because the +former simply speaks more languages than the latter. The opposite would be +problematic, so passing an :class:`ActorRef` where +:class:`ActorRef` is required will lead to a type error. + +Trying it out +------------- + +TODO this section has not been written yet + +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 ` 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 :class:`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 +:class:`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 :meth:`tap`, or +:meth:`widened` combinators; nothing about these is special or internal, new +combinators can be written as external libraries or tailor-made for each project. diff --git a/akka-docs/rst/scala/typed.rst b/akka-docs/rst/scala/typed.rst index 042726bad1..aebf86d0df 100644 --- a/akka-docs/rst/scala/typed.rst +++ b/akka-docs/rst/scala/typed.rst @@ -30,7 +30,7 @@ supplies so that the :class:`HelloWorld` Actor can send back the confirmation message. The behavior of the Actor is defined as the :meth:`greeter` value with the help -of the :class:`Immutable` behavior constructor. This constructor is called +of the :meth:`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 @@ -43,7 +43,7 @@ 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 :class:`HelloWorld` Actor send a message to another -Actor, which is done using the ``!`` operator (pronounced “tell”). Since the +Actor, which is done using the :meth:`tell` method. 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. @@ -223,7 +223,7 @@ 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 -:class:`Same`, as above, but another special value :class:`Stopped`. +:meth:`same`, as above, but another special value :meth:`stopped`. Since :class:`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 :class:`SessionGranted` we may also receive a @@ -244,19 +244,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 :class:`Immutable` behavior decorator. The -provided ``signal`` function will be invoked for signals (subclasses of :class:`Signal`) -or the ``mesg`` function for user messages. +particular one using the :meth:`immutable` behavior decorator. The +provided ``onSignal`` function will be invoked for signals (subclasses of :class:`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 :class:`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 :class:`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 -:class:`Props` we just await its termination. +:class:`Behavior` we just await its termination. Status of this Project and Relation to Akka Actors ================================================== @@ -292,7 +294,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 :class:`And`, -:class:`Or`, :class:`Widened`, :class:`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 :meth:`tap`, or +:meth:`widen` combinators; nothing about these is special or internal, new +combinators can be written as external libraries or tailor-made for each project. diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java index 0f23f89ecb..5aebe73c49 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -5,16 +5,27 @@ package jdocs.akka.typed; //#imports import akka.typed.ActorRef; +import akka.typed.ActorSystem; import akka.typed.Behavior; 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; public class IntroTest { //#hello-world-actor - public static class HelloWorld { + 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 replyTo; @@ -41,6 +52,23 @@ public class IntroTest { } //#hello-world-actor + public static void main(String[] args) { + //#hello-world + final ActorSystem system = + ActorSystem.create("hello", HelloWorld.greeter); + + final CompletionStage reply = + AskPattern.ask(system, + (ActorRef 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 From 902d0d0b3cb19a978b1a6617b35b5d774d420f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Mon, 8 May 2017 13:17:59 +0200 Subject: [PATCH 44/50] Patience config for typed tests #22859 --- .../src/test/scala/akka/typed/ExtensionsSpec.scala | 2 +- .../src/test/scala/akka/typed/TypedSpec.scala | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala index fd56e45bbd..0899dfb337 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/ExtensionsSpec.scala @@ -44,7 +44,7 @@ object InstanceCountingExtension extends ExtensionId[DummyExtension1] { } } -class ExtensionsSpec extends TypedSpecSetup with ScalaFutures { +class ExtensionsSpec extends TypedSpecSetup { object `The extensions subsystem` { diff --git a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala index 79cdf1c3fe..e4460568b9 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/TypedSpec.scala @@ -27,17 +27,22 @@ import org.scalactic.CanEqual import org.junit.runner.RunWith import scala.util.control.NonFatal -import org.scalatest.exceptions.TestFailedException 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. @@ -89,7 +94,6 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup { } implicit val timeout = setTimeout - implicit val patience = PatienceConfig(3.seconds) implicit def scheduler = nativeSystem.scheduler override def afterAll(): Unit = { From 2e4eb42a34223a89b0717e7f716e79c7c8672569 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Wed, 10 May 2017 15:30:54 +0200 Subject: [PATCH 45/50] complete the ChatRoom java example --- akka-docs/rst/java/typed.rst | 38 +++++++++- .../test/java/jdocs/akka/typed/IntroTest.java | 71 ++++++++++++++++--- .../scala/docs/akka/typed/IntroSpec.scala | 2 +- 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/akka-docs/rst/java/typed.rst b/akka-docs/rst/java/typed.rst index 105a6916ad..61068e8c60 100644 --- a/akka-docs/rst/java/typed.rst +++ b/akka-docs/rst/java/typed.rst @@ -207,7 +207,43 @@ problematic, so passing an :class:`ActorRef` where Trying it out ------------- -TODO this section has not been written yet +In order to see this chat room in action we need to write a client Actor that can use it: + +.. includecode:: ../../../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 +:meth:`same`, as above, but another special value :meth:`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: + +.. includecode:: ../../../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 :meth:`immutable` behavior decorator. The +provided ``onSignal`` function will be invoked for signals (subclasses of :class:`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 :class:`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 ================================================== diff --git a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java index 5aebe73c49..5d59744b7d 100644 --- a/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java +++ b/akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java @@ -7,6 +7,7 @@ package jdocs.akka.typed; 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; @@ -16,6 +17,8 @@ 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 { @@ -130,29 +133,75 @@ public class IntroTest { } private static Behavior chatRoom(List> sessions) { - return Actor.immutable((ctx, msg) -> { - if (msg instanceof GetSession) { - GetSession getSession = (GetSession) msg; + return Actor.immutable(Command.class) + .onMessage(GetSession.class, (ctx, getSession) -> { ActorRef wrapper = ctx.spawnAdapter(p -> new PostSessionMessage(getSession.screenName, p.message)); getSession.replyTo.tell(new SessionGranted(wrapper)); - // TODO mutable collection :( - List> newSessions = new ArrayList>(sessions); + List> newSessions = + new ArrayList>(sessions); newSessions.add(getSession.replyTo); return chatRoom(newSessions); - } else if (msg instanceof PostSessionMessage) { - PostSessionMessage post = (PostSessionMessage) msg; + }) + .onMessage(PostSessionMessage.class, (ctx, post) -> { MessagePosted mp = new MessagePosted(post.screenName, post.message); sessions.forEach(s -> s.tell(mp)); return Actor.same(); - } else { - return Actor.unhandled(); - } - }); + }) + .build(); } //#chatroom-behavior } //#chatroom-actor + //#chatroom-gabbler + public static abstract class Gabbler { + private Gabbler() { + } + + public static Behavior 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 main = Actor.deferred(ctx -> { + ActorRef chatRoom = + ctx.spawn(ChatRoom.behavior(), "chatRoom"); + ActorRef 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 system = + ActorSystem.create("ChatRoomDemo", main); + + Await.result(system.whenTerminated(), Duration.create(3, TimeUnit.SECONDS)); + //#chatroom-main + } + } diff --git a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index eeb01e6ce5..65214cacfe 100644 --- a/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala +++ b/akka-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -134,7 +134,7 @@ class IntroSpec extends TypedSpec { } val system = ActorSystem("ChatRoomDemo", main) - Await.result(system.whenTerminated, 1.second) + Await.result(system.whenTerminated, 3.seconds) //#chatroom-main } From 8d9152853b6c0bb9b44fca0a5ee0d1b689018cfb Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sat, 13 May 2017 14:01:37 +0200 Subject: [PATCH 46/50] Actor.deferred should stop when factory yields stopped, #22934 --- .../test/scala/akka/typed/DeferredSpec.scala | 42 ++++++++++++++----- .../typed/internal/adapter/ActorAdapter.scala | 8 +++- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala index 1fc798c28f..1590ada735 100644 --- a/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala +++ b/akka-typed-tests/src/test/scala/akka/typed/DeferredSpec.scala @@ -79,14 +79,36 @@ class DeferredSpec extends TypedSpec { def `must stop when exception from factory`(): Unit = { val probe = TestProbe[Event]("evt") - val behv = Actor.deferred[Command] { _ ⇒ - probe.ref ! Started - throw new RuntimeException("simulated exc from factory") with NoStackTrace + 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 + } } - val ref = start(behv) + start(behv) probe.expectMsg(Started) - ref ! Ping - probe.expectNoMsg(100.millis) + 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 = { @@ -136,10 +158,10 @@ class DeferredSpec extends TypedSpec { } - object `A Restarter (stubbed, native)` extends StubbedTests with NativeSystem - object `A Restarter (stubbed, adapted)` extends StubbedTests with AdaptedSystem + object `A DeferredBehavior (stubbed, native)` extends StubbedTests with NativeSystem + object `A DeferredBehavior (stubbed, adapted)` extends StubbedTests with AdaptedSystem - object `A Restarter (real, native)` extends RealTests with NativeSystem - object `A Restarter (real, adapted)` extends RealTests with AdaptedSystem + object `A DeferredBehavior (real, native)` extends RealTests with NativeSystem + object `A DeferredBehavior (real, adapted)` extends RealTests with AdaptedSystem } diff --git a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala index 81e6170473..9056334ade 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/adapter/ActorAdapter.scala @@ -75,16 +75,20 @@ import akka.util.OptionVal a.SupervisorStrategy.Stop } - override def preStart(): Unit = + 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 = + override def postRestart(reason: Throwable): Unit = { behavior = validateAsInitial(undefer(behavior, ctx)) + if (!isAlive(behavior)) context.stop(self) + } override def postStop(): Unit = { behavior match { From a0a5490223b1392bc4e7d941f97eee0dd2acc71a Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sat, 13 May 2017 15:08:27 +0200 Subject: [PATCH 47/50] use the right name in StubbedActorContext.spawnAdapter --- .../src/main/scala/akka/typed/testkit/StubbedActorContext.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala index dc03597faf..4bbc5c361b 100644 --- a/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala +++ b/akka-typed-testkit/src/main/scala/akka/typed/testkit/StubbedActorContext.scala @@ -71,7 +71,7 @@ class StubbedActorContext[T]( /** * INTERNAL API */ - @InternalApi private[akka] def internalSpawnAdapter[U](f: U ⇒ T, _name: String): ActorRef[U] = { + @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 From d14fb2c2d65a07cf36520a05a752fe28a59a863a Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 16 May 2017 07:56:32 +0200 Subject: [PATCH 48/50] abstract class SupervisorStrategy for Java API, #22961 * missing static forwarders for Java API for Scala 2.11 when using trait + object --- .../java/akka/typed/javadsl/ActorCompile.java | 30 +++++++++++++++++-- .../scala/akka/typed/SupervisorStrategy.scala | 4 +-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java index d81a5e7d42..9a88567812 100644 --- a/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java +++ b/akka-typed-tests/src/test/java/akka/typed/javadsl/ActorCompile.java @@ -11,6 +11,7 @@ import static akka.typed.javadsl.Actor.*; import java.util.concurrent.TimeUnit; import scala.concurrent.duration.Duration; +@SuppressWarnings("unused") public class ActorCompile { interface MyMsg {} @@ -53,7 +54,6 @@ public class ActorCompile { if (msg2 instanceof MyMsgB) { ((MyMsgA) msg).replyTo.tell(((MyMsgB) msg2).greeting); - @SuppressWarnings("unused") ActorRef adapter = ctx2.spawnAdapter(s -> new MyMsgB(s.toUpperCase())); } return same(); @@ -79,12 +79,38 @@ public class ActorCompile { @Override public Behavior receiveMessage(ActorContext ctx, MyMsg msg) throws Exception { - @SuppressWarnings("unused") ActorRef 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 behv = + Actor.restarter(RuntimeException.class, strategy1, + Actor.restarter(IllegalStateException.class, + strategy6, Actor.ignore())); + } + } diff --git a/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala b/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala index be7a5e1e77..86d04177d1 100644 --- a/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala +++ b/akka-typed/src/main/scala/akka/typed/SupervisorStrategy.scala @@ -101,13 +101,13 @@ object SupervisorStrategy { } } -sealed trait SupervisorStrategy { +sealed abstract class SupervisorStrategy { def loggingEnabled: Boolean def withLoggingEnabled(on: Boolean): SupervisorStrategy } -sealed trait BackoffSupervisorStrategy extends SupervisorStrategy { +sealed abstract class BackoffSupervisorStrategy extends SupervisorStrategy { def resetBackoffAfter: FiniteDuration /** From d4c5b43c9d24809cc61db631b87991cfa9578822 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 22 May 2017 14:19:20 +0200 Subject: [PATCH 49/50] =doc small adjustments for merging typed docs to master --- akka-docs/src/main/paradox/scala/typed.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/akka-docs/src/main/paradox/scala/typed.md b/akka-docs/src/main/paradox/scala/typed.md index f15b32af1b..e01b045b1b 100644 --- a/akka-docs/src/main/paradox/scala/typed.md +++ b/akka-docs/src/main/paradox/scala/typed.md @@ -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 @@ -33,7 +33,7 @@ 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 :class:`Same`. +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 @@ -54,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. @@ -150,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 @@ -167,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 @@ -208,7 +208,7 @@ 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 @@ -218,7 +218,7 @@ running behavior into the terminated state. This is why here we do not return 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 -:class:`SessionDenied` event. +`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 @@ -227,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 From 9bcc7dbd35d99b7f69a6ccb1b2af9a66a8ebabbe Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 22 May 2017 16:51:29 +0200 Subject: [PATCH 50/50] +doc akka typed java docs made into paradox and mima fixed --- akka-docs/rst/java/index-actors.rst | 19 -- .../src/main/paradox/java/index-actors.md | 3 +- .../main/paradox/java/typed.md} | 191 +++++++++--------- project/MiMa.scala | 114 +++++------ 4 files changed, 141 insertions(+), 186 deletions(-) delete mode 100644 akka-docs/rst/java/index-actors.rst rename akka-docs/{rst/java/typed.rst => src/main/paradox/java/typed.md} (59%) diff --git a/akka-docs/rst/java/index-actors.rst b/akka-docs/rst/java/index-actors.rst deleted file mode 100644 index ab68fc7e5c..0000000000 --- a/akka-docs/rst/java/index-actors.rst +++ /dev/null @@ -1,19 +0,0 @@ -Actors -====== - -.. toctree:: - :maxdepth: 2 - - actors - typed - fault-tolerance - dispatchers - mailboxes - routing - fsm - persistence - persistence-schema-evolution - persistence-query - persistence-query-leveldb - testing - typed-actors diff --git a/akka-docs/src/main/paradox/java/index-actors.md b/akka-docs/src/main/paradox/java/index-actors.md index 2cfc6bb1ae..6b7db6aa6b 100644 --- a/akka-docs/src/main/paradox/java/index-actors.md +++ b/akka-docs/src/main/paradox/java/index-actors.md @@ -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) -@@@ \ No newline at end of file +@@@ diff --git a/akka-docs/rst/java/typed.rst b/akka-docs/src/main/paradox/java/typed.md similarity index 59% rename from akka-docs/rst/java/typed.rst rename to akka-docs/src/main/paradox/java/typed.md index 61068e8c60..fc51f28ca5 100644 --- a/akka-docs/rst/java/typed.rst +++ b/akka-docs/src/main/paradox/java/typed.md @@ -1,50 +1,48 @@ -.. _typed-java: +# Akka Typed -########## -Akka Typed -########## +@@@ warning -.. warning:: - - This module is currently marked as :ref:`may change ` in the sense +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 :ref:`actor-systems` (and following chapters) Actors are about +@@@ + +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: -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#imports +@@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! -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#hello-world-actor +@@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 :class:`Greet` type contains not only the information of whom to -greet, it also holds an :class:`ActorRef` that the sender of the message -supplies so that the :class:`HelloWorld` Actor can send back the confirmation +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 :meth:`greeter` value with the help -of the :meth:`immutable` behavior constructor. This constructor is called +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 :class:`Same`. +update any state, so we return `Same`. The type of the messages handled by this behavior is declared to be of class -: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`` +`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 :class:`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`` the +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` the compiler will only permit us to send messages of this type, other usage will not be accepted. @@ -52,28 +50,28 @@ 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. +wrapped scope—the `HelloWorld` class. Now we want to try out this Actor, so we must start an ActorSystem to host it: -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#hello-world +@@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. +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 :class:`CompletionStage`. +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 :class:`CompletionStage` that is returned by the “ask” operation is +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 :class:`ActorRef`, which is the -``replyTo`` parameter of the ``Greet`` message, which means that when sending -the reply message to that :class:`ActorRef` the message that fulfills the -:class:`CompletionStage` can only be of type :class:`Greeted`. +the type information that is part of the message protocol: the `ask` operator +takes as argument a function that pass an `ActorRef`, 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 :class:`Greet` command to the Actor and when the +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. @@ -82,21 +80,16 @@ 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 -====================== +## A Little Bit of Theory -The `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: - -.. _`Actor Model`: http://en.wikipedia.org/wiki/Actor_model +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. @@ -142,24 +135,23 @@ 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 -====================== +## 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: -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#chatroom-protocol +@@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`` +Initially the client Actors only get access to an `ActorRef` which allows them to make the first step. Once a client’s session has been -established it gets a :class:`SessionGranted` message that contains a ``handle`` to -unlock the next protocol step, posting messages. The :class:`PostMessage` +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 -:class:`MessagePosted` events can be sent to it. +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 @@ -167,55 +159,54 @@ 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: -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#chatroom-behavior +@@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 :class:`GetSession` command comes in we add that client to the +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 -:class:`ActorRef` that will be used to post messages. In this case we want to -create a very simple Actor that just repackages the :class:`PostMessage` -command into a :class:`PostSessionMessage` command which also includes the +`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 -:meth:`spawnAdapter` method on the :class:`ActorContext`, so that we can then -go on to reply to the client with the :class:`SessionGranted` result. +`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 :class:`Command`. -:class:`GetSession` has been explained already and the -:class:`PostSessionMessage` commands coming from the wrapper Actors will +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 -:class:`PostSessionMessage` commands to arbitrary clients, we reserve that +`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 :class:`GetSession` protocol to include -authentication information to further secure this). Therefore :class:`PostSessionMessage` -has ``private`` visibility and can't be created outside the actor. +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 :class:`PostMessage` is -removed and all clients just get an :class:`ActorRef` to +screen name then we could change the protocol such that `PostMessage` is +removed and all clients just get an `ActorRef` 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 -:class:`ActorRef` is contravariant in its type parameter, meaning that we -can use a :class:`ActorRef` wherever an -:class:`ActorRef` is needed—this makes sense because the +`ctx.getSelf()`. The type-checks work out in that case because +`ActorRef` is contravariant in its type parameter, meaning that we +can use a `ActorRef` wherever an +`ActorRef` is needed—this makes sense because the former simply speaks more languages than the latter. The opposite would be -problematic, so passing an :class:`ActorRef` where -:class:`ActorRef` is required will lead to a type error. +problematic, so passing an `ActorRef` where +`ActorRef` is required will lead to a type error. -Trying it out -------------- +### Trying it out In order to see this chat room in action we need to write a client Actor that can use it: -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#chatroom-gabbler +@@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 -:meth:`same`, as above, but another special value :meth:`stopped`. +`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 @@ -224,61 +215,59 @@ 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: -.. includecode:: ../../../akka-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java#chatroom-main +@@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 +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 +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 :meth:`immutable` behavior decorator. The -provided ``onSignal`` function will be invoked for signals (subclasses of :class:`Signal`) -or the ``onMessage`` 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 is created using `Actor.deferred`, which is like a factory for a behavior. +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 :class:`Terminated` event due to having -called ``ctx.watch`` for it. This allows us to shut down the Actor system: when +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 -================================================== +## 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 ` for multiple major releases of Akka and the -plain ``akka.actor.Actor`` will not be deprecated or go away anytime soon. +@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 ----------------- +### Main Differences -The most prominent difference is the removal of the ``sender()`` functionality. +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 :class:`Actor` trait. In +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 -:class:`ActorContext` as an argument during processing and the lifecycle hooks +`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 :meth:`tap`, or -:meth:`widened` combinators; nothing about these is special or internal, new +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. diff --git a/project/MiMa.scala b/project/MiMa.scala index 44499e4a85..7b8135ef97 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -56,12 +56,12 @@ object MiMa extends AutoPlugin { if (!akka242NewArtifacts.contains(projectName)) akka24NoStreamVersions else Seq.empty } ++ akka24StreamVersions ++ akka24WithScala212 ++ akka25Versions - - case "2.12" => + + case "2.12" => akka24WithScala212 ++ akka25Versions } } - + val akka25PromotedArtifacts = Set( "akka-distributed-data" ) @@ -71,7 +71,7 @@ object MiMa extends AutoPlugin { val adjustedProjectName = if (akka25PromotedArtifacts(projectName) && v.startsWith("2.4")) projectName + "-experimental" - else + else projectName organization %% adjustedProjectName % v }.toSet @@ -99,7 +99,7 @@ object MiMa extends AutoPlugin { val bcIssuesBetween24and25 = Seq( // ##22269 GSet as delta-CRDT ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.GSet.this"), // constructor supplied by companion object - + // # 18262 embed FJP, Mailbox extends ForkJoinTask ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.dispatch.ForkJoinExecutorConfigurator#ForkJoinExecutorServiceFactory.threadFactory"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.dispatch.ForkJoinExecutorConfigurator#ForkJoinExecutorServiceFactory.this"), @@ -118,7 +118,7 @@ object MiMa extends AutoPlugin { // #21875 delta-CRDT ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.GCounter.this"), - + // #22188 ORSet delta-CRDT ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.ORSet.this"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.protobuf.SerializationSupport.versionVectorToProto"), @@ -126,7 +126,7 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.protobuf.SerializationSupport.versionVectorFromBinary"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.cluster.ddata.protobuf.ReplicatedDataSerializer.versionVectorToProto"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.ddata.protobuf.ReplicatedDataSerializer.versionVectorFromProto"), - + // #22141 sharding minCap ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.DDataShardCoordinator.updatingStateTimeout"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.DDataShardCoordinator.waitingForStateTimeout"), @@ -136,7 +136,7 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.pattern.CircuitBreaker#State.callThrough"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.pattern.CircuitBreaker#State.invoke"), - // #21423 Remove deprecated metrics + // #21423 Remove deprecated metrics ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterReadView.clusterMetrics"), ProblemFilters.exclude[MissingClassProblem]("akka.cluster.InternalClusterAction$MetricsTick$"), ProblemFilters.exclude[MissingClassProblem]("akka.cluster.MetricsCollector"), @@ -209,13 +209,13 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[MissingClassProblem]("akka.cluster.protobuf.msg.ClusterMessages$NodeMetrics$Metric"), ProblemFilters.exclude[MissingClassProblem]("akka.cluster.protobuf.msg.ClusterMessages$NodeMetrics$Metric$Builder"), ProblemFilters.exclude[MissingClassProblem]("akka.cluster.protobuf.msg.ClusterMessages$NodeMetrics$Number$Builder"), - + // #22154 Sharding remembering entities with ddata, internal actors FilterAnyProblemStartingWith("akka.cluster.sharding.Shard"), FilterAnyProblemStartingWith("akka.cluster.sharding.PersistentShard"), FilterAnyProblemStartingWith("akka.cluster.sharding.ClusterShardingGuardian"), FilterAnyProblemStartingWith("akka.cluster.sharding.ShardRegion"), - + // #21647 pruning FilterAnyProblemStartingWith("akka.cluster.ddata.PruningState"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.RemovedNodePruning.modifiedByNodes"), @@ -230,7 +230,7 @@ object MiMa extends AutoPlugin { // #21537 coordinated shutdown ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterCoreDaemon.removed"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.convergence"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.convergence"), //#21717 Improvements to AbstractActor API FilterAnyProblemStartingWith("akka.japi.pf.ReceiveBuilder"), @@ -244,8 +244,8 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[MissingTypesProblem]("akka.routing.RoutedActorCell"), ProblemFilters.exclude[MissingTypesProblem]("akka.routing.ResizablePoolCell"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.persistence.AbstractPersistentActor.createReceiveRecover"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.persistence.AbstractPersistentActor.createReceive"), - + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.persistence.AbstractPersistentActor.createReceive"), + // #21423 removal of deprecated stages (in 2.5.x) ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.javadsl.Source.transform"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.javadsl.SubSource.transform"), @@ -307,20 +307,20 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.ActorSystem.isTerminated"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.ActorSystem.awaitTermination"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.ActorSystem.awaitTermination"), - + // #21423 remove deprecated ActorPath.ElementRegex ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.ActorPath.ElementRegex"), - + // #21423 remove some deprecated event bus classes ProblemFilters.exclude[MissingClassProblem]("akka.event.ActorClassification"), ProblemFilters.exclude[MissingClassProblem]("akka.event.EventStream$"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.event.EventStream.this"), ProblemFilters.exclude[MissingClassProblem]("akka.event.japi.ActorEventBus"), - + // #21423 remove deprecated util.Crypt ProblemFilters.exclude[MissingClassProblem]("akka.util.Crypt"), ProblemFilters.exclude[MissingClassProblem]("akka.util.Crypt$"), - + // #21423 removal of deprecated serializer constructors (in 2.5.x) ProblemFilters.exclude[DirectMissingMethodProblem]("akka.remote.serialization.ProtobufSerializer.this"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.remote.serialization.MessageContainerSerializer.this"), @@ -328,11 +328,11 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[DirectMissingMethodProblem]("akka.serialization.JavaSerializer.this"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.serialization.ByteArraySerializer.this"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.protobuf.ClusterMessageSerializer.this"), - + // #21423 removal of deprecated constructor in PromiseActorRef ProblemFilters.exclude[DirectMissingMethodProblem]("akka.pattern.PromiseActorRef.this"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.pattern.PromiseActorRef.apply"), - + // #21423 remove deprecated methods in routing ProblemFilters.exclude[DirectMissingMethodProblem]("akka.routing.Pool.nrOfInstances"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.routing.Group.paths"), @@ -343,7 +343,7 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[DirectMissingMethodProblem]("akka.remote.routing.RemoteRouterConfig.nrOfInstances"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.routing.ClusterRouterGroup.paths"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.routing.ClusterRouterPool.nrOfInstances"), - + // #21423 remove deprecated persist method (persistAll) // This might filter changes to the ordinary persist method also, but not much to do about that ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.persistence.UntypedPersistentActor.persist"), @@ -365,13 +365,13 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.sharding.PersistentShardCoordinator.persistAsync"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.sharding.RemoveInternalClusterShardingData#RemoveOnePersistenceId.persist"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.sharding.RemoveInternalClusterShardingData#RemoveOnePersistenceId.persistAsync"), - + // #21423 remove deprecated ARRAY_OF_BYTE_ARRAY ProblemFilters.exclude[DirectMissingMethodProblem]("akka.remote.serialization.ProtobufSerializer.ARRAY_OF_BYTE_ARRAY"), - + // #21423 remove deprecated constructor in DeadlineFailureDetector ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.remote.DeadlineFailureDetector.this"), - + // #21423 removal of deprecated `PersistentView` (in 2.5.x) ProblemFilters.exclude[MissingClassProblem]("akka.persistence.Update"), ProblemFilters.exclude[MissingClassProblem]("akka.persistence.Update$"), @@ -889,7 +889,7 @@ object MiMa extends AutoPlugin { // Interpreter internals change ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.stream.stage.GraphStageLogic.portToConn"), - // #20994 adding new decode method, since we're on JDK7+ now + // #20994 adding new decode method, since we're on JDK7+ now ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.util.ByteString.decodeString"), // #20508 HTTP: Document how to be able to support custom request methods @@ -1158,24 +1158,7 @@ object MiMa extends AutoPlugin { ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.Shard.messageBuffers_="), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.ShardRegion.totalBufferSize"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.cluster.sharding.ShardRegion.shardBuffers"), - ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.sharding.ShardRegion.shardBuffers_="), - - // #22332 protobuf serializers for remote deployment - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getConfigManifest"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasScopeManifest"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getScopeManifestBytes"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getConfigSerializerId"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasRouterConfigSerializerId"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasRouterConfigManifest"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getRouterConfigSerializerId"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getRouterConfigManifestBytes"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getConfigManifestBytes"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasConfigManifest"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasScopeSerializerId"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getRouterConfigManifest"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.hasConfigSerializerId"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getScopeSerializerId"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.WireFormats#DeployDataOrBuilder.getScopeManifest") + ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.sharding.ShardRegion.shardBuffers_=") ), "2.4.18" -> Seq( ) @@ -1183,38 +1166,39 @@ object MiMa extends AutoPlugin { // * this list ends with the latest released version number // * is kept in sync between release-2.4 and master branch ) - + val Release25Filters = Seq( "2.5.0" -> Seq( - - // #22759 LMDB files - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.env"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.db"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.keyBuffer"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.valueBuffer_="), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.valueBuffer"), - - ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.scaladsl.FlowOps.groupedWeightedWithin"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSubscriber.props"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSource.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSink.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FilePublisher.props"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSubscriber.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FilePublisher.this"), - ProblemFilters.exclude[MissingClassProblem]("akka.stream.impl.fusing.GroupedWithin"), - - ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.traversalBuilder"), - ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.named"), - ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.addAttributes"), - ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.async") + + // #22759 LMDB files + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.env"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.db"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.keyBuffer"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.valueBuffer_="), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.LmdbDurableStore.valueBuffer"), + + ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.scaladsl.FlowOps.groupedWeightedWithin"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSubscriber.props"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSource.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSink.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FilePublisher.props"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FileSubscriber.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.io.FilePublisher.this"), + ProblemFilters.exclude[MissingClassProblem]("akka.stream.impl.fusing.GroupedWithin"), + + ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.traversalBuilder"), + ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.named"), + ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.addAttributes"), + ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("akka.stream.Graph.async") ), "2.5.1" -> Seq( + // #22794 watchWith ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.ActorContext.watchWith"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.DeathWatch.watchWith"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.DeathWatch.akka$actor$dungeon$DeathWatch$$watching"), ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.dungeon.DeathWatch.akka$actor$dungeon$DeathWatch$$watching_="), - + // #22868 store shards ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.DDataShardCoordinator.sendUpdate"), ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.DDataShardCoordinator.waitingForUpdate"),