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.

49a4815846825cd1334fa080c6e71c5d?s=128

Heather Miller

August 14, 2012
Tweet

Transcript

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

  2. Futures/Promises Agenda Execution Ctxs tRY

  3. common theme:

  4. common theme: Pipelining

  5. future &promise scala.concurrent.

  6. First, some Motivation

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

  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
  9. This makes it clear that...

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

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

    abstraction there’s fragmentation in the scala ecosystem no hope of interop!
  12. Furthermore...

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

  14. Furthermore... Java futures neither efficient nor composable 2 we could

    make futures more powerful, by taking advantage of scala’s features 3
  15. can be thought of as a single concurrency abstraction Futures&Promises

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

    Future READ-MANY promise write-once
  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 ✔
  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
  19. Future Promise Future with value Green Red thread waiting on

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

    on the result of another thread meaningful work
  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
  22. Async&NonBlocking

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

    result of future
  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)
  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!
  26. Futures&Promises Thread1 Thread2 Thread3 example

  27. Futures&Promises Promise val p = Promise[Int]() // Thread 1 Thread1

    Thread2 Thread3 (create promise) example
  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
  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
  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
  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
  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]
  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
  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]
  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
  36. context Execution scala.concurrent.

  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
  38. contexts Execution Scala 2.10 introduces

  39. contexts Execution Scala 2.10 introduces provide global threadpool as platform

    service to be shared by all parallel frameworks Goal
  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)
  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)
  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
  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
  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:
  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:
  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
  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; } }
  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]]
  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]
  50. TRY scala.util.

  51. And now onto something completely different.

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

  53. Try is a simple data container Composable ✔ Combinators for

    exceptions ✔ Great for monadic-style exception handling. Divorcing exception handling from the stack.
  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]
  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]
  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]
  57. Try methods on Basic Ops def get: T get Success

    Failure Returns value stored within Success Throws exception stored within Failure
  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
  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
  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
  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
  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
  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
  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)
  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
  66. TO BUILD USING THESE Pipelines

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

  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) }
  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
  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
  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
  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
  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
  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?
  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 }
  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
  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
  78. questions ? http://docs.scala-lang.org/sips/pending/futures-promises.html