-
Notifications
You must be signed in to change notification settings - Fork 33
Scala Futures
Scala Futures are an elegant, non-blocking way to write your code in order to maximize parallelism. However, they present significant challenges to maintaining a Trace Context across the code. But, no worries! Money can help!
Money includes a concurrent
package that provides support to propagate (and extend) Trace Contexts across Scala Futures. It will manage your Trace Contexts seamlessly across Execution Contexts!
It is rather simple to use, pay attention to the namespaces that are imported:
To start and stop spans in Scala Futures, you need to use the com.comcast.money.concurrent.Futures
API.
The following snippet of code will wrap the execution of the block provided in a future. A span will automatically be started before the block starts, and will be guaranteed to be stopped once the block of code is complete (the Future is complete):
import com.comcast.money.concurrent.Futures
import com.comcast.money.core.Money.tracer
import scala.concurrent.ExecutionContext.Implicits.global
def withMoney(indexSize:Long):Future[String] = newTrace("future-span") {
tracer.record("index-size", indexSize)
}
The withMoney
method returns a Future, that future is created inside the Futures.newTrace
function. The string value that is specified in the newTrace
function is the name of the span that will be emitted.
In order to propagate an existing Trace Context across separate futures, you need to use the Futures.continueTrace
API. This will make sure that the execution of the new future happens within the existing Trace Context.
To show this, let's build on our previous example:
import com.comcast.money.concurrent.Futures
import com.comcast.money.core.Money.tracer
import scala.concurrent.ExecutionContext.Implicits.global
def withMoney(indexSize:Long):Future[String] = newTrace("future-span") {
tracer.record("index-size", indexSize)
keepGoing("man")
}
def keepGoing(here:String):Future[String] = continueTrace {
tracer.record("continued-value", here)
}
In this example, the block inside continueTrace
is a Future, and will be executed on a separate thread. By using continueTrace
here, the "continued-value" note will be recorded on the "future-span"! Amazing!
All of the functional combinators that come with Scala Futures are supported as well, so you can do fun things like map, flatMap, collect, transform, foreach, etc. are all supported by Money Futures...
val fut = newTrace("crazy-train") {
tracer.record("begin", "how")
100
}.map { v =>
tracer.record("map", "do")
v
}.collect { case v =>
tracer.record("collect", "you")
v
}.map { v =>
tracer.record("map2", "like?")
200
}
Yes, as crazy as that looks, all of the notes: "begin", "map", "collect", and "map2" will all be captured on the "crazy-train" span.
We can do that too...
val fut = newTrace("root") {
tracer.record("begin", "root")
// Here, we have a nested, child span inside the root parent
newTrace("nested") {
tracer.record("begin", "nested")
Some(456)
}.flatMap { case Some(v) =>
tracer.record("flatMap", "nested")
Future {v}
}.map { case v =>
tracer.record("map", "nested")
v
}
}.flatMap { child =>
tracer.record("flatMap", "root")
child
}.map { case s =>
tracer.record("map", "root")
}
All of the correct notes will be captured on all of the correct spans. You can nest as deeply as you want, and the appropriate parent-child relationships will all be maintained.
Part Black Magic, part Witchcraft.
Sometimes, you cannot control the creation of a Scala Future, as it is surfaced from another library that you do not control (I'm looking at you Play Framework WS API
When you are faced with this scenario, you need to pull the future from the third party library into the existing trace context (span). We do that with the wrapTrace
method in the futures API:
def wrapTrace[T](f: => Future[T])(implicit ec: ExecutionContext): Future[T]
Notice you need to provide f which yields a Future. Here is how to use it in an example:
def withMoney(indexSize:Long):Future[String] = newTrace("future-span") {
// we just created a new span, establishing a trace context in this future
// Future bubbles up from WS API, we need to wrap this in a trace
wrapTrace {
WS.url(url).get().map {
response =>
(response.json \ "thing" \ "jawn").as[String]
}
} onComplete {
case _ => tracer.record("done", "man")
}
}
In the above example, we first create a new span called future-span
. In side there, we need to make a call using the Play WS API which surfaces a Future. In order to make sure that our note "done" -> "man" is recorded in onComplete
, we must make sure that we use wrapTrace
round the WS API call.
- Overview
- Configuration
- Logging Setup
- Performance Considerations
- Java Users Guide
- Scala Users Guide
- Modules
- Contributing