days
-4
-5
hours
-1
0
minutes
-4
-3
seconds
-2
0
search
Beware of missteps

Akka anti-patterns: data races

Manuel Bernhardt
akka
© Shutterstock / Who is Danny

When working with actors, you should always respect the following guideline: Do not, under any circumstances, close over mutable state. Since the actor model is a model, not a framework, it is up to you to make sure that you do, indeed, follow this guideline. Akka will not magically warn you if you misstep; however, your application will begin to act weirdly. In this article, Manuel Bernhardt explores a few ways in which you could misstep.

The actor model makes it possible to build highly-concurrent applications through the notion that actors obey the actor send rule and the actor subsequent processing rule, hence creating a single-threaded environment inside of an actor.

That being said, it’s all an illusion: as we have briefly talked about previously, Akka’s dispatchers make sure that messages are being processed by actors and so the thread by which one message is processed may not be the same as the one by which the next message is going to be processed.

Therefore you should always respect the following guideline when working with actors:

Do not, under any circumstances, close over mutable state.

And since the actor model is a model, not a framework, it is up to you to make sure that you do, indeed, follow this guideline. Akka will not magically warn you if you misstep, rather, your application will begin to act weirdly.

In the following article, we will explore a few ways in which you could misstep.

Offender #1: sharing mutable state

We already talked about this in detail — go read the article if you haven’t done it already. If you happen to share the inner, secret, precious mutable state of an actor by, for example, making it available through a message to a third party, chances are that you’re in for trouble. By doing so, you open up the pandora box, releasing vast amounts of “anything could happen, really” (also known as indeterminism) — another thread may now read and write this state while out-of-sync with the subsequent threads fueling the processing of your initial actor. This is not good.

Offender #2: closing over the sender

wrong-sender-1024x758

THE ACTOR PROCESSES A MESSAGE BY PERFORMING AN ASYNCHRONOUS HTTP CALL. BY THE TIME THE CALL COMPLETES, A MESSAGE FROM ANOTHER ACTOR WAS RECEIVED, CHANGING THE VALUE OF THE SENDER() METHOD

When processing a message you can get access to its sender by calling the sender() method. This method returns the actor reference of the sender of the currently processed message. Now, let’s imagine that you were doing something like this:

def receive = {
  case FetchUrlStatusCode(url) =>
    val response: Future[WSResponse] = ws.url(url).head()
    response.map { wsResponse =>
      // this may not work
      sender() ! UrlStatusCode(url, wsResponse.status)
    }
}

The response in the code above is a future, which is to say that everything in the body of the response.map closure will run asynchronously. In other words, the actor’s message processing will be able to continue before the future holding the response of the HEAD call will complete. As a result, the actor may already be processing another message when sender() is called — possibly returning the wrong sender as a result thereof.

The safer alternative is to capture the original sender:

def receive = {
  case FetchUrlStatusCode(url) =>
    val originalSender = sender()
    val response: Future[WSResponse] = ws.url(url).head()
    response.map { wsResponse =>
      originalSender ! UrlStatusCode(url, wsResponse.status)
    }
}

Offender #3: closing over an actor’s context

We’ve just seen that closing over the sender() method is dangerous. Well, it turns out that an actor’s context accessed via the context() method, also holds mutable state and is therefore not safe to close over.

Consider the following example (taken from chapter 6 of Reactive Web Applications — check this book out if you’re planning on building a reactive application):

class Nitrogliceryn(service: ExplosionService) extends Actor {
   def receive = {
     case Explode =>
       import Contexts.customExecutionContext
       val f: Future[Boom] = service.fetchExplosion
       // closing over the actor context is dangerous
       // since the context relies on running on the same thread
       // than its actor - DO NOT TRY THIS AT HOME
       f.map { boom =>
         context.actorSelection("surroundings") ! boom
       }
  }

Offender #4: using the scheduler with the Runnable variant

Akka lets you schedule tasks thanks to its scheduler, either by scheduling one task to be executed once in the future or by scheduling a repetitive task to be executed after a certain interval. There are two signatures available for this (simplified here):

def schedule(initialDelay: FiniteDuration, interval: FiniteDuration, receiver: ActorRef, message: Any)
 
def schedule(initialDelay: FiniteDuration, interval: FiniteDuration, runnable: Runnable)

The first signature is the one idiomatic to the actor model: once the interval has passed, a message is sent to the actor referenced in the call. The second signature lets you call a Runnable instead — which may feel more familiar when getting started with using the actor model.

And here’s the trap: if your Runnable closes over mutable state in your actor, then you are again in a situation in which both the thread fueling the processing of the actor and the thread fueling the Runnable may try to interact with the same mutable state concurrently. Game over.

Actors and futures: use pipes

In this article, we have so far focused on how to do things the wrong way. When it comes to mixing short-lived asynchronous computation using futures and long-lived asynchronous computation using actors, there is a pattern at your disposal: pipes.

In an Akka actor, a pipe lets you send the result of a completed future (be it a succeeded one or a failed one) to another actor, hence leveraging the message-passing paradigm of the actor model.

You can find out more about futures, actors and pipes in this talk I gave at Scala.io 2014 (slides) or otherwise in chapter 6 of Reactive Web Applications.

This post was originally published on Manuel Bernhardt’s blog.

Author
Manuel Bernhardt
Manuel Bernhardt is a passionate engineer, author, speaker and consultant who has a keen interest in the science of building and operating networked applications that run smoothly despite their distributed nature. Since 2008, he has guided and trained enterprise teams on the transformation to distributed computing. In recent years he is focusing primarily on production systems that embrace the reactive application architecture, using Scala, Play Framework and Akka to this end. Manuel likes to travel and is a frequent speaker at international conferences. He lives in Vienna where he is a co-organizer of the Scala Vienna User Group. Next to thinking, talking about and fiddling with computers he likes to spend time with his family, run, scuba-dive and read. You can find out more about Manuel's recent work at http://manuel.bernhardt.io.

Leave a Reply

Be the First to Comment!

avatar
400
  Subscribe  
Notify of