Date post: | 10-May-2015 |
Category: |
Technology |
Upload: | alexandru-nedelcu |
View: | 2,987 times |
Download: | 1 times |
The Future Starts with a Promise
● Dumb title ● The Future starts whether we want it or not
Performance
“amount of useful work accomplished by a computer system compared to the time and
resources used”
Performance
Currency for things we want
What we want
● Productivity● Scalability● Throughput● Resiliency● Low infrastructure costs● ...
CPU boundvs
I/O bound
Latency Comparison Numbersgist.github.com/jboner/2841832
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2, 200x L1
Compress 1K bytes with Zippy 3,000 ns
Send 1K bytes over 1 Gbps network 10,000 ns
Read 4K randomly from SSD* 150,000 ns
Read 1 MB sequentially from memory 250,000 ns
Round trip within same datacenter 500,000 ns
Read 1 MB sequentially from SSD* 1,000,000 ns 4X memory
Disk seek 10,000,000 ns
Read 1 MB sequentially from disk 20,000,000 ns 80x memory, 20X SSD
Send packet CA>Netherlands>CA 150,000,000 ns
Optimizing I/O bound operations
● For scalability the whole workflow must be based on asynchronous I/O– E.g. Slowloris HTTP DoS
– All Java Servlet Containers (up until Servlets 3.1) are vulnerable
● Must avoid blocking threads in a limited thread-pool
Example of Common Patternvar result = "Not Initialized"
var isDone = false
val producer = new Thread(new Runnable {
def run() {
result = "Hello, World!"
isDone = true
}
})
val consumer = new Thread(new Runnable {
def run() {
// loops until isDone is true
while (!isDone) {}
println(result)
}
})
Example of Common Patternvar result = "Not Initialized"
var isDone = false
val producer = new Thread(new Runnable {
def run() {
result = "Hello, World!"
isDone = true
}
})
val consumer = new Thread(new Runnable {
def run() {
// loops until isDone is true
while (!isDone) {}
println(result)
}
})
What does it print?a) Hello world!
b) Not initialized
c) Nothing (infinite loop)
Example of Common Pattern
What does it print?a) Hello world!
b) Not initialized
c) Nothing (infinite loop)
d) All of the above
var result = "Not Initialized"
var isDone = false
val producer = new Thread(new Runnable {
def run() {
result = "Hello, World!"
isDone = true
}
})
val consumer = new Thread(new Runnable {
def run() {
// loops until isDone is true
while (!isDone) {}
println(result)
}
})
Example of Common Pattern
var result = "Not Initialized"
var isDone = false
val lock = new AnyRef
val producer = new Thread(new Runnable {
def run() {
lock.synchronized {
result = "Hello, World!"
isDone = true
}
}
})
val consumer = new Thread(new Runnable {
def run() {
var exitLoop = false
while (!exitLoop)
lock.synchronized {
if (isDone) {
println(result)
exitLoop = true
}
}
}
})
Locks
● Locks break encapsulation● Locks do not compose● It's easy to make mistakes
– Acquiring too few
– Acquiring too many
– Acquisition in the wrong order
java.util.concurrent.Future
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
(since Java 1.5)
java.util.concurrent.Future
import java.util.concurrent.{Callable, Executors, Future}
val pool = Executors.newCachedThreadPool()
// nonblocking
val future: Future[String] =
pool.submit(new Callable[String] {
def call() = {
// process and return something
"Hello World!"
}
})
// blocks, waiting for result...
val result = future.get()
println(result)
pool.awaitTermination(1, TimeUnit.SECONDS)
java.util.concurrent.Future
● Good API encapsulation● General concept, the executor can be anything:
– a thread
– a thread-pool
– a process running on another machine
SpyMemcached Example
import java.util.concurrent.{TimeUnit, Future}
import net.spy.memcached.{AddrUtil, MemcachedClient}
val client = new MemcachedClient(
AddrUtil.getAddresses("127.0.0.1:11211")
)
// non-blocking
val f: Future[AnyRef] = client.asyncGet("greeting")
// blocking for the result
val obj = f.get(1, TimeUnit.SECONDS)
if (obj == null) {
println("Greeting not available!")
client.set("greeting", 10000, "Hello World!")
}
else
println(obj)
client.shutdown(1, TimeUnit.SECONDS)
java.util.concurrent.Future
public interface Future<V> {
boolean cancel(boolean);
boolean isCancelled();
boolean isDone();
V get();
V get(long, TimeUnit);
}
Problems?
java.util.concurrent.Future
public interface Future<V> {
boolean cancel(boolean);
boolean isCancelled();
boolean isDone();
V get();
V get(long, TimeUnit);
}
Problems● Blocking● Non-composable
Scala's Futures and Promises
● Designed as a part of Akka● Integrated in Scala's standard library (SIP-14)
scala.concurrent.Future
trait Future[+T] extends Awaitable[T] {
abstract def isCompleted: Boolean
abstract def onComplete[U](func: Try[T] => U)
(implicit ec: ExecutionContext): Unit
abstract def value: Option[Try[T]]
// ...
}
scala.concurrent.Future
import concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success}
val f: Future[String] = Future {
"Hello, World!"
}
f.onComplete {
case Success(value) =>
println(value)
case Failure(exception) =>
System.err.println(exception.getMessage)
}
Blocking for the Result
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
import scala.concurrent.duration._
import concurrent.ExecutionContext.Implicits.global
val f: Future[String] = Future {
"Hello, World!"
}
val result = Await.result(f, 10.seconds)
println(result)
scala.concurrent.Future
● onComplete() is too low level● Not composable● Leads to callback hell
Future.foreach()
def squareRoot(x: Double) = Future {
math.sqrt(x)
}
squareRoot(3).foreach { x =>
println(x)
}
Future.foreach()
def squareRoot(x: Double) = Future {
math.sqrt(x)
}
for (x <- squareRoot(3)) {
println(x)
}
Future.map()
def squareRoot(x: Double) = Future {
math.sqrt(x)
}
squareRoot(3).map { x => x + 2 }
Future.map()
def squareRoot(x: Double) = Future {
math.sqrt(x)
}
for (x <- squareRoot(3)) yield x + 2
Future.filter()
import scala.concurrent.{Await, Future}
import concurrent.ExecutionContext.Implicits.global
import concurrent.duration._
val f = Future {
2
}
val r = f.filter(x => x % 2 == 1)
// throws java.util.NoSuchElementException:
// Future.filter predicate is not satisfied
Await.result(r, 1.second)
Future.filter()
import scala.concurrent.{Await, Future}
import concurrent.ExecutionContext.Implicits.global
import concurrent.duration._
val f = Future {
2
}
val r = for (x <- f; if x % 2 == 1) yield x
// throws java.util.NoSuchElementException:
// Future.filter predicate is not satisfied
Await.result(r, 1.second)
On Monads
● It's just a design pattern
On Monads
● It's just a design pattern● A monad is basically a container, or a context● Sometimes, when operating on values, you
want to keep the context
On Monads
● Future[T] is a monadic type● It's a container● It's a context● It allows you to operate on values, even if their
processing finished or not (e.g. within the Future context)
On Monads
● A monadic type M[T] must implement:– a constructor
– map[U](f: T => U): M[U]
– filter(f: T => Boolean): M[T]
– ...
On Monads
● A monadic type M[T] must implement:– a constructor
– map[U](f: T => U): M[U]
– filter(f: T => Boolean): M[T]
– Either of …● flatten()● flatMap[U](f: T => M[U]): M[U]
Future.flatMap()
val client = shade.Memcached(
Configuration("127.0.0.1:11211"),
ActorSystem("default").scheduler,
concurrent.ExecutionContext.Implicits.global
)
val f: Future[String] =
client.get[String]("username") flatMap {
case Some(value) =>
Future.successful("Hello, " + value + "!")
case None =>
client.set("username", "Alex", 30.seconds) map { _ =>
"Hello, Anonymous!"
}
}
Future.flatMap()
val username = client.get[String]("username")
val password = client.get[String]("password")
val userPass: Future[String] =
username.flatMap { user =>
password.map { pass =>
user.getOrElse("anonymous") +
":" +
pass.getOrElse("none")
}
}
Future.flatMap()
val username = client.get[String]("username")
val password = client.get[String]("password")
val userPass: Future[String] =
for (u <- username; p <- password) yield
u.getOrElse("anonymous") +
":" +
p.getOrElse("none")
Future.flatMap()
val user: Future[Option[User]] =
client.get[Option[User]]("user-" + id) flatMap {
// value found in cache
case Some(value) =>
Future.successful(value)
// value not found
case None =>
// fetching from DB
db.fetchUserBy(id) flatMap { value =>
// setting cache
client.set("user-" + id, value, 30.minutes)
.map(_ => value)
}
}
Future.recover()
val client = shade.Memcached(
Configuration("127.0.0.1:11211"),
ActorSystem("default").scheduler,
concurrent.ExecutionContext.Implicits.global
)
client.get[String]("something").recover {
case _: TimeoutException =>
None
}
Future.recoverWith()
googleMaps.search(lat, lon).recoverWith {
case _: APILimitException =>
bingMaps.search(lat, lon)
}
Future.sequence()
val searches = Seq(
googleMaps.search(lat, lon),
bingMaps.search(lat, lon),
geoNames.search(lat, lon)
)
val f: Future[Seq[Option[Location]]] =
Future.sequence(searches)
scala.concurrent.Promise
● The write-side of a Future● An object which can be completed either with a
value or failed with an exception
scala.concurrent.Promise
import concurrent.Promise
val p = Promise[String]()
val f: Future[String] = p.future
// later after processing is done ...
p.success("Hello, World!")
Ning's AsyncHttpClientimport com.ning.http.client._
import concurrent._
class MyAsyncClient(underlying: AsyncHttpClient) {
def fetch(url: String) = {
val promise = Promise[String]()
val handler = new AsyncCompletionHandler[Unit]() {
def onCompleted(resp: Response) {
promise.success(resp.getResponseBodyAsString)
}
def onThrowable(ex: Throwable) {
promise.failure(ex)
}
}
val request = underlying.prepareGet("http://www.google.com/")
request.execute(handler)
promise.future
}
}
Timeout Sampledef withTimeout[T](future: Future, atMost: FiniteDuration) = {
val promise = Promise[T]()
future.onComplete { result =>
promise.tryComplete(result)
}
// schedule the timeout
val scheduler = ActorSystem("default").scheduler
scheduler.scheduleOnce(atMost) {
promise.tryFailure(new TimeoutException)
}
promise.future
}
val value = cacheClient.get[String]("something")
val timed = withTimeout(value, 10.seconds)
timed.recover {
case _: TimeoutException => None
}
Problems
● Debugging● Callback hell still possible
– C# Async is nice
– https://github.com/scala/async
● Too basic for processing streams– Iteratees → uses Future as a building block
Other Alternatives
● Guava● C# Async / System.Threading.Tasks.Task● Q (Javascript)● Twisted Deferred (Python)
Further Learning
● Principles of Reactive Programming (Coursera.org)
● Going Reactive (by Jonas Boner at ScalaDays)● Futures and Promises (docs.scala-lang.org)● Play Framework 2.x
Questions?