How does akka update a mutable state?

332 views Asked by At

I read Akka documentation, but I do not understand:

class MyActor extends Actor {
  private var _state = 0

  override def receive: Receive = {
    case x: Int =>
      if (x != _state) {
        println(s"---------> fail: ${x} and ${_state}")
      }
      _state = x + 1
  }
}


implicit val system = ActorSystem("my-system")
  val ref = system.actorOf(Props[MyActor], "my-actor")
  (0 to 10000).foreach { x =>
    ref ! x
  }

I have a _state variable which is not @volatile and not atomic but at the same time _state is always correct, if I do changes with !-method. How does Akka protect and update the internal state of actors?

2

There are 2 answers

2
Tim On

This is the Classic model for Akka Actors. If you are just learning actors then you should use Typed Actors because that is the supported model going forwards.

With typed actors, the actor system holds the state for each actor, not the actor itself. When an actor needs to process a message the actor system will pass the current state to the actor. The actor will return the new state back to the actor system when it has finished processing the message.

The typed model avoids all synchronisation issues because it does not use any external state, it only uses the state that is passed to it. And it does not modify any external state, it just returns a modified state value.

If you must use Classic actors then you can implement the same model using context.become rather than a var.

4
Levi Ramsey On

Akka is an implementation of the Actor Model of computation. One of the (arguably the) key guarantees made by the actor model is that an actor only ever processes a single message at a time. Simply by virtue of having _state be private to an actor, you get a concurrency guarantee that's at least as strong as if you had every method of an object be @synchronized, with the added bonus of the ! operation to send a message being non-blocking.

Under the hood, a rough (simplified in a few places, but the broad strokes are accurate) outline of how it works and how the guarantee is enforced is:

  • Using the Props the ActorSystem constructs an instance of MyActor, places the only JVM reference to that instance inside an ActorCell (I'm told this terminology, as well as that the deep internals of Akka are "the dungeon", is inspired by the early development team for Akka being based in an office in what was previously a jail in Uppsala, Sweden), and keys that ActorCell with my-actor. In the meantime (technically this happens after system.actorOf has already returned the ActorRef), it constructs an ActorRef to allow user code to refer to the actor.

  • Inside the ActorCell, the receive method is called and the resulting PartialFunction[Any, Unit] (which has a type synonym of Receive) is saved in a field of the ActorCell which corresponds to the actor's behavior.

  • The ! operation on the ActorRef (at least for a local ActorRef) resolves which dispatcher is responsible for the actor and hands the message to the dispatcher. The dispatcher then enqueues the message into the mailbox of the ActorCell corresponding to my-actor (this is done in a thread-safe way).

  • If there is no task currently scheduled to process messages from the actor's mailbox, such a task is enqueued to the dispatcher's execution context to dequeue some (configurable) number of messages from the ActorCell's mailbox and process them, one-at-a-time, in a loop. After that loop, if there are more messages to process, another such task will be enqueued. Processing a message consists of passing it to the Receive stored in the ActorCell's behavior field (this mechanism allows the context.become pattern for changing behavior).

It's the last bit that provides the core of the guarantee that only one thread is ever invoking the Receive logic.