$30 off During Our Annual Pro Sale. View Details »

Futures and Promises in Scala 2.10

Futures and Promises in Scala 2.10

An introduction to Futures and Promises, and the "Try" type (scala.util.Try) coming in Scala 2.10. (Joint work with Philipp Haller).

Props to Viktor Klang-- excellent java.concurrent.util.Future illustration taken from his ScalaDays 2012 talk on the same subject.

Heather Miller

August 14, 2012
Tweet

More Decks by Heather Miller

Other Decks in Programming

Transcript

  1. futures
    &promises
    in Scala 2.10
    PHILIPP HALLER
    HEATHER MILLER

    View Slide

  2. Futures/Promises
    Agenda
    Execution Ctxs
    tRY

    View Slide

  3. common theme:

    View Slide

  4. common theme:
    Pipelining

    View Slide

  5. future
    &promise
    scala.concurrent.

    View Slide

  6. First, some
    Motivation

    View Slide

  7. 1
    Several important
    libraries have their
    own future/promise
    implementation

    View Slide

  8. 1
    Several important
    libraries have their
    own future/promise
    implementation
    java.util.concurrent.
    scala.actors.
    com.twitter.util.
    akka.dispatch.
    scalaz.concurrent.
    net.liftweb.actor.
    FUTURE
    FUTURE
    FUTURE
    FUTURE
    PROMISE
    LAFUTURE

    View Slide

  9. This makes it clear that...

    View Slide

  10. This makes it clear that...
    futures are an important,
    powerful abstraction

    View Slide

  11. This makes it clear that...
    futures are an important,
    powerful abstraction
    there’s fragmentation in
    the scala ecosystem
    no hope of interop!

    View Slide

  12. Furthermore...

    View Slide

  13. Furthermore...
    Java futures neither
    efficient nor composable
    2

    View Slide

  14. Furthermore...
    Java futures neither
    efficient nor composable
    2
    we could make futures more
    powerful, by taking advantage
    of scala’s features
    3

    View Slide

  15. can be thought of as a single
    concurrency abstraction
    Futures&Promises
    Future promise

    View Slide

  16. can be thought of as a single
    concurrency abstraction
    Futures&Promises
    Future
    READ-MANY
    promise
    write-once

    View Slide

  17. can be thought of as a single
    concurrency abstraction
    Futures&Promises
    Future
    READ-MANY
    promise
    write-once
    Start async computation

    important ops
    Assign result value
    ✔ Wait for result ✔ Obtain associated future object

    View Slide

  18. a promise p of type Promise[T]
    can be completed in two ways...
    Success&Failure
    val result: T = ...
    p.success(result)
    Success
    val exc = new Exception(“something went wrong”)
    p.failure(exc)
    Failure

    View Slide

  19. Future
    Promise
    Future with value
    Green
    Red thread waiting on the
    result of another thread
    meaningful work
    java.util.concurrent.future

    View Slide

  20. java.util.concurrent.future
    Future
    Promise
    Future with value
    Green
    Red thread waiting on the
    result of another thread
    meaningful work

    View Slide

  21. what we’d like to do instead
    Future
    Promise
    Future with value
    Green
    Red thread waiting on the
    result of another thread
    meaningful work

    View Slide

  22. Async&NonBlocking

    View Slide

  23. Async&NonBlocking
    goal: Do not block current thread while waiting
    for result of future

    View Slide

  24. Async&NonBlocking
    goal: Do not block current thread while waiting
    for result of future
    Callbacks
    Register callback which is invoked
    (asynchronously) when future is completed
    Async computations never block
    (except for managed blocking)

    View Slide

  25. Async&NonBlocking
    goal: Do not block current thread while waiting
    for result of future
    Callbacks
    Register callback which is invoked
    (asynchronously) when future is completed
    Async computations never block
    (except for managed blocking)
    user doesn’t have to explicitly manage
    callbacks. higher-order functions instead!

    View Slide

  26. Futures&Promises
    Thread1 Thread2 Thread3
    example

    View Slide

  27. Futures&Promises
    Promise
    val p = Promise[Int]() // Thread 1
    Thread1 Thread2 Thread3
    (create promise)
    example

    View Slide

  28. Futures&Promises
    Promise
    Future
    val p = Promise[Int]() // Thread 1
    val f = p.future // Thread 1
    Thread1 Thread2 Thread3
    (create promise)
    (get reference to future)
    example

    View Slide

  29. Futures&Promises
    Promise
    Future
    val p = Promise[Int]() // Thread 1
    val f = p.future // Thread 1
    f onSuccess { // Thread 2
    case x: Int => println(“Successful!”)
    }
    Thread1 Thread2 Thread3
    onSuccess
    callback
    (create promise)
    (get reference to future)
    (register callback)
    example

    View Slide

  30. Futures&Promises
    Promise
    Future
    val p = Promise[Int]() // Thread 1
    val f = p.future // Thread 1
    f onSuccess { // Thread 2
    case x: Int => println(“Successful!”)
    }
    Thread1 Thread2 Thread3
    onSuccess
    callback
    p.success(42) // Thread 1
    42
    42
    (create promise)
    (get reference to future)
    (register callback)
    (write to promise)
    example

    View Slide

  31. Futures&Promises
    Promise
    Future
    val p = Promise[Int]() // Thread 1
    val f = p.future // Thread 1
    f onSuccess { // Thread 2
    case x: Int => println(“Successful!”)
    }
    Thread1 Thread2 Thread3
    onSuccess
    callback
    p.success(42) // Thread 1
    42
    42 Successful!
    Console
    (create promise)
    (get reference to future)
    (register callback)
    (write to promise)
    (execute callback)
    // Thread
    example
    note: onSuccess callback executed even if f has
    already been completed at time of registration

    View Slide

  32. Combinators
    val purchase: Future[Int] = rateQuote map {
    quote => connection.buy(amount, quote)
    }
    val postBySmith: Future[Post] =
    post.filter(_.author == “Smith”)
    Composability thru higher-order funcs
    standard monadic combinators
    map[S](f: T => S): Future[S]
    filter[S](pred: T => Boolean): Future[S]

    View Slide

  33. Combinators
    val purchase: Future[Int] = rateQuote map {
    quote => connection.buy(amount, quote)
    }
    val postBySmith: Future[Post] =
    post.filter(_.author == “Smith”)
    Composability thru higher-order funcs
    standard monadic combinators
    map[S](f: T => S): Future[S]
    filter[S](pred: T => Boolean): Future[S]
    If filter fails: postBySmith completed with NoSuchElementException
    If map fails: purchase is completed with unhandled exception

    View Slide

  34. Combinators
    Additional future-specific higher-
    order functions have been introduced
    def fallbackTo(that: Future[T]): Future[T]
    def firstCompletedOf(futures: Seq[Future[T]]): Future[T]
    def andThen[U](pf: PartialFunction[...]): Future[T]

    View Slide

  35. Combinators
    Additional future-specific higher-
    order functions have been introduced
    def fallbackTo(that: Future[T]): Future[T]
    def firstCompletedOf(futures: Seq[Future[T]]): Future[T]
    def andThen[U](pf: PartialFunction[...]): Future[T]
    ”falls back” to that future in case of failure
    returns a future completed with result of first completed future
    allows one to define a sequential execution over a chain of futures

    View Slide

  36. context
    Execution
    scala.concurrent.

    View Slide

  37. are needed by:
    Threadpools...
    futures
    Actors
    parallel collections
    for executing callbacks and
    function arguments
    for executing message handlers,
    scheduled tasks, etc.
    for executing data-parallel operations

    View Slide

  38. contexts
    Execution
    Scala 2.10 introduces

    View Slide

  39. contexts
    Execution
    Scala 2.10 introduces
    provide global threadpool as
    platform service to be shared by
    all parallel frameworks
    Goal

    View Slide

  40. contexts
    Execution
    Scala 2.10 introduces
    provide global threadpool as
    platform service to be shared by
    all parallel frameworks
    Goal
    scala.concurrent package provides global ExecutionContext
    Default ExecutionContext backed by the most recent fork join pool
    (collaboration with Doug Lea, SUNY Oswego)

    View Slide

  41. Implicit Execution Ctxs
    def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S]
    def onSuccess[U](pf: PartialFunction[T, U])
    (implicit executor: ExecutionContext): Unit
    By default, asynchronous computations are executed on
    the global ExecutionContext which is provided implicitly.
    Implicit parameters make it possible to override the
    default ExecutionContext:
    implicit val context: ExecutionContext = customExecutionContext
    val fut2 = fut1.filter(pred)
    .map(fun)

    View Slide

  42. Implicit Execution Ctxs
    def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S]
    def onSuccess[U](pf: PartialFunction[T, U])
    (implicit executor: ExecutionContext): Unit
    By default, asynchronous computations are executed on
    the global ExecutionContext which is provided implicitly.
    Implicit parameters make it possible to override the
    default ExecutionContext:
    implicit val context: ExecutionContext = customExecutionContext
    val fut2 = fut1.filter(pred)
    .map(fun)
    implicit ExecutionContexts allow sharing ecs
    between frameworks
    Enables flexible selection of execution policy

    View Slide

  43. Future
    the implementation
    def map[S](f: T => S): Future[S] = {
    val p = Promise[S]()
    onComplete {
    case result =>
    try {
    result match {
    case Success(r) => p success f(r)
    case Failure(t) => p failure t
    }
    } catch {
    case t: Throwable => p failure t
    }
    }
    p.future
    }
    Many operations implemented in terms of promises
    simplified example

    View Slide

  44. Future
    the implementation
    REAL
    def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    onComplete {
    case result =>
    try {
    result match {
    case Success(r) => p success f(r)
    case f: Failure[_] => p complete f.asInstanceOf[Failure[S]]
    }
    } catch {
    case NonFatal(t) => p failure t
    }
    }
    p.future
    }
    The real implementation (a) adds an implicit ExecutionContext, (b)
    avoids extra object creations, and (c) catches only non-fatal exceptions:

    View Slide

  45. Promise
    the implementation
    Promise is the work horse of the futures implementation
    def complete(result: Try[T]): this.type =
    if (tryComplete(result)) this
    else throw new IllegalStateException("Promise already completed.")
    A Promise[T] can be in one of two states:
    COMPLETED
    PENDING
    No result has been written to the promise
    State represented using a list of callbacks (initially empty)
    The promise has been assigned a successful result or exce.
    State represented using an instance of Try[T]
    Invoking Promise.complete triggers a transition from state Pending to Completed
    A Promise can be completed at most once:

    View Slide

  46. def tryComplete(value: Try[T]): Boolean = {
    val resolved = resolveTry(value)
    (try {
    @tailrec
    def tryComplete(v: Try[T]): List[CallbackRunnable[T]] = {
    getState match {
    case raw: List[_] =>
    val cur = raw.asInstanceOf[List[CallbackRunnable[T]]]
    if (updateState(cur, v)) cur else tryComplete(v)
    case _ => null
    }
    }
    tryComplete(resolved)
    } finally {
    synchronized { notifyAll() } // Notify any blockers
    }) match {
    case null => false
    case rs if rs.isEmpty => true
    case rs =>
    rs.foreach(_.executeWithValue(resolved)); true
    }
    }
    Completing a Promise

    View Slide

  47. the awkard squad
    abstract class AbstractPromise {
    private volatile Object _ref;
    final static long _refoffset;
    static {
    try {
    _refoffset =
    Unsafe.instance.objectFieldOffset(
    AbstractPromise.class.getDeclaredField("_ref"));
    } catch (Throwable t) {
    throw new ExceptionInInitializerError(t);
    }
    }
    protected boolean updateState(Object oldState, Object newState) {
    return
    Unsafe.instance.compareAndSwapObject(this, _refoffset,
    oldState, newState);
    }
    protected final Object getState() {
    return _ref;
    }
    }

    View Slide

  48. Integrating
    Futures Actors
    &
    Futures are results of asynchronous message sends
    when a response is expected
    Fu
    wr
    val response: Future[Any] = socialGraph ? getFriends(user)
    Implementing synchronous send (untyped):
    def syncSend(to: ActorRef, msg: Any, timeout: Duration): Any = {
    val fut = to ? msg
    Await.result(fut, timeout)
    }
    Recovering types
    val friendsFut: Future[Seq[Friend]] = response.mapTo[Seq[Friend]]

    View Slide

  49. Integrating
    Futures Actors
    &
    Futures are results of asynchronous message sends
    when a response is expected
    Fu
    wr
    val response: Future[Any] = socialGraph ? getFriends(user)
    Implementing synchronous send (untyped):
    def syncSend(to: ActorRef, msg: Any, timeout: Duration): Any = {
    val fut = to ? msg
    Await.result(fut, timeout)
    }
    Recovering types
    val friendsFut: Future[Seq[Friend]] = response.mapTo[Seq[Friend]]
    Future of Friends is either completed with a
    successful result or with a wrapped
    exception if response times out or is not of
    type Seq[Friend]

    View Slide

  50. TRY
    scala.util.

    View Slide

  51. And now onto
    something
    completely
    different.

    View Slide

  52. And now onto
    something
    completely
    different.
    not concurrent,
    not asynchronous

    View Slide

  53. Try
    is a simple data container
    Composable

    Combinators for exceptions

    Great for monadic-style exception handling.
    Divorcing exception
    handling from the stack.

    View Slide

  54. Try
    is a simple data container
    sealed abstract class Try[+T]
    final case class Success[+T](value: T) extends Try[T]
    final case class Failure[+T](val exception: Throwable)
    extends Try[T]

    View Slide

  55. Try
    is a simple data container
    sealed abstract class Try[+T]
    final case class Success[+T](value: T) extends Try[T]
    final case class Failure[+T](val exception: Throwable)
    extends Try[T]

    View Slide

  56. Try
    is a simple data container
    sealed abstract class Try[+T]
    final case class Success[+T](value: T) extends Try[T]
    final case class Failure[+T](val exception: Throwable)
    extends Try[T]

    View Slide

  57. Try
    methods on
    Basic Ops
    def get: T
    get
    Success
    Failure
    Returns value stored within Success
    Throws exception stored within Failure

    View Slide

  58. Try
    methods on
    def getOrElse[U >: T](default: => U)
    Success
    Failure
    Returns value stored within Success
    Returns the given default argument if this is a Failure
    getOrElse
    Basic Ops

    View Slide

  59. Try
    methods on
    def orElse[U >: T](default: => Try[U])
    orElse
    Success
    Failure
    Returns this Try if this is a Success
    Returns the given default argument if this is a Failure
    Basic Ops

    View Slide

  60. Try
    methods on
    def map[U](f: T => U): Try[U]
    map
    Success
    Failure
    Applies the function f to the value from Success
    Returns this if this is a Failure
    monadic Ops

    View Slide

  61. Try
    methods on
    def flatMap[U](f: T => Try[U]): Try[U]
    flatMap
    Success
    Failure
    Applies the function f to the value from Success
    Returns this if this is a Failure
    monadic Ops

    View Slide

  62. Try
    methods on
    def filter(p: T => Boolean): Try[T]
    filter
    Success
    Failure
    Converts this to a Failure if predicate p not satisfied.
    Returns this if this is a Failure
    monadic Ops

    View Slide

  63. Try
    methods on
    def recoverWith[U >: T](f: PartialFunction[Throwable, Try[U]]): Try[U]
    recoverWith
    Success
    Failure
    Returns this if this is a Success
    Applies function f if this is a Failure. (flatMap on exptn)
    exception-specific Ops

    View Slide

  64. Try
    methods on
    def recover[U >: T](f: PartialFunction[Throwable, U]): Try[U]
    recover
    Success
    Failure
    exception-specific Ops
    Returns this if this is a Success
    Applies function f if this is a Failure. (like map on exptn)

    View Slide

  65. Try
    methods on
    def transform[U](s: T => Try[U], f: Throwable => Try[U]): Try[U]
    transform
    Success
    Failure
    Completes this Try by applying function f if this is a Success
    Completes this Try by applying function f if this is a Failure
    exception-specific Ops

    View Slide

  66. TO BUILD
    USING THESE
    Pipelines

    View Slide

  67. TO BUILD
    USING THESE
    Pipelines
    Remember: not concurrent,
    not asynchronous

    View Slide

  68. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }

    View Slide

  69. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }
    GETACCT MIGHT FAIL

    View Slide

  70. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }
    possible arithmetic exception

    View Slide

  71. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }
    Predicate might not be satisfied

    View Slide

  72. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }
    possible arithmetic exception

    View Slide

  73. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }
    UPDATEACCT MIGHT FAIL

    View Slide

  74. Try
    Simple pipelining on
    Example 1
    case class Account(acctNum: Int, balance: Double, interestRate: Double)
    val withdrawal = 1500
    val adjustment = 0.4
    val in = Try(getAcct)
    val withdrawalResult = in map {
    (x: Account) => Account(x.acctNum, x.balance - withdrawal, x.interestRate)
    } filter {
    (x: Account) => x.balance > 12000 // acct in good standing
    } map {
    (x: Account) => Account(x.acctNum, x.balance, x.interestRate + adjustment)
    Try(updateAcct)
    }
    Eliminates nested try blocks
    but how can we handle these failures?

    View Slide

  75. Try
    Simple pipelining on
    Example 2
    ...by using recoverWith, recover, or orElse
    case class Tweet(from: String, retweets: Int)
    val importantTweets = Try {
    server.getTweetList
    } orElse {
    cachedTweetList
    } map { twts =>
    val avgRetweet = twts.map(_.retweets).reduce(_ + _) / twts.length
    (twts, avgRetweet)
    } filter { case (twts, avgRetweet) =>
    twts.map(_.retweets).exists(_ > avgRetweet)
    } map { case (twts, avgRetweet) =>
    twts.filter(_.retweets > avgRetweet)
    } recover {
    case nose: NoSuchElementException => // handle individually
    case usop: UnsupportedOperationException => // handle individually
    case other => // handle individually
    }

    View Slide

  76. Try&Futures
    combining
    case class Friend(name: String, age: String)
    val avgAge = Promise[Int]()
    val fut = future {
    // query a social network...
    List(Friend("Zoe", "25"), Friend("Jean", "27"), Friend("Paul", "3O"))
    }
    fut onComplete { tr =>
    // compute average age of friends
    val result = tr map {
    friends => friends.map(_.age.toInt).reduce(_ + _) / friends.length
    }
    avgAge complete result
    }
    avgAge.future onComplete println

    View Slide

  77. Credits
    PHILIPP HALLER
    ALEX PROKOPEC
    VOJIN JOVANOVIC
    VIKTOR KLANG
    MARIUS ERIKSEN
    HEATHER MILLER
    ROLAND KUHN
    DOUG LEA
    TYPESAFE
    TYPESAFE
    EPFL
    EPFL
    EPFL
    TYPESAFE
    SUNY
    TWITTER
    HAVOC PENNINGTON
    TYPESAFE

    View Slide

  78. questions
    ?
    http://docs.scala-lang.org/sips/pending/futures-promises.html

    View Slide