soc
soc

Reputation: 28413

Porting simple Scala remote actor example to Akka actors

I try to port this simple actor example of sending "Ping" and "Pong" from Scala actors to Akka actors, but I keep getting errors and I wonder if it is just a simple error or some fundamental fault.

Consider this code:

import akka.actor.Actor._
import akka.actor.Actor

case class Message(text: String)

class PingPongActor(name: String) extends Actor {

  def receive = {
      case Message(msg) =>
        println("received: " + msg)
        Thread.sleep(1000)
        self.reply(Message("Ping"))
      case None => println("ping: timed out!")
  }
}

object Ping extends App {
  remote.start("localhost", 2552)
        .register("ping-service", actorOf(new PingPongActor("pong")))

  val actor = remote.actorFor("ping-service", "localhost", 2552)

  actor ! (Message("Ping"))
}

object Pong extends App {
  remote.start("localhost", 2553)
        .register("pong-service", actorOf(new PingPongActor("ping")))

  val actor = remote.actorFor("pong-service", "localhost", 2553)

  actor ! (Message("Pong"))
}

I keep getting this error:

received: Ping
[GENERIC] [07.10.11 23:18] [RemoteServerStarted(akka.remote.netty.NettyRemoteSupport@3ff2cea2)]
[ERROR]   [07.10.11 23:18] [akka:event-driven:dispatcher:global-2] [LocalActorRef] 
   No sender in scope, can't reply.
   You have probably:
      1. Sent a message to an Actor from an instance that is NOT an Actor.
      2. Invoked a method on an TypedActor from an instance NOT an TypedActor.
   You may want to have a look at safe_! for a variant returning a Boolean
akka.actor.IllegalActorStateException: 
   No sender in scope, can't reply.
   You have probably:
      1. Sent a message to an Actor from an instance that is NOT an Actor.
      2. Invoked a method on an TypedActor from an instance NOT an TypedActor.
   You may want to have a look at safe_! for a variant returning a Boolean
[laptop_e3263500-f129-11e0-a78d-001636ff8076]
    at akka.actor.NullChannel$.$bang(Channel.scala:177)
    at akka.actor.ActorRef$class.reply(ActorRef.scala:398)
    at akka.actor.LocalActorRef.reply(ActorRef.scala:605)
    at PingPongActor$$anonfun$receive$1.apply(RemoteActor.scala:21)
    at PingPongActor$$anonfun$receive$1.apply(RemoteActor.scala:15)
    at akka.actor.Actor$class.apply(Actor.scala:545)
    at PingPongActor.apply(RemoteActor.scala:13)

The idea is that I start up two applications, Ping and Pong which try to send a message to each other every second and print it on the terminal (or print an error message if no message is received for two seconds).

Upvotes: 4

Views: 2139

Answers (1)

Thomas Lockney
Thomas Lockney

Reputation: 2587

The biggest fundamental problem with your code is that you're sending the message from outside of an actor and so the response has no where to go. You'll notice in the original example, the initial Message("ping") is sent from within the act() loop of the Ping actor. But really you have a couple of issues and it's better to start over, restructuring the code a bit. Here's an example that works, but it depends on starting the clients in a particular order. Of course, you can rewrite this to keep retrying the connection from PingActor, among other things.

sealed trait Message
case class Ping extends Message
case class Pong extends Message

class PingActor extends Actor {

  override def preStart = {
    val pong = remote.actorFor("pong-service", "localhost", 2553)
    pong ! Ping
  }

  def receive = {
    case Pong => {
      println("Received pong")
      Thread.sleep(1000)
      self.reply(Ping)
    }
  }
}

class PongActor extends Actor {
  def receive = {
    case Ping => {
      println("Received ping")
      Thread.sleep(1000)
      self.reply(Pong)
    }
  }
}

object pingApp extends App {
  val actor = actorOf(new PingActor)
  remote.start("localhost", 2552)
        .register("ping-service", actor)
}

object pongApp extends App {
  val actor = actorOf(new PongActor)
  remote.start("localhost", 2553)
        .register("pong-service", actor)
}

Upvotes: 6

Related Questions