Skip to content

Concurrent

Cleary, Paul edited this page Nov 9, 2015 · 1 revision

Disclaimer: We are actively working on support Scala Futures without the need for using AspectJ. That work is in testing now. Once it is released, this module maybe deprecated.

Why would I use it?

If your application heavily uses Scala Futures, you would need to use the money-concurrent module. If you use Java Futures, sorry, but you are out of luck for the time being with this module (but, hey, if you want to contribute one!)

Getting the module


Add a dependency as follows for maven:

    <dependency>
        <groupId>com.comcast.money</groupId>
        <artifactId>money-concurrent</artifactId>
        <version>0.6.0_2.10</version>
    </dependency>

And here is a dependency for SBT:

    libraryDependencies += "com.comcast.money" %% "money-concurrent" % "0.6.0"

Tracing Futures

Money works by wrapping Scala Futures with an outer, wrapper future that will start and stop a trace span around the execution of your future.

It uses AspectJ in order to guarantee the propagation of the trace context (span) across all of the things that happen within your future.

You see, Scala Futures are difficult. Let's take a look at the following code snippet:

            Future {
              tracer.record("begin", "nested")
              Some(456)
            }.flatMap { case Some(v) =>
              tracer.record("flatMap", "nested")
              Future {v}
            }.map { case _ =>
              tracer.record("map", "nested")
              "hello"
            }

What happens in the above code snippet is that 3 separate tasks are created and submitted to the current execution context. The following walks you through this case (the description is based on Scala 2.10)

  1. The root Future in the code calls Future.apply in the standard scala library. This actually creates a Promise and immediately wraps that Promise in a Runnable and submits it to the execution context.
  2. The flatmap call will create another Promise and link it to the first future via the onComplete method of the future. This method returns a future for the promise that is created.
  3. The map call will create yet another Promise and link it to the Future returned from the flatMap.

Because these things run in the execution context in parallel with say a billion other concurrent tasks, the timing of the execution of each future that is created is non-deterministic. That is to say, by the time flatMap is actually created, the root future may already have been completed (or not).

All of these futures become more complex when you have nested futures, especially if you decide to use different execution contexts for different work (which is an entirely reasonable use case). Ensuring a common trace span across all of these futures is difficult.

tl;dr Futures in scala is not a trivial problem especially when you are trying to maintain a common trace context (trace span) across all of those futures and promises!

A note on the implementation: we are looking at being able to do this using closures and implicits. Currently, this approach felt like it intruded on the codebase more than we'd like to see for the users of the library. That said, a non-AspectJ version implementation to support Scala futures is in the works.

Getting Started

To trace your futures, you need to wrap your existing future declaration with a traced function.

import com.comcast.money.concurrent.Futures._
...
      val future = Futures.traced("easy") {
        Future {
          ... do something here Nostradamus ...
        }
      }

We support more complex cases:

import com.comcast.money.concurrent.Futures._
...
      val future = Futures.traced("medium") {
        Future {
          ... do something here Nostradamus ...
        }.map {
          ... map the value from the future to something else
        }
      }

And even crazy-wack-funky use cases:

import com.comcast.money.concurrent.Futures._
...
      traced("hard") {
        Future {
          Futures.traced("nested") {
            Future {
              "one"
            }.recover {
              case _ =>
                "two"
            }.flatMap {
              case _ =>
                Future{"three"}
            }.map {
              case _ =>
                "four"
            }
          }
        }.flatMap {
          case _ =>
            Future {"five"}
        }.map {
          case _ =>
            "six"
        }
      }