Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Doom, gloom or loom

Doom, gloom or loom

RivieraDEV presentation of OpenJDK project loom

forax

May 16, 2019
Tweet

More Decks by forax

Other Decks in Programming

Transcript

  1. Doom, gloom or loom
    RivieraDev 2019
    Rémi Forax

    View full-size slide

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

    View full-size slide

  3. 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);
    }

    View full-size slide

  4. 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());
    }

    View full-size slide

  5. Continuation
    OpenJDK Project Loom

    View full-size slide

  6. Demo Continuation !

    View full-size slide

  7. 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();

    View full-size slide

  8. 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)

    View full-size slide

  9. 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 !

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. 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)
    }

    View full-size slide

  15. 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)
    ...

    View full-size slide

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

    View full-size slide

  17. 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);

    View full-size slide

  18. 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);
    ...

    View full-size slide

  19. 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

    View full-size slide

  20. 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

    View full-size slide

  21. Under the hood ...

    View full-size slide

  22. … in Kotlin ??

    View full-size slide

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

    View full-size slide

  24. 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

    View full-size slide

  25. 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
    ...

    View full-size slide

  26. 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(...)
    }

    View full-size slide

  27. 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

    View full-size slide

  28. … in Java ??

    View full-size slide

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

    View full-size slide

  30. 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

    View full-size slide

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

    View full-size slide

  32. Demo
    TCP Proxy

    View full-size slide

  33. 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();

    View full-size slide

  34. 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));

    View full-size slide

  35. 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”

    View full-size slide

  36. 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

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide