Top Banner
The Future Starts with a Promise Alexandru Nedelcu bionicspirit.com
51
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: The Future starts with a Promise

The Future Starts with a Promise

Alexandru Nedelcubionicspirit.com

Page 2: The Future starts with a Promise

The Future Starts with a Promise

● Dumb title ● The Future starts whether we want it or not

Page 3: The Future starts with a Promise

Performance

“amount of useful work accomplished by a computer system compared to the time and

resources used”

Page 4: The Future starts with a Promise

Performance

Currency for things we want

Page 5: The Future starts with a Promise

What we want

● Productivity● Scalability● Throughput● Resiliency● Low infrastructure costs● ...

Page 6: The Future starts with a Promise

CPU boundvs

I/O bound

Page 7: The Future starts with a Promise

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

Page 8: The Future starts with a Promise

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

Page 9: The Future starts with a Promise

Going Forward

● Async I/O● Non-blocking● Event-driven● Reactive

Page 10: The Future starts with a Promise

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)

     }

})

Page 11: The Future starts with a Promise

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)

Page 12: The Future starts with a Promise

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)

     }

})

Page 13: The Future starts with a Promise

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

}

}

}

})

Page 14: The Future starts with a Promise

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

Page 15: The Future starts with a Promise

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)

Page 16: The Future starts with a Promise

java.util.concurrent.Future

import java.util.concurrent.{Callable, Executors, Future}

val pool = Executors.newCachedThreadPool()

// non­blocking

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)

Page 17: The Future starts with a Promise

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

Page 18: The Future starts with a Promise

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)

Page 19: The Future starts with a Promise

java.util.concurrent.Future

public interface Future<V> {

boolean cancel(boolean);

boolean isCancelled();

boolean isDone();

V get();

V get(long, TimeUnit);

}

Problems?

Page 20: The Future starts with a Promise

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

Page 21: The Future starts with a Promise

Scala's Futures and Promises

● Designed as a part of Akka● Integrated in Scala's standard library (SIP-14)

Page 22: The Future starts with a Promise

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]]

// ...

}

Page 23: The Future starts with a Promise

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)

}

Page 24: The Future starts with a Promise

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)

Page 25: The Future starts with a Promise

scala.concurrent.Future

● onComplete() is too low level● Not composable● Leads to callback hell

Page 26: The Future starts with a Promise

Future.foreach()

def squareRoot(x: Double) = Future {

math.sqrt(x)

}

squareRoot(3).foreach { x =>

println(x)

}

Page 27: The Future starts with a Promise

Future.foreach()

def squareRoot(x: Double) = Future {

math.sqrt(x)

}

for (x <- squareRoot(3)) {

println(x)

}

Page 28: The Future starts with a Promise

Future.map()

def squareRoot(x: Double) = Future {

math.sqrt(x)

}

squareRoot(3).map { x => x + 2 }

Page 29: The Future starts with a Promise

Future.map()

def squareRoot(x: Double) = Future {

math.sqrt(x)

}

for (x <- squareRoot(3)) yield x + 2

Page 30: The Future starts with a Promise

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)

Page 31: The Future starts with a Promise

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)

Page 32: The Future starts with a Promise

On Monads

● It's just a design pattern

Page 33: The Future starts with a Promise

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

Page 34: The Future starts with a Promise

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)

Page 35: The Future starts with a Promise

On Monads

● A monadic type M[T] must implement:– a constructor

– map[U](f: T => U): M[U]

– filter(f: T => Boolean): M[T]

– ...

Page 36: The Future starts with a Promise

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]

Page 37: The Future starts with a Promise

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!"

}

}

Page 38: The Future starts with a Promise

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")

}

}

Page 39: The Future starts with a Promise

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")

Page 40: The Future starts with a Promise

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)

}

}

Page 41: The Future starts with a Promise

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

}

Page 42: The Future starts with a Promise

Future.recoverWith()

googleMaps.search(lat, lon).recoverWith {

case _: APILimitException =>

bingMaps.search(lat, lon)

}

Page 43: The Future starts with a Promise

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)

Page 44: The Future starts with a Promise

scala.concurrent.Promise

● The write-side of a Future● An object which can be completed either with a

value or failed with an exception

Page 45: The Future starts with a Promise

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!")

Page 46: The Future starts with a Promise

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

}

}

Page 47: The Future starts with a Promise

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

}

Page 48: The Future starts with a Promise

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

Page 49: The Future starts with a Promise

Other Alternatives

● Guava● C# Async / System.Threading.Tasks.Task● Q (Javascript)● Twisted Deferred (Python)

Page 50: The Future starts with a Promise

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

Page 51: The Future starts with a Promise

Questions?