Chris Murphy
Chris Murphy

Reputation: 6509

Possible to communicate with a remote actor from a non-actor?

I know that you can send messages and receive a response from outside an actor if you use ask (? syntax). The problem is I don't know if this can be done when the actor is remote. By 'outside an actor' I mean from normal non-actor code. Can a non-actor talk to a remote actor?

[] is the process boundary and capitalization means its an actor. Previously the set up was this and was working fine:

[a B]

However now I'm trying to make B remote:

[a] [B]

I could probably get it to work if I gave [a] a local actor, but that should not be necessary.

In a there is no ActorContext, only an ActorSystem. Here is the code I am trying to use (remote is B):

val systemName = "THIS_UNIVERSE"
// other vals: remoteIP, remotePort, remoteActorName - all to do with B
val actorSystem = ActorSystem(systemName)
...
val str = s"akka.tcp://$systemName@$remoteIP:$remotePort/user/$remoteActorName"
actorSystem.actorSelection(str).resolveOne().onComplete {
  ...
} 

As you can see even the syntax does not make much sense: there's already an actorSystem, yet in the protocol String systemName needs to be used.

Is communicating with a remote actor from a non actor possible? I haven't found any examples to use as reference.

I do actually know that B is running and in fact it already has another actor that is able to find it and communicate with it.

For the purpose of this question I have just now set up a stand alone Scala program that creates an actor and tries to get an actorRef to B, in a similar manner to the code above. But no joy:

Actor not found for: ActorSelection[Anchor(akka://THIS_UNIVERSE/deadLetters), Path(/user/MyPLC)]

Is it the case that this standalone program will need to have an application.conf available to be loaded? i.e. without such a file it won't be able to find remote actors?

Great - the standalone program can find the remote actor when a src/main/resources/application.conf file is present for sbt to pick up.

So back to the original question: [a] by definition won't have an application.conf file in its class-path as it is not an actor. However I'll put one in and see if it helps...

That helped! In this case application.conf was put at the root of one of the dependent jar files.

So the answer to the question is that you can communicate from a non-actor, however you still need most of the actor stuff - the actor jar files and an application.conf on the class-path.

Upvotes: 1

Views: 422

Answers (1)

sourcedelica
sourcedelica

Reputation: 24040

Yes, you should be able to.

If you are able to communicate from a local actor to the remote, [A] [B], then have B send a message to A, then print the sender's path to make sure you have the correct path.

Regarding the syntax, systemName is the remote actor system's name. actorSystem in your example is the local actor system.

You could have your non-actor code ask the local actor for the remote actor's ref. For example:

val remoteRefFuture = A ? GetRemoteAddress
remoteRefFuture.mapTo[ActorRef].foreach { B => B ! Xyz }  

Double check the IP address in the path and make sure it matches exactly the IP address or hostname you are using in the remoting configuration on the remote side.

Here's a small runnable example. Run two instances of this app. Start the first with command line parameter 2550. Then start the second with 2551.

import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import akka.pattern.ask
import akka.util.Timeout

import scala.concurrent.Await
import scala.util.Success
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

object AskRemote extends App {
  val port = args(0).toInt
  val configStr =
    s"""
       |akka {
       |  actor {
       |    provider = "akka.remote.RemoteActorRefProvider"
       |  }
       |  remote {
       |    enabled-transports = ["akka.remote.netty.tcp"]
       |    netty.tcp {
       |      hostname = "localhost"
       |      port = $port
       |    }
       |  }
       |}
      """.stripMargin
  val config = ConfigFactory.parseString(configStr)
  val system = ActorSystem(s"system$port", config)

  if (port == 2550) {
    system.actorOf(Props(new MyActor), "myActor")
    system.awaitTermination()

  } else {
    implicit val timeout = Timeout(5.seconds)
    val path = s"akka.tcp://system2550@localhost:2550/user/myActor"

    system.actorSelection(path).resolveOne().onComplete {
      case Success(ref) =>
        val fut = ref ? "hello"
        println(Await.result(fut, 5.seconds))
        system.shutdown()
    }
  }
}

class MyActor extends Actor {
  def receive = {
    case s: String =>
      println(s"got $s")
      sender() ! s"you sent $s"
      context.system.shutdown()
  }
}

Upvotes: 3

Related Questions