Slide 1

Slide 1 text

許你⼀一個乾淨的未來 Scala Future API 介紹 美商飛向科技 何永琳 alan@fliptop.com 1 Friday, August 2, 13

Slide 2

Slide 2 text

The Challenge • The clock speed has stopped growing since 2006 • The free lunch is over • Moore’s Law still applies but only the number of cores in a single chip is increasing. • The new reality: Amdahl's Law. ref: http://en.wikipedia.org/wiki/Amdahl's_law 2 Friday, August 2, 13

Slide 3

Slide 3 text

Why Asynchronous is Important 3 Friday, August 2, 13

Slide 4

Slide 4 text

Traditional Model • One thread per request 4 Friday, August 2, 13

Slide 5

Slide 5 text

Traditional Model • IO 5 Green: Thread in good use Red: Thread resource wasted Friday, August 2, 13

Slide 6

Slide 6 text

6 MetadataService ReviewService Recommendation Browse History Friday, August 2, 13

Slide 7

Slide 7 text

Traditional Model • IO 7 Green: Thread in good use Red: Thread resource wasted Friday, August 2, 13

Slide 8

Slide 8 text

8 • stack size: – 320k on 32bit x86 JVM – 1024k on 64bit x86 JVM • scheduling cost. thread creation is expensive! Friday, August 2, 13

Slide 9

Slide 9 text

c10k 9 Friday, August 2, 13

Slide 10

Slide 10 text

250,000,000 cs • c10k - 25,000 boxes 10 Friday, August 2, 13

Slide 11

Slide 11 text

250,000,000 cs • c10k - 25,000 boxes • c100k - 2,500 boxes 11 Friday, August 2, 13

Slide 12

Slide 12 text

250,000,000 cs • c10k - 25,000 boxes • c100k - 2,500 boxes • c1Mk - 250 boxes • c2Mk - 125 boxes 12 Friday, August 2, 13

Slide 13

Slide 13 text

Async, Event Driven Model 13 note: In reality, the callback will be invoked on another thread. Friday, August 2, 13

Slide 14

Slide 14 text

Java5’s Future API 14 Friday, August 2, 13

Slide 15

Slide 15 text

Java5 Future API 15 1 public interface Callable { 2 V call() throws java.lang.Exception; 3 } 4 5 public interface ExecutorService extends Executor { 6 Future submit(Callable tCallable); 7 } 8 9 public interface Future { 10 V get() throws InterruptedException, ExecutionException; 11 V get(long timeout, TimeUnit unit) throws InterruptedException, 12 ExecutionException, TimeoutException; 13 14 boolean isCancelled(); 15 boolean isDone(); 16 17 boolean cancel(boolean mayInterruptIfRunning); 18 } Friday, August 2, 13

Slide 16

Slide 16 text

where is my callback? 16 • Not able to perform an non-blocking operation on the result when it is ready. • Only blocking wait and busy wait are available. – future.get(10, TimeUnit.Minutes) – while (future.isDone() == false) { //do other works if they exists; otherwise Thread.sleep(1000) } Friday, August 2, 13

Slide 17

Slide 17 text

Async callback in Java • On Android, 17 1 abstract class AsyncTask { 2 protected Long doInBackground(Params... params) 3 protected void onProgressUpdate(Progress... progress) 4 protected void onPostExecute(Result result) 5 } 1 interface Handler.Callback { 2 boolean handleMessage (Message msg) 3 } 4 Friday, August 2, 13

Slide 18

Slide 18 text

Async callback in Java • Guava 18 1 ListeningExecutorService service = MoreExecutors.listeningDecorator( 2 Executors.newFixedThreadPool(10)); 3 ListenableFuture explosion = service.submit(new Callable< 4 Explosion>() { 5 public Explosion call() { 6 return pushBigRedButton(); 7 } 8 }); 9 Futures.addCallback(explosion, new FutureCallback() { 10 // we want this handler to run immediately after we push the big red 11 button! 12 public void onSuccess(Explosion explosion) { 13 walkAwayFrom(explosion); 14 } 15 public void onFailure(Throwable thrown) { 16 battleArchNemesis(); // escaped the explosion! 17 } 18 }); Friday, August 2, 13

Slide 19

Slide 19 text

is the callback the answer? • can it do this in Java? 19 public Future plus(Future a, Future b) Friday, August 2, 13

Slide 20

Slide 20 text

1public ListenableFuture plus( 2 final ListenableFuture futureA, 3 final ListenableFuture futureB) { 4 return Futures.transform(futureA, new AsyncFunction() { 5 @Override 6 public ListenableFuture apply(final Integer resultA) throws Exception { 7 return Futures.transform(futureB, new AsyncFunction() { 8 @Override 9 public ListenableFuture apply(Integer resultB) throws Exception { 10 return Futures.immediateCheckedFuture(resultA + resultB); 11 } 12 }); 13 } 14 }); 15} Callback Hell in Guava’s Future public interface AsyncFunction { ListenableFuture apply(I input) throws Exception; } static ListenableFuture transform( ListenableFuture input, AsyncFunction function) one line of valuable code with 14 lines of boilerplate Friday, August 2, 13

Slide 21

Slide 21 text

Callback Hell in Guava’s Future 21 1 ListenableFuture> indexSearch = 2 luceneSearcher.searchAsync(“firstName:martin”); 3 4 Function, ListenableFuture>> queryFunction = 5 new Function, ListenableFuture>>() { 6 @Override 7 public ListenableFuture> apply(final List ids) { 8 return dataService.getPersonsByIdAsync(ids); 9 } 10 }; 11 12 ListenableFuture> results = 13 Futures.chain(indexSearch, queryFunction,executorService); Friday, August 2, 13

Slide 22

Slide 22 text

Callback Hell in NodeJS 22 ref: http://callbackhell.com/ 1. fs.readdir(source, function(err, files) { 2. if (err) { 3. console.log('Error finding files: ' + err) 4. } else { 5. files.forEach(function(filename, fileIndex) { 6. console.log(filename) 7. gm(source + filename).size(function(err, values) { 8. if (err) { 9. console.log('Error identifying file size: ' + err) 10. } else { 11. console.log(filename + ' : ' + values) 12. aspect = (values.width / values.height) 13. widths.forEach(function(width, widthIndex) { 14. height = Math.round(width / aspect) 15. console.log('resizing ' + filename + 'to ' + height + 'x' + height) 16. this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) { 17. if (err) console.log('Error writing file: ' + err) 18. }) 19. }.bind(this)) 20. } 21. }) 22. }) 23. } 24.}) Friday, August 2, 13

Slide 23

Slide 23 text

Scala’s Future API 23 Friday, August 2, 13

Slide 24

Slide 24 text

History of Scala’s Future • scala.actors.Future • akka.dispatch.Future • scalaz.concurrent.Future • net.liftweb.actor.Future • com.twitter.util.Future • SIP-14 24 Friday, August 2, 13

Slide 25

Slide 25 text

Basic ops 25 1. val resultFuture: Future[Int] = Future { 2. 5 3. } 4. 5. • create a future and blocking wait for result. def apply[T](body: 㱺 T): Future[T] Starts an asynchronous computation and returns a Future object with the result of that computation. The result becomes available once the asynchronous computation is completed. Friday, August 2, 13

Slide 26

Slide 26 text

Basic ops 26 1. val resultFuture = Future { 2. Thread.sleep(5000) 3. 5 4. } 5. // throw exception or return a value. 6. val ret = Await.result(resultFuture, 5.minutes) • create a future and blocking wait for result. def result[T](awaitable: Awaitable[T], atMost: Duration): T Await and return the result (of type T) of an Awaitable. Although this method is blocking, the internal use of blocking ensures that the underlying ExecutionContext to properly detect blocking and ensure that there are no deadlocks. Friday, August 2, 13

Slide 27

Slide 27 text

Basic ops 27 • check the response. value: Option[Try[T]] The value of this Future. If the future is not completed the returned value will be None. If the future is completed the value will beSome(Success(t)) if it contains a valid result, or Some(Failure(error)) if it contains an exception. 1. resultFuture.value match { 2. case Some(Success(value)) => // do something with value 3. case Some(Failure(exception)) => // handle exception 4. case None => // do nothing 5. } Friday, August 2, 13

Slide 28

Slide 28 text

callbacks 28 def onSuccess[U](pf: PartialFunction[T, U]): Unit When this future is completed successfully (i.e. with a value), apply the provided partial function to the value if the partial function is defined at that value. def onFailure[U](callback: PartialFunction[Throwable, U]): Unit When this future is completed with a failure (i.e. with a throwable), apply the provided callback to the throwable. 1. val searchFuture: Future[SearchResult] = // invoke search 2. searchFuture.onSuccess { 3. case res: SearchResult => {???}// update UI with the result. 4. } 1. searchFuture.onFailure { 2. case e: SocketTimeoutException => //notify user request timeout 3. case e: ConnectException => // notify user service is 4. // unreachable 5. } Friday, August 2, 13

Slide 29

Slide 29 text

chaining - map 29 map[S](f: (T) 㱺 S): Future[S] Creates a new future by applying a function to the successful result of this future. If this future is completed with an exception then the new future will also contain this exception. 1. val historyFuture: Future[BrowseHistory] = // invoke user service 2. val future: Future[Model] = historyFuture.map { 3. history: BrowseHistory => { 4. // transform BrowseHistory to Model 5. } 6. } 1. val one = Future { 1 } 2. val two = one.map { i => i + 1 } // value is 2. Friday, August 2, 13

Slide 30

Slide 30 text

chaining - flatMap 30 def flatMap[S](f: (T) 㱺 Future[S]): Future[S] Creates a new future by applying a function to the successful result of this future, and returns the result of the function as the new future. 1. val historyFuture: Future[BrowseHistory] = // invoke user service 2. val future: Future[Recommendations] = historyFuture.flatMap { 3. history: BrowseHistory => { 4. service.findRelatedItems(history) // Future[Recommendations] 5. } 6. } Friday, August 2, 13

Slide 31

Slide 31 text

chaining - for comprehension 31 1. val result: Future[Recommendations] = for( 2. history <- service.getBrowseHistory(user); 3. recommendations <- service.findRelatedItems(history) 4. ) yield recommendations 1. val historyFuture: Future[BrowseHistory] = // invoke user service 2. val future: Future[Recommendations] = historyFuture.flatMap { 3. history: BrowseHistory => { 4. service.findRelatedItems(history) 5. } 6. } syntactic sugar to turn callback hell into more readable format Friday, August 2, 13

Slide 32

Slide 32 text

composable • Future.sequence – reducing many Futures into a single Future. 32 1. def plusOne(n: Int): Future[Int] = future { n + 1 } 2. val items = List(1, 2, 3, 4) 3. val futures: List[Future[Int]] = items.map(n => plusOne(n)) 4. val futureOfList: Future[List[Int]] = Future.sequence(futures) Friday, August 2, 13

Slide 33

Slide 33 text

composable 33 def zip[U](that: Future[U]): Future[(T, U)] Zips the values of this and that future, and creates a new future holding the tuple of their results. 1 val a = Future { 1 } 2 val b = Future { 2 } 3 val c = a zip b // return Future[(Int, Int)] Friday, August 2, 13

Slide 34

Slide 34 text

Error Handling 34 def def fallbackTo[U >: T](that: Future[U]): Future[U] Creates a new future which holds the result of this future if it was completed successfully, or, if not, the result of the that future if that is completed successfully. If both futures are failed, the resulting future holds the throwable object of the first future. Using this method will not cause concurrent programs to become nondeterministic. 1 val failed = future.failed(new RuntimeException("Failed")) 2 val default = future.successful( 5 ) 3 val future = failed fallbackTo default 4 Await.result(future, Duration.Zero) // evaluates to 5 Friday, August 2, 13

Slide 35

Slide 35 text

35 def recover[U >: T](pf: PartialFunction[Throwable, U]): Future[U] Creates a new future that will handle any matching throwable that this future might contain. If there is no match, or if this future contains a valid result then the new future will contain the same. Error Handling future {6 / 0} recover { case e: ArithmeticException => 0 } // result: 0 future {6 / 2} recover { case e: ArithmeticException => 0 } // result: 3 1 future { /** network call */ } recover { 2 case e: SocketTimeoutException => ??? 3 case e: IOException => ??? 4 } Friday, August 2, 13

Slide 36

Slide 36 text

36 def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]]): Future[U] Creates a new future that will handle any matching throwable that this future might contain by assigning it a value of another future. If there is no match, or if this future contains a valid result then the new future will contain the same result. 1. val result = Future { 2. defaultDataSource.findItem(5) //returns Future[Option[Item]] 3. } recoverWith { 4. case SocketTimeoutException => backupSource.findItem(5) 5. } Error Handling Friday, August 2, 13

Slide 37

Slide 37 text

ExecutionContext 37 1. def apply[T](body: 㱺 T) (implicit executor: ExecutionContext): Future[T] 2. def map[S](f: (T) 㱺 S) (implicit executor: ExecutionContext): Future[S] 3. def flatMap[S](f: (T) 㱺 Future[S]) (implicit executor: ExecutionContext): Future[S] 4. def onSuccess[U](pf: PartialFunction[T, U]) (implicit executor: ExecutionContext): Unit 5. def onFailure[U](callback: PartialFunction[Throwable, U]) (implicit executor: ExecutionContext): Unit 6. def recover[U >: T](pf: PartialFunction[Throwable, U]) (implicit executor: ExecutionContext): Future[U] 7. def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]]) (implicit executor: ExecutionContext): Future[U] Friday, August 2, 13

Slide 38

Slide 38 text

ExecutionContext • where future get executed. – default: ForkJoinPool – custom: java5 ExecutorService 38 1. implicit val executionContext = 2. ExecutionContext.fromExecutorService( 3. Executors.newFixedThreadPool(5) 4. ) import scala.concurrent.ExecutionContext.Implicits.global Friday, August 2, 13

Slide 39

Slide 39 text

Chaining, composability are the important. we shall never block a thread. 39 Friday, August 2, 13

Slide 40

Slide 40 text

DEMO • A None-blocking parallel external merge sort – sort numbers in files. – files come from s3. Their size can be ranged from 100mb to 10gb. – I don’t want to fully read the file and then sort the file. 40 Friday, August 2, 13

Slide 41

Slide 41 text

41 Stream of Int arrays Stream of sorted data merged Stream of Ints Friday, August 2, 13

Slide 42

Slide 42 text

Java8 • CompletableFuture – almost there but still under construction... 42 Friday, August 2, 13

Slide 43

Slide 43 text

Q&A alan@fliptop.com twitter: @yunglinho 43 Friday, August 2, 13

Slide 44

Slide 44 text

44 Friday, August 2, 13