Thursday, December 25, 2008

Remote Actors in Scala

A well known feature of Scala is it support for message passing concurrency via Actors similar to Erlang. In Chapter 30 of "Programming in Scala" Scala's Actors is described in detail.

However, it is not so well-known that Scala also supports actors distributed across different nodes (Remote Actors). Here is the Ping-Pong example with the Ping actor and the Pong actor running in different processes on (possibly) different (cluster) nodes. The example also shows the actor-linking support of Scala. Linking an actor a to an actor b, means that a is notified if b terminated.

Messages:

package de.dirkmeister.pingpong

case object Ping case object Pong case object Quit

Ping:
package de.dirkmeister.pingpong

import scala.actors.Actor import scala.actors.Actor._ import scala.actors.Exit import scala.actors.remote.RemoteActor._ import scala.actors.remote.Node

object RemotePingApp { def main(args: Array[String]) : Unit = { val port = args(0).toInt val peer = Node(args(1), args(2).toInt) val ping = new RemotePing(port, peer, 16) ping.start() } } class RemotePing(port: Int, peer: Node, count: Int) extends Actor { trapExit = true // (1)

def act() {
alive(port)     // (2)
register('Ping, self) // (3)

val pong = select(peer, 'Pong) // (4)
link(pong)             // (5)

var pingsLeft = count - 1
pong ! Ping     // (6)
while (true) {
  receive {     // (7)
    case Pong =>
      Console.println("Ping: pong")
      if (pingsLeft > 0) {
        pong ! Ping
        pingsLeft -= 1
      } else {
        Console.println("Ping: start termination")
        pong ! Quit     // (8)
        // Terminate ping after Pong exited (by linking)
      }
    case Exit(pong, 'normal) => // (9)
        Console.println("Ping: stop")
        exit()
  }
}

} }

Pong:
package de.dirkmeister.pingpong

import scala.actors.Actor import scala.actors.Actor._ import scala.actors.remote.RemoteActor._

object RemotePongApp { def main(args: Array[String]) : Unit = { val port = args(0).toInt val pong = new RemotePong(port) pong.start() } } class RemotePong(port: Int) extends Actor { def act() { alive(port) register('Pong, self)

while (true) {
  receive {
    case Ping =>
      Console.println("Pong: ping")
      sender ! Pong
    case Quit =>
      Console.println("Pong: stop")
      exit()    // (10)
  }
}

} }

Notes: (1) By setting trapExit, the linked actor is notified by sending an Exit(sender, reason) message. Otherwise the termination is either ignored (if reason is 'normal) or the linked actor is terminated, too (reason != 'normal). (2) alive(port) (member of the RemoteActor object) starts the remote service listening on the given port (3) register(symbol, actor) (member of the RemoteActor object) registers the given actor using the symbol. The other actors can then lookup the actor by the hostname and port and this symbol. (4) This lookup is done by the select(node, symbol) method that returns an proxy actor, which managed the complete transmission. (5) The link method links the Ping actor with the Pong actor, so that the current actor is notified if the Pong actor is terminated. This example shows that this also works remotely. (6) and (7) That proxy actor is used to send messages to the remote node. Sending and receiving remote messages is similar to local messages. Well, everything must be serializable, but the use of case classes is recommended anyway. (8) The Quit message stops they Pong actor. See (10) (9) When the Pong actor terminates, an Exit(sender, reason) message is sent to the Ping actor. This is linking system is used for error handling, here it is used to terminate the Ping actor, too. (10) The Pong actor calls the exit() method, which terminates the actor with the reason 'normal.

I think Scala's Remote Actors are really nice. Well, it lacks Erlang's capability to spawn actors on a different node and maybe other things, but it has the property that it isn't written in a 20 years old language: It is written in a modern, OO/functional-hybrid language.

According to a comment on the Lambda blog, there is (or was?) and effort to use the Java P2P protocol JXTA for remote actors. That would be cool, but I found nothing newer about that effort. Here is a description about clustering Scala actors via Terracotta. Here one using Oracle Coherence for that.

6 comments:

  1. Automatically imported comment
    Author: Jens Alfke
    Date: Wednesday 28. January 2009


    Thanks for this example! I've been trying to get remote agents to work, with no success. Unfortunately when I copy and paste your sample code and run it, it fails in the same way as mine does. Both apps run but no messages are received. If I turn on actor debugging (Debug.level = 99), I get the following when RemotePongApp starts:

    Info: Thread[Thread-0,5,main]: corePoolSize = 4, maxPoolSize = 256
    Info: Thread[Thread-6,5,main]: waiting for new connection...
    Info: created service at Node(172.18.111.57,9000)

    Then when the it gets a connection (from the RemotePingApp on the same machine) it logs:

    Info: Started new Thread[Thread-7,5,main]
    Info: Thread[Thread-6,5,main]: caught scala.MatchError: Node(172.18.111.57,9001)
    Info: Thread[Thread-6,5,main]: shutting down...

    Any ideas? I'm on Mac OS X 10.5.6, running Scala 2.7.3 (the DarwinPorts distribution.)

    ReplyDelete
  2. Automatically imported comment
    Author: dmeister
    Date: Thursday 29. January 2009


    I tried it here again with RemotePongApp started with the arguments 8012, and RemotePingApp started with 8011 localhost 8012, and everything was fine. I also tried using my local ip address instead of localhost.

    I also use Mac OS X 10.5.6 and scala 2.7.3. Strange. Your arguments have been 9000, and 9001 [your ip] 9000?

    ReplyDelete
  3. Automatically imported comment
    Author: Jens Alfke
    Date: Thursday 29. January 2009


    I tried the same arguments you used, and still the same error. Looking at the source, the exception must be thrown from TcpService.scala:238, in the TcpServiceWorker.readNode method. But that's matching on a Node, and the exception says the offending object is a Node, so I don't see how this could fail ... unless there are two different Node classes. Weird.

    ReplyDelete
  4. Automatically imported comment
    Author: Dirk Meister
    Date: Thursday 26. March 2009


    The problem described here by Jens should with ticktet #1686.

    ReplyDelete
  5. Automatically imported comment
    Author: RemoteActor in Scala — You shot the invisible swordsman.
    Date: Wednesday 01. April 2009


    [...] code for this post was heavily borrowed fromthe simple code posted at dirkmeister.de, but it took me a while to make even that work, so I figured some even-more-simplistic tutorial [...]

    ReplyDelete
  6. Automatically imported comment
    Author: Mark Mendel
    Date: Sunday 19. April 2009


    This is the first remote ping pong example I've found that actually works...almost.

    Once I added the classloader fix mentioned above, it worked up until termination.

    Pong terminates with java.net.SocketException: Socket closed
    Ping says "start termination" then hangs.

    I then added "@serializable" to all of the message case objects, and it works perfectly.

    Bewildering to me... I don't understand how it worked at all w/o the serializable

    BTW, I'm running ubunutu 8.10...milage may vary!

    ReplyDelete