From 10c87d58325da2d129a3e0ea7c568cd57a1201b1 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 13 Oct 2011 18:52:52 +0200 Subject: [PATCH 1/9] split out fault handling stuff from ActorCell.scala to FaultHandling.scala --- .../src/main/scala/akka/actor/ActorCell.scala | 148 ----------------- .../main/scala/akka/actor/FaultHandling.scala | 155 ++++++++++++++++++ 2 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 akka-actor/src/main/scala/akka/actor/FaultHandling.scala diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 910777685c..3063cfaf01 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -52,154 +52,6 @@ private[akka] trait ActorContext extends ActorRefFactory { } -case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) { - def requestRestartPermission(maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Boolean = { - val denied = if (maxNrOfRetries.isEmpty && withinTimeRange.isEmpty) - false // Never deny an immortal - else if (maxNrOfRetries.nonEmpty && maxNrOfRetries.get < 1) - true //Always deny if no chance of restarting - else if (withinTimeRange.isEmpty) { - // restrict number of restarts - val retries = maxNrOfRetriesCount + 1 - maxNrOfRetriesCount = retries //Increment number of retries - retries > maxNrOfRetries.get - } else { - // cannot restart more than N within M timerange - val retries = maxNrOfRetriesCount + 1 - - val windowStart = restartTimeWindowStartNanos - val now = System.nanoTime - // we are within the time window if it isn't the first restart, or if the window hasn't closed - val insideWindow = if (windowStart == 0) true else (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(withinTimeRange.get) - - if (windowStart == 0 || !insideWindow) //(Re-)set the start of the window - restartTimeWindowStartNanos = now - - // reset number of restarts if window has expired, otherwise, increment it - maxNrOfRetriesCount = if (windowStart != 0 && !insideWindow) 1 else retries // increment number of retries - - val restartCountLimit = if (maxNrOfRetries.isDefined) maxNrOfRetries.get else 1 - - // the actor is dead if it dies X times within the window of restart - insideWindow && retries > restartCountLimit - } - - denied == false // if we weren't denied, we have a go - } -} - -sealed abstract class FaultHandlingStrategy { - - def trapExit: List[Class[_ <: Throwable]] - - def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] - - def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit - - def handleSupervisorFailing(supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { - if (children.nonEmpty) - children.foreach(_.child.suspend()) - } - - def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { - if (children.nonEmpty) - children.foreach(_.child.restart(cause)) - } - - /** - * Returns whether it processed the failure or not - */ - final def handleFailure(fail: Failed, children: Vector[ChildRestartStats]): Boolean = { - if (trapExit.exists(_.isAssignableFrom(fail.cause.getClass))) { - processFailure(fail, children) - true - } else false - } -} - -object AllForOneStrategy { - def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int): AllForOneStrategy = - new AllForOneStrategy(trapExit, if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) -} - -/** - * Restart all actors linked to the same supervisor when one fails, - * trapExit = which Throwables should be intercepted - * maxNrOfRetries = the number of times an actor is allowed to be restarted - * withinTimeRange = millisecond time window for maxNrOfRetries, negative means no window - */ -case class AllForOneStrategy(trapExit: List[Class[_ <: Throwable]], - maxNrOfRetries: Option[Int] = None, - withinTimeRange: Option[Int] = None) extends FaultHandlingStrategy { - def this(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) - - def this(trapExit: Array[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toList, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) - - def this(trapExit: java.util.List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toArray.toList.asInstanceOf[List[Class[_ <: Throwable]]], - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) - - def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = { - children collect { - case stats if stats.child != child ⇒ stats.child.stop(); stats //2 birds with one stone: remove the child + stop the other children - } //TODO optimization to drop all children here already? - } - - def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit = { - if (children.nonEmpty) { - if (children.forall(_.requestRestartPermission(maxNrOfRetries, withinTimeRange))) - children.foreach(_.child.restart(fail.cause)) - else - children.foreach(_.child.stop()) - } - } -} - -object OneForOneStrategy { - def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int): OneForOneStrategy = - new OneForOneStrategy(trapExit, if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) -} - -/** - * Restart an actor when it fails - * trapExit = which Throwables should be intercepted - * maxNrOfRetries = the number of times an actor is allowed to be restarted - * withinTimeRange = millisecond time window for maxNrOfRetries, negative means no window - */ -case class OneForOneStrategy(trapExit: List[Class[_ <: Throwable]], - maxNrOfRetries: Option[Int] = None, - withinTimeRange: Option[Int] = None) extends FaultHandlingStrategy { - def this(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) - - def this(trapExit: Array[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toList, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) - - def this(trapExit: java.util.List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toArray.toList.asInstanceOf[List[Class[_ <: Throwable]]], - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) - - def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = - children.filterNot(_.child == child) - - def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit = { - children.find(_.child == fail.actor) match { - case Some(stats) ⇒ - if (stats.requestRestartPermission(maxNrOfRetries, withinTimeRange)) - fail.actor.restart(fail.cause) - else - fail.actor.stop() //TODO optimization to drop child here already? - case None ⇒ throw new AssertionError("Got Failure from non-child: " + fail) - } - } -} - private[akka] object ActorCell { val contextStack = new ThreadLocal[Stack[ActorContext]] { override def initialValue = Stack[ActorContext]() diff --git a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala new file mode 100644 index 0000000000..a30fb5efe6 --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala @@ -0,0 +1,155 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +import java.util.concurrent.TimeUnit + +case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) { + def requestRestartPermission(maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Boolean = { + val denied = if (maxNrOfRetries.isEmpty && withinTimeRange.isEmpty) + false // Never deny an immortal + else if (maxNrOfRetries.nonEmpty && maxNrOfRetries.get < 1) + true //Always deny if no chance of restarting + else if (withinTimeRange.isEmpty) { + // restrict number of restarts + val retries = maxNrOfRetriesCount + 1 + maxNrOfRetriesCount = retries //Increment number of retries + retries > maxNrOfRetries.get + } else { + // cannot restart more than N within M timerange + val retries = maxNrOfRetriesCount + 1 + + val windowStart = restartTimeWindowStartNanos + val now = System.nanoTime + // we are within the time window if it isn't the first restart, or if the window hasn't closed + val insideWindow = if (windowStart == 0) true else (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(withinTimeRange.get) + + if (windowStart == 0 || !insideWindow) //(Re-)set the start of the window + restartTimeWindowStartNanos = now + + // reset number of restarts if window has expired, otherwise, increment it + maxNrOfRetriesCount = if (windowStart != 0 && !insideWindow) 1 else retries // increment number of retries + + val restartCountLimit = if (maxNrOfRetries.isDefined) maxNrOfRetries.get else 1 + + // the actor is dead if it dies X times within the window of restart + insideWindow && retries > restartCountLimit + } + + denied == false // if we weren't denied, we have a go + } +} + +sealed abstract class FaultHandlingStrategy { + + def trapExit: List[Class[_ <: Throwable]] + + def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] + + def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit + + def handleSupervisorFailing(supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { + if (children.nonEmpty) + children.foreach(_.child.suspend()) + } + + def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { + if (children.nonEmpty) + children.foreach(_.child.restart(cause)) + } + + /** + * Returns whether it processed the failure or not + */ + final def handleFailure(fail: Failed, children: Vector[ChildRestartStats]): Boolean = { + if (trapExit.exists(_.isAssignableFrom(fail.cause.getClass))) { + processFailure(fail, children) + true + } else false + } +} + +object AllForOneStrategy { + def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int): AllForOneStrategy = + new AllForOneStrategy(trapExit, if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) +} + +/** + * Restart all actors linked to the same supervisor when one fails, + * trapExit = which Throwables should be intercepted + * maxNrOfRetries = the number of times an actor is allowed to be restarted + * withinTimeRange = millisecond time window for maxNrOfRetries, negative means no window + */ +case class AllForOneStrategy(trapExit: List[Class[_ <: Throwable]], + maxNrOfRetries: Option[Int] = None, + withinTimeRange: Option[Int] = None) extends FaultHandlingStrategy { + def this(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(trapExit, + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(trapExit: Array[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(trapExit.toList, + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(trapExit: java.util.List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(trapExit.toArray.toList.asInstanceOf[List[Class[_ <: Throwable]]], + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = { + children collect { + case stats if stats.child != child ⇒ stats.child.stop(); stats //2 birds with one stone: remove the child + stop the other children + } //TODO optimization to drop all children here already? + } + + def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit = { + if (children.nonEmpty) { + if (children.forall(_.requestRestartPermission(maxNrOfRetries, withinTimeRange))) + children.foreach(_.child.restart(fail.cause)) + else + children.foreach(_.child.stop()) + } + } +} + +object OneForOneStrategy { + def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int): OneForOneStrategy = + new OneForOneStrategy(trapExit, if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) +} + +/** + * Restart an actor when it fails + * trapExit = which Throwables should be intercepted + * maxNrOfRetries = the number of times an actor is allowed to be restarted + * withinTimeRange = millisecond time window for maxNrOfRetries, negative means no window + */ +case class OneForOneStrategy(trapExit: List[Class[_ <: Throwable]], + maxNrOfRetries: Option[Int] = None, + withinTimeRange: Option[Int] = None) extends FaultHandlingStrategy { + def this(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(trapExit, + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(trapExit: Array[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(trapExit.toList, + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(trapExit: java.util.List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(trapExit.toArray.toList.asInstanceOf[List[Class[_ <: Throwable]]], + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = + children.filterNot(_.child == child) + + def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit = { + children.find(_.child == fail.actor) match { + case Some(stats) ⇒ + if (stats.requestRestartPermission(maxNrOfRetries, withinTimeRange)) + fail.actor.restart(fail.cause) + else + fail.actor.stop() //TODO optimization to drop child here already? + case None ⇒ throw new AssertionError("Got Failure from non-child: " + fail) + } + } +} + From 25e8eb14227ddea41e404aabd160f4faec939d4e Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 15 Oct 2011 11:18:25 +0200 Subject: [PATCH 2/9] teach new tricks to old FaultHandlingStrategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Escalate explicitly does the previous non-handled case - Restart does the obvious - Stop just terminates the child (useful for ActorInitializationException or “temporary” actors) - Resume directly resumes the child (immortal actors) - trapExit list replaced by Decider (total function cause=>action) - there are factories which accept (most of) the old inputs - can build a sorted list of (cause, action)-pairs to make a Decider which picks the action for the most specific enclosing cause type Also add DeathPactException for case of unhandled Terminated message. --- .../src/main/scala/akka/actor/Actor.scala | 36 ++- .../main/scala/akka/actor/FaultHandling.scala | 223 +++++++++++++----- .../src/main/scala/akka/actor/Props.scala | 11 +- .../src/main/scala/akka/dispatch/Future.scala | 2 +- 4 files changed, 196 insertions(+), 76 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index c5834ac633..d80d1285c7 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -18,6 +18,7 @@ import akka.experimental import akka.{ AkkaApplication, AkkaException } import scala.reflect.BeanProperty +import scala.util.control.NoStackTrace import com.eaio.uuid.UUID @@ -62,34 +63,41 @@ case object PoisonPill extends AutoReceivedMessage with PossiblyHarmful case object Kill extends AutoReceivedMessage with PossiblyHarmful +case class Terminated(@BeanProperty actor: ActorRef, @BeanProperty cause: Throwable) extends PossiblyHarmful + case object ReceiveTimeout extends PossiblyHarmful -case class Terminated(@BeanProperty actor: ActorRef, @BeanProperty cause: Throwable) - // Exceptions for Actors -class ActorStartException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { +class IllegalActorStateException private[akka] (message: String, cause: Throwable = null) + extends AkkaException(message, cause) { def this(msg: String) = this(msg, null); } -class IllegalActorStateException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { +class ActorKilledException private[akka] (message: String, cause: Throwable) + extends AkkaException(message, cause) + with NoStackTrace { def this(msg: String) = this(msg, null); } -class ActorKilledException private[akka] (message: String, cause: Throwable) extends AkkaException(message, cause) { +class ActorInitializationException private[akka] (message: String, cause: Throwable = null) + extends AkkaException(message, cause) { def this(msg: String) = this(msg, null); } -class ActorInitializationException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { +class ActorTimeoutException private[akka] (message: String, cause: Throwable = null) + extends AkkaException(message, cause) { def this(msg: String) = this(msg, null); } -class ActorTimeoutException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { +class InvalidMessageException private[akka] (message: String, cause: Throwable = null) + extends AkkaException(message, cause) + with NoStackTrace { def this(msg: String) = this(msg, null); } -class InvalidMessageException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { - def this(msg: String) = this(msg, null); -} +case class DeathPactException private[akka] (dead: ActorRef, cause: Throwable) + extends AkkaException("monitored actor " + dead + " terminated", cause) + with NoStackTrace /** * This message is thrown by default when an Actors behavior doesn't match a message @@ -391,8 +399,10 @@ trait Actor { * by default it does: EventHandler.warning(self, message) */ def unhandled(message: Any) { - //EventHandler.warning(self, message) - throw new UnhandledMessageException(message, self) + message match { + case Terminated(dead, cause) ⇒ throw new DeathPactException(dead, cause) + case _ ⇒ throw new UnhandledMessageException(message, self) + } } /** @@ -444,7 +454,7 @@ trait Actor { msg match { case msg if behaviorStack.nonEmpty && behaviorStack.head.isDefinedAt(msg) ⇒ behaviorStack.head.apply(msg) case msg if behaviorStack.isEmpty && processingBehavior.isDefinedAt(msg) ⇒ processingBehavior.apply(msg) - case unknown ⇒ unhandled(unknown) //This is the only line that differs from processingbehavior + case unknown ⇒ unhandled(unknown) } } } diff --git a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala index a30fb5efe6..d654539e5d 100644 --- a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala +++ b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala @@ -4,50 +4,115 @@ package akka.actor import java.util.concurrent.TimeUnit +import scala.annotation.tailrec +import scala.collection.mutable.ArrayBuffer +import scala.collection.JavaConversions._ +import java.lang.{ Iterable ⇒ JIterable } case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) { - def requestRestartPermission(maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Boolean = { - val denied = if (maxNrOfRetries.isEmpty && withinTimeRange.isEmpty) - false // Never deny an immortal - else if (maxNrOfRetries.nonEmpty && maxNrOfRetries.get < 1) - true //Always deny if no chance of restarting - else if (withinTimeRange.isEmpty) { - // restrict number of restarts - val retries = maxNrOfRetriesCount + 1 - maxNrOfRetriesCount = retries //Increment number of retries - retries > maxNrOfRetries.get - } else { - // cannot restart more than N within M timerange - val retries = maxNrOfRetriesCount + 1 - val windowStart = restartTimeWindowStartNanos - val now = System.nanoTime - // we are within the time window if it isn't the first restart, or if the window hasn't closed - val insideWindow = if (windowStart == 0) true else (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(withinTimeRange.get) - - if (windowStart == 0 || !insideWindow) //(Re-)set the start of the window - restartTimeWindowStartNanos = now - - // reset number of restarts if window has expired, otherwise, increment it - maxNrOfRetriesCount = if (windowStart != 0 && !insideWindow) 1 else retries // increment number of retries - - val restartCountLimit = if (maxNrOfRetries.isDefined) maxNrOfRetries.get else 1 - - // the actor is dead if it dies X times within the window of restart - insideWindow && retries > restartCountLimit + def requestRestartPermission(retriesWindow: (Option[Int], Option[Int])): Boolean = + retriesWindow match { + case (Some(retries), _) if retries < 1 ⇒ false + case (Some(retries), None) ⇒ maxNrOfRetriesCount += 1; maxNrOfRetriesCount <= retries + case (x @ (Some(_) | None), Some(window)) ⇒ retriesInWindowOkay(if (x.isDefined) x.get else 1, window) + case (None, _) ⇒ true } - denied == false // if we weren't denied, we have a go + private def retriesInWindowOkay(retries: Int, window: Int): Boolean = { + /* + * Simple window algorithm: window is kept open for a certain time + * after a restart and if enough restarts happen during this time, it + * denies. Otherwise window closes and the scheme starts over. + */ + val retriesDone = maxNrOfRetriesCount + 1 + val now = System.nanoTime + val windowStart = + if (restartTimeWindowStartNanos == 0) { + restartTimeWindowStartNanos = now + now + } else restartTimeWindowStartNanos + val insideWindow = (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(window) + if (insideWindow) { + maxNrOfRetriesCount = retriesDone + retriesDone <= retries + } else { + maxNrOfRetriesCount = 1 + restartTimeWindowStartNanos = now + true + } } } -sealed abstract class FaultHandlingStrategy { +object FaultHandlingStrategy { + sealed trait Action + case object Resume extends Action + case object Restart extends Action + case object Stop extends Action + case object Escalate extends Action - def trapExit: List[Class[_ <: Throwable]] + type Decider = PartialFunction[Class[_ <: Throwable], Action] + type JDecider = akka.japi.Function[Class[_ <: Throwable], Action] + type CauseAction = (Class[_ <: Throwable], Action) + + /** + * Backwards compatible Decider builder which just checks whether one of + * the given Throwables matches the cause and restarts, otherwise escalates. + */ + def makeDecider(trapExit: Array[Class[_ <: Throwable]]): Decider = + { case x ⇒ if (trapExit exists (_ isAssignableFrom x)) Restart else Escalate } + + /** + * Backwards compatible Decider builder which just checks whether one of + * the given Throwables matches the cause and restarts, otherwise escalates. + */ + def makeDecider(trapExit: List[Class[_ <: Throwable]]): Decider = + { case x ⇒ if (trapExit exists (_ isAssignableFrom x)) Restart else Escalate } + + /** + * Backwards compatible Decider builder which just checks whether one of + * the given Throwables matches the cause and restarts, otherwise escalates. + */ + def makeDecider(trapExit: JIterable[Class[_ <: Throwable]]): Decider = makeDecider(trapExit.toList) + + /** + * Decider builder for Iterables of cause-action pairs, e.g. a map obtained + * from configuration; will sort the pairs so that the most specific type is + * checked before all its subtypes, allowing carving out subtrees of the + * Throwable hierarchy. + */ + def makeDecider(flat: Iterable[CauseAction]): Decider = { + val actions = sort(flat) + return { case x ⇒ actions find (_._1 isAssignableFrom x) map (_._2) getOrElse Escalate } + } + + def makeDecider(func: JDecider): Decider = { + case x ⇒ func(x) + } + + /** + * Sort so that subtypes always precede their supertypes, but without + * obeying any order between unrelated subtypes (insert sort). + */ + def sort(in: Iterable[CauseAction]): Seq[CauseAction] = + (new ArrayBuffer[CauseAction](in.size) /: in) { (buf, ca) ⇒ + buf.indexWhere(_._1 isAssignableFrom ca._1) match { + case -1 ⇒ buf append ca + case x ⇒ buf insert (x, ca) + } + buf + } +} + +abstract class FaultHandlingStrategy { + + import FaultHandlingStrategy._ + + def decider: Decider def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] - def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit + def processFailure(restart: Boolean, fail: Failed, children: Vector[ChildRestartStats]): Unit def handleSupervisorFailing(supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { if (children.nonEmpty) @@ -63,16 +128,25 @@ sealed abstract class FaultHandlingStrategy { * Returns whether it processed the failure or not */ final def handleFailure(fail: Failed, children: Vector[ChildRestartStats]): Boolean = { - if (trapExit.exists(_.isAssignableFrom(fail.cause.getClass))) { - processFailure(fail, children) - true - } else false + val cause = fail.cause.getClass + val action = if (decider.isDefinedAt(cause)) decider(cause) else Escalate + action match { + case Resume ⇒ fail.actor.resume(); true + case Restart ⇒ processFailure(true, fail, children); true + case Stop ⇒ processFailure(false, fail, children); true + case Escalate ⇒ false + } } } object AllForOneStrategy { def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int): AllForOneStrategy = - new AllForOneStrategy(trapExit, if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + new AllForOneStrategy(FaultHandlingStrategy.makeDecider(trapExit), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): AllForOneStrategy = + new AllForOneStrategy(FaultHandlingStrategy.makeDecider(trapExit), maxNrOfRetries, withinTimeRange) + def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Option[Int]): AllForOneStrategy = + new AllForOneStrategy(FaultHandlingStrategy.makeDecider(trapExit), maxNrOfRetries, None) } /** @@ -81,20 +155,31 @@ object AllForOneStrategy { * maxNrOfRetries = the number of times an actor is allowed to be restarted * withinTimeRange = millisecond time window for maxNrOfRetries, negative means no window */ -case class AllForOneStrategy(trapExit: List[Class[_ <: Throwable]], +case class AllForOneStrategy(decider: FaultHandlingStrategy.Decider, maxNrOfRetries: Option[Int] = None, withinTimeRange: Option[Int] = None) extends FaultHandlingStrategy { - def this(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(decider: FaultHandlingStrategy.JDecider, maxNrOfRetries: Int, withinTimeRange: Int) = + this(FaultHandlingStrategy.makeDecider(decider), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), + if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(trapExit: JIterable[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(FaultHandlingStrategy.makeDecider(trapExit), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), + if (withinTimeRange < 0) None else Some(withinTimeRange)) def this(trapExit: Array[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toList, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + this(FaultHandlingStrategy.makeDecider(trapExit), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), + if (withinTimeRange < 0) None else Some(withinTimeRange)) - def this(trapExit: java.util.List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toArray.toList.asInstanceOf[List[Class[_ <: Throwable]]], - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + /* + * this is a performance optimization to avoid re-allocating the pairs upon + * every call to requestRestartPermission, assuming that strategies are shared + * across actors and thus this field does not take up much space + */ + val retriesWindow = (maxNrOfRetries, withinTimeRange) def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = { children collect { @@ -102,9 +187,9 @@ case class AllForOneStrategy(trapExit: List[Class[_ <: Throwable]], } //TODO optimization to drop all children here already? } - def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit = { + def processFailure(restart: Boolean, fail: Failed, children: Vector[ChildRestartStats]): Unit = { if (children.nonEmpty) { - if (children.forall(_.requestRestartPermission(maxNrOfRetries, withinTimeRange))) + if (restart && children.forall(_.requestRestartPermission(retriesWindow))) children.foreach(_.child.restart(fail.cause)) else children.foreach(_.child.stop()) @@ -114,7 +199,12 @@ case class AllForOneStrategy(trapExit: List[Class[_ <: Throwable]], object OneForOneStrategy { def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int): OneForOneStrategy = - new OneForOneStrategy(trapExit, if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + new OneForOneStrategy(FaultHandlingStrategy.makeDecider(trapExit), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): OneForOneStrategy = + new OneForOneStrategy(FaultHandlingStrategy.makeDecider(trapExit), maxNrOfRetries, withinTimeRange) + def apply(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Option[Int]): OneForOneStrategy = + new OneForOneStrategy(FaultHandlingStrategy.makeDecider(trapExit), maxNrOfRetries, None) } /** @@ -123,28 +213,39 @@ object OneForOneStrategy { * maxNrOfRetries = the number of times an actor is allowed to be restarted * withinTimeRange = millisecond time window for maxNrOfRetries, negative means no window */ -case class OneForOneStrategy(trapExit: List[Class[_ <: Throwable]], +case class OneForOneStrategy(decider: FaultHandlingStrategy.Decider, maxNrOfRetries: Option[Int] = None, withinTimeRange: Option[Int] = None) extends FaultHandlingStrategy { - def this(trapExit: List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(decider: FaultHandlingStrategy.JDecider, maxNrOfRetries: Int, withinTimeRange: Int) = + this(FaultHandlingStrategy.makeDecider(decider), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), + if (withinTimeRange < 0) None else Some(withinTimeRange)) + + def this(trapExit: JIterable[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = + this(FaultHandlingStrategy.makeDecider(trapExit), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), + if (withinTimeRange < 0) None else Some(withinTimeRange)) def this(trapExit: Array[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toList, - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + this(FaultHandlingStrategy.makeDecider(trapExit), + if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), + if (withinTimeRange < 0) None else Some(withinTimeRange)) - def this(trapExit: java.util.List[Class[_ <: Throwable]], maxNrOfRetries: Int, withinTimeRange: Int) = - this(trapExit.toArray.toList.asInstanceOf[List[Class[_ <: Throwable]]], - if (maxNrOfRetries < 0) None else Some(maxNrOfRetries), if (withinTimeRange < 0) None else Some(withinTimeRange)) + /* + * this is a performance optimization to avoid re-allocating the pairs upon + * every call to requestRestartPermission, assuming that strategies are shared + * across actors and thus this field does not take up much space + */ + val retriesWindow = (maxNrOfRetries, withinTimeRange) def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = - children.filterNot(_.child == child) + children.filterNot(_.child == child) // TODO: check: I think this copies the whole vector in addition to allocating a closure ... - def processFailure(fail: Failed, children: Vector[ChildRestartStats]): Unit = { + def processFailure(restart: Boolean, fail: Failed, children: Vector[ChildRestartStats]): Unit = { children.find(_.child == fail.actor) match { case Some(stats) ⇒ - if (stats.requestRestartPermission(maxNrOfRetries, withinTimeRange)) + if (restart && stats.requestRestartPermission(retriesWindow)) fail.actor.restart(fail.cause) else fail.actor.stop() //TODO optimization to drop child here already? diff --git a/akka-actor/src/main/scala/akka/actor/Props.scala b/akka-actor/src/main/scala/akka/actor/Props.scala index 45528091f7..ba668ae280 100644 --- a/akka-actor/src/main/scala/akka/actor/Props.scala +++ b/akka-actor/src/main/scala/akka/actor/Props.scala @@ -16,10 +16,17 @@ import collection.immutable.Stack * FIXME document me */ object Props { + import FaultHandlingStrategy._ + final val defaultCreator: () ⇒ Actor = () ⇒ throw new UnsupportedOperationException("No actor creator specified!") final val defaultDispatcher: MessageDispatcher = null final val defaultTimeout: Timeout = Timeout(Duration.MinusInf) - final val defaultFaultHandler: FaultHandlingStrategy = OneForOneStrategy(classOf[Exception] :: Nil, None, None) + final val defaultDecider: Decider = { + case _: ActorInitializationException ⇒ Stop + case _: Exception ⇒ Restart + case _ ⇒ Escalate + } + final val defaultFaultHandler: FaultHandlingStrategy = OneForOneStrategy(defaultDecider, None, None) final val defaultSupervisor: Option[ActorRef] = None final val noHotSwap: Stack[Actor.Receive] = Stack.empty final val randomAddress: String = "" @@ -34,6 +41,8 @@ object Props { */ def apply(): Props = default + def empty = Props(context ⇒ { case null ⇒ }) + /** * Returns a Props that has default values except for "creator" which will be a function that creates an instance * of the supplied type using the default constructor diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index dc302c5af9..ac478b7a81 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -7,7 +7,7 @@ package akka.dispatch import akka.AkkaException import akka.event.EventHandler -import akka.actor.{ Actor, UntypedChannel, Timeout, ExceptionChannel } +import akka.actor.{ UntypedChannel, Timeout, ExceptionChannel } import scala.Option import akka.japi.{ Procedure, Function ⇒ JFunc, Option ⇒ JOption } From d3837b9fc3f384bd51a847340d218b0bb67ec6cf Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 18 Oct 2011 15:39:26 +0200 Subject: [PATCH 3/9] Introduce parental supervision, BUT TESTS ARE STILL FAILING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - every actor is now supervised, where the root of the tree is app.guardian, which has its supervisor field set to a special ActorRef obtained from provider.theOneWhoWalksTheBubblesOfSpaceTime (this name is meant to indicate that this ref is outside of the universe, cf. Michio Kaku) - changed all tests to obtain specially supervised children (i.e. not top-level) via (supervisor ? Props).as[ActorRef].get - add private[akka] ScalaActorRef.sendSystemMessage for sending Supervise() - everything routing or remote is broken wrt. supervision, as that was not “properly” implemented to begin with, will be tackled after app/supervision/eventbus/AkkaSpec are stabilized enough --- .../ActorFireForgetRequestReplySpec.scala | 6 +- .../scala/akka/actor/ActorLifeCycleSpec.scala | 17 +++--- .../test/scala/akka/actor/ActorRefSpec.scala | 4 +- .../scala/akka/actor/DeathWatchSpec.scala | 20 ++++++- .../src/test/scala/akka/actor/IOActor.scala | 4 +- .../actor/LocalActorRefProviderSpec.scala | 8 +-- .../scala/akka/actor/LoggingReceiveSpec.scala | 2 +- .../akka/actor/RestartStrategySpec.scala | 46 +++++++------- .../test/scala/akka/actor/SchedulerSpec.scala | 7 ++- .../test/scala/akka/actor/Supervisor.scala | 10 ++++ .../akka/actor/SupervisorHierarchySpec.scala | 16 +++-- .../scala/akka/actor/SupervisorMiscSpec.scala | 15 ++--- .../scala/akka/actor/SupervisorSpec.scala | 44 +++++++------- .../scala/akka/actor/SupervisorTreeSpec.scala | 9 +-- .../test/scala/akka/actor/Ticket669Spec.scala | 8 +-- .../src/main/scala/akka/AkkaApplication.scala | 27 ++++++--- .../src/main/scala/akka/actor/ActorCell.scala | 42 +++++-------- .../src/main/scala/akka/actor/ActorRef.scala | 37 +++++++++--- .../scala/akka/actor/ActorRefProvider.scala | 60 ++++++++++++++----- .../src/main/scala/akka/actor/Props.scala | 23 +------ .../akka/dispatch/AbstractDispatcher.scala | 16 ++--- .../main/scala/akka/event/EventHandler.scala | 2 +- .../scala/akka/remote/RemoteInterface.scala | 2 +- .../src/main/scala/akka/routing/Pool.scala | 2 +- .../src/main/scala/akka/routing/Routing.scala | 8 +-- .../akka/remote/NetworkEventStream.scala | 4 +- .../akka/remote/RemoteActorRefProvider.scala | 19 +++--- .../main/scala/akka/remote/RemoteDaemon.scala | 20 ++++--- .../serialization/SerializationProtocol.scala | 7 +-- .../serialization/ActorSerializeSpec.scala | 8 +-- .../scala/akka/testkit/TestActorRef.scala | 9 ++- .../main/scala/akka/testkit/TestFSMRef.scala | 8 +-- .../src/main/scala/akka/testkit/TestKit.scala | 2 +- .../scala/akka/testkit/TestActorRefSpec.scala | 4 +- 34 files changed, 290 insertions(+), 226 deletions(-) create mode 100644 akka-actor-tests/src/test/scala/akka/actor/Supervisor.scala diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala index 461868df21..895cc31c79 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala @@ -45,10 +45,6 @@ object ActorFireForgetRequestReplySpec { } } - class Supervisor extends Actor { - def receive = { case _ ⇒ () } - } - object state { var s = "NIL" val finished = TestBarrier(2) @@ -83,7 +79,7 @@ class ActorFireForgetRequestReplySpec extends AkkaSpec with BeforeAndAfterEach { "should shutdown crashed temporary actor" in { filterEvents(EventFilter[Exception]("Expected")) { val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(0)))) - val actor = actorOf(Props[CrashingActor].withSupervisor(supervisor)) + val actor = (supervisor ? Props[CrashingActor]).as[ActorRef].get actor.isShutdown must be(false) actor ! "Die" state.finished.await diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala index 52e1bd6d58..12456fd3ff 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala @@ -33,12 +33,13 @@ class ActorLifeCycleSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitS "invoke preRestart, preStart, postRestart when using OneForOneStrategy" in { filterException[ActorKilledException] { val id = newUuid().toString - val supervisor = actorOf(Props(self ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(3)))) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(3)))) val gen = new AtomicInteger(0) - val restarter = actorOf(Props(new LifeCycleTestActor(id, gen) { + val restarterProps = Props(new LifeCycleTestActor(id, gen) { override def preRestart(reason: Throwable, message: Option[Any]) { report("preRestart") } override def postRestart(reason: Throwable) { report("postRestart") } - }).withSupervisor(supervisor)) + }) + val restarter = (supervisor ? restarterProps).as[ActorRef].get expectMsg(("preStart", id, 0)) restarter ! Kill @@ -66,9 +67,10 @@ class ActorLifeCycleSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitS "default for preRestart and postRestart is to call postStop and preStart respectively" in { filterException[ActorKilledException] { val id = newUuid().toString - val supervisor = actorOf(Props(self ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(3)))) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(3)))) val gen = new AtomicInteger(0) - val restarter = actorOf(Props(new LifeCycleTestActor(id, gen)).withSupervisor(supervisor)) + val restarterProps = Props(new LifeCycleTestActor(id, gen)) + val restarter = (supervisor ? restarterProps).as[ActorRef].get expectMsg(("preStart", id, 0)) restarter ! Kill @@ -95,9 +97,10 @@ class ActorLifeCycleSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitS "not invoke preRestart and postRestart when never restarted using OneForOneStrategy" in { val id = newUuid().toString - val supervisor = actorOf(Props(self ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(3)))) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(3)))) val gen = new AtomicInteger(0) - val a = actorOf(Props(new LifeCycleTestActor(id, gen)).withSupervisor(supervisor)) + val props = Props(new LifeCycleTestActor(id, gen)) + val a = (supervisor ? props).as[ActorRef].get expectMsg(("preStart", id, 0)) a ! "status" expectMsg(("OK", id, 0)) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala index 9ab8615594..747f776f9b 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala @@ -394,12 +394,12 @@ class ActorRefSpec extends AkkaSpec { val boss = actorOf(Props(new Actor { - val ref = actorOf( + val ref = context.actorOf( Props(new Actor { def receive = { case _ ⇒ } override def preRestart(reason: Throwable, msg: Option[Any]) = latch.countDown() override def postRestart(reason: Throwable) = latch.countDown() - }).withSupervisor(self)) + })) protected def receive = { case "sendKill" ⇒ ref ! Kill } }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), 2, 1000))) diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala index ca8fdad334..897d3a6180 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala @@ -69,8 +69,9 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende "notify with a Terminated message once when an Actor is stopped but not when restarted" in { filterException[ActorKilledException] { - val supervisor = actorOf(Props(context ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(2)))) - val terminal = actorOf(Props(context ⇒ { case x ⇒ context.channel ! x }).withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(2)))) + val terminalProps = Props(context ⇒ { case x ⇒ context.channel ! x }) + val terminal = (supervisor ? terminalProps).as[ActorRef].get testActor startsMonitoring terminal @@ -85,6 +86,21 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende supervisor.stop() } } + + "fail a monitor which does not handle Terminated()" in { + filterEvents(EventFilter[ActorKilledException], EventFilter[DeathPactException]) { + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(0)))) + + val failed, brother = (supervisor ? Props.empty).as[ActorRef].get + brother startsMonitoring failed + testActor startsMonitoring brother + + failed ! Kill + expectMsgPF() { + case Terminated(brother, DeathPactException(failed, _: ActorKilledException)) ⇒ true + } + } + } } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala b/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala index 85c29e1033..78ca017679 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala @@ -31,7 +31,7 @@ object IOActorSpec { socket write bytes } } - }).withSupervisor(optionSelf)) + })) def receive = { case msg: NewClient ⇒ @@ -102,7 +102,7 @@ object IOActorSpec { } } } - }).withSupervisor(self)) + })) def receive = { case msg: NewClient ⇒ createWorker forward msg diff --git a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala index 9987a2dfcd..f3d0168613 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala @@ -38,19 +38,19 @@ class LocalActorRefProviderSpec extends AkkaSpec { val address = "new-actor" + i spawn { - a1 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), address)) + a1 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), app.guardian, address)) latch.countDown() } spawn { - a2 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), address)) + a2 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), app.guardian, address)) latch.countDown() } spawn { - a3 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), address)) + a3 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), app.guardian, address)) latch.countDown() } spawn { - a4 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), address)) + a4 = Some(provider.actorOf(Props(creator = () ⇒ new NewActor), app.guardian, address)) latch.countDown() } diff --git a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala index 0af11ba652..e767ddeeb7 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala @@ -129,7 +129,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd expectMsg(EventHandler.Debug(supervisor, "started")) - val actor = TestActorRef[TestLogActor](Props[TestLogActor].withSupervisor(supervisor)) + val actor = new TestActorRef[TestLogActor](app, Props[TestLogActor], supervisor, "none") expectMsgPF() { case EventHandler.Debug(ref, msg: String) ⇒ ref == supervisor && msg.startsWith("now supervising") diff --git a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala index a43665e91a..545901d3f3 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala @@ -28,16 +28,14 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { "A RestartStrategy" must { "ensure that slave stays dead after max restarts within time range" in { - val boss = actorOf(Props(new Actor { - protected def receive = { case _ ⇒ () } - }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), 2, 1000))) + val boss = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), 2, 1000))) val restartLatch = new StandardLatch val secondRestartLatch = new StandardLatch val countDownLatch = new CountDownLatch(3) val stopLatch = new StandardLatch - val slave = actorOf(Props(new Actor { + val slaveProps = Props(new Actor { protected def receive = { case Ping ⇒ countDownLatch.countDown() @@ -54,7 +52,8 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { override def postStop() = { stopLatch.open } - }).withSupervisor(boss)) + }) + val slave = (boss ? slaveProps).as[ActorRef].get slave ! Ping slave ! Crash @@ -75,13 +74,11 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { } "ensure that slave is immortal without max restarts and time range" in { - val boss = actorOf(Props(new Actor { - def receive = { case _ ⇒ () } - }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, None))) + val boss = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, None))) val countDownLatch = new CountDownLatch(100) - val slave = actorOf(Props(new Actor { + val slaveProps = Props(new Actor { protected def receive = { case Crash ⇒ throw new Exception("Crashing...") @@ -90,7 +87,8 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { override def postRestart(reason: Throwable) = { countDownLatch.countDown() } - }).withSupervisor(boss)) + }) + val slave = (boss ? slaveProps).as[ActorRef].get (1 to 100) foreach { _ ⇒ slave ! Crash } assert(countDownLatch.await(120, TimeUnit.SECONDS)) @@ -98,9 +96,7 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { } "ensure that slave restarts after number of crashes not within time range" in { - val boss = actorOf(Props(new Actor { - def receive = { case _ ⇒ () } - }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), 2, 500))) + val boss = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), 2, 500))) val restartLatch = new StandardLatch val secondRestartLatch = new StandardLatch @@ -108,7 +104,7 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { val pingLatch = new StandardLatch val secondPingLatch = new StandardLatch - val slave = actorOf(Props(new Actor { + val slaveProps = Props(new Actor { protected def receive = { case Ping ⇒ @@ -129,7 +125,8 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { secondRestartLatch.open } } - }).withSupervisor(boss)) + }) + val slave = (boss ? slaveProps).as[ActorRef].get slave ! Ping slave ! Crash @@ -156,16 +153,14 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { } "ensure that slave is not restarted after max retries" in { - val boss = actorOf(Props(new Actor { - def receive = { case _ ⇒ () } - }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), Some(2), None))) + val boss = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), Some(2), None))) val restartLatch = new StandardLatch val secondRestartLatch = new StandardLatch val countDownLatch = new CountDownLatch(3) val stopLatch = new StandardLatch - val slave = actorOf(Props(new Actor { + val slaveProps = Props(new Actor { protected def receive = { case Ping ⇒ countDownLatch.countDown() @@ -181,7 +176,8 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { override def postStop() = { stopLatch.open } - }).withSupervisor(boss)) + }) + val slave = (boss ? slaveProps).as[ActorRef].get slave ! Ping slave ! Crash @@ -212,10 +208,13 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { val countDownLatch = new CountDownLatch(2) val boss = actorOf(Props(new Actor { - def receive = { case t: Terminated ⇒ maxNoOfRestartsLatch.open } + def receive = { + case p: Props ⇒ reply(context.actorOf(p)) + case t: Terminated ⇒ maxNoOfRestartsLatch.open + } }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, Some(1000)))) - val slave = actorOf(Props(new Actor { + val slaveProps = Props(new Actor { protected def receive = { case Ping ⇒ countDownLatch.countDown() @@ -229,7 +228,8 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { override def postStop() = { stopLatch.open } - }).withSupervisor(boss)) + }) + val slave = (boss ? slaveProps).as[ActorRef].get boss startsMonitoring slave diff --git a/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala index 4e3ea20fe1..2aa2c3452e 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala @@ -106,15 +106,16 @@ class SchedulerSpec extends AkkaSpec with BeforeAndAfterEach { val restartLatch = new StandardLatch val pingLatch = new CountDownLatch(6) - val supervisor = actorOf(Props(context ⇒ { case _ ⇒ }).withFaultHandler(AllForOneStrategy(List(classOf[Exception]), 3, 1000))) - val actor = actorOf(Props(new Actor { + val supervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), 3, 1000))) + val props = Props(new Actor { def receive = { case Ping ⇒ pingLatch.countDown() case Crash ⇒ throw new Exception("CRASH") } override def postRestart(reason: Throwable) = restartLatch.open - }).withSupervisor(supervisor)) + }) + val actor = (supervisor ? props).as[ActorRef].get collectFuture(app.scheduler.schedule(actor, Ping, 500, 500, TimeUnit.MILLISECONDS)) // appx 2 pings before crash diff --git a/akka-actor-tests/src/test/scala/akka/actor/Supervisor.scala b/akka-actor-tests/src/test/scala/akka/actor/Supervisor.scala new file mode 100644 index 0000000000..023490da31 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/Supervisor.scala @@ -0,0 +1,10 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +class Supervisor extends Actor { + def receive = { + case x: Props ⇒ reply(context.actorOf(x)) + } +} diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala index 4404e53c20..cc16c2da08 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala @@ -12,7 +12,9 @@ object SupervisorHierarchySpec { class FireWorkerException(msg: String) extends Exception(msg) class CountDownActor(countDown: CountDownLatch) extends Actor { - protected def receive = { case _ ⇒ } + protected def receive = { + case p: Props ⇒ reply(context.actorOf(p)) + } override def postRestart(reason: Throwable) = { countDown.countDown() } @@ -27,12 +29,13 @@ class SupervisorHierarchySpec extends AkkaSpec { "restart manager and workers in AllForOne" in { val countDown = new CountDownLatch(4) - val boss = actorOf(Props(self ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), None, None))) + val boss = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), None, None))) - val manager = actorOf(Props(new CountDownActor(countDown)).withFaultHandler(AllForOneStrategy(List(), None, None)).withSupervisor(boss)) + val managerProps = Props(new CountDownActor(countDown)).withFaultHandler(AllForOneStrategy(List(), None, None)) + val manager = (boss ? managerProps).as[ActorRef].get - val workerProps = Props(new CountDownActor(countDown)).withSupervisor(manager) - val workerOne, workerTwo, workerThree = actorOf(workerProps) + val workerProps = Props(new CountDownActor(countDown)) + val workerOne, workerTwo, workerThree = (manager ? workerProps).as[ActorRef].get filterException[ActorKilledException] { workerOne ! Kill @@ -48,7 +51,8 @@ class SupervisorHierarchySpec extends AkkaSpec { val countDownMessages = new CountDownLatch(1) val countDownMax = new CountDownLatch(1) val boss = actorOf(Props(new Actor { - val crasher = self startsMonitoring actorOf(Props(new CountDownActor(countDownMessages)).withSupervisor(self)) + val crasher = context.actorOf(Props(new CountDownActor(countDownMessages))) + self startsMonitoring crasher protected def receive = { case "killCrasher" ⇒ crasher ! Kill diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala index ea4776981d..cab176d184 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala @@ -16,26 +16,23 @@ class SupervisorMiscSpec extends AkkaSpec { filterEvents(EventFilter[Exception]("Kill")) { val countDownLatch = new CountDownLatch(4) - val supervisor = actorOf(Props(new Actor { - def receive = { case _ ⇒ } - }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 5000))) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 5000))) val workerProps = Props(new Actor { override def postRestart(cause: Throwable) { countDownLatch.countDown() } - protected def receive = { case "status" ⇒ this.reply("OK") case _ ⇒ this.self.stop() } - }).withSupervisor(supervisor) + }) - val actor1 = actorOf(workerProps.withDispatcher(app.dispatcherFactory.newPinnedDispatcher("pinned"))) + val actor1 = (supervisor ? workerProps.withDispatcher(app.dispatcherFactory.newPinnedDispatcher("pinned"))).as[ActorRef].get - val actor2 = actorOf(workerProps.withDispatcher(app.dispatcherFactory.newPinnedDispatcher("pinned"))) + val actor2 = (supervisor ? workerProps.withDispatcher(app.dispatcherFactory.newPinnedDispatcher("pinned"))).as[ActorRef].get - val actor3 = actorOf(workerProps.withDispatcher(app.dispatcherFactory.newDispatcher("test").build)) + val actor3 = (supervisor ? workerProps.withDispatcher(app.dispatcherFactory.newDispatcher("test").build)).as[ActorRef].get - val actor4 = actorOf(workerProps.withDispatcher(app.dispatcherFactory.newPinnedDispatcher("pinned"))) + val actor4 = (supervisor ? workerProps.withDispatcher(app.dispatcherFactory.newPinnedDispatcher("pinned"))).as[ActorRef].get actor1 ! Kill actor2 ! Kill diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala index c4604253c8..b802583340 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala @@ -54,7 +54,7 @@ object SupervisorSpec { class Master extends Actor { - val temp = context.actorOf(Props[PingPongActor].withSupervisor(self)) + val temp = context.actorOf(Props[PingPongActor]) def receive = { case Die ⇒ (temp.?(Die, TimeoutMillis)).get @@ -71,52 +71,49 @@ class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte // Creating actors and supervisors // ===================================================== + private def child(supervisor: ActorRef, props: Props): ActorRef = (supervisor ? props).as[ActorRef].get + def temporaryActorAllForOne = { - val supervisor = actorOf(Props(AllForOneStrategy(List(classOf[Exception]), Some(0)))) - val temporaryActor = actorOf(Props[PingPongActor].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), Some(0)))) + val temporaryActor = child(supervisor, Props[PingPongActor]) (temporaryActor, supervisor) } def singleActorAllForOne = { - val supervisor = actorOf(Props(AllForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) - val pingpong = actorOf(Props[PingPongActor].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) + val pingpong = child(supervisor, Props[PingPongActor]) (pingpong, supervisor) } def singleActorOneForOne = { - val supervisor = actorOf(Props(OneForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) - val pingpong = actorOf(Props[PingPongActor].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) + val pingpong = child(supervisor, Props[PingPongActor]) (pingpong, supervisor) } def multipleActorsAllForOne = { - val supervisor = actorOf(Props(AllForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) - val pingpong1 = actorOf(Props[PingPongActor].withSupervisor(supervisor)) - val pingpong2 = actorOf(Props[PingPongActor].withSupervisor(supervisor)) - val pingpong3 = actorOf(Props[PingPongActor].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) + val pingpong1, pingpong2, pingpong3 = child(supervisor, Props[PingPongActor]) (pingpong1, pingpong2, pingpong3, supervisor) } def multipleActorsOneForOne = { - val supervisor = actorOf(Props(OneForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) - val pingpong1 = actorOf(Props[PingPongActor].withSupervisor(supervisor)) - val pingpong2 = actorOf(Props[PingPongActor].withSupervisor(supervisor)) - val pingpong3 = actorOf(Props[PingPongActor].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) + val pingpong1, pingpong2, pingpong3 = child(supervisor, Props[PingPongActor]) (pingpong1, pingpong2, pingpong3, supervisor) } def nestedSupervisorsAllForOne = { - val topSupervisor = actorOf(Props(AllForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) - val pingpong1 = actorOf(Props[PingPongActor].withSupervisor(topSupervisor)) + val topSupervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), 3, TimeoutMillis))) + val pingpong1 = child(topSupervisor, Props[PingPongActor]) - val middleSupervisor = actorOf(Props(AllForOneStrategy(Nil, 3, TimeoutMillis)).withSupervisor(topSupervisor)) - val pingpong2 = actorOf(Props[PingPongActor].withSupervisor(middleSupervisor)) - val pingpong3 = actorOf(Props[PingPongActor].withSupervisor(middleSupervisor)) + val middleSupervisor = child(topSupervisor, Props[Supervisor].withFaultHandler(AllForOneStrategy(Nil, 3, TimeoutMillis))) + val pingpong2, pingpong3 = child(middleSupervisor, Props[PingPongActor]) (pingpong1, pingpong2, pingpong3, topSupervisor) } @@ -290,9 +287,9 @@ class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte "must attempt restart when exception during restart" in { val inits = new AtomicInteger(0) - val supervisor = actorOf(Props(OneForOneStrategy(classOf[Exception] :: Nil, 3, 10000))) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(OneForOneStrategy(classOf[Exception] :: Nil, 3, 10000))) - val dyingActor = actorOf(Props(new Actor { + val dyingProps = Props(new Actor { inits.incrementAndGet if (inits.get % 2 == 0) throw new IllegalStateException("Don't wanna!") @@ -301,7 +298,8 @@ class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte case Ping ⇒ tryReply(PongMessage) case Die ⇒ throw new RuntimeException("Expected") } - }).withSupervisor(supervisor)) + }) + val dyingActor = (supervisor ? dyingProps).as[ActorRef].get intercept[RuntimeException] { (dyingActor.?(Die, TimeoutMillis)).get diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala index fb4ae089c9..85cbda16a2 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala @@ -20,13 +20,10 @@ class SupervisorTreeSpec extends AkkaSpec with ImplicitSender { "be able to kill the middle actor and see itself and its child restarted" in { filterException[ActorKilledException] { within(5 seconds) { - val p = Props(new Actor { - def receive = { case false ⇒ } - override def preRestart(reason: Throwable, msg: Option[Any]) { testActor ! self.address } - }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 1000)) + val p = Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 1000)) val headActor = actorOf(p) - val middleActor = actorOf(p.withSupervisor(headActor)) - val lastActor = actorOf(p.withSupervisor(middleActor)) + val middleActor = (headActor ? p).as[ActorRef].get + val lastActor = (middleActor ? p).as[ActorRef].get middleActor ! Kill expectMsg(middleActor.address) diff --git a/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala b/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala index c32832fbc1..6620aeebf3 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala @@ -18,8 +18,8 @@ class Ticket669Spec extends AkkaSpec with BeforeAndAfterAll with ImplicitSender "A supervised actor with lifecycle PERMANENT" should { "be able to reply on failure during preRestart" in { filterEvents(EventFilter[Exception]("test")) { - val supervisor = actorOf(Props(AllForOneStrategy(List(classOf[Exception]), 5, 10000))) - val supervised = actorOf(Props[Supervised].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), 5, 10000))) + val supervised = (supervisor ? Props[Supervised]).as[ActorRef].get supervised.!("test")(Some(testActor)) expectMsg("failure1") @@ -29,8 +29,8 @@ class Ticket669Spec extends AkkaSpec with BeforeAndAfterAll with ImplicitSender "be able to reply on failure during postStop" in { filterEvents(EventFilter[Exception]("test")) { - val supervisor = actorOf(Props(AllForOneStrategy(List(classOf[Exception]), Some(0), None))) - val supervised = actorOf(Props[Supervised].withSupervisor(supervisor)) + val supervisor = actorOf(Props[Supervisor].withFaultHandler(AllForOneStrategy(List(classOf[Exception]), Some(0), None))) + val supervised = (supervisor ? Props[Supervised]).as[ActorRef].get supervised.!("test")(Some(testActor)) expectMsg("failure2") diff --git a/akka-actor/src/main/scala/akka/AkkaApplication.scala b/akka-actor/src/main/scala/akka/AkkaApplication.scala index ce67f66745..7a35872c90 100644 --- a/akka-actor/src/main/scala/akka/AkkaApplication.scala +++ b/akka-actor/src/main/scala/akka/AkkaApplication.scala @@ -155,27 +155,36 @@ class AkkaApplication(val name: String, val config: Configuration) extends Actor val defaultAddress = new InetSocketAddress(hostname, AkkaConfig.RemoteServerPort) - if (ConfigVersion != Version) - throw new ConfigurationException("Akka JAR version [" + Version + - "] does not match the provided config version [" + ConfigVersion + "]") - // TODO correctly pull its config from the config val dispatcherFactory = new Dispatchers(this) implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher + val reflective = new ReflectiveAccess(this) + + // TODO think about memory consistency effects when doing funky stuff inside an ActorRefProvider's constructor + val provider: ActorRefProvider = reflective.createProvider + + // TODO make this configurable + protected[akka] val guardian: ActorRef = { + import akka.actor.FaultHandlingStrategy._ + new LocalActorRef(this, + Props(context ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy { + case _: ActorKilledException ⇒ Stop + case _: Exception ⇒ Restart + }).withDispatcher(dispatcher), + provider.theOneWhoWalksTheBubblesOfSpaceTime, + "ApplicationSupervisor", + true) + } + val eventHandler = new EventHandler(this) val log: Logging = new EventHandlerLogging(eventHandler, this) - val reflective = new ReflectiveAccess(this) - // TODO think about memory consistency effects when doing funky stuff inside an ActorRefProvider's constructor val deployer = new Deployer(this) - // TODO think about memory consistency effects when doing funky stuff inside an ActorRefProvider's constructor - val provider: ActorRefProvider = reflective.createProvider - val deathWatch = provider.createDeathWatch() val typedActor = new TypedActor(this) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 3063cfaf01..3afde89403 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -64,11 +64,14 @@ private[akka] class ActorCell( val app: AkkaApplication, val self: ActorRef with ScalaActorRef, val props: Props, + val supervisor: ActorRef, var receiveTimeout: Option[Long], var hotswap: Stack[PartialFunction[Any, Unit]]) extends ActorContext { import ActorCell._ + protected def guardian = self + def provider = app.provider var futureTimeout: Option[ScheduledFuture[AnyRef]] = None //FIXME TODO Doesn't need to be volatile either, since it will only ever be accessed when a message is processed @@ -92,14 +95,8 @@ private[akka] class ActorCell( def start(): Unit = { mailbox = dispatcher.createMailbox(this) - if (props.supervisor.isDefined) { - props.supervisor.get match { - case l: LocalActorRef ⇒ - // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ - l.underlying.dispatcher.systemDispatch(l.underlying, akka.dispatch.Supervise(self)) //FIXME TODO Support all ActorRefs? - case other ⇒ throw new UnsupportedOperationException("Supervision failure: " + other + " cannot be a supervisor, only LocalActorRefs can") - } - } + // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ + supervisor.sendSystemMessage(akka.dispatch.Supervise(self)) dispatcher.attach(this) } @@ -127,9 +124,6 @@ private[akka] class ActorCell( def children: Iterable[ActorRef] = _children.map(_.child) - //TODO FIXME remove this method - def supervisor: Option[ActorRef] = props.supervisor - def postMessageToMailbox(message: Any, channel: UntypedChannel): Unit = dispatcher dispatch Envelope(this, message, channel) def postMessageToMailboxAndCreateFutureResultWithTimeout( @@ -193,7 +187,7 @@ private[akka] class ActorCell( // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) } finally { - if (supervisor.isDefined) supervisor.get ! Failed(self, e) else self.stop() + supervisor ! Failed(self, e) } } @@ -224,7 +218,7 @@ private[akka] class ActorCell( // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) } finally { - if (supervisor.isDefined) supervisor.get ! Failed(self, e) else self.stop() + supervisor ! Failed(self, e) } } @@ -252,16 +246,13 @@ private[akka] class ActorCell( } } } finally { - val cause = new ActorKilledException("Stopped") //FIXME TODO make this an object, can be reused everywhere try { - if (supervisor.isDefined) supervisor.get ! ChildTerminated(self, cause) + val cause = new ActorKilledException("Stopped") //FIXME TODO make this an object, can be reused everywhere + supervisor ! ChildTerminated(self, cause) + app.deathWatch.publish(Terminated(self, cause)) } finally { - try { - app.deathWatch.publish(Terminated(self, cause)) - } finally { - currentMessage = null - clearActorContext() - } + currentMessage = null + clearActorContext() } } } @@ -320,11 +311,8 @@ private[akka] class ActorCell( channel.sendException(e) - if (supervisor.isDefined) { - props.faultHandler.handleSupervisorFailing(self, _children) - supervisor.get ! Failed(self, e) - } else - dispatcher.resume(this) + props.faultHandler.handleSupervisorFailing(self, _children) + supervisor ! Failed(self, e) if (e.isInstanceOf[InterruptedException]) throw e //Re-throw InterruptedExceptions as expected } finally { @@ -343,7 +331,7 @@ private[akka] class ActorCell( } def handleFailure(fail: Failed): Unit = if (!props.faultHandler.handleFailure(fail, _children)) { - if (supervisor.isDefined) throw fail.cause else self.stop() + throw fail.cause } def handleChildTerminated(child: ActorRef): Unit = _children = props.faultHandler.handleChildTerminated(child, _children) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 41e8a6aeac..ff5bae9044 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -148,19 +148,20 @@ abstract class ActorRef extends ActorRefShared with UntypedChannel with ReplyCha class LocalActorRef private[akka] ( app: AkkaApplication, props: Props, - givenAddress: String, //Never refer to this internally instead use "address" + _supervisor: ActorRef, + _givenAddress: String, val systemService: Boolean = false, private[akka] val uuid: Uuid = newUuid, receiveTimeout: Option[Long] = None, hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap) extends ActorRef with ScalaActorRef { - final val address: String = givenAddress match { + final val address: String = _givenAddress match { case null | Props.randomAddress ⇒ uuid.toString case other ⇒ other } - private[this] val actorCell = new ActorCell(app, this, props, receiveTimeout, hotswap) + private[this] val actorCell = new ActorCell(app, this, props, _supervisor, receiveTimeout, hotswap) actorCell.start() /** @@ -224,6 +225,8 @@ class LocalActorRef private[akka] ( instance } + protected[akka] def sendSystemMessage(message: SystemMessage) { underlying.dispatcher.systemDispatch(underlying, message) } + protected[akka] def postMessageToMailbox(message: Any, channel: UntypedChannel): Unit = actorCell.postMessageToMailbox(message, channel) @@ -273,6 +276,8 @@ trait ActorRefShared { */ trait ScalaActorRef extends ActorRefShared with ReplyChannel[Any] { ref: ActorRef ⇒ + protected[akka] def sendSystemMessage(message: SystemMessage): Unit + /** * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. *

@@ -329,6 +334,8 @@ case class SerializedActorRef(uuid: Uuid, address: String, hostname: String, por */ trait UnsupportedActorRef extends ActorRef with ScalaActorRef { + private[akka] def uuid: Uuid = unsupported + def startsMonitoring(actorRef: ActorRef): ActorRef = unsupported def stopsMonitoring(actorRef: ActorRef): ActorRef = unsupported @@ -339,26 +346,38 @@ trait UnsupportedActorRef extends ActorRef with ScalaActorRef { protected[akka] def restart(cause: Throwable): Unit = unsupported + def stop(): Unit = unsupported + + def address: String = unsupported + + def isShutdown = false + + protected[akka] def sendSystemMessage(message: SystemMessage) {} + + protected[akka] def postMessageToMailbox(msg: Any, channel: UntypedChannel) {} + + protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout(msg: Any, timeout: Timeout, channel: UntypedChannel): Future[Any] = unsupported + private def unsupported = throw new UnsupportedOperationException("Not supported for %s".format(getClass.getName)) } class DeadLetterActorRef(app: AkkaApplication) extends UnsupportedActorRef { val brokenPromise = new KeptPromise[Any](Left(new ActorKilledException("In DeadLetterActorRef, promises are always broken.")))(app.dispatcher) - val address: String = "akka:internal:DeadLetterActorRef" + override val address: String = "akka:internal:DeadLetterActorRef" - private[akka] val uuid: akka.actor.Uuid = new com.eaio.uuid.UUID(0L, 0L) //Nil UUID + private[akka] override val uuid: akka.actor.Uuid = new com.eaio.uuid.UUID(0L, 0L) //Nil UUID override def startsMonitoring(actorRef: ActorRef): ActorRef = actorRef override def stopsMonitoring(actorRef: ActorRef): ActorRef = actorRef - def isShutdown(): Boolean = true + override def isShutdown(): Boolean = true - def stop(): Unit = () + override def stop(): Unit = () - protected[akka] def postMessageToMailbox(message: Any, channel: UntypedChannel): Unit = app.eventHandler.debug(this, message) + protected[akka] override def postMessageToMailbox(message: Any, channel: UntypedChannel): Unit = app.eventHandler.debug(this, message) - protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout( + protected[akka] override def postMessageToMailboxAndCreateFutureResultWithTimeout( message: Any, timeout: Timeout, channel: UntypedChannel): Future[Any] = { app.eventHandler.debug(this, message); brokenPromise } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 7133c82fcd..50d9c243f5 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -12,26 +12,28 @@ import java.util.concurrent.ConcurrentHashMap import com.eaio.uuid.UUID import akka.AkkaException import akka.event.{ ActorClassification, DeathWatch, EventHandler } -import akka.dispatch.{ Future, MessageDispatcher, Promise } +import akka.dispatch._ /** * Interface for all ActorRef providers to implement. */ trait ActorRefProvider { - def actorOf(props: Props, address: String): ActorRef + def actorOf(props: Props, supervisor: ActorRef, address: String): ActorRef = actorOf(props, supervisor, address, false) - def actorOf(props: RoutedProps, address: String): ActorRef + def actorOf(props: RoutedProps, supervisor: ActorRef, address: String): ActorRef def actorFor(address: String): Option[ActorRef] - private[akka] def actorOf(props: Props, address: String, systemService: Boolean): ActorRef + private[akka] def actorOf(props: Props, supervisor: ActorRef, address: String, systemService: Boolean): ActorRef private[akka] def evict(address: String): Boolean private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] private[akka] def createDeathWatch(): DeathWatch + + private[akka] def theOneWhoWalksTheBubblesOfSpaceTime: ActorRef } /** @@ -43,6 +45,11 @@ trait ActorRefFactory { def dispatcher: MessageDispatcher + /** + * Father of all children created by this interface. + */ + protected def guardian: ActorRef + def actorOf(props: Props): ActorRef = actorOf(props, Props.randomAddress) /* @@ -50,7 +57,7 @@ trait ActorRefFactory { * the same address can race on the cluster, and then you never know which * implementation wins */ - def actorOf(props: Props, address: String): ActorRef = provider.actorOf(props, address) + def actorOf(props: Props, address: String): ActorRef = provider.actorOf(props, guardian, address, false) def actorOf[T <: Actor](implicit m: Manifest[T]): ActorRef = actorOf(Props(m.erasure.asInstanceOf[Class[_ <: Actor]])) @@ -65,7 +72,7 @@ trait ActorRefFactory { def actorOf(props: RoutedProps): ActorRef = actorOf(props, Props.randomAddress) - def actorOf(props: RoutedProps, address: String): ActorRef = provider.actorOf(props, address) + def actorOf(props: RoutedProps, address: String): ActorRef = provider.actorOf(props, guardian, address) def findActor(address: String): Option[ActorRef] = provider.actorFor(address) @@ -78,9 +85,32 @@ class ActorRefProviderException(message: String) extends AkkaException(message) */ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { - private val actors = new ConcurrentHashMap[String, AnyRef] + /** + * Top-level anchor for the supervision hierarchy of this actor system. Will + * receive only Supervise/ChildTerminated system messages or Failure message. + */ + private[akka] val theOneWhoWalksTheBubblesOfSpaceTime: ActorRef = new UnsupportedActorRef { + override def address = app.name + ":BubbleWalker" - def actorOf(props: Props, address: String): ActorRef = actorOf(props, address, false) + override def toString = address + + protected[akka] override def postMessageToMailbox(msg: Any, channel: UntypedChannel) { + msg match { + case Failed(child, ex) ⇒ child.stop() + case ChildTerminated(child, ex) ⇒ // TODO execute any installed termination handlers + case _ ⇒ app.eventHandler.error(this, this + " received unexpected message " + msg) + } + } + + protected[akka] override def sendSystemMessage(message: SystemMessage) { + message match { + case Supervise(child) ⇒ // TODO register child in some map to keep track of it and enable shutdown after all dead + case _ ⇒ app.eventHandler.error(this, this + " received unexpected system message " + message) + } + } + } + + private val actors = new ConcurrentHashMap[String, AnyRef] def actorFor(address: String): Option[ActorRef] = actors.get(address) match { case null ⇒ None @@ -93,9 +123,9 @@ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { */ private[akka] def evict(address: String): Boolean = actors.remove(address) ne null - private[akka] def actorOf(props: Props, address: String, systemService: Boolean): ActorRef = { + private[akka] def actorOf(props: Props, supervisor: ActorRef, address: String, systemService: Boolean): ActorRef = { if ((address eq null) || address == Props.randomAddress) { - val actor = new LocalActorRef(app, props, address, systemService = true) + val actor = new LocalActorRef(app, props, supervisor, address, systemService = true) actors.putIfAbsent(actor.address, actor) match { case null ⇒ actor case other ⇒ throw new IllegalStateException("Same uuid generated twice for: " + actor + " and " + other) @@ -110,7 +140,7 @@ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { // create a local actor case None | Some(DeploymentConfig.Deploy(_, _, DeploymentConfig.Direct, _, _, DeploymentConfig.LocalScope)) ⇒ - new LocalActorRef(app, props, address, systemService) // create a local actor + new LocalActorRef(app, props, supervisor, address, systemService) // create a local actor // create a routed actor ref case deploy @ Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, _, DeploymentConfig.LocalScope)) ⇒ @@ -127,9 +157,9 @@ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { } val connections: Iterable[ActorRef] = - if (nrOfInstances.factor > 0) Vector.fill(nrOfInstances.factor)(new LocalActorRef(app, props, "", systemService)) else Nil + if (nrOfInstances.factor > 0) Vector.fill(nrOfInstances.factor)(new LocalActorRef(app, props, supervisor, "", systemService)) else Nil - actorOf(RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), address) + actorOf(RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), supervisor, address) case _ ⇒ throw new Exception("Don't know how to create this actor ref! Why?") } @@ -155,7 +185,9 @@ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { /** * Creates (or fetches) a routed actor reference, configured by the 'props: RoutedProps' configuration. */ - def actorOf(props: RoutedProps, address: String): ActorRef = { + def actorOf(props: RoutedProps, supervisor: ActorRef, address: String): ActorRef = { + // FIXME: this needs to take supervision into account! + //FIXME clustering should be implemented by cluster actor ref provider //TODO Implement support for configuring by deployment ID etc //TODO If address matches an already created actor (Ahead-of-time deployed) return that actor diff --git a/akka-actor/src/main/scala/akka/actor/Props.scala b/akka-actor/src/main/scala/akka/actor/Props.scala index ba668ae280..26fee852bb 100644 --- a/akka-actor/src/main/scala/akka/actor/Props.scala +++ b/akka-actor/src/main/scala/akka/actor/Props.scala @@ -80,8 +80,7 @@ object Props { case class Props(creator: () ⇒ Actor = Props.defaultCreator, @transient dispatcher: MessageDispatcher = Props.defaultDispatcher, timeout: Timeout = Props.defaultTimeout, - faultHandler: FaultHandlingStrategy = Props.defaultFaultHandler, - supervisor: Option[ActorRef] = Props.defaultSupervisor) { + faultHandler: FaultHandlingStrategy = Props.defaultFaultHandler) { /** * No-args constructor that sets all the default values * Java API @@ -90,8 +89,7 @@ case class Props(creator: () ⇒ Actor = Props.defaultCreator, creator = Props.defaultCreator, dispatcher = Props.defaultDispatcher, timeout = Props.defaultTimeout, - faultHandler = Props.defaultFaultHandler, - supervisor = Props.defaultSupervisor) + faultHandler = Props.defaultFaultHandler) /** * Returns a new Props with the specified creator set @@ -129,21 +127,4 @@ case class Props(creator: () ⇒ Actor = Props.defaultCreator, */ def withFaultHandler(f: FaultHandlingStrategy) = copy(faultHandler = f) - /** - * Returns a new Props with the specified supervisor set, if null, it's equivalent to withSupervisor(Option.none()) - * Java API - */ - def withSupervisor(s: ActorRef) = copy(supervisor = Option(s)) - - /** - * Returns a new Props with the specified supervisor set - * Java API - */ - def withSupervisor(s: akka.japi.Option[ActorRef]) = copy(supervisor = s.asScala) - - /** - * Returns a new Props with the specified supervisor set - * Scala API - */ - def withSupervisor(s: scala.Option[ActorRef]) = copy(supervisor = s) } diff --git a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala index 58502fa334..bb67b618b8 100644 --- a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala @@ -56,14 +56,14 @@ object SystemMessage { sealed trait SystemMessage extends PossiblyHarmful { var next: SystemMessage = _ } -case class Create() extends SystemMessage -case class Recreate(cause: Throwable) extends SystemMessage -case class Suspend() extends SystemMessage -case class Resume() extends SystemMessage -case class Terminate() extends SystemMessage -case class Supervise(child: ActorRef) extends SystemMessage -case class Link(subject: ActorRef) extends SystemMessage -case class Unlink(subject: ActorRef) extends SystemMessage +case class Create() extends SystemMessage // send to self from Dispatcher.register +case class Recreate(cause: Throwable) extends SystemMessage // sent to self from ActorCell.restart +case class Suspend() extends SystemMessage // sent to self from ActorCell.suspend +case class Resume() extends SystemMessage // sent to self from ActorCell.resume +case class Terminate() extends SystemMessage // sent to self from ActorCell.stop +case class Supervise(child: ActorRef) extends SystemMessage // sent to supervisor ActorRef from ActorCell.start +case class Link(subject: ActorRef) extends SystemMessage // sent to self from ActorCell.startsMonitoring +case class Unlink(subject: ActorRef) extends SystemMessage // sent to self from ActorCell.stopsMonitoring final case class TaskInvocation(app: AkkaApplication, function: () ⇒ Unit, cleanup: () ⇒ Unit) extends Runnable { def run() { diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index a668c16249..fa867eeada 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -204,7 +204,7 @@ class EventHandler(app: AkkaApplication) extends ListenerManagement { defaultListeners foreach { listenerName ⇒ try { ReflectiveAccess.getClassFor[Actor](listenerName) match { - case Right(actorClass) ⇒ addListener(new LocalActorRef(app, Props(actorClass).withDispatcher(EventHandlerDispatcher), Props.randomAddress, systemService = true)) + case Right(actorClass) ⇒ addListener(new LocalActorRef(app, Props(actorClass).withDispatcher(EventHandlerDispatcher), app.guardian, Props.randomAddress, systemService = true)) case Left(exception) ⇒ throw exception } } catch { diff --git a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala index 5df0607365..69b0040efb 100644 --- a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala @@ -190,7 +190,7 @@ abstract class RemoteSupport(val app: AkkaApplication) extends ListenerManagemen lazy val eventHandler: ActorRef = { implicit object format extends StatelessActorFormat[RemoteEventHandler] val clazz = classOf[RemoteEventHandler] - val handler = app.provider.actorOf(Props(clazz), clazz.getName, true) + val handler = app.provider.actorOf(Props(clazz), app.guardian, clazz.getName, true) // add the remote client and server listener that pipes the events to the event handler system addListener(handler) handler diff --git a/akka-actor/src/main/scala/akka/routing/Pool.scala b/akka-actor/src/main/scala/akka/routing/Pool.scala index 803bdf1a51..3554cfb1af 100644 --- a/akka-actor/src/main/scala/akka/routing/Pool.scala +++ b/akka-actor/src/main/scala/akka/routing/Pool.scala @@ -93,7 +93,7 @@ trait DefaultActorPool extends ActorPool { this: Actor ⇒ protected[akka] var _delegates = Vector[ActorRef]() - val defaultProps: Props = Props.default.withSupervisor(this.self).withDispatcher(this.context.dispatcher) + val defaultProps: Props = Props.default.withDispatcher(this.context.dispatcher) override def postStop() { _delegates foreach evict diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index d499b80efb..28d253d172 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -94,7 +94,7 @@ object Routing { * An Abstract convenience implementation for building an ActorReference that uses a Router. */ abstract private[akka] class AbstractRoutedActorRef(val props: RoutedProps) extends UnsupportedActorRef { - private[akka] val uuid: Uuid = newUuid + private[akka] override val uuid: Uuid = newUuid val router = props.routerFactory() @@ -120,14 +120,14 @@ abstract private[akka] class AbstractRoutedActorRef(val props: RoutedProps) exte * A RoutedActorRef is an ActorRef that has a set of connected ActorRef and it uses a Router to send a message to * on (or more) of these actors. */ -private[akka] class RoutedActorRef(val routedProps: RoutedProps, val address: String) extends AbstractRoutedActorRef(routedProps) { +private[akka] class RoutedActorRef(val routedProps: RoutedProps, override val address: String) extends AbstractRoutedActorRef(routedProps) { @volatile private var running: Boolean = true - def isShutdown: Boolean = !running + override def isShutdown: Boolean = !running - def stop() { + override def stop() { synchronized { if (running) { running = false diff --git a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala index b67f782c36..efd645cefd 100644 --- a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala +++ b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala @@ -64,8 +64,10 @@ class NetworkEventStream(val app: AkkaApplication) { import NetworkEventStream._ + // FIXME: check that this supervision is correct private[akka] val channel = app.provider.actorOf( - Props[Channel].copy(dispatcher = app.dispatcherFactory.newPinnedDispatcher("NetworkEventStream")), Props.randomAddress, systemService = true) + Props[Channel].copy(dispatcher = app.dispatcherFactory.newPinnedDispatcher("NetworkEventStream")), + app.guardian, Props.randomAddress, systemService = true) /** * Registers a network event stream listener (asyncronously). diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 225612812f..6d6c2d14c2 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -31,6 +31,8 @@ class RemoteActorRefProvider(val app: AkkaApplication) extends ActorRefProvider import java.util.concurrent.ConcurrentHashMap import akka.dispatch.Promise + private[akka] val theOneWhoWalksTheBubblesOfSpaceTime: ActorRef = new UnsupportedActorRef {} + val local = new LocalActorRefProvider(app) val remote = new Remote(app) @@ -44,10 +46,8 @@ class RemoteActorRefProvider(val app: AkkaApplication) extends ActorRefProvider def defaultDispatcher = app.dispatcher def defaultTimeout = app.AkkaConfig.ActorTimeout - def actorOf(props: Props, address: String): ActorRef = actorOf(props, address, false) - - def actorOf(props: Props, address: String, systemService: Boolean): ActorRef = - if (systemService) local.actorOf(props, address, systemService) + def actorOf(props: Props, supervisor: ActorRef, address: String, systemService: Boolean): ActorRef = + if (systemService) local.actorOf(props, supervisor, address, systemService) else { val newFuture = Promise[ActorRef](5000)(defaultDispatcher) // FIXME is this proper timeout? @@ -71,7 +71,7 @@ class RemoteActorRefProvider(val app: AkkaApplication) extends ActorRefProvider if (isReplicaNode) { // we are on one of the replica node for this remote actor - new LocalActorRef(app, props, address, false) + new LocalActorRef(app, props, supervisor, address, false) } else { // we are on the single "reference" node uses the remote actors on the replica nodes @@ -115,10 +115,10 @@ class RemoteActorRefProvider(val app: AkkaApplication) extends ActorRefProvider connections.keys foreach { useActorOnNode(_, address, props.creator) } - actorOf(RoutedProps(routerFactory = routerFactory, connectionManager = connectionManager), address) + actorOf(RoutedProps(routerFactory = routerFactory, connectionManager = connectionManager), supervisor, address) } - case deploy ⇒ local.actorOf(props, address, systemService) + case deploy ⇒ local.actorOf(props, supervisor, address, systemService) } } catch { case e: Exception ⇒ @@ -139,7 +139,8 @@ class RemoteActorRefProvider(val app: AkkaApplication) extends ActorRefProvider /** * Copied from LocalActorRefProvider... */ - def actorOf(props: RoutedProps, address: String): ActorRef = { + // FIXME: implement supervision + def actorOf(props: RoutedProps, supervisor: ActorRef, address: String): ActorRef = { if (props.connectionManager.isEmpty) throw new ConfigurationException("RoutedProps used for creating actor [" + address + "] has zero connections configured; can't create a router") new RoutedActorRef(props, address) } @@ -244,6 +245,8 @@ private[akka] case class RemoteActorRef private[akka] ( def isShutdown: Boolean = !running + protected[akka] def sendSystemMessage(message: SystemMessage): Unit = unsupported + def postMessageToMailbox(message: Any, channel: UntypedChannel) { val chSender = if (channel.isInstanceOf[ActorRef]) Some(channel.asInstanceOf[ActorRef]) else None remote.send[Any](message, chSender, None, remoteAddress, true, this, loader) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteDaemon.scala b/akka-remote/src/main/scala/akka/remote/RemoteDaemon.scala index 449badf073..48e113187e 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteDaemon.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteDaemon.scala @@ -44,12 +44,14 @@ class Remote(val app: AkkaApplication) extends RemoteService { private[remote] lazy val remoteDaemonSupervisor = app.actorOf(Props( OneForOneStrategy(List(classOf[Exception]), None, None))) // is infinite restart what we want? + // FIXME check that this supervision is okay private[remote] lazy val remoteDaemon = new LocalActorRef( app, - props = Props(new RemoteDaemon(this)).withDispatcher(app.dispatcherFactory.newPinnedDispatcher("Remote")).withSupervisor(remoteDaemonSupervisor), - givenAddress = remoteDaemonServiceName, - systemService = true) + Props(new RemoteDaemon(this)).withDispatcher(app.dispatcherFactory.newPinnedDispatcher("Remote")), + app.guardian, + remoteDaemonServiceName, + true) private[remote] lazy val remoteClientLifeCycleHandler = app.actorOf(Props(new Actor { def receive = { @@ -172,36 +174,40 @@ class RemoteDaemon(val remote: Remote) extends Actor { // } } + // FIXME: handle real remote supervision def handle_fun0_unit(message: RemoteProtocol.RemoteDaemonMessageProtocol) { new LocalActorRef(app, Props( context ⇒ { case f: Function0[_] ⇒ try { f() } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), Props.randomAddress, systemService = true) ! payloadFor(message, classOf[Function0[Unit]]) + }).copy(dispatcher = computeGridDispatcher), app.guardian, Props.randomAddress, systemService = true) ! payloadFor(message, classOf[Function0[Unit]]) } + // FIXME: handle real remote supervision def handle_fun0_any(message: RemoteProtocol.RemoteDaemonMessageProtocol) { new LocalActorRef(app, Props( context ⇒ { case f: Function0[_] ⇒ try { reply(f()) } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), Props.randomAddress, systemService = true) forward payloadFor(message, classOf[Function0[Any]]) + }).copy(dispatcher = computeGridDispatcher), app.guardian, Props.randomAddress, systemService = true) forward payloadFor(message, classOf[Function0[Any]]) } + // FIXME: handle real remote supervision def handle_fun1_arg_unit(message: RemoteProtocol.RemoteDaemonMessageProtocol) { new LocalActorRef(app, Props( context ⇒ { case (fun: Function[_, _], param: Any) ⇒ try { fun.asInstanceOf[Any ⇒ Unit].apply(param) } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), Props.randomAddress, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]]) + }).copy(dispatcher = computeGridDispatcher), app.guardian, Props.randomAddress, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]]) } + // FIXME: handle real remote supervision def handle_fun1_arg_any(message: RemoteProtocol.RemoteDaemonMessageProtocol) { new LocalActorRef(app, Props( context ⇒ { case (fun: Function[_, _], param: Any) ⇒ try { reply(fun.asInstanceOf[Any ⇒ Any](param)) } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), Props.randomAddress, systemService = true) forward payloadFor(message, classOf[Tuple2[Function1[Any, Any], Any]]) + }).copy(dispatcher = computeGridDispatcher), app.guardian, Props.randomAddress, systemService = true) forward payloadFor(message, classOf[Tuple2[Function1[Any, Any], Any]]) } def handleFailover(message: RemoteProtocol.RemoteDaemonMessageProtocol) { diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 2e4d177aba..d1d35269ce 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -201,15 +201,14 @@ class ActorSerialization(val app: AkkaApplication, remote: RemoteSupport) { } val props = Props(creator = factory, - timeout = if (protocol.hasTimeout) protocol.getTimeout else app.AkkaConfig.ActorTimeout, - supervisor = storedSupervisor //TODO what dispatcher should it use? + timeout = if (protocol.hasTimeout) protocol.getTimeout else app.AkkaConfig.ActorTimeout //TODO what dispatcher should it use? //TODO what faultHandler should it use? - // ) val receiveTimeout = if (protocol.hasReceiveTimeout) Some(protocol.getReceiveTimeout) else None //TODO FIXME, I'm expensive and slow - val ar = new LocalActorRef(app, props, protocol.getAddress, false, actorUuid, receiveTimeout, storedHotswap) + // FIXME: what to do if storedSupervisor is empty? + val ar = new LocalActorRef(app, props, storedSupervisor getOrElse app.guardian, protocol.getAddress, false, actorUuid, receiveTimeout, storedHotswap) //Deserialize messages { diff --git a/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala b/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala index 6a31c6eea3..90e433c26e 100644 --- a/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala +++ b/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala @@ -23,7 +23,7 @@ class ActorSerializeSpec extends AkkaSpec with BeforeAndAfterAll { "Serializable actor" must { "must be able to serialize and de-serialize a stateful actor with a given serializer" ignore { - val actor1 = new LocalActorRef(app, Props[MyJavaSerializableActor], Props.randomAddress, systemService = true) + val actor1 = new LocalActorRef(app, Props[MyJavaSerializableActor], app.guardian, Props.randomAddress, systemService = true) (actor1 ? "hello").get must equal("world 1") (actor1 ? "hello").get must equal("world 2") @@ -39,7 +39,7 @@ class ActorSerializeSpec extends AkkaSpec with BeforeAndAfterAll { "must be able to serialize and deserialize a MyStatelessActorWithMessagesInMailbox" ignore { - val actor1 = new LocalActorRef(app, Props[MyStatelessActorWithMessagesInMailbox], Props.randomAddress, systemService = true) + val actor1 = new LocalActorRef(app, Props[MyStatelessActorWithMessagesInMailbox], app.guardian, Props.randomAddress, systemService = true) for (i ← 1 to 10) actor1 ! "hello" actor1.underlying.dispatcher.mailboxSize(actor1.underlying) must be > (0) @@ -57,7 +57,7 @@ class ActorSerializeSpec extends AkkaSpec with BeforeAndAfterAll { "must be able to serialize and deserialize a PersonActorWithMessagesInMailbox" ignore { val p1 = Person("debasish ghosh", 25, SerializeSpec.Address("120", "Monroe Street", "Santa Clara", "95050")) - val actor1 = new LocalActorRef(app, Props[PersonActorWithMessagesInMailbox], Props.randomAddress, systemService = true) + val actor1 = new LocalActorRef(app, Props[PersonActorWithMessagesInMailbox], app.guardian, Props.randomAddress, systemService = true) (actor1 ! p1) (actor1 ! p1) (actor1 ! p1) @@ -103,7 +103,7 @@ class ActorSerializeSpec extends AkkaSpec with BeforeAndAfterAll { "serialize actor that accepts protobuf message" ignore { "must serialize" ignore { - val actor1 = new LocalActorRef(app, Props[MyActorWithProtobufMessagesInMailbox], Props.randomAddress, systemService = true) + val actor1 = new LocalActorRef(app, Props[MyActorWithProtobufMessagesInMailbox], app.guardian, Props.randomAddress, systemService = true) val msg = MyMessage(123, "debasish ghosh", true) val b = ProtobufProtocol.MyMessage.newBuilder.setId(msg.id).setName(msg.name).setStatus(msg.status).build for (i ← 1 to 10) actor1 ! b diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index 8794d00c50..b612a7a7f8 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -19,8 +19,8 @@ import akka.AkkaApplication * @author Roland Kuhn * @since 1.1 */ -class TestActorRef[T <: Actor](_app: AkkaApplication, props: Props, address: String) - extends LocalActorRef(_app, props.withDispatcher(new CallingThreadDispatcher(_app)), address, false) { +class TestActorRef[T <: Actor](_app: AkkaApplication, _props: Props, _supervisor: ActorRef, address: String) + extends LocalActorRef(_app, _props.withDispatcher(new CallingThreadDispatcher(_app)), _supervisor, address, false) { /** * Directly inject messages into actor receive behavior. Any exceptions * thrown will be available to you, while still being able to use @@ -48,7 +48,10 @@ object TestActorRef { def apply[T <: Actor](props: Props)(implicit app: AkkaApplication): TestActorRef[T] = apply[T](props, Props.randomAddress) - def apply[T <: Actor](props: Props, address: String)(implicit app: AkkaApplication): TestActorRef[T] = new TestActorRef(app, props, address) + def apply[T <: Actor](props: Props, address: String)(implicit app: AkkaApplication): TestActorRef[T] = apply[T](props, app.guardian, address) + + def apply[T <: Actor](props: Props, supervisor: ActorRef, address: String)(implicit app: AkkaApplication): TestActorRef[T] = + new TestActorRef(app, props, supervisor, address) def apply[T <: Actor](implicit m: Manifest[T], app: AkkaApplication): TestActorRef[T] = apply[T](Props.randomAddress) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala index 9b3b59c6bd..6ea2e058c6 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala @@ -34,8 +34,8 @@ import akka.AkkaApplication * @author Roland Kuhn * @since 1.2 */ -class TestFSMRef[S, D, T <: Actor](app: AkkaApplication, props: Props, address: String)(implicit ev: T <:< FSM[S, D]) - extends TestActorRef(app, props, address) { +class TestFSMRef[S, D, T <: Actor](app: AkkaApplication, props: Props, supervisor: ActorRef, address: String)(implicit ev: T <:< FSM[S, D]) + extends TestActorRef(app, props, supervisor, address) { private def fsm: T = underlyingActor @@ -81,8 +81,8 @@ class TestFSMRef[S, D, T <: Actor](app: AkkaApplication, props: Props, address: object TestFSMRef { def apply[S, D, T <: Actor](factory: ⇒ T)(implicit ev: T <:< FSM[S, D], app: AkkaApplication): TestFSMRef[S, D, T] = - new TestFSMRef(app, Props(creator = () ⇒ factory), Props.randomAddress) + new TestFSMRef(app, Props(creator = () ⇒ factory), app.guardian, Props.randomAddress) def apply[S, D, T <: Actor](factory: ⇒ T, address: String)(implicit ev: T <:< FSM[S, D], app: AkkaApplication): TestFSMRef[S, D, T] = - new TestFSMRef(app, Props(creator = () ⇒ factory), address) + new TestFSMRef(app, Props(creator = () ⇒ factory), app.guardian, address) } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index f4541f97f6..6a0f60a533 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -99,7 +99,7 @@ class TestKit(_app: AkkaApplication) { * ActorRef of the test actor. Access is provided to enable e.g. * registration as message target. */ - val testActor: ActorRef = new LocalActorRef(app, Props(new TestActor(queue)).copy(dispatcher = new CallingThreadDispatcher(app)), "testActor" + TestKit.testActorId.incrementAndGet(), true) + val testActor: ActorRef = new LocalActorRef(app, Props(new TestActor(queue)).copy(dispatcher = new CallingThreadDispatcher(app)), app.guardian, "testActor" + TestKit.testActorId.incrementAndGet(), true) private var end: Duration = Duration.Inf diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index 4546c60a85..434c053e54 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -172,11 +172,11 @@ class TestActorRefSpec extends AkkaSpec with BeforeAndAfterEach { val boss = TestActorRef(Props(new TActor { - val ref = TestActorRef(Props(new TActor { + val ref = new TestActorRef(app, Props(new TActor { def receiveT = { case _ ⇒ } override def preRestart(reason: Throwable, msg: Option[Any]) { counter -= 1 } override def postRestart(reason: Throwable) { counter -= 1 } - }).withSupervisor(self)) + }), self, "child") def receiveT = { case "sendKill" ⇒ ref ! Kill } }).withFaultHandler(OneForOneStrategy(List(classOf[ActorKilledException]), 5, 1000))) From 172ab31f2ae4396b5174f4b8711a96523736fb1e Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 20 Oct 2011 20:45:02 +0200 Subject: [PATCH 4/9] improve some, but tests are STILL FAILING - AkkaApplication.stop() will stop the guardian; when that stops, it will send ChildTerminated to the BubbleWalker, who will then complete a Promise - AkkaApplication.terminationFuture is that Promise and available for registration of onComplete callback, used now to shutdown the Scheduler - AkkaSpec extends BeforeAndAfterAll and uses that to call app.stop(), exposing atStartup() and atTermination() overridable hooks - fixed some tests --- .../akka/actor/RestartStrategySpec.scala | 6 +- .../scala/akka/actor/SupervisorSpec.scala | 6 +- .../scala/akka/actor/SupervisorTreeSpec.scala | 7 +- .../test/scala/akka/actor/Ticket669Spec.scala | 5 +- .../akka/actor/dispatch/ActorModelSpec.scala | 32 +++------ .../src/main/scala/akka/AkkaApplication.scala | 28 ++++++-- .../src/main/scala/akka/actor/Actor.scala | 3 + .../src/main/scala/akka/actor/ActorCell.scala | 49 ++++++++----- .../scala/akka/actor/ActorRefProvider.scala | 13 +++- .../main/scala/akka/actor/FaultHandling.scala | 70 +++++++++---------- .../main/scala/akka/dispatch/Mailbox.scala | 12 +++- .../test/scala/akka/testkit/AkkaSpec.scala | 17 ++++- 12 files changed, 152 insertions(+), 96 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala index 545901d3f3..159d611c28 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala @@ -12,13 +12,13 @@ import java.util.concurrent.{ TimeUnit, CountDownLatch } import org.multiverse.api.latches.StandardLatch import akka.testkit.AkkaSpec -class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll { +class RestartStrategySpec extends AkkaSpec { - override def beforeAll() { + override def atStartup() { app.eventHandler.notify(Mute(EventFilter[Exception]("Crashing..."))) } - override def afterAll() { + override def atTermination() { app.eventHandler.notify(UnMuteAll) } diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala index b802583340..24c104debe 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala @@ -63,7 +63,7 @@ object SupervisorSpec { } } -class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfterAll { +class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach { import SupervisorSpec._ @@ -118,13 +118,13 @@ class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte (pingpong1, pingpong2, pingpong3, topSupervisor) } - override def beforeAll() = { + override def atStartup() = { app.eventHandler notify Mute(EventFilter[Exception]("Die"), EventFilter[IllegalStateException]("Don't wanna!"), EventFilter[RuntimeException]("Expected")) } - override def afterAll() = { + override def atTermination() = { app.eventHandler notify UnMuteAll } diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala index 85cbda16a2..897ddc06b0 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala @@ -20,7 +20,12 @@ class SupervisorTreeSpec extends AkkaSpec with ImplicitSender { "be able to kill the middle actor and see itself and its child restarted" in { filterException[ActorKilledException] { within(5 seconds) { - val p = Props[Supervisor].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 1000)) + val p = Props(new Actor { + def receive = { + case p: Props ⇒ this reply context.actorOf(p) + } + override def preRestart(cause: Throwable, msg: Option[Any]) { testActor ! self.address } + }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 1000)) val headActor = actorOf(p) val middleActor = (headActor ? p).as[ActorRef].get val lastActor = (middleActor ? p).as[ActorRef].get diff --git a/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala b/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala index 6620aeebf3..b1f56963b5 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala @@ -13,7 +13,10 @@ import akka.testkit.ImplicitSender class Ticket669Spec extends AkkaSpec with BeforeAndAfterAll with ImplicitSender { import Ticket669Spec._ - override def beforeAll = Thread.interrupted() //remove interrupted status. + // TODO: does this really make sense? + override def atStartup() { + Thread.interrupted() //remove interrupted status. + } "A supervised actor with lifecycle PERMANENT" should { "be able to reply on failure during preRestart" in { diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala index b8a672aaf1..c9efc0a0fd 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala @@ -14,6 +14,7 @@ import org.junit.{ After, Test } import akka.actor._ import util.control.NoStackTrace import akka.AkkaApplication +import akka.util.duration._ object ActorModelSpec { @@ -346,20 +347,6 @@ abstract class ActorModelSpec extends AkkaSpec { assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) } - "suspend and resume a failing non supervised permanent actor" in { - filterEvents(EventFilter[Exception]("Restart")) { - implicit val dispatcher = newInterceptedDispatcher - val a = newTestActor(dispatcher) - val done = new CountDownLatch(1) - a ! Restart - a ! CountDown(done) - assertCountDown(done, Testing.testTime(3000), "Should be suspended+resumed and done with next message within 3 seconds") - a.stop() - assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 2, - msgsProcessed = 2, suspensions = 1, resumes = 1) - } - } - "not process messages for a suspended actor" in { implicit val dispatcher = newInterceptedDispatcher val a = newTestActor(dispatcher).asInstanceOf[LocalActorRef] @@ -380,13 +367,15 @@ abstract class ActorModelSpec extends AkkaSpec { } "handle waves of actors" in { - implicit val dispatcher = newInterceptedDispatcher + val dispatcher = newInterceptedDispatcher + val props = Props[DispatcherActor].withDispatcher(dispatcher) def flood(num: Int) { val cachedMessage = CountDownNStop(new CountDownLatch(num)) - (1 to num) foreach { _ ⇒ - newTestActor(dispatcher) ! cachedMessage - } + val boss = actorOf(Props(context ⇒ { + case "run" ⇒ + for (_ ← 1 to num) context.actorOf(props) ! cachedMessage + })) ! "run" try { assertCountDown(cachedMessage.latch, Testing.testTime(10000), "Should process " + num + " countdowns") } catch { @@ -421,8 +410,9 @@ abstract class ActorModelSpec extends AkkaSpec { } "continue to process messages when a thread gets interrupted" in { - filterEvents(EventFilter[InterruptedException]("Ping!"), EventFilter[akka.event.EventHandler.EventHandlerException]) { + filterEvents(EventFilter[InterruptedException], EventFilter[akka.event.EventHandler.EventHandlerException]) { implicit val dispatcher = newInterceptedDispatcher + implicit val timeout = Timeout(5 seconds) val a = newTestActor(dispatcher) val f1 = a ? Reply("foo") val f2 = a ? Reply("bar") @@ -433,11 +423,11 @@ abstract class ActorModelSpec extends AkkaSpec { assert(f1.get === "foo") assert(f2.get === "bar") - assert((intercept[InterruptedException] { + assert((intercept[ActorInterruptedException] { f3.get }).getMessage === "Ping!") assert(f4.get === "foo2") - assert((intercept[InterruptedException] { + assert((intercept[ActorInterruptedException] { f5.get }).getMessage === "Ping!") assert(f6.get === "bar2") diff --git a/akka-actor/src/main/scala/akka/AkkaApplication.scala b/akka-actor/src/main/scala/akka/AkkaApplication.scala index 7a35872c90..129e9a8aac 100644 --- a/akka-actor/src/main/scala/akka/AkkaApplication.scala +++ b/akka-actor/src/main/scala/akka/AkkaApplication.scala @@ -7,14 +7,14 @@ import akka.config._ import akka.actor._ import java.net.InetAddress import com.eaio.uuid.UUID -import dispatch.{ Dispatcher, Dispatchers } +import akka.dispatch.{ Dispatcher, Dispatchers, Future, DefaultPromise } import akka.util.Duration -import util.ReflectiveAccess +import akka.util.ReflectiveAccess import java.util.concurrent.TimeUnit import akka.dispatch.BoundedMailbox import akka.dispatch.UnboundedMailbox import akka.routing.Routing -import remote.RemoteSupport +import akka.remote.RemoteSupport import akka.serialization.Serialization import akka.event.EventHandler import akka.event.EventHandlerLogging @@ -71,6 +71,10 @@ object AkkaApplication { def apply(): AkkaApplication = new AkkaApplication() + sealed trait ExitStatus + case object Stopped extends ExitStatus + case class Failed(cause: Throwable) extends ExitStatus + } class AkkaApplication(val name: String, val config: Configuration) extends ActorRefFactory { @@ -160,9 +164,12 @@ class AkkaApplication(val name: String, val config: Configuration) extends Actor implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher + def terminationFuture: Future[ExitStatus] = provider.terminationFuture + + // TODO think about memory consistency effects when doing funky stuff inside constructor val reflective = new ReflectiveAccess(this) - // TODO think about memory consistency effects when doing funky stuff inside an ActorRefProvider's constructor + // TODO think about memory consistency effects when doing funky stuff inside constructor val provider: ActorRefProvider = reflective.createProvider // TODO make this configurable @@ -178,18 +185,29 @@ class AkkaApplication(val name: String, val config: Configuration) extends Actor true) } + // TODO think about memory consistency effects when doing funky stuff inside constructor val eventHandler = new EventHandler(this) + // TODO think about memory consistency effects when doing funky stuff inside constructor val log: Logging = new EventHandlerLogging(eventHandler, this) - // TODO think about memory consistency effects when doing funky stuff inside an ActorRefProvider's constructor + // TODO think about memory consistency effects when doing funky stuff inside constructor val deployer = new Deployer(this) val deathWatch = provider.createDeathWatch() + // TODO think about memory consistency effects when doing funky stuff inside constructor val typedActor = new TypedActor(this) + // TODO think about memory consistency effects when doing funky stuff inside constructor val serialization = new Serialization(this) val scheduler = new DefaultScheduler + terminationFuture.onComplete(_ ⇒ scheduler.shutdown()) + + // TODO shutdown all that other stuff, whatever that may be + def stop(): Unit = { + guardian.stop() + } + } diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index d80d1285c7..d3ef4a04c9 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -99,6 +99,9 @@ case class DeathPactException private[akka] (dead: ActorRef, cause: Throwable) extends AkkaException("monitored actor " + dead + " terminated", cause) with NoStackTrace +// must not pass InterruptedException to other threads +case class ActorInterruptedException private[akka] (cause: Throwable) extends AkkaException(cause.getMessage, cause) with NoStackTrace + /** * This message is thrown by default when an Actors behavior doesn't match a message */ diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 3afde89403..586f2c9d81 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -7,7 +7,7 @@ package akka.actor import akka.dispatch._ import akka.util._ import scala.annotation.tailrec -import scala.collection.immutable.Stack +import scala.collection.immutable.{ Stack, TreeMap } import scala.collection.JavaConverters import java.util.concurrent.{ ScheduledFuture, TimeUnit } import java.util.{ Collection ⇒ JCollection, Collections ⇒ JCollections } @@ -76,7 +76,7 @@ private[akka] class ActorCell( var futureTimeout: Option[ScheduledFuture[AnyRef]] = None //FIXME TODO Doesn't need to be volatile either, since it will only ever be accessed when a message is processed - var _children: Vector[ChildRestartStats] = Vector.empty + var _children = TreeMap[ActorRef, ChildRestartStats]() var currentMessage: Envelope = null @@ -122,7 +122,7 @@ private[akka] class ActorCell( subject } - def children: Iterable[ActorRef] = _children.map(_.child) + def children: Iterable[ActorRef] = _children.keys def postMessageToMailbox(message: Any, channel: UntypedChannel): Unit = dispatcher dispatch Envelope(this, message, channel) @@ -211,7 +211,7 @@ private[akka] class ActorCell( dispatcher.resume(this) //FIXME should this be moved down? - props.faultHandler.handleSupervisorRestarted(cause, self, _children) + props.faultHandler.handleSupervisorRestarted(cause, self, children) } catch { case e ⇒ try { app.eventHandler.error(e, self, "error while creating actor") @@ -239,14 +239,15 @@ private[akka] class ActorCell( if (a ne null) a.postStop() } finally { //Stop supervised actors - val links = _children - if (links.nonEmpty) { - _children = Vector.empty - links.foreach(_.child.stop()) + val c = children + if (c.nonEmpty) { + _children = TreeMap.empty + for (child ← c) child.stop() } } } finally { try { + // when changing this, remember to update the match in the BubbleWalker val cause = new ActorKilledException("Stopped") //FIXME TODO make this an object, can be reused everywhere supervisor ! ChildTerminated(self, cause) app.deathWatch.publish(Terminated(self, cause)) @@ -259,8 +260,8 @@ private[akka] class ActorCell( def supervise(child: ActorRef): Unit = { val links = _children - if (!links.exists(_.child == child)) { - _children = links :+ ChildRestartStats(child) + if (!links.contains(child)) { + _children = _children.updated(child, ChildRestartStats()) if (app.AkkaConfig.DebugLifecycle) app.eventHandler.debug(self, "now supervising " + child) } else app.eventHandler.warning(self, "Already supervising " + child) } @@ -309,12 +310,18 @@ private[akka] class ActorCell( // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) - channel.sendException(e) - - props.faultHandler.handleSupervisorFailing(self, _children) - supervisor ! Failed(self, e) - - if (e.isInstanceOf[InterruptedException]) throw e //Re-throw InterruptedExceptions as expected + // make sure that InterruptedException does not leave this thread + if (e.isInstanceOf[InterruptedException]) { + val ex = ActorInterruptedException(e) + channel.sendException(ex) + props.faultHandler.handleSupervisorFailing(self, children) + supervisor ! Failed(self, ex) + throw e //Re-throw InterruptedExceptions as expected + } else { + channel.sendException(e) + props.faultHandler.handleSupervisorFailing(self, children) + supervisor ! Failed(self, e) + } } finally { checkReceiveTimeout // Reschedule receive timeout } @@ -330,11 +337,15 @@ private[akka] class ActorCell( } } - def handleFailure(fail: Failed): Unit = if (!props.faultHandler.handleFailure(fail, _children)) { - throw fail.cause + def handleFailure(fail: Failed): Unit = _children.get(fail.actor) match { + case Some(stats) ⇒ if (!props.faultHandler.handleFailure(fail, stats, _children)) throw fail.cause + case None ⇒ app.eventHandler.warning(self, "dropping " + fail + " from unknown child") } - def handleChildTerminated(child: ActorRef): Unit = _children = props.faultHandler.handleChildTerminated(child, _children) + def handleChildTerminated(child: ActorRef): Unit = { + _children -= child + props.faultHandler.handleChildTerminated(child, children) + } // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ def restart(cause: Throwable): Unit = dispatcher.systemDispatch(this, Recreate(cause)) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 50d9c243f5..4b5c0ebf32 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -34,6 +34,8 @@ trait ActorRefProvider { private[akka] def createDeathWatch(): DeathWatch private[akka] def theOneWhoWalksTheBubblesOfSpaceTime: ActorRef + + private[akka] def terminationFuture: Future[AkkaApplication.ExitStatus] } /** @@ -85,6 +87,8 @@ class ActorRefProviderException(message: String) extends AkkaException(message) */ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { + val terminationFuture = new DefaultPromise[AkkaApplication.ExitStatus](Timeout.never)(app.dispatcher) + /** * Top-level anchor for the supervision hierarchy of this actor system. Will * receive only Supervise/ChildTerminated system messages or Failure message. @@ -96,9 +100,12 @@ class LocalActorRefProvider(val app: AkkaApplication) extends ActorRefProvider { protected[akka] override def postMessageToMailbox(msg: Any, channel: UntypedChannel) { msg match { - case Failed(child, ex) ⇒ child.stop() - case ChildTerminated(child, ex) ⇒ // TODO execute any installed termination handlers - case _ ⇒ app.eventHandler.error(this, this + " received unexpected message " + msg) + case Failed(child, ex) ⇒ child.stop() + case ChildTerminated(child, ex) ⇒ ex match { + case a: ActorKilledException if a.getMessage == "Stopped" ⇒ terminationFuture.completeWithResult(AkkaApplication.Stopped) + case x ⇒ terminationFuture.completeWithResult(AkkaApplication.Failed(x)) + } + case _ ⇒ app.eventHandler.error(this, this + " received unexpected message " + msg) } } diff --git a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala index d654539e5d..873a9f1451 100644 --- a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala +++ b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala @@ -9,7 +9,7 @@ import scala.collection.mutable.ArrayBuffer import scala.collection.JavaConversions._ import java.lang.{ Iterable ⇒ JIterable } -case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) { +case class ChildRestartStats(var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) { def requestRestartPermission(retriesWindow: (Option[Int], Option[Int])): Boolean = retriesWindow match { @@ -51,8 +51,8 @@ object FaultHandlingStrategy { case object Stop extends Action case object Escalate extends Action - type Decider = PartialFunction[Class[_ <: Throwable], Action] - type JDecider = akka.japi.Function[Class[_ <: Throwable], Action] + type Decider = PartialFunction[Throwable, Action] + type JDecider = akka.japi.Function[Throwable, Action] type CauseAction = (Class[_ <: Throwable], Action) /** @@ -60,14 +60,14 @@ object FaultHandlingStrategy { * the given Throwables matches the cause and restarts, otherwise escalates. */ def makeDecider(trapExit: Array[Class[_ <: Throwable]]): Decider = - { case x ⇒ if (trapExit exists (_ isAssignableFrom x)) Restart else Escalate } + { case x ⇒ if (trapExit exists (_ isInstance x)) Restart else Escalate } /** * Backwards compatible Decider builder which just checks whether one of * the given Throwables matches the cause and restarts, otherwise escalates. */ def makeDecider(trapExit: List[Class[_ <: Throwable]]): Decider = - { case x ⇒ if (trapExit exists (_ isAssignableFrom x)) Restart else Escalate } + { case x ⇒ if (trapExit exists (_ isInstance x)) Restart else Escalate } /** * Backwards compatible Decider builder which just checks whether one of @@ -83,7 +83,7 @@ object FaultHandlingStrategy { */ def makeDecider(flat: Iterable[CauseAction]): Decider = { val actions = sort(flat) - return { case x ⇒ actions find (_._1 isAssignableFrom x) map (_._2) getOrElse Escalate } + return { case x ⇒ actions find (_._1 isInstance x) map (_._2) getOrElse Escalate } } def makeDecider(func: JDecider): Decider = { @@ -110,30 +110,36 @@ abstract class FaultHandlingStrategy { def decider: Decider - def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] + /** + * This method is called after the child has been removed from the set of children. + */ + def handleChildTerminated(child: ActorRef, children: Iterable[ActorRef]): Unit - def processFailure(restart: Boolean, fail: Failed, children: Vector[ChildRestartStats]): Unit + /** + * This method is called to act on the failure of a child: restart if the flag is true, stop otherwise. + */ + def processFailure(restart: Boolean, fail: Failed, stats: ChildRestartStats, children: Iterable[(ActorRef, ChildRestartStats)]): Unit - def handleSupervisorFailing(supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { + def handleSupervisorFailing(supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { if (children.nonEmpty) - children.foreach(_.child.suspend()) + children.foreach(_.suspend()) } - def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Vector[ChildRestartStats]): Unit = { + def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { if (children.nonEmpty) - children.foreach(_.child.restart(cause)) + children.foreach(_.restart(cause)) } /** * Returns whether it processed the failure or not */ - final def handleFailure(fail: Failed, children: Vector[ChildRestartStats]): Boolean = { - val cause = fail.cause.getClass + final def handleFailure(fail: Failed, stats: ChildRestartStats, children: Iterable[(ActorRef, ChildRestartStats)]): Boolean = { + val cause = fail.cause val action = if (decider.isDefinedAt(cause)) decider(cause) else Escalate action match { case Resume ⇒ fail.actor.resume(); true - case Restart ⇒ processFailure(true, fail, children); true - case Stop ⇒ processFailure(false, fail, children); true + case Restart ⇒ processFailure(true, fail, stats, children); true + case Stop ⇒ processFailure(false, fail, stats, children); true case Escalate ⇒ false } } @@ -181,18 +187,17 @@ case class AllForOneStrategy(decider: FaultHandlingStrategy.Decider, */ val retriesWindow = (maxNrOfRetries, withinTimeRange) - def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = { - children collect { - case stats if stats.child != child ⇒ stats.child.stop(); stats //2 birds with one stone: remove the child + stop the other children - } //TODO optimization to drop all children here already? + def handleChildTerminated(child: ActorRef, children: Iterable[ActorRef]): Unit = { + children foreach (_.stop()) + //TODO optimization to drop all children here already? } - def processFailure(restart: Boolean, fail: Failed, children: Vector[ChildRestartStats]): Unit = { + def processFailure(restart: Boolean, fail: Failed, stats: ChildRestartStats, children: Iterable[(ActorRef, ChildRestartStats)]): Unit = { if (children.nonEmpty) { - if (restart && children.forall(_.requestRestartPermission(retriesWindow))) - children.foreach(_.child.restart(fail.cause)) + if (restart && children.forall(_._2.requestRestartPermission(retriesWindow))) + children.foreach(_._1.restart(fail.cause)) else - children.foreach(_.child.stop()) + children.foreach(_._1.stop()) } } } @@ -239,18 +244,13 @@ case class OneForOneStrategy(decider: FaultHandlingStrategy.Decider, */ val retriesWindow = (maxNrOfRetries, withinTimeRange) - def handleChildTerminated(child: ActorRef, children: Vector[ChildRestartStats]): Vector[ChildRestartStats] = - children.filterNot(_.child == child) // TODO: check: I think this copies the whole vector in addition to allocating a closure ... + def handleChildTerminated(child: ActorRef, children: Iterable[ActorRef]): Unit = {} - def processFailure(restart: Boolean, fail: Failed, children: Vector[ChildRestartStats]): Unit = { - children.find(_.child == fail.actor) match { - case Some(stats) ⇒ - if (restart && stats.requestRestartPermission(retriesWindow)) - fail.actor.restart(fail.cause) - else - fail.actor.stop() //TODO optimization to drop child here already? - case None ⇒ throw new AssertionError("Got Failure from non-child: " + fail) - } + def processFailure(restart: Boolean, fail: Failed, stats: ChildRestartStats, children: Iterable[(ActorRef, ChildRestartStats)]): Unit = { + if (restart && stats.requestRestartPermission(retriesWindow)) + fail.actor.restart(fail.cause) + else + fail.actor.stop() //TODO optimization to drop child here already? } } diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index df730efd8c..0885839b3e 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -29,6 +29,9 @@ private[dispatch] object Mailbox { final val Closed = 2 // secondary status: Scheduled bit may be added to Open/Suspended final val Scheduled = 4 + + // static constant for enabling println debugging of message processing (for hardcore bugs) + final val debug = false } /** @@ -147,9 +150,8 @@ abstract class Mailbox(val actor: ActorCell) extends AbstractMailbox with Messag } final def run = { - try { processMailbox() } catch { - case ie: InterruptedException ⇒ Thread.currentThread().interrupt() //Restore interrupt - } finally { + try processMailbox() + finally { setAsIdle() dispatcher.registerForExecution(this, false, false) } @@ -170,6 +172,7 @@ abstract class Mailbox(val actor: ActorCell) extends AbstractMailbox with Messag var processedMessages = 0 val deadlineNs = if (dispatcher.isThroughputDeadlineTimeDefined) System.nanoTime + TimeUnit.MILLISECONDS.toNanos(dispatcher.throughputDeadlineTime) else 0 do { + if (debug) println(actor + " processing message " + nextMessage.message + " from " + nextMessage.channel) nextMessage.invoke processAllSystemMessages() //After we're done, process all system messages @@ -193,11 +196,13 @@ abstract class Mailbox(val actor: ActorCell) extends AbstractMailbox with Messag var nextMessage = systemDrain() try { while (nextMessage ne null) { + if (debug) println(actor + " processing system message " + nextMessage) actor systemInvoke nextMessage nextMessage = nextMessage.next // don’t ever execute normal message when system message present! if (nextMessage eq null) nextMessage = systemDrain() } + if (debug) println(actor + " has finished processing system messages") } catch { case e ⇒ actor.app.eventHandler.error(e, this, "exception during processing system messages, dropping " + SystemMessage.size(nextMessage) + " messages!") @@ -239,6 +244,7 @@ trait DefaultSystemMessageQueue { self: Mailbox ⇒ @tailrec final def systemEnqueue(message: SystemMessage): Unit = { + if (Mailbox.debug) println(actor + " having system message enqueued: " + message) val head = systemQueueGet /* * this write is safely published by the compareAndSet contained within diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index e08f3b7d9a..b4341e949d 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -4,14 +4,27 @@ package akka.testkit import akka.config.Configuration -import org.scalatest.WordSpec +import org.scalatest.{ WordSpec, BeforeAndAfterAll } import org.scalatest.matchers.MustMatchers import akka.AkkaApplication import akka.actor.{ Actor, ActorRef, Props } import akka.dispatch.MessageDispatcher abstract class AkkaSpec(_application: AkkaApplication = AkkaApplication()) - extends TestKit(_application) with WordSpec with MustMatchers { + extends TestKit(_application) with WordSpec with MustMatchers with BeforeAndAfterAll { + + final override def beforeAll { + atStartup() + } + + final override def afterAll { + app.stop() + atTermination() + } + + protected def atStartup() {} + + protected def atTermination() {} def this(config: Configuration) = this(new AkkaApplication(getClass.getSimpleName, AkkaApplication.defaultConfig ++ config)) From 3b698b9470155c6ad4f90103142a6ffc351f4bf5 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 20 Oct 2011 23:37:54 +0200 Subject: [PATCH 5/9] nearly done, only two known test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - moved typed actor factories to app/context (like actor factories) - fixed a few misplaced supervision changes, all such tests green now - actually test akka-reference.conf in ConfigSpec - made DispatcherActorSpec more deterministic (failed intermittently here, was due to race towards thread pool) - wrapped all actor initialization failures into ActorInitializationException and made sure that this leads to Stop - default to Stop on ActorKilledException - fixed ActorModelSpec to separately supervise the “waves of actors” because otherwise the app.guardian is way too busy processing all those ChildTerminated messages - change ActorCell._children from Vector[Stats] to TreeMap[ActorRef, Stats] for performance reasons, have not measured memory impact, yet - ensured that InterrupedException does not leave current thread via Failed message to supervisor (wrapped in ActorInterruptedException) - set core-size=1 and max-size=4 for default dispatcher during test --- .../scala/akka/actor/SupervisorSpec.scala | 13 +- .../scala/akka/actor/TypedActorSpec.scala | 16 +- .../actor/dispatch/DispatcherActorSpec.scala | 8 +- .../test/scala/akka/config/ConfigSpec.scala | 3 +- .../scala/akka/routing/ActorPoolSpec.scala | 35 +---- .../src/main/scala/akka/AkkaApplication.scala | 7 +- .../src/main/scala/akka/actor/Actor.scala | 6 +- .../src/main/scala/akka/actor/ActorCell.scala | 23 +-- .../src/main/scala/akka/actor/FSM.scala | 8 +- akka-actor/src/main/scala/akka/actor/IO.scala | 8 +- .../src/main/scala/akka/actor/Props.scala | 1 + .../main/scala/akka/actor/TypedActor.scala | 147 +++++++++--------- .../src/main/scala/akka/routing/Routing.scala | 2 +- .../scala/akka/util/ListenerManagement.scala | 5 +- .../akka/remote/RemoteActorRefProvider.scala | 1 + .../scala/akka/remote/MultiJvmSync.scala | 11 +- .../scala/akka/testkit/TestActorRef.scala | 2 +- config/akka.test.conf | 6 + project/AkkaBuild.scala | 2 +- 19 files changed, 149 insertions(+), 155 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala index 24c104debe..7cf0220f26 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorSpec.scala @@ -55,14 +55,17 @@ object SupervisorSpec { class Master extends Actor { val temp = context.actorOf(Props[PingPongActor]) + self startsMonitoring temp + var s: UntypedChannel = _ def receive = { - case Die ⇒ (temp.?(Die, TimeoutMillis)).get - case _: Terminated ⇒ + case Die ⇒ temp ! Die; s = context.channel + case Terminated(`temp`, cause) ⇒ s ! cause } } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach { import SupervisorSpec._ @@ -147,12 +150,12 @@ class SupervisorSpec extends AkkaSpec with BeforeAndAfterEach { "not restart programmatically linked temporary actor" in { val master = actorOf(Props[Master].withFaultHandler(OneForOneStrategy(List(classOf[Exception]), Some(0)))) - intercept[RuntimeException] { - (master.?(Die, TimeoutMillis)).get + (master.?(Die, TimeoutMillis)).get match { + case r: RuntimeException ⇒ r === ExceptionMessage } sleepFor(1 second) - messageLog.size must be(0) + messageLogPoll must be(null) } "not restart temporary actor" in { diff --git a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala index 7df3a592ca..ebae2d148a 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala @@ -7,6 +7,7 @@ package akka.actor import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach } import akka.japi.{ Option ⇒ JOption } import akka.util.Duration +import akka.util.duration._ import akka.dispatch.{ Dispatchers, Future, KeptPromise } import akka.serialization.Serialization import java.util.concurrent.atomic.AtomicReference @@ -145,10 +146,10 @@ class TypedActorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte newFooBar(Props().withTimeout(Timeout(d))) def newFooBar(props: Props): Foo = - app.typedActor.typedActorOf(classOf[Foo], classOf[Bar], props) + app.typedActorOf(classOf[Foo], classOf[Bar], props) def newStacked(props: Props = Props().withTimeout(Timeout(2000))): Stacked = - app.typedActor.typedActorOf(classOf[Stacked], classOf[StackedImpl], props) + app.typedActorOf(classOf[Stacked], classOf[StackedImpl], props) def mustStop(typedActor: AnyRef) = app.typedActor.stop(typedActor) must be(true) @@ -260,7 +261,12 @@ class TypedActorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte "be able to handle exceptions when calling methods" in { filterEvents(EventFilter[IllegalStateException]("expected")) { - val t = newFooBar + val boss = actorOf(Props(context ⇒ { + case p: Props ⇒ context.channel ! context.typedActorOf(classOf[Foo], classOf[Bar], p) + }).withFaultHandler(OneForOneStrategy { + case e: IllegalStateException if e.getMessage == "expected" ⇒ FaultHandlingStrategy.Resume + })) + val t = (boss ? Props().withTimeout(2 seconds)).as[Foo].get t.incr() t.failingPigdog() @@ -292,7 +298,7 @@ class TypedActorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte } "be able to support implementation only typed actors" in { - val t = app.typedActor.typedActorOf[Foo, Bar](Props()) + val t = app.typedActorOf[Foo, Bar](Props()) val f = t.futurePigdog(200) val f2 = t.futurePigdog(0) f2.isCompleted must be(false) @@ -302,7 +308,7 @@ class TypedActorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte } "be able to support implementation only typed actors with complex interfaces" in { - val t = app.typedActor.typedActorOf[Stackable1 with Stackable2, StackedImpl]() + val t = app.typedActorOf[Stackable1 with Stackable2, StackedImpl]() t.stackable1 must be("foo") t.stackable2 must be("bar") mustStop(t) diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala index 59255bd473..d677e23fc1 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala @@ -67,24 +67,26 @@ class DispatcherActorSpec extends AkkaSpec { val works = new AtomicBoolean(true) val latch = new CountDownLatch(100) + val thereWeAre = new CountDownLatch(1) val start = new CountDownLatch(1) val fastOne = actorOf( Props(context ⇒ { case "sabotage" ⇒ works.set(false) }).withDispatcher(throughputDispatcher)) val slowOne = actorOf( Props(context ⇒ { - case "hogexecutor" ⇒ start.await + case "hogexecutor" ⇒ thereWeAre.countDown(); start.await case "ping" ⇒ if (works.get) latch.countDown() }).withDispatcher(throughputDispatcher)) slowOne ! "hogexecutor" (1 to 100) foreach { _ ⇒ slowOne ! "ping" } + assert(thereWeAre.await(2, TimeUnit.SECONDS)) fastOne ! "sabotage" start.countDown() - val result = latch.await(10, TimeUnit.SECONDS) + latch.await(10, TimeUnit.SECONDS) fastOne.stop() slowOne.stop() - assert(result === true) + assert(latch.getCount() === 0) } "respect throughput deadline" in { diff --git a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala index 24abbc8ad0..531ad1e0e4 100644 --- a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala @@ -5,8 +5,9 @@ package akka.config import akka.testkit.AkkaSpec +import akka.AkkaApplication -class ConfigSpec extends AkkaSpec { +class ConfigSpec extends AkkaSpec(AkkaApplication("ConfigSpec", Configuration.fromFile("config/akka-reference.conf"))) { "The default configuration file (i.e. akka-reference.conf)" must { "contain all configuration properties for akka-actor that are used in code with their correct defaults" in { diff --git a/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala index 0c1f381b1b..15b23dec0f 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala @@ -330,7 +330,7 @@ class ActorPoolSpec extends AkkaSpec { "support typed actors" in { import RoutingSpec._ - import app.typedActor._ + def createPool = new Actor with DefaultActorPool with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with Filter with RunningMeanBackoff with BasicRampup { def lowerBound = 1 def upperBound = 5 @@ -340,11 +340,11 @@ class ActorPoolSpec extends AkkaSpec { def rampupRate = 0.1 def backoffRate = 0.50 def backoffThreshold = 0.50 - def instance(p: Props) = getActorRefFor(typedActorOf[Foo, FooImpl](p)) + def instance(p: Props) = app.typedActor.getActorRefFor(context.typedActorOf[Foo, FooImpl](p)) def receive = _route } - val pool = createProxy[Foo](createPool, Props().withFaultHandler(faultHandler)) + val pool = app.createProxy[Foo](createPool, Props().withFaultHandler(faultHandler)) val results = for (i ← 1 to 100) yield (i, pool.sq(i, 100)) @@ -357,7 +357,7 @@ class ActorPoolSpec extends AkkaSpec { val deathCount = new AtomicInteger(0) val keepDying = new AtomicBoolean(false) - val pool1 = actorOf( + val pool1, pool2 = actorOf( Props(new Actor with DefaultActorPool with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicFilter { def lowerBound = 2 def upperBound = 5 @@ -368,30 +368,7 @@ class ActorPoolSpec extends AkkaSpec { def selectionCount = 1 def receive = _route def pressureThreshold = 1 - def instance(p: Props) = actorOf(p.withCreator(new Actor { - if (deathCount.get > 5) deathCount.set(0) - if (deathCount.get > 0) { deathCount.incrementAndGet; throw new IllegalStateException("keep dying") } - def receive = { - case akka.Die ⇒ - if (keepDying.get) deathCount.incrementAndGet - throw new RuntimeException - case _ ⇒ pingCount.incrementAndGet - } - })) - }).withFaultHandler(faultHandler)) - - val pool2 = actorOf( - Props(new Actor with DefaultActorPool with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicFilter { - def lowerBound = 2 - def upperBound = 5 - def rampupRate = 0.1 - def backoffRate = 0.1 - def backoffThreshold = 0.5 - def partialFill = true - def selectionCount = 1 - def receive = _route - def pressureThreshold = 1 - def instance(p: Props) = actorOf(p.withCreator(new Actor { + def instance(p: Props) = context.actorOf(p.withCreator(new Actor { if (deathCount.get > 5) deathCount.set(0) if (deathCount.get > 0) { deathCount.incrementAndGet; throw new IllegalStateException("keep dying") } def receive = { @@ -414,7 +391,7 @@ class ActorPoolSpec extends AkkaSpec { def selectionCount = 1 def receive = _route def pressureThreshold = 1 - def instance(p: Props) = actorOf(p.withCreator(new Actor { + def instance(p: Props) = context.actorOf(p.withCreator(new Actor { if (deathCount.get > 5) deathCount.set(0) if (deathCount.get > 0) { deathCount.incrementAndGet; throw new IllegalStateException("keep dying") } diff --git a/akka-actor/src/main/scala/akka/AkkaApplication.scala b/akka-actor/src/main/scala/akka/AkkaApplication.scala index 129e9a8aac..cedfc56c32 100644 --- a/akka-actor/src/main/scala/akka/AkkaApplication.scala +++ b/akka-actor/src/main/scala/akka/AkkaApplication.scala @@ -77,7 +77,7 @@ object AkkaApplication { } -class AkkaApplication(val name: String, val config: Configuration) extends ActorRefFactory { +class AkkaApplication(val name: String, val config: Configuration) extends ActorRefFactory with TypedActorFactory { def this(name: String) = this(name, AkkaApplication.defaultConfig) def this() = this("default") @@ -177,8 +177,9 @@ class AkkaApplication(val name: String, val config: Configuration) extends Actor import akka.actor.FaultHandlingStrategy._ new LocalActorRef(this, Props(context ⇒ { case _ ⇒ }).withFaultHandler(OneForOneStrategy { - case _: ActorKilledException ⇒ Stop - case _: Exception ⇒ Restart + case _: ActorKilledException ⇒ Stop + case _: ActorInitializationException ⇒ Stop + case _: Exception ⇒ Restart }).withDispatcher(dispatcher), provider.theOneWhoWalksTheBubblesOfSpaceTime, "ApplicationSupervisor", diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index d3ef4a04c9..4413a05e29 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -79,9 +79,9 @@ class ActorKilledException private[akka] (message: String, cause: Throwable) def this(msg: String) = this(msg, null); } -class ActorInitializationException private[akka] (message: String, cause: Throwable = null) - extends AkkaException(message, cause) { - def this(msg: String) = this(msg, null); +case class ActorInitializationException private[akka] (actor: ActorRef, message: String, cause: Throwable = null) + extends AkkaException(message, cause) with NoStackTrace { + def this(msg: String) = this(null, msg, null); } class ActorTimeoutException private[akka] (message: String, cause: Throwable = null) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 586f2c9d81..2be9eef251 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -18,7 +18,7 @@ import akka.AkkaApplication * Exposes contextual information for the actor and the current message. * TODO: everything here for current compatibility - could be limited more */ -private[akka] trait ActorContext extends ActorRefFactory { +private[akka] trait ActorContext extends ActorRefFactory with TypedActorFactory { def self: ActorRef with ScalaActorRef @@ -72,6 +72,8 @@ private[akka] class ActorCell( protected def guardian = self + protected def typedActor = app.typedActor + def provider = app.provider var futureTimeout: Option[ScheduledFuture[AnyRef]] = None //FIXME TODO Doesn't need to be volatile either, since it will only ever be accessed when a message is processed @@ -163,7 +165,7 @@ private[akka] class ActorCell( val instance = props.creator() if (instance eq null) - throw new ActorInitializationException("Actor instance passed to actorOf can't be 'null'") + throw ActorInitializationException(self, "Actor instance passed to actorOf can't be 'null'") instance } finally { @@ -182,13 +184,14 @@ private[akka] class ActorCell( checkReceiveTimeout if (app.AkkaConfig.DebugLifecycle) app.eventHandler.debug(self, "started") } catch { - case e ⇒ try { - app.eventHandler.error(e, self, "error while creating actor") - // prevent any further messages to be processed until the actor has been restarted - dispatcher.suspend(this) - } finally { - supervisor ! Failed(self, e) - } + case e ⇒ + try { + app.eventHandler.error(e, self, "error while creating actor") + // prevent any further messages to be processed until the actor has been restarted + dispatcher.suspend(this) + } finally { + supervisor ! Failed(self, ActorInitializationException(self, "exception during creation", e)) + } } def recreate(cause: Throwable): Unit = try { @@ -218,7 +221,7 @@ private[akka] class ActorCell( // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) } finally { - supervisor ! Failed(self, e) + supervisor ! Failed(self, ActorInitializationException(self, "exception during re-creation", e)) } } diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 4beff7229d..f1dbd61a16 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -466,14 +466,10 @@ trait FSM[S, D] extends ListenerManagement { } } case SubscribeTransitionCallBack(actorRef) ⇒ + // TODO use DeathWatch to clean up list addListener(actorRef) // send current state back as reference point - try { - actorRef ! CurrentState(self, currentState.stateName) - } catch { - case e: ActorInitializationException ⇒ - app.eventHandler.warning(context.self, "trying to register not running listener") - } + actorRef ! CurrentState(self, currentState.stateName) case UnsubscribeTransitionCallBack(actorRef) ⇒ removeListener(actorRef) case value ⇒ { diff --git a/akka-actor/src/main/scala/akka/actor/IO.scala b/akka-actor/src/main/scala/akka/actor/IO.scala index a47e287bca..600d3a334a 100644 --- a/akka-actor/src/main/scala/akka/actor/IO.scala +++ b/akka-actor/src/main/scala/akka/actor/IO.scala @@ -394,12 +394,8 @@ private[akka] class IOWorker(app: AkkaApplication, ioManager: ActorRef, val buff case Some(channel) ⇒ channel.close channels -= handle - try { - handle.owner ! IO.Closed(handle, cause) - } catch { - case e: ActorInitializationException ⇒ - app.eventHandler debug (ioManager, "IO.Handle's owner not running") - } + // TODO: what if handle.owner is no longer running? + handle.owner ! IO.Closed(handle, cause) case None ⇒ } } diff --git a/akka-actor/src/main/scala/akka/actor/Props.scala b/akka-actor/src/main/scala/akka/actor/Props.scala index 26fee852bb..b3ae7f27ef 100644 --- a/akka-actor/src/main/scala/akka/actor/Props.scala +++ b/akka-actor/src/main/scala/akka/actor/Props.scala @@ -23,6 +23,7 @@ object Props { final val defaultTimeout: Timeout = Timeout(Duration.MinusInf) final val defaultDecider: Decider = { case _: ActorInitializationException ⇒ Stop + case _: ActorKilledException ⇒ Stop case _: Exception ⇒ Restart case _ ⇒ Escalate } diff --git a/akka-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-actor/src/main/scala/akka/actor/TypedActor.scala index ea23e77bd7..1345f436d3 100644 --- a/akka-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/TypedActor.scala @@ -120,6 +120,76 @@ object TypedActor { implicit def timeout = app.AkkaConfig.ActorTimeout } +trait TypedActorFactory { this: ActorRefFactory ⇒ + + protected def typedActor: TypedActor + + /** + * Creates a new TypedActor proxy using the supplied Props, + * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or + * all interfaces (Class.getInterfaces) if it's not an interface class + */ + def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Class[T], props: Props): R = + typedActor.createProxyAndTypedActor(this, interface, impl.newInstance, props, interface.getClassLoader) + + /** + * Creates a new TypedActor proxy using the supplied Props, + * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or + * all interfaces (Class.getInterfaces) if it's not an interface class + */ + def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Creator[T], props: Props): R = + typedActor.createProxyAndTypedActor(this, interface, impl.create, props, interface.getClassLoader) + + def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Class[T], props: Props, loader: ClassLoader): R = + typedActor.createProxyAndTypedActor(this, interface, impl.newInstance, props, loader) + + /** + * Creates a new TypedActor proxy using the supplied Props, + * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or + * all interfaces (Class.getInterfaces) if it's not an interface class + */ + def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Creator[T], props: Props, loader: ClassLoader): R = + typedActor.createProxyAndTypedActor(this, interface, impl.create, props, loader) + + /** + * Creates a new TypedActor proxy using the supplied Props, + * the interfaces usable by the returned proxy is the supplied implementation class' interfaces (Class.getInterfaces) + */ + def typedActorOf[R <: AnyRef, T <: R](impl: Class[T], props: Props, loader: ClassLoader): R = + typedActor.createProxyAndTypedActor(this, impl, impl.newInstance, props, loader) + + /** + * Creates a new TypedActor proxy using the supplied Props, + * the interfaces usable by the returned proxy is the supplied implementation class' interfaces (Class.getInterfaces) + */ + def typedActorOf[R <: AnyRef, T <: R](props: Props = Props(), loader: ClassLoader = null)(implicit m: Manifest[T]): R = { + val clazz = m.erasure.asInstanceOf[Class[T]] + typedActor.createProxyAndTypedActor(this, clazz, clazz.newInstance, props, if (loader eq null) clazz.getClassLoader else loader) + } + + /** + * Creates a proxy given the supplied Props, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself, + * to create TypedActor proxies, use typedActorOf + */ + def createProxy[R <: AnyRef](constructor: ⇒ Actor, props: Props = Props(), loader: ClassLoader = null)(implicit m: Manifest[R]): R = + typedActor.createProxy[R](this, typedActor.extractInterfaces(m.erasure), (ref: AtomVar[R]) ⇒ constructor, props, if (loader eq null) m.erasure.getClassLoader else loader) + + /** + * Creates a proxy given the supplied Props, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself, + * to create TypedActor proxies, use typedActorOf + */ + def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: Creator[Actor], props: Props, loader: ClassLoader): R = + typedActor.createProxy(this, interfaces, (ref: AtomVar[R]) ⇒ constructor.create, props, loader) + + /** + * Creates a proxy given the supplied Props, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself, + * to create TypedActor proxies, use typedActorOf + */ + def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: ⇒ Actor, props: Props, loader: ClassLoader): R = + typedActor.createProxy[R](this, interfaces, (ref: AtomVar[R]) ⇒ constructor, props, loader) + +} + //TODO Document this class, not only in Scaladoc, but also in a dedicated typed-actor.rst, for both java and scala /** * A TypedActor in Akka is an implementation of the Active Objects Pattern, i.e. an object with asynchronous method dispatch @@ -140,50 +210,6 @@ object TypedActor { class TypedActor(val app: AkkaApplication) { import TypedActor.MethodCall - - /** - * Creates a new TypedActor proxy using the supplied Props, - * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or - * all interfaces (Class.getInterfaces) if it's not an interface class - */ - def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Class[T], props: Props): R = - createProxyAndTypedActor(interface, impl.newInstance, props, interface.getClassLoader) - - /** - * Creates a new TypedActor proxy using the supplied Props, - * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or - * all interfaces (Class.getInterfaces) if it's not an interface class - */ - def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Creator[T], props: Props): R = - createProxyAndTypedActor(interface, impl.create, props, interface.getClassLoader) - - def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Class[T], props: Props, loader: ClassLoader): R = - createProxyAndTypedActor(interface, impl.newInstance, props, loader) - - /** - * Creates a new TypedActor proxy using the supplied Props, - * the interfaces usable by the returned proxy is the supplied interface class (if the class represents an interface) or - * all interfaces (Class.getInterfaces) if it's not an interface class - */ - def typedActorOf[R <: AnyRef, T <: R](interface: Class[R], impl: Creator[T], props: Props, loader: ClassLoader): R = - createProxyAndTypedActor(interface, impl.create, props, loader) - - /** - * Creates a new TypedActor proxy using the supplied Props, - * the interfaces usable by the returned proxy is the supplied implementation class' interfaces (Class.getInterfaces) - */ - def typedActorOf[R <: AnyRef, T <: R](impl: Class[T], props: Props, loader: ClassLoader): R = - createProxyAndTypedActor(impl, impl.newInstance, props, loader) - - /** - * Creates a new TypedActor proxy using the supplied Props, - * the interfaces usable by the returned proxy is the supplied implementation class' interfaces (Class.getInterfaces) - */ - def typedActorOf[R <: AnyRef, T <: R](props: Props = Props(), loader: ClassLoader = null)(implicit m: Manifest[T]): R = { - val clazz = m.erasure.asInstanceOf[Class[T]] - createProxyAndTypedActor(clazz, clazz.newInstance, props, if (loader eq null) clazz.getClassLoader else loader) - } - /** * Stops the underlying ActorRef for the supplied TypedActor proxy, if any, returns whether it could stop it or not */ @@ -205,27 +231,6 @@ class TypedActor(val app: AkkaApplication) { */ def isTypedActor(proxyOrNot: AnyRef): Boolean = invocationHandlerFor(proxyOrNot) ne null - /** - * Creates a proxy given the supplied Props, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself, - * to create TypedActor proxies, use typedActorOf - */ - def createProxy[R <: AnyRef](constructor: ⇒ Actor, props: Props = Props(), loader: ClassLoader = null)(implicit m: Manifest[R]): R = - createProxy[R](extractInterfaces(m.erasure), (ref: AtomVar[R]) ⇒ constructor, props, if (loader eq null) m.erasure.getClassLoader else loader) - - /** - * Creates a proxy given the supplied Props, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself, - * to create TypedActor proxies, use typedActorOf - */ - def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: Creator[Actor], props: Props, loader: ClassLoader): R = - createProxy(interfaces, (ref: AtomVar[R]) ⇒ constructor.create, props, loader) - - /** - * Creates a proxy given the supplied Props, this is not a TypedActor, so you'll need to implement the MethodCall handling yourself, - * to create TypedActor proxies, use typedActorOf - */ - def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: ⇒ Actor, props: Props, loader: ClassLoader): R = - createProxy[R](interfaces, (ref: AtomVar[R]) ⇒ constructor, props, loader) - /* Internal API */ private[akka] def invocationHandlerFor(typedActor_? : AnyRef): TypedActorInvocationHandler = @@ -239,15 +244,15 @@ class TypedActor(val app: AkkaApplication) { } else null - private[akka] def createProxy[R <: AnyRef](interfaces: Array[Class[_]], constructor: (AtomVar[R]) ⇒ Actor, props: Props, loader: ClassLoader): R = { + private[akka] def createProxy[R <: AnyRef](supervisor: ActorRefFactory, interfaces: Array[Class[_]], constructor: (AtomVar[R]) ⇒ Actor, props: Props, loader: ClassLoader): R = { val proxyVar = new AtomVar[R] - configureAndProxyLocalActorRef[R](interfaces, proxyVar, props.withCreator(constructor(proxyVar)), loader) + configureAndProxyLocalActorRef[R](supervisor, interfaces, proxyVar, props.withCreator(constructor(proxyVar)), loader) } - private[akka] def createProxyAndTypedActor[R <: AnyRef, T <: R](interface: Class[_], constructor: ⇒ T, props: Props, loader: ClassLoader): R = - createProxy[R](extractInterfaces(interface), (ref: AtomVar[R]) ⇒ new TypedActor[R, T](ref, constructor), props, loader) + private[akka] def createProxyAndTypedActor[R <: AnyRef, T <: R](supervisor: ActorRefFactory, interface: Class[_], constructor: ⇒ T, props: Props, loader: ClassLoader): R = + createProxy[R](supervisor, extractInterfaces(interface), (ref: AtomVar[R]) ⇒ new TypedActor[R, T](ref, constructor), props, loader) - private[akka] def configureAndProxyLocalActorRef[T <: AnyRef](interfaces: Array[Class[_]], proxyVar: AtomVar[T], props: Props, loader: ClassLoader): T = { + private[akka] def configureAndProxyLocalActorRef[T <: AnyRef](supervisor: ActorRefFactory, interfaces: Array[Class[_]], proxyVar: AtomVar[T], props: Props, loader: ClassLoader): T = { //Warning, do not change order of the following statements, it's some elaborate chicken-n-egg handling val actorVar = new AtomVar[ActorRef](null) val timeout = props.timeout match { @@ -256,7 +261,7 @@ class TypedActor(val app: AkkaApplication) { } val proxy: T = Proxy.newProxyInstance(loader, interfaces, new TypedActorInvocationHandler(actorVar)(timeout)).asInstanceOf[T] proxyVar.set(proxy) // Chicken and egg situation we needed to solve, set the proxy so that we can set the self-reference inside each receive - val ref = app.actorOf(props) + val ref = supervisor.actorOf(props) actorVar.set(ref) //Make sure the InvocationHandler gets ahold of the actor reference, this is not a problem since the proxy hasn't escaped this method yet proxyVar.get } diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 28d253d172..77ce3de642 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -362,7 +362,7 @@ trait ScatterGatherRouter extends BasicRouter with Serializable { private def scatterGather[S, G >: S](message: Any, timeout: Timeout)(implicit sender: Option[ActorRef]): Future[G] = { val responses = connectionManager.connections.iterable.flatMap { actor ⇒ try { - if (actor.isShutdown) throw new ActorInitializationException("For compatability - check death first") + if (actor.isShutdown) throw ActorInitializationException(actor, "For compatability - check death first", new Exception) // for stack trace Some(actor.?(message, timeout)(sender).asInstanceOf[Future[S]]) } catch { case e: Exception ⇒ diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala index 0607ccb6a9..775f5f674e 100644 --- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala +++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala @@ -44,8 +44,7 @@ trait ListenerManagement { def hasListeners: Boolean = !listeners.isEmpty /** - * Checks if a specific listener is registered. ActorInitializationException leads to removal of listener if that - * one isShutdown. + * Checks if a specific listener is registered. Pruned eventually when isShutdown==true in notify. */ def hasListener(listener: ActorRef): Boolean = listeners.contains(listener) @@ -62,7 +61,7 @@ trait ListenerManagement { } /** - * Execute f with each listener as argument. ActorInitializationException is not handled. + * Execute f with each listener as argument. */ protected[akka] def foreachListener(f: (ActorRef) ⇒ Unit) { val iterator = listeners.iterator diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 6d6c2d14c2..67b33d036f 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -32,6 +32,7 @@ class RemoteActorRefProvider(val app: AkkaApplication) extends ActorRefProvider import akka.dispatch.Promise private[akka] val theOneWhoWalksTheBubblesOfSpaceTime: ActorRef = new UnsupportedActorRef {} + private[akka] def terminationFuture = new DefaultPromise[AkkaApplication.ExitStatus](Timeout.never)(app.dispatcher) val local = new LocalActorRefProvider(app) val remote = new Remote(app) diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/MultiJvmSync.scala b/akka-remote/src/multi-jvm/scala/akka/remote/MultiJvmSync.scala index 26c10b4ea4..4a35d8c03f 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/MultiJvmSync.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/MultiJvmSync.scala @@ -4,23 +4,20 @@ package akka.remote -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers -import org.scalatest.BeforeAndAfterAll - +import akka.testkit.AkkaSpec import akka.util.Duration -trait MultiJvmSync extends WordSpec with MustMatchers with BeforeAndAfterAll { +trait MultiJvmSync extends AkkaSpec { def nodes: Int - override def beforeAll() = { + override def atStartup() = { onStart() MultiJvmSync.start(getClass.getName, nodes) } def onStart() {} - override def afterAll() = { + override def atTermination() = { MultiJvmSync.end(getClass.getName, nodes) onEnd() } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index b612a7a7f8..3402429ddd 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -59,7 +59,7 @@ object TestActorRef { import ReflectiveAccess.{ createInstance, noParams, noArgs } createInstance[T](m.erasure, noParams, noArgs) match { case Right(value) ⇒ value - case Left(exception) ⇒ throw new ActorInitializationException( + case Left(exception) ⇒ throw new ActorInitializationException(null, "Could not instantiate Actor" + "\nMake sure Actor is NOT defined inside a class/trait," + "\nif so put it outside the class/trait, f.e. in a companion object," + diff --git a/config/akka.test.conf b/config/akka.test.conf index 59f9e520cf..25161e06c4 100644 --- a/config/akka.test.conf +++ b/config/akka.test.conf @@ -7,4 +7,10 @@ include "akka-reference.conf" akka { event-handlers = ["akka.testkit.TestEventListener"] event-handler-level = "ERROR" + actor { + default-dispatcher { + core-pool-size-factor = 1 + max-pool-size-factor = 4 + } + } } diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 6a529657cb..98d0624abb 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -394,7 +394,7 @@ object AkkaBuild extends Build { object Dependencies { import Dependency._ - val testkit = Seq(Test.scalatest) + val testkit = Seq(Test.scalatest, Test.junit) val actorTests = Seq( Test.junit, Test.scalatest, Test.multiverse, Test.commonsMath, Test.mockito, From bb942750aa97f4298156979ca4cd1847b944195f Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 21 Oct 2011 17:01:22 +0200 Subject: [PATCH 6/9] make most AkkaSpec-based tests runnable in Eclipse --- .../test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala | 1 + .../src/test/scala/akka/actor/ActorLifeCycleSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala | 1 + .../src/test/scala/akka/actor/ActorTimeoutSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/ChannelSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/ClusterSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala | 1 + .../src/test/scala/akka/actor/FSMTransitionSpec.scala | 1 + .../src/test/scala/akka/actor/ForwardActorSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/IOActor.scala | 1 + .../src/test/scala/akka/actor/LocalActorRefProviderSpec.scala | 1 + .../src/test/scala/akka/actor/ReceiveTimeoutSpec.scala | 1 + .../src/test/scala/akka/actor/RestartStrategySpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala | 1 + .../src/test/scala/akka/actor/SupervisorHierarchySpec.scala | 1 + .../src/test/scala/akka/actor/SupervisorMiscSpec.scala | 1 + .../src/test/scala/akka/actor/SupervisorTreeSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala | 1 + akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala | 1 + .../src/test/scala/akka/actor/dispatch/ActorModelSpec.scala | 2 ++ .../scala/akka/actor/dispatch/BalancingDispatcherSpec.scala | 1 + .../test/scala/akka/actor/dispatch/DispatcherActorSpec.scala | 1 + .../test/scala/akka/actor/dispatch/DispatcherActorsSpec.scala | 1 + .../src/test/scala/akka/actor/dispatch/DispatchersSpec.scala | 1 + .../src/test/scala/akka/actor/dispatch/PinnedActorSpec.scala | 1 + .../src/test/scala/akka/actor/routing/ListenerSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala | 1 + .../src/test/scala/akka/dispatch/MailboxConfigSpec.scala | 2 +- .../src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala | 1 + .../src/test/scala/akka/dispatch/PromiseStreamSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala | 1 + .../src/test/scala/akka/routing/ActorPoolSpec.scala | 1 + .../test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala | 1 + .../src/test/scala/akka/serialization/SerializeSpec.scala | 1 + akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala | 1 + akka-http/src/test/scala/config/ConfigSpec.scala | 2 +- .../src/test/scala/akka/serialization/ActorSerializeSpec.scala | 1 + .../src/test/scala/transactor/CoordinatedIncrementSpec.scala | 1 + akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala | 1 + akka-stm/src/test/scala/transactor/TransactorSpec.scala | 1 + akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala | 1 + akka-testkit/src/test/scala/akka/testkit/TestFSMRefSpec.scala | 1 + akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala | 1 + akka-testkit/src/test/scala/akka/testkit/TestTimeSpec.scala | 1 + 50 files changed, 51 insertions(+), 2 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala index b53f2cd998..2a327a35d9 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorFireForgetRequestReplySpec.scala @@ -51,6 +51,7 @@ object ActorFireForgetRequestReplySpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorFireForgetRequestReplySpec extends AkkaSpec with BeforeAndAfterEach { import ActorFireForgetRequestReplySpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala index 5c9dd2ec2f..aca8ae829b 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorLifeCycleSpec.scala @@ -16,6 +16,7 @@ object ActorLifeCycleSpec { } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorLifeCycleSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender { import ActorLifeCycleSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala index 0e727f6e7a..5fdf0487e5 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala @@ -113,6 +113,7 @@ object ActorRefSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorRefSpec extends AkkaSpec { import akka.actor.ActorRefSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorTimeoutSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorTimeoutSpec.scala index 35ddfecd22..46a345a7c2 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorTimeoutSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorTimeoutSpec.scala @@ -8,6 +8,7 @@ import akka.dispatch.FutureTimeoutException import akka.util.duration._ import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorTimeoutSpec extends AkkaSpec with BeforeAndAfterAll { def actorWithTimeout(t: Timeout): ActorRef = actorOf(Props(creator = () ⇒ new Actor { diff --git a/akka-actor-tests/src/test/scala/akka/actor/ChannelSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ChannelSpec.scala index c453f73cef..8461b4f39c 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ChannelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ChannelSpec.scala @@ -8,6 +8,7 @@ import akka.dispatch._ import akka.testkit.TestActorRef import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ChannelSpec extends AkkaSpec { "A Channel" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/ClusterSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ClusterSpec.scala index a1c948fdca..4863e4f6a9 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ClusterSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ClusterSpec.scala @@ -2,6 +2,7 @@ package akka.actor import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ClusterSpec extends AkkaSpec { "ClusterSpec: A Deployer" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala index 0a775a3684..086f6a0d6f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala @@ -9,6 +9,7 @@ import akka.testkit._ import akka.util.duration._ import java.util.concurrent.atomic._ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender { "The Death Watch" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala index a19b4fbca0..4917edd341 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala @@ -8,6 +8,7 @@ import akka.testkit.AkkaSpec import akka.util.duration._ import DeploymentConfig._ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DeployerSpec extends AkkaSpec { "A Deployer" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala index d7b8ec79d3..6d85c6997c 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala @@ -99,6 +99,7 @@ object FSMActorSpec { case class CodeState(soFar: String, code: String) } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class FSMActorSpec extends AkkaSpec(Configuration("akka.actor.debug.fsm" -> true)) with ImplicitSender { import FSMActorSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala index ab787b12bf..109b21d790 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala @@ -8,6 +8,7 @@ import akka.testkit.{ AkkaSpec, ImplicitSender } import akka.util.Duration import akka.util.duration._ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class FSMTimingSpec extends AkkaSpec with ImplicitSender { import FSMTimingSpec._ import FSM._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala index c72c095ce2..874a209c6f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala @@ -35,6 +35,7 @@ object FSMTransitionSpec { } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class FSMTransitionSpec extends AkkaSpec with ImplicitSender { import FSMTransitionSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/ForwardActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ForwardActorSpec.scala index 55cfbd5fd7..1aff230560 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ForwardActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ForwardActorSpec.scala @@ -27,6 +27,7 @@ object ForwardActorSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ForwardActorSpec extends AkkaSpec { import ForwardActorSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala index 9c5b1d99af..66257f89f4 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala @@ -6,6 +6,7 @@ package akka.actor import akka.testkit._ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class HotSwapSpec extends AkkaSpec { "An Actor" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala b/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala index d61500c2b4..107df964ae 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/IOActor.scala @@ -170,6 +170,7 @@ object IOActorSpec { } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class IOActorSpec extends AkkaSpec with BeforeAndAfterEach { import IOActorSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala index f3d0168613..9f43c0232f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala @@ -19,6 +19,7 @@ object LocalActorRefProviderSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class LocalActorRefProviderSpec extends AkkaSpec { import akka.actor.LocalActorRefProviderSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala index 11fa223377..944bf11a49 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala @@ -9,6 +9,7 @@ import akka.util.duration._ import java.util.concurrent.atomic.AtomicInteger +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ReceiveTimeoutSpec extends AkkaSpec { "An actor with receive timeout" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala index af2f84808b..1fa42ac61b 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala @@ -12,6 +12,7 @@ import java.util.concurrent.{ TimeUnit, CountDownLatch } import org.multiverse.api.latches.StandardLatch import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class RestartStrategySpec extends AkkaSpec { override def atStartup() { diff --git a/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala index 2aa2c3452e..9d4f0ebd4e 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala @@ -7,6 +7,7 @@ import org.multiverse.api.latches.StandardLatch import java.util.concurrent.{ ScheduledFuture, ConcurrentLinkedQueue, CountDownLatch, TimeUnit } import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class SchedulerSpec extends AkkaSpec with BeforeAndAfterEach { private val futures = new ConcurrentLinkedQueue[ScheduledFuture[AnyRef]]() diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala index 43bbfc243f..860478f862 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala @@ -21,6 +21,7 @@ object SupervisorHierarchySpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class SupervisorHierarchySpec extends AkkaSpec { import SupervisorHierarchySpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala index 97c5461741..e33b7ab878 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorMiscSpec.scala @@ -8,6 +8,7 @@ import akka.dispatch.{ PinnedDispatcher, Dispatchers } import java.util.concurrent.{ TimeUnit, CountDownLatch } import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class SupervisorMiscSpec extends AkkaSpec { "A Supervisor" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala index 71392cdf92..e5b6283c36 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala @@ -13,6 +13,7 @@ import akka.testkit.{ TestKit, EventFilter, filterEvents, filterException } import akka.testkit.AkkaSpec import akka.testkit.ImplicitSender +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class SupervisorTreeSpec extends AkkaSpec with ImplicitSender { "In a 3 levels deep supervisor tree (linked in the constructor) we" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala b/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala index 57f028ce07..c864af36fa 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/Ticket669Spec.scala @@ -10,6 +10,7 @@ import akka.testkit.{ TestKit, filterEvents, EventFilter } import akka.testkit.AkkaSpec import akka.testkit.ImplicitSender +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class Ticket669Spec extends AkkaSpec with BeforeAndAfterAll with ImplicitSender { import Ticket669Spec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala index ebae2d148a..4727825b9f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala @@ -136,6 +136,7 @@ object TypedActorSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TypedActorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfterAll { import TypedActorSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala index cd524318c6..d07fa3ee76 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala @@ -440,6 +440,7 @@ abstract class ActorModelSpec extends AkkaSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DispatcherModelSpec extends ActorModelSpec { import ActorModelSpec._ @@ -469,6 +470,7 @@ class DispatcherModelSpec extends ActorModelSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class BalancingDispatcherModelSpec extends ActorModelSpec { import ActorModelSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala index d0353b86eb..c30db1d5bc 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala @@ -5,6 +5,7 @@ import akka.dispatch.{ Mailbox, Dispatchers } import akka.actor.{ LocalActorRef, IllegalActorStateException, Actor, Props } import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class BalancingDispatcherSpec extends AkkaSpec { def newWorkStealer() = app.dispatcherFactory.newBalancingDispatcher("pooled-dispatcher", 1).build diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala index bb5be1ca27..02fa4b0689 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorSpec.scala @@ -24,6 +24,7 @@ object DispatcherActorSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DispatcherActorSpec extends AkkaSpec { import DispatcherActorSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorsSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorsSpec.scala index f18658f3b8..650024ebca 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorsSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatcherActorsSpec.scala @@ -9,6 +9,7 @@ import akka.testkit.AkkaSpec * * @author Jan Van Besien */ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DispatcherActorsSpec extends AkkaSpec { class SlowActor(finishedCounter: CountDownLatch) extends Actor { diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala index 97b670f502..3e8336be51 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala @@ -9,6 +9,7 @@ import akka.dispatch._ import akka.testkit.AkkaSpec import akka.config.Configuration +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DispatchersSpec extends AkkaSpec { import app.dispatcherFactory._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/PinnedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/PinnedActorSpec.scala index 3564ab3dcb..a2f0a785de 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/PinnedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/PinnedActorSpec.scala @@ -18,6 +18,7 @@ object PinnedActorSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class PinnedActorSpec extends AkkaSpec with BeforeAndAfterEach { import PinnedActorSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/actor/routing/ListenerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/routing/ListenerSpec.scala index 2422538467..fdd1844141 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/routing/ListenerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/routing/ListenerSpec.scala @@ -6,6 +6,7 @@ import akka.actor.Actor._ import akka.routing._ import java.util.concurrent.atomic.AtomicInteger +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ListenerSpec extends AkkaSpec { "Listener" must { diff --git a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala index 531ad1e0e4..8833866e38 100644 --- a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala @@ -7,6 +7,7 @@ package akka.config import akka.testkit.AkkaSpec import akka.AkkaApplication +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ConfigSpec extends AkkaSpec(AkkaApplication("ConfigSpec", Configuration.fromFile("config/akka-reference.conf"))) { "The default configuration file (i.e. akka-reference.conf)" must { diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala index 6d5f8b9ea1..417ee1e441 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -36,6 +36,7 @@ object FutureSpec { class JavaFutureSpec extends JavaFutureTests with JUnitSuite +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class FutureSpec extends AkkaSpec with Checkers with BeforeAndAfterAll { import FutureSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala index b4cc86cca2..a19898a502 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala @@ -11,7 +11,7 @@ import akka.util.Duration._ import akka.actor.{ LocalActorRef, Actor, NullChannel } import akka.testkit.AkkaSpec -@RunWith(classOf[JUnitRunner]) +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) abstract class MailboxSpec extends AkkaSpec with BeforeAndAfterAll with BeforeAndAfterEach { def name: String diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala index e3ab6d2ed7..0c599937d2 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala @@ -3,6 +3,7 @@ package akka.dispatch import akka.actor.{ Props, LocalActorRef, Actor } import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class PriorityDispatcherSpec extends AkkaSpec { "A PriorityDispatcher" must { diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/PromiseStreamSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/PromiseStreamSpec.scala index 582c5bfd63..4c4c0c9ee7 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/PromiseStreamSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/PromiseStreamSpec.scala @@ -6,6 +6,7 @@ import akka.actor.Timeout import akka.util.duration._ import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class PromiseStreamSpec extends AkkaSpec { "A PromiseStream" must { diff --git a/akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala b/akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala index c96a6b85fc..4861dd9ea5 100644 --- a/akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala @@ -20,6 +20,7 @@ object EventBusSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) abstract class EventBusSpec(busName: String) extends AkkaSpec with BeforeAndAfterEach { import EventBusSpec._ type BusType <: EventBus diff --git a/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala index cf88186157..ad48435996 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala @@ -25,6 +25,7 @@ object ActorPoolSpec { val faultHandler = OneForOneStrategy(List(classOf[Exception]), 5, 1000) } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorPoolSpec extends AkkaSpec { import ActorPoolSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala index ca566f726a..0650aec50e 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala @@ -8,6 +8,7 @@ import akka.testkit.AkkaSpec import akka.actor.DeploymentConfig._ import akka.routing.Routing.Broadcast +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ConfiguredLocalRoutingSpec extends AkkaSpec { "round robin router" must { diff --git a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala index dab51d076f..1313694260 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -19,6 +19,7 @@ object RoutingSpec { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class RoutingSpec extends AkkaSpec { import akka.routing.RoutingSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index 501b8e25e9..72cac70ff9 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -17,6 +17,7 @@ object SerializeSpec { case class Record(id: Int, person: Person) } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class SerializeSpec extends AkkaSpec { import SerializeSpec._ diff --git a/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala b/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala index 5e46f3ec05..1b54c709d8 100644 --- a/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala @@ -4,6 +4,7 @@ import akka.actor._ import akka.routing._ import akka.testkit.AkkaSpec +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class Ticket703Spec extends AkkaSpec { "A ? call to an actor pool" should { diff --git a/akka-http/src/test/scala/config/ConfigSpec.scala b/akka-http/src/test/scala/config/ConfigSpec.scala index 5423dd8aa7..d73099840b 100644 --- a/akka-http/src/test/scala/config/ConfigSpec.scala +++ b/akka-http/src/test/scala/config/ConfigSpec.scala @@ -9,7 +9,7 @@ import akka.testkit.AkkaSpec import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner -@RunWith(classOf[JUnitRunner]) +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ConfigSpec extends AkkaSpec { "The default configuration file (i.e. akka-reference.conf)" should { diff --git a/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala b/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala index 7abc54ead9..9eeab66c7a 100644 --- a/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala +++ b/akka-remote/src/test/scala/akka/serialization/ActorSerializeSpec.scala @@ -9,6 +9,7 @@ import akka.serialization.SerializeSpec.Person case class MyMessage(id: Long, name: String, status: Boolean) +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class ActorSerializeSpec extends AkkaSpec with BeforeAndAfterAll { lazy val remote: Remote = { diff --git a/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala b/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala index 35b6128fb1..284786ef5a 100644 --- a/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala +++ b/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala @@ -53,6 +53,7 @@ object CoordinatedIncrement { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class CoordinatedIncrementSpec extends AkkaSpec with BeforeAndAfterAll { import CoordinatedIncrement._ diff --git a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala index 18b08f920c..e4b0eed68e 100644 --- a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala +++ b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala @@ -98,6 +98,7 @@ object FickleFriends { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class FickleFriendsSpec extends AkkaSpec with BeforeAndAfterAll { import FickleFriends._ diff --git a/akka-stm/src/test/scala/transactor/TransactorSpec.scala b/akka-stm/src/test/scala/transactor/TransactorSpec.scala index 52313ace13..5069781833 100644 --- a/akka-stm/src/test/scala/transactor/TransactorSpec.scala +++ b/akka-stm/src/test/scala/transactor/TransactorSpec.scala @@ -75,6 +75,7 @@ object SimpleTransactor { } } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TransactorSpec extends AkkaSpec { import TransactorIncrement._ import SimpleTransactor._ diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index 814c65aca2..3fb594a91e 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -90,6 +90,7 @@ object TestActorRefSpec { } +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TestActorRefSpec extends AkkaSpec with BeforeAndAfterEach { import TestActorRefSpec._ diff --git a/akka-testkit/src/test/scala/akka/testkit/TestFSMRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestFSMRefSpec.scala index 38145c36e7..e7a7ec9e51 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestFSMRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestFSMRefSpec.scala @@ -9,6 +9,7 @@ import org.scalatest.{ BeforeAndAfterEach, WordSpec } import akka.actor._ import akka.util.duration._ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TestFSMRefSpec extends AkkaSpec { import FSM._ diff --git a/akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala index e162cad834..930e5c1454 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestProbeSpec.scala @@ -8,6 +8,7 @@ import akka.event.EventHandler import akka.dispatch.Future import akka.util.duration._ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TestProbeSpec extends AkkaSpec { "A TestProbe" must { diff --git a/akka-testkit/src/test/scala/akka/testkit/TestTimeSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestTimeSpec.scala index daf89cac4b..ab3101b827 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestTimeSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestTimeSpec.scala @@ -4,6 +4,7 @@ import org.scalatest.matchers.MustMatchers import org.scalatest.{ BeforeAndAfterEach, WordSpec } import akka.util.Duration +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class TestTimeSpec extends AkkaSpec with BeforeAndAfterEach { val tf = Duration.timeFactor From fc8ab7dad8fe67b5898ecdca53d07e0f59da1585 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 21 Oct 2011 18:47:44 +0200 Subject: [PATCH 7/9] fix CallingThreadDispatcher and re-enable its test --- .../akka/actor/dispatch/ActorModelSpec.scala | 77 +++++++++++++------ .../CallingThreadDispatcherModelSpec.scala | 43 +---------- .../main/scala/akka/dispatch/Mailbox.scala | 2 +- .../testkit/CallingThreadDispatcher.scala | 47 +++++++---- config/akka.test.conf | 2 +- 5 files changed, 88 insertions(+), 83 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala index d07fa3ee76..755e122ec1 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala @@ -243,6 +243,9 @@ abstract class ActorModelSpec extends AkkaSpec { protected def newInterceptedDispatcher: MessageDispatcherInterceptor protected def dispatcherType: String + // BalancingDispatcher of course does not work when another actor is in the pool, so overridden below + protected def wavesSupervisorDispatcher(dispatcher: MessageDispatcher) = dispatcher + "A " + dispatcherType must { "must dynamically handle its own life cycle" in { @@ -325,28 +328,6 @@ abstract class ActorModelSpec extends AkkaSpec { thread.start() } - "process messages in parallel" in { - implicit val dispatcher = newInterceptedDispatcher - val aStart, aStop, bParallel = new CountDownLatch(1) - val a, b = newTestActor(dispatcher) - - a ! Meet(aStart, aStop) - assertCountDown(aStart, Testing.testTime(3000), "Should process first message within 3 seconds") - - b ! CountDown(bParallel) - assertCountDown(bParallel, Testing.testTime(3000), "Should process other actors in parallel") - - aStop.countDown() - - a.stop - b.stop - - while (!a.isShutdown && !b.isShutdown) {} //Busy wait for termination - - assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) - assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) - } - "not process messages for a suspended actor" in { implicit val dispatcher = newInterceptedDispatcher val a = newTestActor(dispatcher).asInstanceOf[LocalActorRef] @@ -375,13 +356,15 @@ abstract class ActorModelSpec extends AkkaSpec { val boss = actorOf(Props(context ⇒ { case "run" ⇒ for (_ ← 1 to num) context.actorOf(props) ! cachedMessage - })) ! "run" + }).withDispatcher(wavesSupervisorDispatcher(dispatcher))) + boss ! "run" try { assertCountDown(cachedMessage.latch, Testing.testTime(10000), "Should process " + num + " countdowns") } catch { case e ⇒ System.err.println("Error: " + e.getMessage + " missing count downs == " + cachedMessage.latch.getCount() + " out of " + num) } + boss.stop() } for (run ← 1 to 3) { flood(40000) @@ -467,6 +450,28 @@ class DispatcherModelSpec extends ActorModelSpec { assert(each.await.exception.get.isInstanceOf[ActorKilledException]) a.stop() } + + "process messages in parallel" in { + implicit val dispatcher = newInterceptedDispatcher + val aStart, aStop, bParallel = new CountDownLatch(1) + val a, b = newTestActor(dispatcher) + + a ! Meet(aStart, aStop) + assertCountDown(aStart, Testing.testTime(3000), "Should process first message within 3 seconds") + + b ! CountDown(bParallel) + assertCountDown(bParallel, Testing.testTime(3000), "Should process other actors in parallel") + + aStop.countDown() + + a.stop + b.stop + + while (!a.isShutdown && !b.isShutdown) {} //Busy wait for termination + + assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) + assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) + } } } @@ -481,4 +486,30 @@ class BalancingDispatcherModelSpec extends ActorModelSpec { ThreadPoolConfig(app)).build.asInstanceOf[MessageDispatcherInterceptor] def dispatcherType = "Balancing Dispatcher" + + override def wavesSupervisorDispatcher(dispatcher: MessageDispatcher) = app.dispatcher + + "A " + dispatcherType must { + "process messages in parallel" in { + implicit val dispatcher = newInterceptedDispatcher + val aStart, aStop, bParallel = new CountDownLatch(1) + val a, b = newTestActor(dispatcher) + + a ! Meet(aStart, aStop) + assertCountDown(aStart, Testing.testTime(3000), "Should process first message within 3 seconds") + + b ! CountDown(bParallel) + assertCountDown(bParallel, Testing.testTime(3000), "Should process other actors in parallel") + + aStop.countDown() + + a.stop + b.stop + + while (!a.isShutdown && !b.isShutdown) {} //Busy wait for termination + + assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) + assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) + } + } } diff --git a/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala b/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala index da7c8d2a2b..c977709cbc 100644 --- a/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala @@ -7,50 +7,11 @@ import akka.actor.dispatch.ActorModelSpec import java.util.concurrent.CountDownLatch import org.junit.{ After, Test } -// TODO fix this test when the CallingThreadDispatcher is fixed -/* +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class CallingThreadDispatcherModelSpec extends ActorModelSpec { import ActorModelSpec._ - def newInterceptedDispatcher = new CallingThreadDispatcher with MessageDispatcherInterceptor + def newInterceptedDispatcher = new CallingThreadDispatcher(app, "test", true) with MessageDispatcherInterceptor def dispatcherType = "Calling Thread Dispatcher" - // A CallingThreadDispatcher can by design not process messages in parallel, - // so disable this test - //override def dispatcherShouldProcessMessagesInParallel {} - - // This test needs to be adapted: CTD runs the flood completely sequentially - // with start, invocation, stop, schedule shutdown, abort shutdown, repeat; - // add "keeper" actor to lock down the dispatcher instance, since the - // frequent attempted shutdown seems rather costly (random timing failures - // without this fix) - // override def dispatcherShouldHandleWavesOfActors { - // implicit val dispatcher = newInterceptedDispatcher - // - // def flood(num: Int) { - // val cachedMessage = CountDownNStop(new CountDownLatch(num)) - // val keeper = newTestActor - // (1 to num) foreach { _ ⇒ - // newTestActor ! cachedMessage - // } - // keeper.stop() - // assertCountDown(cachedMessage.latch, 10000, "Should process " + num + " countdowns") - // } - // for (run ← 1 to 3) { - // flood(10000) - // assertDispatcher(dispatcher)(starts = run, stops = run) - // } - // } - - //override def dispatcherShouldCompleteAllUncompletedSenderFuturesOnDeregister { - //Can't handle this... - //} - - @After - def after { - //remove the interrupted status since we are messing with interrupted exceptions. - Thread.interrupted() - } - } -*/ diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 7beb0ffe95..40dcbfd9df 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -15,7 +15,7 @@ import annotation.tailrec class MessageQueueAppendFailedException(message: String, cause: Throwable = null) extends AkkaException(message, cause) -private[dispatch] object Mailbox { +object Mailbox { type Status = Int diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index b6cb07cb02..dc27e036a9 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -31,11 +31,12 @@ import akka.AkkaApplication * within one of its methods taking a closure argument. */ -object CallingThreadDispatcher { +private[testkit] object CallingThreadDispatcher { // PRIVATE DATA private var queues = Map[CallingThreadMailbox, Set[WeakReference[NestingQueue]]]() + private var lastGC = 0l // we have to forget about long-gone threads sometime private def gc { @@ -49,7 +50,11 @@ object CallingThreadDispatcher { } else { queues += mbox -> Set(new WeakReference(q)) } - gc + val now = System.nanoTime + if (now - lastGC > 1000000000l) { + lastGC = now + gc + } } /* @@ -57,20 +62,16 @@ object CallingThreadDispatcher { * given mailbox. When this method returns, the queue will be entered * (active). */ - protected[akka] def gatherFromAllInactiveQueues(mbox: CallingThreadMailbox, own: NestingQueue): Unit = synchronized { + protected[akka] def gatherFromAllOtherQueues(mbox: CallingThreadMailbox, own: NestingQueue): Unit = synchronized { if (!own.isActive) own.enter if (queues contains mbox) { for { ref ← queues(mbox) - q = ref.get - if (q ne null) && !q.isActive - /* - * if q.isActive was false, then it cannot change to true while we are - * holding the mbox.suspende.switch's lock under which we are currently - * executing - */ + val q = ref.get + if (q ne null) && (q ne own) } { while (q.peek ne null) { + // this is safe because this method is only ever called while holding the suspendSwitch monitor own.push(q.pop) } } @@ -129,7 +130,7 @@ class CallingThreadDispatcher(_app: AkkaApplication, val name: String = "calling val queue = mbox.queue val wasActive = queue.isActive val switched = mbox.suspendSwitch.switchOff { - gatherFromAllInactiveQueues(mbox, queue) + gatherFromAllOtherQueues(mbox, queue) } if (switched && !wasActive) { runQueue(mbox, queue) @@ -142,11 +143,11 @@ class CallingThreadDispatcher(_app: AkkaApplication, val name: String = "calling protected[akka] override def systemDispatch(receiver: ActorCell, message: SystemMessage) { val mbox = getMailbox(receiver) - mbox.lock.lock - try { - receiver systemInvoke message - } finally { - mbox.lock.unlock + mbox.systemEnqueue(message) + val queue = mbox.queue + if (!queue.isActive) { + queue.enter + runQueue(mbox, queue) } } @@ -190,6 +191,7 @@ class CallingThreadDispatcher(_app: AkkaApplication, val name: String = "calling assert(queue.isActive) mbox.lock.lock val recurse = try { + mbox.processAllSystemMessages() val handle = mbox.suspendSwitch.fold[Envelope] { queue.leave null @@ -200,6 +202,7 @@ class CallingThreadDispatcher(_app: AkkaApplication, val name: String = "calling } if (handle ne null) { try { + if (Mailbox.debug) println(mbox.actor + " processing message " + handle) mbox.actor.invoke(handle) if (warnings) handle.channel match { case f: ActorPromise if !f.isCompleted ⇒ @@ -208,6 +211,10 @@ class CallingThreadDispatcher(_app: AkkaApplication, val name: String = "calling } true } catch { + case ie: InterruptedException ⇒ + app.eventHandler.error(this, ie) + Thread.currentThread().interrupt() + true case e ⇒ app.eventHandler.error(this, e) queue.leave @@ -217,6 +224,8 @@ class CallingThreadDispatcher(_app: AkkaApplication, val name: String = "calling queue.leave false } else false + } catch { + case e ⇒ queue.leave; throw e } finally { mbox.lock.unlock } @@ -244,7 +253,11 @@ class NestingQueue { class CallingThreadMailbox(val dispatcher: MessageDispatcher, _receiver: ActorCell) extends Mailbox(_receiver) with DefaultSystemMessageQueue { private val q = new ThreadLocal[NestingQueue]() { - override def initialValue = new NestingQueue + override def initialValue = { + val queue = new NestingQueue + CallingThreadDispatcher.registerQueue(CallingThreadMailbox.this, queue) + queue + } } def queue = q.get diff --git a/config/akka.test.conf b/config/akka.test.conf index 25161e06c4..7ff55c0fcd 100644 --- a/config/akka.test.conf +++ b/config/akka.test.conf @@ -6,7 +6,7 @@ include "akka-reference.conf" akka { event-handlers = ["akka.testkit.TestEventListener"] - event-handler-level = "ERROR" + event-handler-level = "WARNING" actor { default-dispatcher { core-pool-size-factor = 1 From 92321cd498c322875e71952496304d3c02ac13b3 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 21 Oct 2011 18:51:54 +0200 Subject: [PATCH 8/9] relax over-eager time constraint in FSMTimingSpec --- akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala index 109b21d790..094bd4f196 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMTimingSpec.scala @@ -44,7 +44,7 @@ class FSMTimingSpec extends AkkaSpec with ImplicitSender { } "receive single-shot timer" in { - within(50 millis, 150 millis) { + within(50 millis, 250 millis) { fsm ! TestSingleTimer expectMsg(Tick) expectMsg(Transition(fsm, TestSingleTimer, Initial)) From b39bef69ec78a5909ae66cf34a4b1a5ed20f85c5 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 21 Oct 2011 19:07:17 +0200 Subject: [PATCH 9/9] Fix bug in DeathWatchSpec (I had forgotten to wrap a Failed) --- .../src/test/scala/akka/actor/DeathWatchSpec.scala | 9 +++++---- akka-actor/src/main/scala/akka/dispatch/Mailbox.scala | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala index 086f6a0d6f..485de60d42 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala @@ -104,10 +104,11 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende "fail a monitor which does not handle Terminated()" in { filterEvents(EventFilter[ActorKilledException], EventFilter[DeathPactException]) { + case class FF(fail: Failed) val supervisor = actorOf(Props[Supervisor] .withFaultHandler(new OneForOneStrategy(FaultHandlingStrategy.makeDecider(List(classOf[Exception])), Some(0)) { override def handleFailure(fail: Failed, stats: ChildRestartStats, children: Iterable[(ActorRef, ChildRestartStats)]) = { - testActor ! fail + testActor ! FF(fail) super.handleFailure(fail, stats, children) } })) @@ -118,9 +119,9 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende failed ! Kill val result = receiveWhile(3 seconds, messages = 3) { - case Failed(`failed`, _: ActorKilledException) ⇒ 1 - case Failed(`brother`, DeathPactException(`failed`)) ⇒ 2 - case Terminated(`brother`) ⇒ 3 + case FF(Failed(`failed`, _: ActorKilledException)) ⇒ 1 + case FF(Failed(`brother`, DeathPactException(`failed`))) ⇒ 2 + case Terminated(`brother`) ⇒ 3 } testActor must not be 'shutdown result must be(Seq(1, 2, 3)) diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 40dcbfd9df..d03655489b 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -31,6 +31,7 @@ object Mailbox { final val Scheduled = 4 // mailbox debugging helper using println (see below) + // TODO take this out before release final val debug = false }