diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorSelectionSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorSelectionSpec.scala index df1c3471ff..87c9f9057f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorSelectionSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorSelectionSpec.scala @@ -61,6 +61,12 @@ class ActorSelectionSpec extends AkkaSpec("akka.loglevel=DEBUG") with DefaultTim val asked = Await.result((selection ? Identify(selection)).mapTo[ActorIdentity], timeout.duration) asked.ref must be(result) asked.correlationId must be(selection) + + implicit val ec = system.dispatcher + val resolved = Await.result(selection.resolveOne(timeout.duration).mapTo[ActorRef] recover { case _ ⇒ null }, + timeout.duration) + Option(resolved) must be(result) + result } @@ -291,6 +297,24 @@ class ActorSelectionSpec extends AkkaSpec("akka.loglevel=DEBUG") with DefaultTim expectNoMsg(1 second) } + "resolve one actor with explicit timeout" in { + val s = system.actorSelection(system / "c2") + // Java and Scala API + Await.result(s.resolveOne(1.second.dilated), timeout.duration) must be === c2 + } + + "resolve one actor with implicit timeout" in { + val s = system.actorSelection(system / "c2") + // Scala API; implicit timeout from DefaultTimeout trait + Await.result(s.resolveOne(), timeout.duration) must be === c2 + } + + "resolve non-existing with Failure" in { + intercept[ActorNotFound] { + Await.result(system.actorSelection(system / "none").resolveOne(1.second.dilated), timeout.duration) + } + } + "compare equally" in { ActorSelection(c21, "../*/hello") must be === ActorSelection(c21, "../*/hello") ActorSelection(c21, "../*/hello").## must be === ActorSelection(c21, "../*/hello").## diff --git a/akka-actor/src/main/scala/akka/actor/ActorSelection.scala b/akka-actor/src/main/scala/akka/actor/ActorSelection.scala index 2417768dc3..7312043480 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSelection.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSelection.scala @@ -4,11 +4,19 @@ package akka.actor import language.implicitConversions -import scala.collection.immutable -import java.util.regex.Pattern -import akka.util.Helpers -import akka.routing.MurmurHash import scala.annotation.tailrec +import scala.collection.immutable +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.concurrent.duration._ +import scala.util.Success +import scala.util.Failure +import java.util.regex.Pattern +import akka.pattern.ask +import akka.routing.MurmurHash +import akka.util.Helpers +import akka.util.Timeout +import akka.dispatch.ExecutionContexts /** * An ActorSelection is a logical view of a section of an ActorSystem's tree of Actors, @@ -40,6 +48,38 @@ abstract class ActorSelection extends Serializable { anchor.tell(toMessage(msg, path, path.length - 1), sender) } + /** + * Resolve the [[ActorRef]] matching this selection. + * The result is returned as a Future that is completed with the [[ActorRef]] + * if such an actor exists. It is completed with failure [[ActorNotFound]] if + * no such actor exists or the identification didn't complete within the + * supplied `timeout`. + * + * Under the hood it talks to the actor to verify its existence and acquire its + * [[ActorRef]]. + */ + def resolveOne()(implicit timeout: Timeout): Future[ActorRef] = { + implicit val ec = ExecutionContexts.sameThreadExecutionContext + val p = Promise[ActorRef]() + this.ask(Identify(None)) onComplete { + case Success(ActorIdentity(_, Some(ref))) ⇒ p.success(ref) + case _ ⇒ p.failure(ActorNotFound(this)) + } + p.future + } + + /** + * Resolve the [[ActorRef]] matching this selection. + * The result is returned as a Future that is completed with the [[ActorRef]] + * if such an actor exists. It is completed with failure [[ActorNotFound]] if + * no such actor exists or the identification didn't complete within the + * supplied `timeout`. + * + * Under the hood it talks to the actor to verify its existence and acquire its + * [[ActorRef]]. + */ + def resolveOne(timeout: FiniteDuration): Future[ActorRef] = resolveOne()(timeout) + override def toString: String = { (new java.lang.StringBuilder).append("ActorSelection["). append(anchor.toString). @@ -107,3 +147,11 @@ trait ScalaActorSelection { def !(msg: Any)(implicit sender: ActorRef = Actor.noSender) = tell(msg, sender) } + +/** + * When [[ActorSelection#resolveOne]] can't identify the actor the + * `Future` is completed with this failure. + */ +@SerialVersionUID(1L) +case class ActorNotFound(selection: ActorSelection) extends RuntimeException("Actor not found for: " + selection) + diff --git a/akka-actor/src/main/scala/akka/pattern/Patterns.scala b/akka-actor/src/main/scala/akka/pattern/Patterns.scala index 34f137f27c..3c3a854577 100644 --- a/akka-actor/src/main/scala/akka/pattern/Patterns.scala +++ b/akka-actor/src/main/scala/akka/pattern/Patterns.scala @@ -146,6 +146,8 @@ object Patterns { * Register an onComplete callback on this [[scala.concurrent.Future]] to send * the result to the given [[akka.actor.ActorRef]] or [[akka.actor.ActorSelection]]. * Returns the original Future to allow method chaining. + * If the future was completed with failure it is sent as a [[akka.actor.Status.Failure]] + * to the recipient. * * Recommended usage example: * diff --git a/akka-actor/src/main/scala/akka/pattern/PipeToSupport.scala b/akka-actor/src/main/scala/akka/pattern/PipeToSupport.scala index dc6594df73..ca992dbbbd 100644 --- a/akka-actor/src/main/scala/akka/pattern/PipeToSupport.scala +++ b/akka-actor/src/main/scala/akka/pattern/PipeToSupport.scala @@ -51,6 +51,9 @@ trait PipeToSupport { * pipe(someFuture) to nextActor * * }}} + * + * The successful result of the future is sent as a message to the recipient, or + * the failure is sent in a [[akka.actor.Status.Failure]] to the recipient. */ implicit def pipe[T](future: Future[T])(implicit executionContext: ExecutionContext): PipeableFuture[T] = new PipeableFuture(future) } diff --git a/akka-docs/rst/java/remoting.rst b/akka-docs/rst/java/remoting.rst index 512d25be6a..ae5c346bf4 100644 --- a/akka-docs/rst/java/remoting.rst +++ b/akka-docs/rst/java/remoting.rst @@ -71,6 +71,8 @@ To acquire an :class:`ActorRef` for an :class:`ActorSelection` you need to send a message to the selection and use the ``getSender`` reference of the reply from the actor. There is a built-in ``Identify`` message that all Actors will understand and automatically reply to with a ``ActorIdentity`` message containing the +:class:`ActorRef`. This can also be done with the ``resolveOne`` method of +the :class:`ActorSelection`, which returns a ``Future`` of the matching :class:`ActorRef`. .. note:: diff --git a/akka-docs/rst/java/untyped-actors.rst b/akka-docs/rst/java/untyped-actors.rst index 00cb8885cc..59c89c54c8 100644 --- a/akka-docs/rst/java/untyped-actors.rst +++ b/akka-docs/rst/java/untyped-actors.rst @@ -274,7 +274,9 @@ occupying it. ``ActorSelection`` cannot be watched for this reason. It is possible to resolve the current incarnation's ``ActorRef`` living under the path by sending an ``Identify`` message to the ``ActorSelection`` which will be replied to with an ``ActorIdentity`` containing the correct reference -(see :ref:`actorSelection-java`). +(see :ref:`actorSelection-java`). This can also be done with the ``resolveOne`` +method of the :class:`ActorSelection`, which returns a ``Future`` of the matching +:class:`ActorRef`. .. _deathwatch-java: @@ -427,6 +429,12 @@ of that reply is guaranteed, it still is a normal message. .. includecode:: code/docs/actor/UntypedActorDocTest.java :include: import-identify,identify +You can also acquire an :class:`ActorRef` for an :class:`ActorSelection` with +the ``resolveOne`` method of the :class:`ActorSelection`. It returns a ``Future`` +of the matching :class:`ActorRef` if such an actor exists. It is completed with +failure [[akka.actor.ActorNotFound]] if no such actor exists or the identification +didn't complete within the supplied `timeout`. + Remote actor addresses may also be looked up, if :ref:`remoting ` is enabled: .. includecode:: code/docs/actor/UntypedActorDocTest.java#selection-remote diff --git a/akka-docs/rst/project/migration-guide-2.1.x-2.2.x.rst b/akka-docs/rst/project/migration-guide-2.1.x-2.2.x.rst index f2277aab07..013aadefcb 100644 --- a/akka-docs/rst/project/migration-guide-2.1.x-2.2.x.rst +++ b/akka-docs/rst/project/migration-guide-2.1.x-2.2.x.rst @@ -262,6 +262,12 @@ the actor. There is a built-in ``Identify`` message that all Actors will underst and automatically reply to with a ``ActorIdentity`` message containing the :class:`ActorRef`. +You can also acquire an :class:`ActorRef` for an :class:`ActorSelection` with +the ``resolveOne`` method of the :class:`ActorSelection`. It returns a ``Future`` +of the matching :class:`ActorRef` if such an actor exists. It is completed with +failure [[akka.actor.ActorNotFound]] if no such actor exists or the identification +didn't complete within the supplied `timeout`. + Read more about ``actorSelection`` in :ref:`docs for Java ` or :ref:`docs for Scala `. diff --git a/akka-docs/rst/scala/actors.rst b/akka-docs/rst/scala/actors.rst index c526d14e5b..e3f380b102 100644 --- a/akka-docs/rst/scala/actors.rst +++ b/akka-docs/rst/scala/actors.rst @@ -363,7 +363,9 @@ occupying it. ``ActorSelection`` cannot be watched for this reason. It is possible to resolve the current incarnation's ``ActorRef`` living under the path by sending an ``Identify`` message to the ``ActorSelection`` which will be replied to with an ``ActorIdentity`` containing the correct reference -(see :ref:`actorSelection-scala`). +(see :ref:`actorSelection-scala`). This can also be done with the ``resolveOne`` +method of the :class:`ActorSelection`, which returns a ``Future`` of the matching +:class:`ActorRef`. .. _deathwatch-scala: @@ -510,6 +512,12 @@ of that reply is guaranteed, it still is a normal message. .. includecode:: code/docs/actor/ActorDocSpec.scala#identify +You can also acquire an :class:`ActorRef` for an :class:`ActorSelection` with +the ``resolveOne`` method of the :class:`ActorSelection`. It returns a ``Future`` +of the matching :class:`ActorRef` if such an actor exists. It is completed with +failure [[akka.actor.ActorNotFound]] if no such actor exists or the identification +didn't complete within the supplied `timeout`. + Remote actor addresses may also be looked up, if :ref:`remoting ` is enabled: .. includecode:: code/docs/actor/ActorDocSpec.scala#selection-remote diff --git a/akka-docs/rst/scala/remoting.rst b/akka-docs/rst/scala/remoting.rst index 31cf8667b5..7d1ddc1359 100644 --- a/akka-docs/rst/scala/remoting.rst +++ b/akka-docs/rst/scala/remoting.rst @@ -78,6 +78,8 @@ To acquire an :class:`ActorRef` for an :class:`ActorSelection` you need to send a message to the selection and use the ``sender`` reference of the reply from the actor. There is a built-in ``Identify`` message that all Actors will understand and automatically reply to with a ``ActorIdentity`` message containing the +:class:`ActorRef`. This can also be done with the ``resolveOne`` method of +the :class:`ActorSelection`, which returns a ``Future`` of the matching :class:`ActorRef`. .. note::