Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Kotlin Coroutines Reloaded

2.064 Aufrufe

Veröffentlicht am

Presented at JVM Language Summit, 2017

Veröffentlicht in: Technologie
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Antworten 
    Sind Sie sicher, dass Sie …  Ja  Nein
    Ihre Nachricht erscheint hier

Kotlin Coroutines Reloaded

  1. 1. Kotlin Coroutines Reloaded Presented at JVM Language Summit, 2017 /Roman Elizarov @ JetBrains
  2. 2. Speaker: Roman Elizarov • 16+ years experience • Previously developed high-perf trading software @ Devexperts • Teach concurrent & distributed programming @ St. Petersburg ITMO University • Chief judge @ Northeastern European Region of ACM ICPC • Now work on Kotlin @ JetBrains
  3. 3. Agenda • Recap of Kotlin coroutines prototype • Presented @ JVMLS, 2016 by Andrey Breslav • The issues in the prototype design • and the solutions that were found • Design released to public in Kotlin 1.1 • Evolved coroutines support libraries • Challenges & future directions
  4. 4. Recap of Kotlin coroutines prototype Presented @ JVMLS, 2016 by Andrey Breslav
  5. 5. Async the C# (JS, TS, Dart, etc) way async Task<String> Work() { … } async Task MoreWork() { Console.WriteLine("Work started"); var str = await Work(); Console.WriteLine($"Work completed {str}"); }
  6. 6. Async the C# (JS, TS, Dart, etc) way async Task<String> work() { … } async Task MoreWork() { Console.WriteLine("Work started"); var str = await Work(); Console.WriteLine($"Work completed {str}"); }
  7. 7. Async the Kotlin way (prototype) fun work(): CompletableFuture<String> { … } fun moreWork() = async { println("Work started") val str = await(work()) println("Work completed: $str") }
  8. 8. Async the Kotlin way (prototype) fun work(): CompletableFuture<String> { … } fun moreWork() = async { println("Work started") val str = await(work()) println("Work completed: $str") } Functions vs Keywords Extensibility Runs on stock JVM 1.6+ Purely local -- no global bytecode transforms
  9. 9. Suspending functions A grand unification between async/await and generate/yield
  10. 10. Suspending functions: use val str = await(work())
  11. 11. Suspending functions: use val str = await(work()) CompletableFuture<String> // String result
  12. 12. Suspending functions: declare (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Unit CompletableFuture<String> // String result
  13. 13. Suspending functions: declare (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Unit CompletableFuture<String> callback void // String result Magic signature transformation
  14. 14. Suspending functions: declare (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Unit CompletableFuture<String> callback void // String result Continuation is a generic callback interface interface Continuation<in T> { fun resume(value: T) fun resumeWithException(exception: Throwable) }
  15. 15. Suspending functions: implement (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } CompletableFuture<String> // String result
  16. 16. Suspending functions: implement (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } CompletableFuture<String> // String result
  17. 17. Suspending functions: implement (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } CompletableFuture<String> // String result
  18. 18. Suspending functions: implement (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } CompletableFuture<String> // String result
  19. 19. Suspending functions: implement (prototype) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } CompletableFuture<String> // String result Simple, but wrong!
  20. 20. A problematic example Where grand vision meets reality
  21. 21. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } }
  22. 22. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } What if work() always returns a future that is already complete?
  23. 23. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine
  24. 24. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await
  25. 25. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete
  26. 26. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete await$lambda
  27. 27. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete await$lambda
  28. 28. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete await$lambda ContinuationImpl.resume
  29. 29. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete await$lambda ContinuationImpl.resume problem$stateMachine
  30. 30. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete await$lambda ContinuationImpl.resume problem$stateMachine await
  31. 31. A problematic example suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>) { f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun problem() = async { repeat(10_000) { await(work()) } } stack problem$stateMachine await CompletableFuture.whenComplete await$lambda ContinuationImpl.resume problem$stateMachine await … StackOverflowError
  32. 32. A solution A difference between knowing the path & walking the path.
  33. 33. A solution val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Unit CompletableFuture<String> // String result
  34. 34. A solution (0): stack unwind convention val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? CompletableFuture<String> // String result
  35. 35. A solution (0) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? CompletableFuture<String> // String result T | COROUTINE_SUSPENDED
  36. 36. A solution (0) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? CompletableFuture<String> // String result T | COROUTINE_SUSPENDED Did not suspend -> Returns result
  37. 37. A solution (0) val str = await(work()) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? CompletableFuture<String> // String result T | COROUTINE_SUSPENDED Did not suspend -> Returns result Did suspend -> WILL invoke continuation
  38. 38. A solution (0) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? { … }
  39. 39. A solution? suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? { val consensus = AtomicReference<Any?>(UNDECIDED) f.whenComplete { value, exception -> if (exception != null) { if (!consensus.compareAndSet(UNDECIDED, Fail(exception))) c.resumeWithException(exception) } else { if (!consensus.compareAndSet(UNDECIDED, value)) c.resume(value) } } consensus.compareAndSet(UNDECIDED, COROUTINE_SUSPENDED) val result = consensus.get() if (result is Fail) throw result.exception return result }
  40. 40. A solution? suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? { val consensus = AtomicReference<Any?>(UNDECIDED) f.whenComplete { value, exception -> if (exception != null) { if (!consensus.compareAndSet(UNDECIDED, Fail(exception))) c.resumeWithException(exception) } else { if (!consensus.compareAndSet(UNDECIDED, value)) c.resume(value) } } consensus.compareAndSet(UNDECIDED, COROUTINE_SUSPENDED) val result = consensus.get() if (result is Fail) throw result.exception return result } Wat?
  41. 41. A solution (1): Call/declaration fidelity
  42. 42. A solution (1) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any?
  43. 43. A solution (1) suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? suspend fun <T> await(f: CompletableFuture<T>): T natural signature
  44. 44. A solution (1) fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? suspend fun <T> await(f: CompletableFuture<T>): T Compiles as (on JVM)
  45. 45. A solution (1) fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? suspend fun <T> await(f: CompletableFuture<T>): T Compiles as (on JVM) CPS Transformation
  46. 46. A solution (1) fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? suspend fun <T> await(f: CompletableFuture<T>) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } Recover continuation
  47. 47. A solution (1) fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? suspend fun <T> await(f: CompletableFuture<T>) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } Inspired by call/cc from Scheme Recover continuation
  48. 48. A solution (1) fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? suspend fun <T> await(f: CompletableFuture<T>) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } Works as Inspired by call/cc from Scheme
  49. 49. A solution (1) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … }
  50. 50. A solution (1) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } inline suspend fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?): T
  51. 51. A solution (1) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } inline suspend fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?): T T | COROUTINE_SUSPENDED
  52. 52. A solution (1) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } inline suspend fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?): T Intrinsic T | COROUTINE_SUSPENDED
  53. 53. A solution (1) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } inline suspend fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?): T fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?, c: Continuation<T>): Any? = block(c) Compiles as (on JVM) Intrinsic CPS Transformation
  54. 54. A solution (1) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } inline suspend fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?): T fun <T> suspendCoroutineOrReturn( crossinline block: (Continuation<T>) -> Any?, c: Continuation<T>): Any? = block(c) Compiles as (on JVM) Intrinsic CPS Transformation
  55. 55. A solution (2): Tail suspension
  56. 56. A solution (2) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } Tail suspend invocation: Continuation pass-through
  57. 57. A solution (2) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? = suspendCoroutineOrReturn(c) { c -> … } Tail suspend invocation: Continuation pass-through Compiles as (on JVM) CPS Transformation
  58. 58. A solution (2) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? = suspendCoroutineOrReturn(c) { c -> … } Tail suspend invocation: Continuation pass-through Compiles as (on JVM) CPS Transformation
  59. 59. A solution (2) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutineOrReturn { c -> … } fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Any? { … } Tail suspend invocation: Continuation pass-through Compiles as (on JVM) CPS Transformation
  60. 60. A solution (3): Abstraction
  61. 61. A solution (3) public inline suspend fun <T> suspendCoroutine( crossinline block: (Continuation<T>) -> Unit): T = suspendCoroutineOrReturn { c: Continuation<T> -> val safe = SafeContinuation(c) block(safe) safe.getResult() }
  62. 62. A solution (3) public inline suspend fun <T> suspendCoroutine( crossinline block: (Continuation<T>) -> Unit): T = suspendCoroutineOrReturn { c: Continuation<T> -> val safe = SafeContinuation(c) block(safe) safe.getResult() }
  63. 63. A solution (3) public inline suspend fun <T> suspendCoroutine( crossinline block: (Continuation<T>) -> Unit): T = suspendCoroutineOrReturn { c: Continuation<T> -> val safe = SafeContinuation(c) block(safe) safe.getResult() } Any? Is gone
  64. 64. A solution (3) public inline suspend fun <T> suspendCoroutine( crossinline block: (Continuation<T>) -> Unit): T = suspendCoroutineOrReturn { c: Continuation<T> -> val safe = SafeContinuation(c) block(safe) safe.getResult() } Encapsulates result consensus algorithm
  65. 65. A solution (4): Putting it all together
  66. 66. A solution (4) suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutine { c -> f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } Looks simple, works correctly!
  67. 67. Recap steps to solution • T | COROUTINE_SUSPENDED (Any?) to allow invocations that do not suspend and thus avoid StackOverflowError • Call/declaration fidelity via CPS transformation • Introduce call/cc (suspendCoroutineOrReturn) to recover hidden continuation • Tail call invocation support to recover prototype semantics • Use abstraction (suspendCoroutine) to hide implementation complexities from end-users
  68. 68. The final touch: await extension suspend fun <T> await(f: CompletableFuture<T>): T = suspendCoroutine { c -> f.whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } }
  69. 69. The final touch: await extension suspend fun <T> CompletableFuture<T>.await(): T = suspendCoroutine { c -> whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } }
  70. 70. The final touch: await extension suspend fun <T> CompletableFuture<T>.await(): T = suspendCoroutine { c -> whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun moreWork() = async { println("Work started") val str = await(work()) println("Work completed: $str") }
  71. 71. The final touch: await extension suspend fun <T> CompletableFuture<T>.await(): T = suspendCoroutine { c -> whenComplete { value, exception -> if (exception != null) c.resumeWithException(exception) else c.resume(value) } } fun moreWork() = async { println("Work started") val str = work().await() println("Work completed: $str") } Reads left-to-right just like it executes
  72. 72. Coroutine builders The mystery of inception
  73. 73. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { val controller = FutureController<T>() c(controller).resume(Unit) return controller.future }
  74. 74. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { val controller = FutureController<T>() c(controller).resume(Unit) return controller.future } A special modifier
  75. 75. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { val controller = FutureController<T>() c(controller).resume(Unit) return controller.future } Magic signature transformation
  76. 76. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { val controller = FutureController<T>() c(controller).resume(Unit) return controller.future } A boilerplate to start coroutine
  77. 77. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { … } class FutureController<T> { val future = CompletableFuture<T>() operator fun handleResult(value: T, c: Continuation<Nothing>) { future.complete(value) } operator fun handleException(exception: Throwable, c: Continuation<Nothing>) { future.completeExceptionally(exception) } }
  78. 78. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { … } class FutureController<T> { val future = CompletableFuture<T>() operator fun handleResult(value: T, c: Continuation<Nothing>) { future.complete(value) } operator fun handleException(exception: Throwable, c: Continuation<Nothing>) { future.completeExceptionally(exception) } }
  79. 79. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { … } class FutureController<T> { val future = CompletableFuture<T>() operator fun handleResult(value: T, c: Continuation<Nothing>) { future.complete(value) } operator fun handleException(exception: Throwable, c: Continuation<Nothing>) { future.completeExceptionally(exception) } } was never used
  80. 80. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { … } class FutureController<T> { val future = CompletableFuture<T>() operator fun handleResult(value: T) { future.complete(value) } operator fun handleException(exception: Throwable) { future.completeExceptionally(exception) } }
  81. 81. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { … } class FutureController<T> { val future = CompletableFuture<T>() operator fun handleResult(value: T) { future.complete(value) } operator fun handleException(exception: Throwable) { future.completeExceptionally(exception) } } Looks like Continuation<T>
  82. 82. Coroutine builders (prototype) fun <T> async( coroutine c: FutureController<T>.() -> Continuation<Unit> ): CompletableFuture<T> { … } class FutureController<T> { val future = CompletableFuture<T>() operator fun handleResult(value: T) { future.complete(value) } operator fun handleException(exception: Throwable) { future.completeExceptionally(exception) } } Something that takes Continuation<T>
  83. 83. Coroutine builders: evolved
  84. 84. Coroutine builders fun <T> async( c: suspend () -> T ): CompletableFuture<T> { val controller = FutureController<T>() c.startCoroutine(completion = controller) return controller.future }
  85. 85. Coroutine builders fun <T> async( c: suspend () -> T ): CompletableFuture<T> { val controller = FutureController<T>() c.startCoroutine(completion = controller) return controller.future } natural signature No special coroutine keyword
  86. 86. Coroutine builders fun <T> async( c: suspend () -> T ): CompletableFuture<T> { val controller = FutureController<T>() c.startCoroutine(completion = controller) return controller.future } Provided by standard library
  87. 87. Coroutine builders fun <T> async( c: suspend () -> T ): CompletableFuture<T> { … } class FutureController<T> : Continuation<T> { val future = CompletableFuture<T>() override fun resume(value: T) { future.complete(value) } override fun resumeWithException(exception: Throwable) { future.completeExceptionally(exception) } }
  88. 88. Coroutine builders fun <T> async( c: suspend () -> T ): CompletableFuture<T> { … } class FutureController<T> : Continuation<T> { val future = CompletableFuture<T>() override fun resume(value: T) { future.complete(value) } override fun resumeWithException(exception: Throwable) { future.completeExceptionally(exception) } } Serves as completion continuation for coroutine
  89. 89. Bonus features Free as in cheese
  90. 90. Arbitrary suspending calls suspend fun <T> suspending(block: suspend () -> T): T = suspendCoroutine { continuation -> block.startCoroutine(completion = continuation) }
  91. 91. Arbitrary suspending calls suspend fun <T> suspending(block: suspend () -> T): T = suspendCoroutine { continuation -> block.startCoroutine(completion = continuation) }
  92. 92. Arbitrary suspending calls suspend fun <T> suspending(block: suspend () -> T): T = suspendCoroutine { continuation -> block.startCoroutine(completion = continuation) }
  93. 93. Arbitrary suspending calls suspend fun <T> suspending(block: suspend () -> T): T = suspendCoroutine { continuation -> block.startCoroutine(completion = continuation) } fun moreWork() = async { println("Work started") val str = work().await() println("Work completed: $str") } Returns CompletableFuture
  94. 94. Arbitrary suspending calls suspend fun <T> suspending(block: suspend () -> T): T = suspendCoroutine { continuation -> block.startCoroutine(completion = continuation) } suspend fun moreWork(): T = suspending { println("Work started") val str = work().await() println("Work completed: $str") }
  95. 95. Arbitrary suspending calls suspend fun <T> suspending(block: suspend () -> T): T = suspendCoroutine { continuation -> block.startCoroutine(completion = continuation) } suspend fun moreWork(): T = suspending { println("Work started") val str = work().await() println("Work completed: $str") } Tail suspend invocation Non-tail (arbitrary) suspend invocation
  96. 96. Arbitrary suspending calls suspend fun moreWork() { println("Work started") val str = work().await() println("Work completed: $str") } Non-tail (arbitrary) suspend invocation
  97. 97. Stackless vs Stackful coroutines The crucial distinction
  98. 98. Stackless vs Stackful coroutines Stackless Stackful Restrictions Use in special ctx Use anywhere Implemented in C#, Scala, Kotlin, … Quasar, Javaflow, …
  99. 99. Stackless vs Stackful coroutines Stackless Stackful Restrictions Use in special ctx Use anywhere Implemented in C#, Scala, Kotlin, … Quasar, Javaflow, …
  100. 100. Stackless vs Stackful coroutines Stackless Stackful Restrictions Use in special ctx Use anywhere Implemented in C#, Scala, Kotlin, … Quasar, Javaflow, … Can suspend in? suspend functions throws SuspendExecution / @Suspendable functions
  101. 101. Stackless vs Stackful coroutines Stackless Stackful Restrictions Use in special ctx Use anywhere Implemented in C#, Scala, … Kotlin, Quasar, Javaflow, …
  102. 102. Stackless vs Stackful coroutines Stackless Stackful Restrictions Use in special ctx Use anywhere Implemented in C#, Scala, Kotlin, Quasar, JavaFlow, … LISP, Go, …
  103. 103. Stackless vs Stackful coroutines Stackless Stackful Restrictions Use in special ctx Use anywhere Implemented in C#, Scala, … Kotlin, Quasar, Javaflow, … False dichotomy
  104. 104. Async vs Suspending functions The actual difference
  105. 105. fun work() = async { … } suspend fun work() { … }
  106. 106. fun work(): CompletableFuture<String> = async { … } suspend fun work(): String { … } In Kotlin you have a choice
  107. 107. fun workAsync(): CompletableFuture<String> = async { … } suspend fun work(): String { … }
  108. 108. workAsync() VALID –> produces CompletableFuture<String> workAsync().await() VALID –> produces String concurrent & async behavior sequential behavior The most needed one, yet the most syntactically clumsy, esp. for suspend-heavy (CSP) code style The problem with async
  109. 109. Kotlin suspending functions imitate sequential behavior by default Concurrency is hard Concurrency has to be explicit
  110. 110. Composability Making those legos click
  111. 111. Builders fun <T> async( c: suspend () -> T ): CompletableFuture<T> { … }
  112. 112. Builders fun <T> async( c: suspend () -> T ): ListenableFuture<T> { … }
  113. 113. Builders fun <T> async( c: suspend () -> T ): MyOwnFuture<T> { … }
  114. 114. Suspending functions fun <T> async( c: suspend () -> T ): MyOwnFuture<T> { … } suspend fun <T> CompletableFuture<T>.await(): T { … }
  115. 115. Suspending functions fun <T> async( c: suspend () -> T ): MyOwnFuture<T> { … } suspend fun <T> ListenableFuture<T>.await(): T { … }
  116. 116. Suspending functions fun <T> async( c: suspend () -> T ): MyOwnFuture<T> { … } suspend fun <T> MyOwnFuture<T>.await(): T { … }
  117. 117. Suspending functions fun <T> async( c: suspend () -> T ): MyOwnFuture<T> { … } suspend fun <T> MyOwnFuture<T>.await(): T { … } fun moreWorkAsync() = async { println("Work started") val str = workAsync().await() println("Work completed: $str") } All combinations need to compose
  118. 118. Composability: evolved • Kotlin suspending functions are composable by default • Asynchronous use-case • Define asynchronous suspending functions anywhere • Use them inside any asynchronous coroutine builder • Or inside other suspending functions • generate/yield coroutines are synchronous • Restricted via a special @RestrictsSuspension annotation • Opt-in to define synchronous coroutines
  119. 119. Coroutine context The last piece of composability puzzle
  120. 120. asyncUI (prototype) asyncUI { val image = await(loadImage(url)) myUI.updateImage(image) } Supposed to work in UI thread
  121. 121. asyncUI (prototype) asyncUI { val image = await(loadImage(url)) myUI.updateImage(image) } A special coroutine builder
  122. 122. asyncUI (prototype) asyncUI { val image = await(loadImage(url)) myUI.updateImage(image) } A special suspending function in its scope Composability problem again!
  123. 123. asyncUI: evolved async(UI) { val image = loadImageAsync(url).await() myUI.updateImage(image) }
  124. 124. asyncUI: evolved async(UI) { val image = loadImageAsync(url).await() myUI.updateImage(image) } Explicit context convention for all builders Can intercept continuation to resume in the appropriate thread
  125. 125. The actual Continuation interface async(UI) { val image = loadImageAsync(url).await() myUI.updateImage(image) } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) } Is used to transparently lookup an interceptor for resumes
  126. 126. The actual Continuation interface async(UI) { val image = loadImageAsync(url).await() myUI.updateImage(image) } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) } Does not have to be aware – receives intercepted continuation to work with
  127. 127. Thread-safety A million-dollar quest for correctly-synchronized (data-race free) code
  128. 128. A need for happens-before relation fun moreWork() = async { val list = ArrayList<String>() val str = work().await() list.add(str) }
  129. 129. A need for happens-before relation fun moreWork() = async { val list = ArrayList<String>() val str = work().await() list.add(str) } One thread
  130. 130. A need for happens-before relation fun moreWork() = async { val list = ArrayList<String>() val str = work().await() list.add(str) } One thread Suspends / resumes
  131. 131. A need for happens-before relation fun moreWork() = async { val list = ArrayList<String>() val str = work().await() list.add(str) } One thread Suspends / resumes Another thread
  132. 132. A need for happens-before relation fun moreWork() = async { val list = ArrayList<String>() val str = work().await() list.add(str) } One thread Suspends / resumes Another thread Is there a data race? Do we need volatile when spilling locals to a state machine?
  133. 133. A need for happens-before relation fun moreWork() = async { val list = ArrayList<String>() val str = work().await() list.add(str) } There is no data race here! await establishes happens-before relation happens-before
  134. 134. Challenges If it only was all that simple…
  135. 135. Thread confinement vs coroutines fun moreWork() = async { synchronized(monitor) { val str = work().await() } }
  136. 136. Thread confinement vs coroutines fun moreWork() = async { synchronized(monitor) { val str = work().await() } } MONITORENTER in one thread Suspends / resumes MONITOREXIT in another thread IllegalMonitorStateException
  137. 137. Thread confinement vs coroutines • Monitors • Locks (j.u.c.l.ReentrantLock) • Thread-locals • … • Thread.currentThread() A CoroutineContext is a map of coroutine-local elements as replacement
  138. 138. Stack traces in exceptions
  139. 139. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } stack moreWork
  140. 140. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } stack moreWork work
  141. 141. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } stack moreWork work await
  142. 142. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } stack moreWork work Work$StateMachine : Continuation ---------- fun resume(v)
  143. 143. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } stack Work$StateMachine.resume work
  144. 144. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } JVM stack Work$StateMachine.resume work
  145. 145. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } JVM stack Work$StateMachine.resume work Coroutine stack moreWork work
  146. 146. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } JVM stack Work$StateMachine.resume work Coroutine stack moreWork work Gets thrown
  147. 147. Stack traces in exceptions suspend fun moreWork() { work() } suspend fun work() { someAsyncOp().await() throw Exception() } JVM stack Work$StateMachine.resume work Coroutine stack moreWork work Gets thrown User wants
  148. 148. Library evolution Going beyond the language & stdlib
  149. 149. Library evolution: already there • Communication & synchronization primitives • Job: A completable & cancellable entity to represent a coroutine • Deferred: A future with suspend fun await (and no thread-blocking methods) • Mutex: with suspend fun lock • Channel: SendChannel & ReceiveChannel • RendezvousChannel – synchronous rendezvous • ArrayChannel – fixed size buffer • LinkedListChannel – unlimited buffer • ConflatedChannel – only the most recent sent element is kept • BroadcastChannel: multiple subscribes, all receive
  150. 150. Library evolution: already there • Coroutine builders • launch (fire & forget, returns a Job) • async (returns Deferred) • future (integration with CompletableFuture & ListenableFuture) • runBlocking (to explicitly delimit code that blocks a thread) • actor / produce (consume & produce messages over channels) • publish (integration with reactive streams, returns Publisher) • rxCompletable, rxSingle, rxObservable, rxFlowable (RxJava 1/2)
  151. 151. Library evolution: already there • Top-level functions • select expression to await multiple events for full CSP-style programming • delay for time-based logic in coroutines • Extensions • await for all kinds of futures (JDK, Guava, RxJava) • aRead, aWrite, etc for AsynchronousXxxChannel in NIO • Cancellation & job (coroutine/actor) hierarchies • withTimeout for a composable way for any suspend function run w/timeout
  152. 152. Library evolution: WIP • Serialization / migration of coroutines • Migrating libraries to Kotlin/JS & Kotlin/Native (lang support – done) • Full scope of pipelining operators on channels (filter, map, etc) • Optimizations for single-producer and/or single-consumer cases • Allow 3rd party primitives to participate in select (alternatives) • ByteChannel for suspendable IO (http client/server, websocket, etc) • See also ktor.io
  153. 153. A closing note on terminology • We don’t use the term fiber/strand/green threads/… • The term coroutine works just as well • ”Fibers describe essentially the same concept as coroutines” © Wikipedia Kotlin Coroutines are very light-weight threads
  154. 154. Wrap up Let’s call it a day
  155. 155. Experimental status of coroutines • This design is new and unlike mainstream • For some very good reasons • We want community to try it for real • So we released it as an experimental feature • We guarantee backwards compatibility • Old code compiled with coroutines continues to work • We reserve the right to break forward compatibility • We may add things so new code may not run w/old RT • Design will be finalized at a later point • Old code will continue to work via support library • Migration aids to the final design will be provided opt-in flag
  156. 156. Thank you Any questions? Slides are available at www.slideshare.net/elizarov email elizarov at gmail relizarov

×