Concurrency made easy with Scala and Akka

A few posts ago I looked at Erlang and how it solves concurrency using the actor model. I truly enjoyed playing with this strategy and found the concept intriguing.

That's why i decided to look into how concurrency using actors is solved in other languages as well. In this post I'll take a quick look at Scala and how we can work with actors using the Akka library.

It's worth mentioning that Scala originally had it’s own Actor library. However, in version 2.10 Akka became the default Actor library and in 2.11 the Scala Actor library got deprecated.

So what are we creating?

I’m going to keep it to the absolute basic and recreate the example I made in the Erlang post.

We created a small chatroom where we had two types of actors, server actors and client actors. Clients will then be able to connect to the server in order to participate in the chatroom.

Communication between the actors

In Akka, actors are very lightweight concurrent entities that communicate asynchronously by sending messages between each other. As with Erlang, Scala uses pattern matching to figure out what kind of a messages it receives.

To create the different types of messages, we just define a few case classes.

abstract class Msg

case class Send(msg: String) extends Msg  
case class NewMsg(from: String, msg: String) extends Msg  
case class Info(msg: String) extends Msg

case class Connect(username: String) extends Msg  
case class Broadcast(msg: String) extends Msg  
case object Disconnect extends Msg  

We'll see how the different case classes are used when we're studying the actor implementations. So let's get to it!

Time to look at the server actor

class Server extends Actor {

    var clients = List[(String, ActorRef)]();

    def receive = {
      case Connect(username) => {
          broadcast(Info(f"$username%s joined the chat"))
          clients = (username,sender) :: clients
          context.watch(sender)
        }
      case Broadcast(msg) => {
          val username = getUsername(sender)
          broadcast(NewMsg(username, msg))
      }
      case Terminated(client) => {
        val username = getUsername(client)
        clients = clients.filter(sender != _._2)
        broadcast(Info(f"$username%s left the chat"))
      }
    }

    def broadcast(msg: Msg) {
        clients.foreach(x => x._2 ! msg)
    }

    def getUsername(actor: ActorRef): String = {
        clients.filter(actor == _._2).head._1
    }
 }

To define an actor, we need to create a class that extends the Actor trait. This trait contains several methods that we can override to add functionality. However, for this post we're going to focus on the receive method.

receive is the only abstract method that we need to override to create a valid actor. This method will fetch messages from the mailbox and run it through our pattern matching to figure out what to do.

Let's go through the different patterns

In the server actor we've defined three patterns. Let's take a look at what they do.

Connect(username) - Cool, someone is trying to connect to our chat!

The first thing we do is to notify all the other clients that a new client has connected.

As with Erlang, we use the exclamation point to send messages to other actors.

Next we add the new client to our client list. sender is defined in the Actor trait and contains the ActorRef of the message sender.

The last thing we do is to make the server start monitoring the client actor by calling watch. Doing this, the server actor will receive a Terminated message if the client actor is terminated. Pretty cool right?

Broadcast(msg) - A new message for the chatroom!

This case doesn't contain anything special. It just finds the username of the sender based on the ActorRef and then sends a NewMsg to all the clients connected to the server.

Terminated(client) - Aww, someone disconnected...

Since we registered for Terminated messages from clients when they connected, we need to handle them. In this case the server simply notifies the other clients that a client disconnected, then removes the client from the client list.

Next up, the client actor

class Client(val username: String, server: ActorRef) extends Actor {

  server ! Connect(username)

  def receive = {
    case NewMsg(from, msg) => {
      println(f"[$username%s's client] - $from%s: $msg%s")
    }
    case Send(msg) => server ! Broadcast(msg)
    case Info(msg) => {
      println(f"[$username%s's client] - $msg%s")
    }
    case Disconnect => {
      self ! PoisonPill
    }
  } 
}

There isn't much actor related to tell that hasn't been told in the server part. However, there are a few things worth mentioning.

  • On creation, the client sends a Connect message to the server.

  • On Disconnect, the client sends a PoisonPill message to itself to trigger the termination. A Terminated message will then be published to DeathWatch, which will then notify our server actor.

Let the chatting begin!

To keep the chat a bit more natural, I’m using sbt console to interact with the actors.

First, let's startup the console and get in a few dependencies.

$ sbt console
> import scamsg._
> import akka.actor._

Now, let's create our ActorSystem.

> val system = ActorSystem("System")

This is the entry point for creating our actors. Actors created under the same ActorSystem share several configurations and form a hierarchical group.

Creating an ActorSystem is quite expensive, so you shouldn't create them without having a good reason.

Next, let's create our server actor.

> val server = system.actorOf(Props[Server])

To create the actor, we use ActorSystem.actorOf which creates a child under this context. Props is an ActorRef config object for creating new actors.

Ok, time to add a client!

> val c1 = system.actorOf(Props(new Client("Sam", server)))

> c1 ! Send("Hi, anyone here?")
[Sam's client] - Sam: Hi, anyone here?

Let's bring in someone for Sam to talk to.

> val c2 = system.actorOf(Props(new Client("Mia", server)))
[Sam's client] - Mia joined the chat

> val c3 = system.actorOf(Props(new Client("Luke", server)))
[Mia's client] - Luke joined the chat
[Sam's client] - Luke joined the chat

> c2 ! Send("Hello")
[Mia's client] - Mia: Hello
[Sam's client] - Mia: Hello
[Luke's client] - Mia: Hello

Great, let's now try to terminate Luke's client to see that the server actor will get notified.

> c3 ! Disconnect
[Mia's client] - Luke left the chat
[Sam's client] - Luke left the chat

That's it for now

That's it for this quick introduction on creating actors using Scala and Akka. I truly enjoy working with Akka, so I'll probably get back to the topic, covering more features like supervision strategies and distribution.

You can find the source code of the example on GitHub.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe