Slide 1

Slide 1 text

Doom, gloom or loom RivieraDev 2019 Rémi Forax

Slide 2

Slide 2 text

Me, Myself and I Java Champion, OpenJDK amber & valhalla ex: jigsaw, lambda, invokedynamic

Slide 3

Slide 3 text

Imperative vs Reactive ?? Imperative with Spring 5: @GetMapping("/tweets/{id}") public ResponseEntity getTweetById(@PathVariable("id") String tweetId) { return tweetRepository.findById(tweetId) // Optional<...> .map(ResponseEntity::ok) .orElseGet(ResponseEntity::notFound); }

Slide 4

Slide 4 text

Imperative vs Reactive ?? Imperative with Spring 5: @GetMapping("/tweets/{id}") public ResponseEntity getTweetById(@PathVariable("id") String tweetId) { return tweetRepository.findById(tweetId) // Optional<...> .map(ResponseEntity::ok) .orElseGet(ResponseEntity::notFound); } Reactive with Spring 5: @GetMapping("/tweets/{id}") public Mono> getTweetById(@PathVariable("id") String tweetId) { return tweetRepository.findById(tweetId) // Mono<...> .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound()); }

Slide 5

Slide 5 text

Continuation OpenJDK Project Loom

Slide 6

Slide 6 text

Demo Continuation !

Slide 7

Slide 7 text

A simple example var scope = new ContinuationScope("scope"); var continuation = new Continuation(scope, () -> { System.out.println("hello rivieradev"); Continuation.yield(scope); System.out.println("i'm back"); }); continuation.run(); System.out.println("restart"); continuation.run();

Slide 8

Slide 8 text

A continuation A continuation wraps a Runnable/Callable – Multiple entry points Operations: Outside the continuation: start/restart the continuation ● continuation.run() Inside the continuation: jump back to the caller ● Continuation.yield(scope)

Slide 9

Slide 9 text

But Why ??

Slide 10

Slide 10 text

Thread are heavyweight ! A Thread – Share the same address space – Lightweight Lock ● no context switch but – Thread stack is pre-allocated (=> OOM if a lot of threads) – Thread scheduling is done by the OS !

Slide 11

Slide 11 text

Concurrency models continuation model Memory OS VM T1 T2 T3 lock thread model

Slide 12

Slide 12 text

Concurrency models continuation model Memory OS VM T1 T2 T3 lock thread model Memory OS VM T1 T2 lock C1 C2 C3

Slide 13

Slide 13 text

It already exists in C#, Kotlin or JavaScript ??

Slide 14

Slide 14 text

Demo async()/Deferred.await() in Kotlin

Slide 15

Slide 15 text

async()/Deferred.await() in Kotlin ● runBlocking { val deferred1 = async { delay(1_000) "1" } val deferred2 = async { delay(1_000) "2" } val deferreds = listOf(deferred1, deferred2) //wait for all results val results = deferreds.map { it.await() } println(results) }

Slide 16

Slide 16 text

Suspended function suspend fun doIt(s: String): String { delay(1_000) return s } fun kotlinxCompositionExample() { runBlocking { val deferred1 = async { doIt("1") } val deferred2 = async { doIt("2") } val deferreds = listOf(deferred1, deferred2) ...

Slide 17

Slide 17 text

Demo Fiber.schedule()/join() in Java

Slide 18

Slide 18 text

Fiber.schedule/join in Java var fiber1 = Fiber.schedule(() -> { Thread.sleep(1_000); return "1"; }); var fiber2 = Fiber.schedule(() -> { Thread.sleep(1_000); return "2"; }); var fibers = List.of(fiber1, fiber2); //wait for all results var results = fibers.stream().map(Fiber::join).collect(toList()); System.out.println(results);

Slide 19

Slide 19 text

No suspended keyword ! static String doIt(String s) throws InterruptedException { sleep(1_000); return s; } static void javaExample() { var fiber1 = schedule(() -> doIt("1")); var fiber2 = schedule(() -> doIt("2")); var fibers = List.of(fiber1, fiber2); ...

Slide 20

Slide 20 text

Fiber Wrap a Runnable/Callable Scheduled using an ExecutorService fork/join common pool by default A fiber run on a thread until a blocking call

Slide 21

Slide 21 text

Fiber / blocking calls Blocking calls (read, write, sleep) – Yield from the fiber, schedule another one – Re-scheduled ● When a read/write/locks/etc will not block ● Maybe scheduled on another thread

Slide 22

Slide 22 text

Under the hood ...

Slide 23

Slide 23 text

… in Kotlin ??

Slide 24

Slide 24 text

Suspended function in bytecode ? In Kotlin, the compiler does the transformation suspend fun doIt(s: String): String { delay(1_000) return s }

Slide 25

Slide 25 text

suspend doIt() in bytecode public static final java.lang.Object doIt(java.lang.String, kotlin.coroutines.Continuation super java.lang.String>); Code: 0: aload_1 1: instanceof #11 // class AsyncCompositionKt$doIt$1 4: ifeq 36 7: aload_1 8: checkcast #11 // class AsyncCompositionKt$doIt$1 11: astore_3 12: aload_3 13: getfield #15 // Field AsyncCompositionKt$doIt$1.label:I 16: ldc #16 // int -2147483648 18: iand 19: ifeq 36 22: aload_3 23: dup 24: getfield #15 // Field AsyncCompositionKt$doIt$1.label:I 27: ldc #16 // int -2147483648 29: isub 30: putfield #15 // Field AsyncCompositionKt$doIt$1.label:I 33: goto 45 36: new #11 // class AsyncCompositionKt$doIt$1 39: dup 40: aload_1 41: invokespecial #20 // Method AsyncCompositionKt$doIt$1."":(Lkotlin/coroutines/Continuation;)V 44: astore_3 45: aload_3 46: getfield #24 // Field AsyncCompositionKt$doIt$1.result:Ljava/lang/Object; 49: astore_2 50: invokestatic #30 // Method kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object; 53: astore 4

Slide 26

Slide 26 text

suspend doIt() in bytecode public static final java.lang.Object doIt(java.lang.String, kotlin.coroutines.Continuation super java.lang.String>); ... 55: aload_3 56: getfield #15 // Field AsyncCompositionKt$doIt$1.label:I 59: tableswitch { // 0 to 1 0: 80 1: 110 default: 126 } 80: aload_2 81: invokestatic #36 // Method kotlin/ResultKt.throwOnFailure:(Ljava/lang/Object;)V 84: ldc2_w #37 // long 1000l 87: aload_3 88: aload_3 89: aload_0 90: putfield #41 // Field AsyncCompositionKt$doIt$1.L$0:Ljava/lang/Object; 93: aload_3 94: iconst_1 95: putfield #15 // Field AsyncCompositionKt$doIt$1.label:I 98: invokestatic #47 // Method kotlinx/coroutines/DelayKt.delay:(JLkotlin/coroutines/Continuation;)Ljava/lang/Object; 101: dup 102: aload 4 104: if_acmpne 123 107: aload 4 109: areturn 110: aload_3 111: getfield #41 // Field AsyncCompositionKt$doIt$1.L$0:Ljava/lang/Object; 114: checkcast #49 // class java/lang/String 117: astore_0 118: aload_2 ...

Slide 27

Slide 27 text

suspend doIt() in Java final class AsyncCompositionKt$doIt$1 extends kotlin.coroutines.jvm.internal.ContinuationImpl { int label; Object L$0; ... } public static final Object doIt(String s, kotlin.coroutines.Continuation super String> cont) { AsyncCompositionKt$doIt$1 c; if (continuation instanceof AsyncCompositionKt$doIt$1) { c = (AsyncCompositionKt$doIt$1) cont; } else { c = new AsyncCompositionKt$doIt$1(); } switch(c.label) { case 0: c.L$0 = s; c.label = 1; DelayKt.delay(1_000, c); // <– suspended method return (String) c.L$0; case 1: return (String) c.L$0; default: throw new ISE(...) }

Slide 28

Slide 28 text

Kotlin async/await Transformation in state machine by the compiler Store in fields even if the method call is not suspended – Can be improved ?? Generate a lot of bytecodes Can be improved at bit Kotlinc uses javac as back-end => lot of checkcasts

Slide 29

Slide 29 text

… in Java ??

Slide 30

Slide 30 text

How does it work ? Yield copy the all stack frames on heap ! main run f1 f2 heap run f1 f2

Slide 31

Slide 31 text

How does it work ? Then run() move some stack frames back main f2 heap stack bang! The VM tries to guess how many stack frames should be moved A read of the stack bang trigger the copy of the remaining frames

Slide 32

Slide 32 text

Delimited Continuation vs Thread Scheduling explicit (yield, run) – No OS context switch No heap reservation – Only store what is actually needed !

Slide 33

Slide 33 text

Demo TCP Proxy

Slide 34

Slide 34 text

TCP Proxy with Threads var server = ServerSocketChannel.open(); server.bind(new InetSocketAddress(7777)); System.out.println("server bound to " + server.getLocalAddress()); var remote = SocketChannel.open(); remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), 7)); System.out.println("accepting ..."); var client = server.accept(); new Thread(runnable(client, remote)).start(); new Thread(runnable(remote, client)).start();

Slide 35

Slide 35 text

TCP Proxy with Fibers var server = ServerSocketChannel.open(); server.bind(new InetSocketAddress(7777)); System.out.println("server bound to " + server.getLocalAddress()); var remote = SocketChannel.open(); remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), 7)); System.out.println("accepting ..."); var client = server.accept(); var executor = Executors.newSingleThreadExecutor(); //var executor = ForkJoinPool.commonPool(); Fiber.schedule(executor, runnable(client, remote)); Fiber.schedule(executor, runnable(remote, client));

Slide 36

Slide 36 text

Fiber vs Continuation A fiber delegates the suspension (yield) to the continuation – All blocking calls do internally a yield – The JDK code calls run() on the continuation when it can be rescheduled All the JDK needs to be patched to be “fiber aware”

Slide 37

Slide 37 text

Fiber/Continuation limitation Continuation/Fiber – Can not yield if there is a native frame on the stack Fiber – Yielding inside a synchronized block pins the fiber to that thread ● The same thread as to be used when re-scheduling ● This limitation may be removed in the future

Slide 38

Slide 38 text

Summary

Slide 39

Slide 39 text

Kotlin vs Loom Kotlin – requires a new compiler – Bytecode bloat (parts can be improved) but Fast ! – Split world, “suspended” or not Java Loom – requires a new VM – No fat but not fast (yet ?) – One world

Slide 40

Slide 40 text

Reactive Programming is Dead ? One aspect is managing asynchronous calls but Reactive APIs may also provides – back-pressure – auto-retry, failover, etc – data flow computation lazy computation, cycle detection, incremental updates

Slide 41

Slide 41 text

Questions ?