Scala Futures
Lorenzo Barasti
EPISODE II
MAY THE FORs BE WITH YOU
What is a Future?
A Future is
an object holding a value
which may become available
at some point.
Scala Futures
What is it
good for?
What is it good for?
Running expensive computations...
val inverseFuture: Future[Matrix] = Future {
fatMatrix.inverse()
}
...off the main thread
Retrieving data from a database...
val query = coffees
.filter(_.name.startsWith("ESPRESSO"))
.map(_.name)
val coffeeNames: Future[Seq[String]] = db.run(query.result)
...without blocking
What is it good for?
Issuing an HTTP request...
val responseFuture: Future[HttpResponse] =
Http().singleRequest(HttpRequest(uri = "http://akka.io"))
...without having to wait for the response
What is it good for?
Running concurrent computations
val vectors: Seq[(Float, Float)]
def length(vector: (Float, Float)): Float
val lengths: Seq[Float] = vectors.map(v => length(v))
What is it good for?
Running concurrent computations
val vectors: Seq[(Float, Float)]
def length(vector: (Float, Float)): Float
val lengths: Seq[Future[Float]] = vectors.map(v => Future(length(v)))
What is it good for?
But why?
But why?
Responsiveness
Error Isolation
Performance
Code Composability
How does it work?
How does it work?
Future {
Matrix.factorize(mat)
} // => Future[(Matrix, Matrix)]
Task A
db.run(query)
// => Future[Seq[Coffee]]
Task B
http.singleRequest(request)
// => Future[HttpResponse]
Task C
How does it work?
Task A
db.run(query)
// => Future[Seq[Coffee]]
Task B
http.singleRequest(request)
// => Future[HttpResponse]
Task C
Execution
Context
passed implicitly
Future {
Matrix.factorize(mat)
} // => Future[(Matrix, Matrix)]
How does it work?
Shared Queue
Task A
Execution Context
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Execution Context
Scheduler
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task B
Task C
Task D
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task C
Done
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task A
Task D
Task B
Task E
How does it work?
Shared Queue
Fixed Thread Pool
Thread 1
Thread 2
Thread 3
Task D
Task B
Task E
Task A
Done
OK, but how do I
use them?
How to
Handling a successful future with .forEach
def hasCycle(g: Graph): Boolean
Future(hasCycle(g)).forEach { cycleFound =>
if(cycleFound) println("I found a cycle")
else println("The graph is acyclic")
}
// => _: Future[Unit]
Handling a successful future with .map
def hasCycle(g: Graph): Boolean
def topologicalOrdering(g: Graph): Seq[Int]
Future(hasCycle(g)).map { cycleFound =>
if(!cycleFound) Some(topologicalOrdering(g))
else None
}
// => _: Future[Option[Seq[Int]]]
How to
Dealing with side-effects with .andThen
def colouring(g: Graph, upperBound: Int): Int
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g).andThen {
case Failure(ex) => println(
"Could not compute upper bound")
case Success(upperBound) =>
updateUpperBoundCache(g, upperBound)
}.map(upperBound => colouring(g, upperBound))
// => Future[Int]
How to
Running dependent operations with .flatMap
def colouring(g: Graph, upperBound: Int): Future[Int]
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g)
.recover(_ => g.size)
.map(upperBound => colouring(g, upperBound))
.andThen {
case Success(Success(colours)) => println(g, colours)
}
// => Future[Future[Int]]
How to
Running dependent operations with .flatMap
def colouring(g: Graph, upperBound: Int): Future[Int]
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g)
.recover(_ => g.size)
.flatMap(upperBound => colouring(g, upperBound))
.andThen {
case Success(colour) => println(g, colour)
}
// => Future[Int]
How to
Handling failures with .recover
def colouring(g: Graph, upperBound: Int): Int
def findColouringUpperBound(g: Graph): Future[Int]
findColouringUpperBound(g).recover { exeption =>
println("Could not compute upper bound, " +
"using graph size as fallback")
g.size
}.map(upperBound => colouring(g, upperBound))
// => Future[Int]
How to
What about my
old code?
Effective Refactoring
Avoid instantiating futures on known values
val userCache: Map[Int, User]
def getUserFromDb(id: Int): Future[User]
val userId: Int = 42
def getUser(id: Int): Future[User] = {
if(userCache.has(id))
Future(userCache(id))
else
getUserFromDb(id)
}
Avoid instantiating futures on known values
val userCache: Map[Int, User]
def getUserFromDb(id: Int): Future[User]
val userId: Int = 42
def getUser(id: Int): Future[User] = {
if(userCache.has(id))
Future.successful(userCache(id))
else
getUserFromDb(id)
}
Effective Refactoring
Use .andThen to wrap side-effects and return the original value
def transform(user: User): PowerUser
storage.selectFileById(id).map { result =>
log.debug(s"Retrieved object:$id filesSize:'${result.size}'")
result
}.map(transform)
// => Future[PowerUser]
Effective Refactoring
Use .andThen to wrap side-effects and return the original value
def transform(user: User): PowerUser
storage.selectFileById(id).andThen {
case Success(result) =>
log.debug(s"Retrieved object:$id filesSize:'${result.size}'")
}.map(transform)
// => Future[PowerUser]
Effective Refactoring
Use .transform/.transformWith for fine grained control over failures
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
val g: Graph
getColourUpperBound(g).flatMap(n => colouring(g, n))
.recover(_ => colouringHeuristic(g))
// => Future[Seq[Int]]
Effective Refactoring
Which error are we handling?
Use .transform/.transformWith for fine grained control over failures
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
val g = Graph(...)
getColourUpperBound(g).transform {
case Success(n) => colouring(g, n)
case Failure(_) => colouringHeuristic(g)
}
// Future[Seq[Int]]
Effective Refactoring
Use for comprehension
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
def sendResult(colouring: Seq[Int]): Future[HttpResponse]
def report(result: Seq[Int]): Report
val g = Graph(...)
getColourUpperBound(g).flatMap { upperBound =>
colouring(g, upperBound).flatMap { result =>
sendResult(result).map { _ =>
report(result)
}
}
}
// Future[Report]
Effective Refactoring
Use for comprehension
def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
def sendResult(colouring: Seq[Int]): Future[HttpResponse]
def report(result: Seq[Int]): Report
val g = Graph(...)
for {
upperBound <- getColourUpperBound(g)
result <- colouring(g, upperBound)
_ <- sendResult(result)
} yield report(result)
// Future[Report]
Effective Refactoring
NICE!
So, I have this Actor handling a Future...
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
GetUserProfile
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
GetUserProfile
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
GetUserProfile
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
GetUserProfile
UserProfile
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
GetUserProfile
Cache
Actors and Futures
Actor A
UserProfile
Actor
UserProfile
API
Akka Application
GetUserProfile
UserProfile
Cache
Actor A: just ask
val userProfileActor: ActorRef
val userId: UserId
(userProfileActor ? GetUserProfile(userId))
.mapTo[UserProfile]
.andThen { case profile => println(userId, profile) }
Actors and Futures
Send a message and wait for a response
Actor A: just ask
val userProfileActor: ActorRef
val userId: UserId
(userProfileActor ? GetUserProfile(userId))
.mapTo[UserProfile]
.andThen { case profile => println(userId, profile) }
Actors and Futures
The type of the response is Any
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
fetch(userId).map { profile =>
cache += userId -> profile
sender() ! profile
}
}
}
Actors and Futures
UserProfileActor
ALERT! Closing over sender()
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
fetch(userId).map { profile =>
cache += userId -> profile
sender() ! profile
}
}
}
Actors and Futures
UserProfileActor
Won't return the sender you expect
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
val ref = sender()
fetch(userId).map { profile =>
cache += userId -> profile
ref ! profile
}
}
}
Actors and Futures
UserProfileActor
Capture the sender in the right context
val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
val ref = sender()
fetch(userId).map { profile =>
cache += userId -> profile
ref ! profile
}
}
}
Actors and Futures
UserProfileActor
Suspicious...
val http: HttpRef
var cache: Map[UserId, UserProfile]
var subscribers: Map[UserId, Seq[ActorRef]]
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
if(subscribers(userId).isEmpty)
fetch(userId).foreach { profile =>
cache += userId -> profile
subscribers(userId).foreach(_ ! profile)
subscribers -= userId
}
subscribers += userId -> (sender() +: subscribers(userId))
}
}
Actors and Futures
UserProfileActor
Changing the actor state
val http: HttpRef
var cache: Map[UserId, UserProfile]
var subscribers: Map[UserId, Seq[ActorRef]]
def receive: Receive = {
case GetUserProfile(userId) =>
cache.get(userId) match {
case Some(profile) => sender() ! profile
case None =>
if(subscribers(userId).isEmpty)
fetch(userId).map(FetchedUserProfile) pipeTo self
val updatedSubs = sender() +: subscribers(userId)
subscribers += userId -> updatedSubs
}
case FetchedUserProfile(profile) =>
cache += profile.userId -> profile
subscribers(profile.userId).foreach(_ ! profile)
subscribers = subscribers - profile.userId
Actors and Futures
Wooooooo!
Actors and Futures
What if the call fails?
def receive: Receive = {
// ...
case failure @ Status.Failure(InvalidResponse(userId, statusCode)) =>
subscribers(userId).foreach(_ ! failure)
subscribers = subscribers - userId
case failure @ Status.Failure(MalformedJsonException(_)) =>
subscribers(userId).foreach(_ ! failure)
subscribers = subscribers - userId
}
Can you say
that again?
Your application
with Futures
Fast
Responsive
Awesome
Your Futures
Composable API
Maintainable code
Available in many colours!
Should have used Scala Futures
Bad error handling
pipeTo
Actors
Akka Application
ask
Concept
Lorenzo Barasti
Jacob Taylor-Hindle
Powered by
@lbarasti
lbarasti.github.io
@glassandonehalf
References
- A good place to start your journey on the way to non-blocking IO in Scala
- More on ExecutionContext in relation to jdbc and database interactions
- Handling blocking operations in Akka HTTP
- Some clarity on the blocking construct
- Nice and clear intro to the ask pattern
- The ask pattern from the official Akka Docs - make sure you digest the warning sections
- About closing over an actor sender method
- All that can go wrong with closing over internal Actor state
- About message delivery in Akka system. A must if you're using Akka
- All you need to know about Akka dispatchers - from the docs
- A super dense and insightful article about the Akka dispatcher
- About picking the right ExecutionContext for the job
- Official docs for ForkJoinPool
Scala Futures
By Lorenzo Barasti
Scala Futures
EPISODE II - MAY THE FORs BE WITH YOU
- 1,361