Early on in my experience programming with Akka, I came across the post “Attention: Seq Is Not Immutable”. From that point on, I’ve always been careful to use scala.collection.immutable.Seq rather than scala.collection.Seq when designing messages to pass between actors, and in general to ensure messages are immutable.

Why is this important in Akka? Let’s have a look at an example.

case class Message(parts: Seq[String])

class Receiver extends Actor {

  import context.dispatcher

  case object DoProcess

  var parts: Option[Seq[String]] = None

  override def receive: Receive = {

    case Message(p)  =>
      parts = Some(p)
      context.system.scheduler.scheduleOnce(5 seconds, self, DoProcess)

    case DoProcess =>
      parts.foreach(_.foreach(println))
      context.system.terminate()
  }
}

We have a simple message that contains a sequence of String. Since we have not imported scala.collection.immutable.Seq this is a mutable sequence. The Receiver actor, on receiving a message, assigns the sequence to a var and schedules the output of the sequence for 5 seconds later. The schedule is present to give us a chance to mutate the sequence from outside of the actor before the output occurs.

We have a Sender actor which sends a sequence to the receiver, then after 1 second mutates the sequence by adding another element to it:

case class Start(receiver: ActorRef)

class Sender extends Actor {

  import context.dispatcher

  case object Mutate

  val buffer = ArrayBuffer("A", "B", "C")

  override def receive: Receive = {

    case Start(rec) =>
      rec ! Message(buffer)
      context.system.scheduler.scheduleOnce(1 seconds, self, Mutate)

    case Mutate => buffer += "D"

  }
}

Then all we need is some simple bootstrapping code to run the program:

object Example extends App {

  val system = ActorSystem("Example")

  val receiver = system.actorOf(Props[Receiver])
  val sender = system.actorOf(Props[Sender])

  sender ! Start(receiver)
}

The full project is available on Github. Running it with sbt run yields the following output:

A
B
C
D

The Sender actor has been able to mutate the contents of the message after sending it. In this case, because of the 5 second delay in the Receiver, the output is predictable. It is almost certain (except for any very inaccurate behaviour from the scheduler) that the mutation occurs before the output. Without the scheduling delays the behaviour would be highly unpredictable and would depend on how the actors are scheduled by the actor system. This goes against the actor model and is usually highly undesirable.

Even if you have a use-case where you think it might be desirable to mutate a message after sending it, deliberately violating the concept of immutable messages between Actors, there is another reason to avoid doing so. One of the advantages of the Actor module is location transparency; the application behaves the same way whether it is running in a single JVM, multiple JVMs on a single machine, or across different machines.

To run the example between two different JVMs, run the following two commands in two terminals:

sbt 'runMain com.mjlivesey.mutableakka.RemoteExample receiver 2552 2553'
sbt 'runMain com.mjlivesey.mutableakka.RemoteExample sender 2552 2553'

The receiver will yield:

A
B
C

When remoting, the message is serialized in the sender, transmitted to the receiver, and deserialized. The sequence on the receiver is therefore not the same object as the sequence on the sender and so mutating the sequence in the sender has no effect. This means we have an application which operates differently when we try and scale it using Akka remoting.

If you develop an application which relies on mutating messages after they are sent, that application will not be able to scale when required, losing one of the advantages of using Akka and the Actor model. Use immutable messages in all cases, and make sure you’re using scala.collection.immutable.Seq.

PS if you’re using Akka with Java, you might be interested in my post about achieving immutability in Java.