diff --git a/akka-actor-tests/src/test/scala/akka/pattern/AskSpec.scala b/akka-actor-tests/src/test/scala/akka/pattern/AskSpec.scala index 3ab2c2a3bd..2055e3c62f 100644 --- a/akka-actor-tests/src/test/scala/akka/pattern/AskSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/pattern/AskSpec.scala @@ -6,7 +6,7 @@ package akka.pattern import language.postfixOps import akka.actor._ -import akka.testkit.AkkaSpec +import akka.testkit.{ TestProbe, AkkaSpec } import akka.util.Timeout import scala.concurrent.Await import scala.concurrent.duration._ @@ -111,6 +111,94 @@ class AskSpec extends AkkaSpec { Await.result(identityFuture, 5 seconds) should be(echo) } + "work when reply uses actor selection" in { + implicit val timeout = Timeout(0.5 seconds) + val deadListener = TestProbe() + system.eventStream.subscribe(deadListener.ref, classOf[DeadLetter]) + + val echo = system.actorOf(Props(new Actor { def receive = { case x ⇒ context.actorSelection(sender().path) ! x } }), "select-echo2") + val f = echo ? "hi" + + Await.result(f, 1 seconds) should be("hi") + + deadListener.expectNoMsg(200 milliseconds) + } + + "throw AskTimeoutException on using *" in { + implicit val timeout = Timeout(0.5 seconds) + val deadListener = TestProbe() + system.eventStream.subscribe(deadListener.ref, classOf[DeadLetter]) + + val echo = system.actorOf(Props(new Actor { def receive = { case x ⇒ context.actorSelection("/temp/*") ! x } }), "select-echo3") + val f = echo ? "hi" + intercept[AskTimeoutException] { + Await.result(f, 1 seconds) + } + + deadListener.expectMsgClass(200 milliseconds, classOf[DeadLetter]) + } + + "throw AskTimeoutException on using .." in { + implicit val timeout = Timeout(0.5 seconds) + val deadListener = TestProbe() + system.eventStream.subscribe(deadListener.ref, classOf[DeadLetter]) + + val echo = system.actorOf(Props(new Actor { + def receive = { + case x ⇒ + val name = sender.path.name + val parent = sender.path.parent + context.actorSelection(parent / ".." / "temp" / name) ! x + } + }), "select-echo4") + val f = echo ? "hi" + intercept[AskTimeoutException] { + Await.result(f, 1 seconds) should be("hi") + } + + deadListener.expectMsgClass(200 milliseconds, classOf[DeadLetter]) + } + + "send to DeadLetter when child does not exist" in { + implicit val timeout = Timeout(0.5 seconds) + val deadListener = TestProbe() + system.eventStream.subscribe(deadListener.ref, classOf[DeadLetter]) + + val echo = system.actorOf(Props(new Actor { + def receive = { + case x ⇒ + val name = sender.path.name + val parent = sender.path.parent + context.actorSelection(parent / "missing") ! x + } + }), "select-echo5") + val f = echo ? "hi" + intercept[AskTimeoutException] { + Await.result(f, 1 seconds) should be("hi") + } + deadListener.expectMsgClass(200 milliseconds, classOf[DeadLetter]) + } + + "send DeadLetter when answering to grandchild" in { + implicit val timeout = Timeout(0.5 seconds) + val deadListener = TestProbe() + system.eventStream.subscribe(deadListener.ref, classOf[DeadLetter]) + + val echo = system.actorOf(Props(new Actor { + def receive = { + case x ⇒ + val name = sender.path.name + val parent = sender.path.parent + context.actorSelection(sender().path / "missing") ! x + } + }), "select-echo6") + val f = echo ? "hi" + intercept[AskTimeoutException] { + Await.result(f, 1 seconds) should be(ActorSelectionMessage("hi", Vector(SelectChildName("missing")), false)) + } + deadListener.expectMsgClass(200 milliseconds, classOf[DeadLetter]) + } + } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index f14b73c992..0f602e998f 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -599,6 +599,38 @@ private[akka] class VirtualPathContainer( private val children = new ConcurrentHashMap[String, InternalActorRef] + /** + * In [[ActorSelectionMessage]]s only [[SelectChildName]] elements + * are supported, otherwise messages are sent to [[EmptyLocalActorRef]]. + */ + override def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit = message match { + case sel @ ActorSelectionMessage(msg, elements, wildcardFanOut) ⇒ { + require(elements.nonEmpty) + + def emptyRef = new EmptyLocalActorRef(provider, path / sel.elements.map(_.toString), + provider.systemGuardian.underlying.system.eventStream) + + elements.head match { + case SelectChildName(name) ⇒ + getChild(name) match { + case null ⇒ + if (!wildcardFanOut) + emptyRef.tell(msg, sender) + case child ⇒ + if (elements.tail.isEmpty) { + child ! msg + } else if (!wildcardFanOut) { + emptyRef.tell(msg, sender) + } + } + case _ ⇒ + if (!wildcardFanOut) + emptyRef.tell(msg, sender) + } + } + case _ ⇒ super.!(message) + } + def addChild(name: String, ref: InternalActorRef): Unit = { children.put(name, ref) match { case null ⇒ // okay