Slide 1

Slide 1 text

Future Evolution Java and Scala Extended version Andrey Ershov @andrershov [email protected]

Slide 2

Slide 2 text

About me ● Working at Dino Systems ● 8+ year of Java ● Desktop, Android, Enterprise, Distributed Systems ● Lead in video-conferencing project ● Enjoy reading distributed systems papers a lot 2

Slide 3

Slide 3 text

Agenda ● Future, ListenableFuture, CompletableFuture in Java ● Future в Scala ● Comparison ● Logging in async applications ● Exception handling in async applications 3

Slide 4

Slide 4 text

What is the Future? “A Future represents the result of an asynchronous computation” / JavaDoc “A future represents a value, detached from time” / Victor Klang 4

Slide 5

Slide 5 text

Java 5 Future 5

Slide 6

Slide 6 text

Java 5 Future is blocking The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready. / JavaDoc 6

Slide 7

Slide 7 text

But it can be useful Future fetchUrl(String url) { return executor.submit(()->http.get(url)); } List fetchResults (List urls){ return urls.stream() .map(url -> WS.fetchUrl(url)) .map(future -> future.get()) .collect(Collectors.toList()); } 7

Slide 8

Slide 8 text

Analysis 1) Each call to web-service consumes a thread. Not compatible with non-blocking IO. 2) Each call to Future.get() is blocking, but it’s ok when all results are needed 8

Slide 9

Slide 9 text

What if we need the fastest reply only? pool = Executors.newFixedThreadPool(5); completionService = new ExecutorCompletionService<>(pool); void fetchUrl(String url) { completionService.submit(()->http.get(url)); } Result firstResult (List urls){ urls.forEach(url -> WS.fetchUrl(url)); return completionService.take().get(); } 9

Slide 10

Slide 10 text

ExecutorCompletionService 10

Slide 11

Slide 11 text

FutureTask 11 Future submit(Runnable r) { t = newTaskFor(r); this.execute(t); return t; }

Slide 12

Slide 12 text

QueuingFuture 12

Slide 13

Slide 13 text

Async Servlets (server-side IO) void doGet(HttpRequest request) { AsyncContext ctx = request.startAsync(); Future res = service.doSmth(); ctx.complete(res.get()); } Servlet-container thread is still blocked! 13 HttpResponse doGet(HttpRequest request) { return service.doSmth(); }

Slide 14

Slide 14 text

ListenableFuture 14 Executor

Slide 15

Slide 15 text

ListenableFuture creation Future future = executor.submit(task); ListenableFuture listenableFuture = JdkFutureAdapters.listenInPoolThread(future); Each future requires a thread to listen it! 15

Slide 16

Slide 16 text

ListenableFuture creation - the right way pool = Executors.newFixedThreadPool(5); executor = MoreExecutors.listenableDecorator(pool); ListenableFuture future = executor.submit(task); 16

Slide 17

Slide 17 text

Servlet 3.0 with ListenableFuture pool = Executors.newFixedThreadPool(5); executor = MoreExecutors.listenableDecorator(pool); ListenableFuture doSmth() { return executor.submit(()->...); } void doGet(HttpRequest request) { AsyncContext ctx = request.startAsync(); ListenableFuture future = service.doSmth(); future.addListener(res -> ctx.complete(res), MoreExecutors.sameThreadExecutor()); } 17

Slide 18

Slide 18 text

Callbacks? void doSmth(callback) { step1().addListener(res1 -> step2(res1).addListener(res2 -> step3(res2).addListener(res3 -> callback(res3); } } } } 18 CPS Callback hell

Slide 19

Slide 19 text

ListenableFuture composition. Futures class 19 import static com.google.common.util.concurrent.Futures.*; ListenableFuture doSmth() { return transformAsync( transformAsync(step1(), res1 -> step2(res1)), res2 -> step3(res2)); } No CPS Still looks like callback hell

Slide 20

Slide 20 text

Fluent API 20 ListenableFuture doSmth() { step1() .transformAsync(res1 -> step2(res1)) .transformAsync(res2 -> step3(res2)); } Not in Guava

Slide 21

Slide 21 text

SettableFuture ● SettableFuture is ListenableFuture ● set() and setException() methods ● Useful to convert callback API to Future API 21

Slide 22

Slide 22 text

Callback to SettableFuture void get(URL url, Consumer cb) 22 => ListenableFuture get(URL url) { SettableFuture future = new SettableFuture<>(); get(url, res -> fut.set(res)); return future; }

Slide 23

Slide 23 text

Java 8 CompletableFuture ● Appeared in Java 8 ● CompletionStage (interface) similar to ListenableFuture ● CompletableFuture (class) similar to SettableFuture ● A part of standard library ● Fluent API for Future composition 23

Slide 24

Slide 24 text

CompletableFuture composition CompletionStage doSmth() { step1() .thenCompose(res1 -> step2(res1)) .thenCompose(res2 -> step3(res2)); } 24

Slide 25

Slide 25 text

CompletableFuture exception handling ● Methods thenCompose, thenApply, etc. do not handle exceptions, but throw deeper in the chain 25 Throws exception CompletionStage doSmth() { step1() .thenCompose(res1 -> step2(res1)) .thenCompose(res2 -> step3(res2)) .exceptionally(e -> recoverFromException(e)); }

Slide 26

Slide 26 text

3 method types ● thenCompose - uses current thread ● thenComposeAsync - uses FJP.commonPool() ● thenComposeAsync with executor - uses passed ExecutorService 26

Slide 27

Slide 27 text

Be aware of StackOverflowError CF current = CF.completedFuture<>(1); for (int i=0; i<10000; i++) { current = current.thenApply(val -> val + 1); } 27 CF first = new CF<>(); CF current = first; for (int i=0; i<10000; i++) { current = current.thenApply(val -> val + 1); } return first.complete(1); StackOverflow? 1) Neither 2) 1 sample 3) 2 sample 4) Both

Slide 28

Slide 28 text

StackOverflowError 28

Slide 29

Slide 29 text

And fix... 29

Slide 30

Slide 30 text

30

Slide 31

Slide 31 text

31

Slide 32

Slide 32 text

For comprehensions Assume step3() requires input from both step1() and step2() 32 step1().thenCompose(res1 -> step2(res1).thenCompose(res2-> step3(res1, res2); ) ); Still looks like callback hell

Slide 33

Slide 33 text

For comprehensions 33 step1().flatMap(res1 => step2(res1).flatMap(res2 => step3(res1, res2); ) );

Slide 34

Slide 34 text

For comprehensions for { res1 <- step1() res2 <- step2(res) res3 <- step3(res1, res2) } yield res3 34 step1().flatMap(res1 => step2(res1).flatMap(res2 => step3(res1, res2).map(res3=> res3 ) );

Slide 35

Slide 35 text

For comprehensions ● In Scala any class with methods below can be used in for comprehensions ○ flatMap ○ map ○ withFilter 35

Slide 36

Slide 36 text

For comprehensions is taken from... main :: IO() = do putStr “What’s your name?” name <- readLn putStr (“Hello, “ ++ name) 36 main :: IO() = (putStr “What’s your name?”) >> (readLn >>= (\name -> (putStr (“Hello, “ ++ name))))

Slide 37

Slide 37 text

1 vs 3 functions 37

Slide 38

Slide 38 text

Scala. ExecutionContext ● Mostly ExecutionContext.global is used ● It’s using ForkJoinPool underneath ● You can define your own ExecutionContext to run tasks on the same thread 38

Slide 39

Slide 39 text

Same thread ExecutionContext object SameThreadExecutor extends ExecutionContextExecutor { def execute(runnable: Runnable): Unit = { runnable.run() } } 39 Any problems with that?

Slide 40

Slide 40 text

StackOverflowError 40

Slide 41

Slide 41 text

Trampolining 41

Slide 42

Slide 42 text

Tail-recursion def factorial(n: Int) = { if (n==1) return 1; n*factorial(n-1); } 42 def factorial(curr: Int, n: Int) = { if (n==1) return curr; factorial(curr*n, n-1); }

Slide 43

Slide 43 text

Mutual recursion problem 43

Slide 44

Slide 44 text

Trampolining to the rescue 44

Slide 45

Slide 45 text

Mutual recursion trampolining 45

Slide 46

Slide 46 text

Trampolined ExecutionContext https://github.com/playframework/playframework/blob/master/framework/src/play-s treams/src/main/scala/play/api/libs/streams/Execution.scala#L31 1) Store next task in the thread local variable 2) Once current task is finished run through tasks stored in thread local 46

Slide 47

Slide 47 text

Zipping futures 47

Slide 48

Slide 48 text

Exception handling 1 48

Slide 49

Slide 49 text

Exception handling 2 49

Slide 50

Slide 50 text

List[Future[T]] -> Future[List[T]] 50

Slide 51

Slide 51 text

Making Scala Futures more concise implicit class FutureOps[A](f: Future[A]) (implicit context: ExecutionContext) { def >>= [B](handler: A => Future[B]): Future[B] = f flatMap handler def >> [B](handler: => Future[B]): Future[B] = f flatMap {_ => handler} } 51

Slide 52

Slide 52 text

Making Scala Futures more concise step1 .flatMap (_ => step2()) .flatMap(res2 => step3(res2)) 52 step1 >> step2 >>= step3

Slide 53

Slide 53 text

Future.unit & Future.never 53 Future is covariant and Nothing is a subtype of every type

Slide 54

Slide 54 text

Future.never naive implementation 54 Memory leak!

Slide 55

Slide 55 text

Cancel future 55 Nothing in Scala!

Slide 56

Slide 56 text

Cancel future in Java ● What mayInterruptIfRunning means? ● Unlike plain old Java 5 future, mayInterruptIfRunning=true does not cause thread to be interrupted for CompletableFuture 56

Slide 57

Slide 57 text

Cancel action when future is cancelled (async API) CompletableFuture sendRequest(Request req) { CF future = new CF(); server.send(req).addListener(resp -> { future.complete(resp); }); future.exceptionally(ex -> { if (ex instanceof CancellationException) { sever.cancel(req); } return null; }); return future; } 57

Slide 58

Slide 58 text

Cancel action when future is cancelled (sync API) CompletableFuture sendRequest(Request req) { CF cf = new CF(); Future future = exec.submit(()->{ cf.complete(server.send(request)); //server.send handles interruptions }); cf.exceptionally(ex -> { if (ex instanceof CancellationException) { future.cancel(true); } throw ex; }); return cf; } 58

Slide 59

Slide 59 text

Summary (Part 1) ● Programming with Future’s is not much more difficult than writing blocking code ● Future in Java 5 is very limited ● Move to Java 8 (you still don’t use it?) ● Use ListenableFuture if you can’t, but it’s not pleasure w/o lambdas ● Learn Scala 59

Slide 60

Slide 60 text

Logging 60

Slide 61

Slide 61 text

Logging in single-threaded application ● Just log what you need ● You get easy to read logs, because only single thread is writing to logs 61

Slide 62

Slide 62 text

Logging in multi-threaded application 1) Multiple threads are working concurrently => messages from different threads are interleaving 2) At least, you need to add ThreadId to the log to grep messages 3) Ideally, you need to add context (sessionId, requestId) to each log line 4) MDC (Mapped Diagnostic Context) 62

Slide 63

Slide 63 text

Logging in async application 1) MDC rely on ThreadLocal => could not be used with futures 2) You need to explicitly pass logging context from one stage to another 63

Slide 64

Slide 64 text

Logging in async application (Java) class ContextResult { final C context; final R result; ContextResult newResult(R2 newRes){ return new ContextResult(context, newRes); } ContextResult map(Function f) { return new ContextResult(context, f.apply(newRes)); } void throwException(Throwable t){ throw new ContextException(context, t); } } 64

Slide 65

Slide 65 text

Logging in async application (Java) class ContextException extends Exception { private C context; ContextException(C context, Throwable cause) { super(cause); this.context = context; } C getContext(){ return context; } } 65

Slide 66

Slide 66 text

Logging in async application (Java) CF.supplyAsync(() -> new ContextResult(sessionId, 1)) .thenApply(r -> r.newResult(r.result+1)) .thenApply(r -> r.map(x -> x+1)) .thenApply(r -> r.throwException(new Exception())) .exceptionally(t -> { ContextException ex = (ContextException) t; String sessionId = ex.getContext(); log.error(“Error in session {}”, sessionId); }); 66

Slide 67

Slide 67 text

Logging in async application (Scala) implicit val context = “123”; Future {1} .map(x => x+1) .map(x => throw Exception()) .recover{case e => log.error(“Exception in session {}”, context)}; 67

Slide 68

Slide 68 text

Exception handling 68

Slide 69

Slide 69 text

Exception handling in sync application (in Java) 1) Unchecked exceptions int foo() 2) Checked exceptions int foo() throws Exception 69

Slide 70

Slide 70 text

Exception handling in sync application (in Scala) Only unchecked exceptions def foo() : Int 70

Slide 71

Slide 71 text

Exceptions pros/cons Pros: 1) Exceptions contain stack-trace info Cons: 1) Exceptions are expensive (stack unwinding) 2) No information about exception in method signature (only for unchecked exception) 71

Slide 72

Slide 72 text

Functional style error handling. Try sealed trait Try[+T] case class Success[+T](value: T) extends Try[T] case class Failure[T <: Nothing](exception: Throwable) extends Try[T] object Try { def apply[T](f: => T): Try[T] = try { Success(f) } catch { case NonFatal(e) => Failure(e) } } 72

Slide 73

Slide 73 text

Try monad sealed trait Try[+T] { def flatMap[U](f: (T) => Try[U]): Try[U] def map[U](f: (T) => U): Try[U] def foreach[U](f: T => U): Unit def isSuccess: Boolean def isFailure: Boolean def getOrElse(f: => T): T = f } 73

Slide 74

Slide 74 text

Try for-comprehension val result = for { v <- Try("5".toInt) k <- Try("six".toInt) z <- Try("9".toInt) } yield( v + k + z) result match { case Success(r) => r === 20 case Failure(e) => throw new IllegalStateException() } 74

Slide 75

Slide 75 text

Try vs unchecked exceptions 1) Try is functional approach to unchecked exception 2) Try in method return type says “Function may throw, but particular error type is unknown” 75

Slide 76

Slide 76 text

Either type sealed abstract class Either[+A, +B] final case class Right[+A, +B](value: B) extends Either[A, B] final case class Left[+A, +B](value: A) extends Either[A, B] Either is right-biased @since Scala 2.12 and could be used in for comprehensions Either in method return type says: “Function may throw and here is the type of possible error” 76

Slide 77

Slide 77 text

Method signatures with Either def getUserByEmail(email: String) : Either[String, User] def getFacebookProfile(user: User) : Either[String, FacebookProfile] def getUserFacebookAge(profile: FacebookProfile): Either[String, Int] Now function might return Either[ErrorType, ValueType] and explicitly specify that it may return error of particular type 77

Slide 78

Slide 78 text

Either for-comprehensions val ageOrError = for { user <- getUserByEmail(email) profile <- getFacebookProfile(user) age <- getUserAge(profile) } yield age 78 ageOrError.match { case Left(error) => log.error(“error occured {}”, error) case Right(age) => log.info(“user age is {}”, age) } Not that useful, to represent error type as string

Slide 79

Slide 79 text

Errors as case classes sealed trait UserServiceError case class AuthError(why: String) extends UserServiceError case object ConnectionError extends UserServiceError 79 sealed trait SocialNetworksServiceError case object NoUserProfile extends SocialNetworksServiceError case object ConnectionError extends SocialNetworksServiceError sealed trait FacebookServiceError case object NoSuchUser extends FacebookServiceError case object FieldValueUnknown(fieldName: String) extends FacebookServiceError case object ConnectionError extends FacebookServiceError

Slide 80

Slide 80 text

New method signatures def getUserByEmail(email: String) : Either[UserServiceError, User] def getFacebookProfile(user: User) : Either[SocialNetworksServiceError, FacebookProfile] def getUserFacebookAge(profile: FacebookProfile): Either[FacebookServiceErorr, Int] 80

Slide 81

Slide 81 text

Example with HttpResponse Either[HttpResponse, HttpResponse] response = for { user <- getUserByEmail(email) .left.map { case AuthError(why: String) => Forbidden(why) case ConnectionError => InternalServerError() } profile <- getFacebookProfile(user) .left.map { case …. } age <- getUserFacebookAge(profile) .left.map { case …. } } yield Ok(age) 81

Slide 82

Slide 82 text

Exception handling in async application 1) Future might complete either successfully or exceptionally 2) Exception handling in async programming is similar to unchecked exception in sync programming 3) You don’t know which exception might be thrown 82

Slide 83

Slide 83 text

Future method signatures def getUserByEmail(email: String) : Future[User] //throws UserServiceException def getFacebookProfile(user: User) : Future[FacebookProfile] //throws SocialNetworksServiceException def getUserFacebookAge(profile: FacebookProfile): Future[Int] //throws FacebookServiceException 83

Slide 84

Slide 84 text

Handling all exceptions in onComplete val ageFut = for { user <- getUserByEmail(email) profile <- getFacebookProfile(user) age <- getUserAge(profile) } yield age 84 ageFut onComplete { case Failure(t) => { if (t instanceof AuthException) return Forbidden(); if (t instance ConnectionException) return InternalServerError(); … return InternalServiceError(); } } Need to know all exceptions!

Slide 85

Slide 85 text

Handling exceptions right after method call Future[HttpResponse] response = for { user <- getUserByEmail(email) .recover { //it’s important to recover with exception, otherwise other statements in for will run case AuthError(why: String) => throw HttpException(Forbidden(why)) case ConnectionError => throw HttpException(InternalServerError()) } …. } yield Ok(age) 85 Not bad, but compiler does not check exceptions that might be thrown response.recover{ case HttpException(response) => response }

Slide 86

Slide 86 text

Future[Either] method signatures def getUserByEmail(email: String) : Future[Either[UserServiceError, User]] def getFacebookProfile(user: User) : Future[Either[SocialNetworksServiceErorr, FacebookProfile]] def getUserFacebookAge(profile: FacebookProfile): Future[Either[FacebookServiceError, Int]] 86

Slide 87

Slide 87 text

You want to write like this response = for { user <- getUserByEmail(email) |> fromFutureEither { case AuthError(why: String) => Forbidden(why) case ConnectionError => InternalServerError() } profile <- getFacebookProfile(user) |> fromFutureEither { case …. } age <- getUserFacebookAge(profile) |> fromFutureEither { case …. } } yield Ok(age) 87

Slide 88

Slide 88 text

Let’s define FutureEither... case class FutureEither[L, R](future: Future[Either[L, R]]) { def flatMap[R2](f: R => FutureEither[R2]): FutureEither[R2] = { val result = future flatMap { case Left(l) => Future.successful(Left(l)) case Right(r) => f(r).future } FutureEither(result) } } 88

Slide 89

Slide 89 text

… and fromFutureEither def fromFutureEither[A, B] (err: A=>HttpResponse) (future: Future[Either[A, B]]) : FutureEither[HttpResponse, B] = FutureEither(future.map(_.left.map(err)) 89

Slide 90

Slide 90 text

And now you can do it! responseFE : FutureEither[HttpResponse, HttpResponse] = for { user <- getUserByEmail(email) |> fromFutureEither { case AuthError(why: String) => Forbidden(why) case ConnectionError => InternalServerError() } …. } yield Ok(age) 90 responseFE.future //Future[Either[HttpResponse, HttpResponse]]

Slide 91

Slide 91 text

EitherT[F[_], A, B] 1) Cats or scalaz library 2) Monad transformer 3) Wraps F[Either[A, B]] 4) Works for all monads F[_] type FutureEither[A, B] = EitherT[Future, A, B] 91

Slide 92

Slide 92 text

Using EitherT responseFE : EitherT[Future, HttpResponse, HttpResponse] = for { user <- getUserByEmail(email) |> fromFutureEither { case AuthError(why: String) => Forbidden(why) case ConnectionError => InternalServerError() } …. } yield Ok(age) 92 responseFE.run //Future[Either[HttpResponse, HttpResponse]]

Slide 93

Slide 93 text

Summary (Part 2) ● MDC does not work for async applications ● You need to explicitly pass logging context ● … or use implicit in Scala ● Checked exceptions is not bad ● Use Either instead of checked exception in Scala ● Make sure to use Either in monadic style ● Future[Either] is similar to checked exceptions in async apps ● Use EitherT monad-transformer to combine Future[Either] in for-comprehensions 93

Slide 94

Slide 94 text

Reading list 1. http://www.nurkiewicz.com/2013/05/java-8-completablefuture-in-action.html 2. https://github.com/google/guava/wiki/ListenableFutureExplained 3. https://github.com/viktorklang/blog 4. http://yanns.github.io/blog/2016/02/10/trampoline-execution-context-with-scal a-futures/ 5. http://blog.higher-order.com/assets/trampolines.pdf 6. https://www.slideshare.net/GaryCoady/unsucking-error-handling-with-futures 7. https://alexn.org/blog/2017/01/30/asynchronous-programming-scala.html 8. https://monix.io/docs/2x/eval/task.html 9. https://github.com/Kotlin/kotlin-coroutines 10. https://ponyfoo.com/articles/understanding-javascript-async-await 11. http://docs.scala-lang.org/sips/pending/async.html 94

Slide 95

Slide 95 text

Reading list 1. http://altair.cs.oswego.edu/mailman/listinfo/concurrency-interest 2. Brian Goetz, Java Concurrency in Practice 3. Felix Frank, Learning Concurrent Programming in Scala 4. java.util.concurrent.* 5. scala.concurrent.* 95

Slide 96

Slide 96 text

Andrey Ershov [email protected] @andrershov 96