-
Notifications
You must be signed in to change notification settings - Fork 33
Concurrent
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.
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!)
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"
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)
- The root
Future
in the code callsFuture.apply
in the standard scala library. This actually creates aPromise
and immediately wraps that Promise in aRunnable
and submits it to the execution context. - The
flatmap
call will create anotherPromise
and link it to the first future via theonComplete
method of the future. This method returns a future for the promise that is created. - The
map
call will create yet anotherPromise
and link it to the Future returned from theflatMap
.
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.
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"
}
}
- Overview
- Configuration
- Logging Setup
- Performance Considerations
- Java Users Guide
- Scala Users Guide
- Modules
- Contributing