k4200’s notes and thoughts

Programmer side of k4200

First steps to Akka Remote Actors

I've been working on my personal project, Lifthub, which uses Lift, Akka, Gitorious etc. I'm pretty new to Akka and even the concept of Actors, so it took me quite a while to understand it and get the code running.

Here are first steps to Akka Remote Actors for those who aren't familiar with Akka and/or Actors. Actually, Akka has a lot of great documentation (and sometimes overwhelming for beginners), so I'll just put pointers to it when appropriate.

Set up a Remote Actor

An Akka Remote Actor listens on a port and communicates with clients via TCP, so it's pretty much a server. I'll call it "Server" depending on the context in this artricle.

According to a page on the official site, there are two ways to start up a Server, but one of the two is deprecated as of Akka 1.1, so we should use the other one, server side setup.

Here is a sample code included in the git repository of Akka. Client code is also included.

Define messages

The example page on the Akka page sends a String and receives a String as a response, which isn't that practical. Another page about "Actors" (not specific to Remote Actors) has a section about messages.

In short, messages must be immutable, so primitives or case classes are recommended. I created cases classes extending a sealed trait.

sealed trait SampleEvent

// Messages
case class FooEvent(f1: Int, f2: String) extends SampleEvent
case class BarEvent(f1: String) extends SampleEvent

Basic structure

Assume the following scenario.

  • Server receives a message from an Actor client
  • Server passes it to a helper object (call it Helper)
  • Helper does something and returns a Box value
  • Server returns it as a response.

The code would be like the following:

// Server
class SomeActor extends Actor {
  def receive = {
    case FooEvent(f1, f2) =>
      val res: Box[Int] = Helper.doFoo(f1, f2)
      self.reply(res)
    case BarEvent(f1) => // ....
  }
}

// Client
object Client {
  val server = remote.actorFor(....)
  // Returns the same type as Helper.doFoo
  def foo(f1: Int, f2: String): Box[Int] = {
    server !! (FooEvent(f1, f2) match {
      case Some(x) => x match {
        case box: Box[Int] => box // causes a warning because of type erasure
        case _ => Failure("unknown response")
      }
      case None => Failure("timeout")
    }
  }
}

Define responses

The above code works, but I'm not so sure that it's good. I think it's better to define case classes instead of returning Box directly.

trait SampleResponse

// Messages
case class FooResponse(res: Box[Int]) extends SampleResponse
case class BarResponse(res: Box[String]) extends SampleResponse

Now, the client code looks like the following.

// Client
object Client {
  val server = remote.actorFor(....)
  // Returns the same type as Helper.doFoo
  def foo(f1: Int, f2: String): Box[Int] = {
    server !! (FooEvent(f1, f2) match {
      case Some(x) => x match {
        case FooResponse(box) => box // type safe
        case _ => Failure("unknown response")
      }
      case None => Failure("timeout")
    }
  }
}

This example just eliminates the warning, but you want to include more information in a response in other scenarios, and case classes would help for the purpose.

Other things to consider

Serialization

Messages and responses are passed via network, and must be able to serializable. Here is a page dedicated to serialization.

At first, I was trying to pass a Lift mapper class and got the error shown below:

java.io.NotSerializableException: net.liftweb.util.FatLazy
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1528)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1493)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1416)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1528)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1493)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1416)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1528)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1493)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1416)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
        at akka.serialization.Serializer$Java$class.toBinary(Serializer.scala:58)
        at akka.serialization.Serializer$Java$.toBinary(Serializer.scala:53)
        at akka.remote.MessageSerializer$.serialize(MessageSerializer.scala:72)
        at akka.serialization.RemoteActorSerialization$.createRemoteMessageProtocolBuilder(SerializationProtocol.scala:329)
        at akka.remote.netty.RemoteClient.send(NettyRemoteSupport.scala:176)
        at akka.remote.netty.NettyRemoteClientModule$$anonfun$send$1.apply(NettyRemoteSupport.scala:59)
        at akka.remote.netty.NettyRemoteClientModule$$anonfun$send$1.apply(NettyRemoteSupport.scala:59)
        at akka.remote.netty.NettyRemoteClientModule$class.withClientFor(NettyRemoteSupport.scala:86)
        at akka.remote.netty.NettyRemoteSupport.withClientFor(NettyRemoteSupport.scala:504)
        at akka.remote.netty.NettyRemoteClientModule$class.send(NettyRemoteSupport.scala:59)
        at akka.remote.netty.NettyRemoteSupport.send(NettyRemoteSupport.scala:504)
        at akka.remote.netty.NettyRemoteClientModule$class.send(NettyRemoteSupport.scala:59)
        at akka.remote.netty.NettyRemoteSupport.send(NettyRemoteSupport.scala:504)
        at akka.actor.RemoteActorRef.postMessageToMailboxAndCreateFutureResultWithTimeout(ActorRef.scala:1147)
        at akka.actor.ScalaActorRef$class.$bang$bang(ActorRef.scala:1316)
        at akka.actor.RemoteActorRef.$bang$bang(ActorRef.scala:1117)
Configuration

You can fine tune Akka Remote Actors by using a config file. I haven't looked at it yet, but I'll use it and write an entry about it.