Deep dive into Coroutines on JVM Roman Elizarov elizarov at - - PowerPoint PPT Presentation

deep dive into coroutines on jvm
SMART_READER_LITE
LIVE PREVIEW

Deep dive into Coroutines on JVM Roman Elizarov elizarov at - - PowerPoint PPT Presentation

Deep dive into Coroutines on JVM Roman Elizarov elizarov at JetBrains There is no magic Continuation Passing Style (CPS) A toy problem fun postItem(item: Item) { val token = requestToken () val post = createPost (token, item) processPost


slide-1
SLIDE 1

elizarov at JetBrains Roman Elizarov

Deep dive into Coroutines

  • n JVM
slide-2
SLIDE 2

There is no magic

slide-3
SLIDE 3

Continuation Passing Style (CPS)

slide-4
SLIDE 4

A toy problem

fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Direct style

slide-5
SLIDE 5

Direct style

fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

slide-6
SLIDE 6

Direct style

fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Continuation

slide-7
SLIDE 7

Continuation-Passing Style

fun postItem(item: Item) { requestToken { token -> val post = createPost(token, item) processPost(post) } }

Continuation

CPS == Callbacks

slide-8
SLIDE 8

Continuation-Passing Style

fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } }

slide-9
SLIDE 9

Coroutines Direct Style

suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

slide-10
SLIDE 10

How does it work?

Behind the scenes

slide-11
SLIDE 11

Kotlin suspending functions

Kotlin

suspend fun createPost(token: Token, item: Item): Post { … }

slide-12
SLIDE 12

CPS Transformation

Java/JVM

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … }

slide-13
SLIDE 13

CPS Transformation

callback Java/JVM

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … }

slide-14
SLIDE 14

Continuation

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }

slide-15
SLIDE 15

Continuation

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }

slide-16
SLIDE 16

Continuation

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }

slide-17
SLIDE 17

Continuation

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }

slide-18
SLIDE 18

Continuation

Continuation is a generic callback interface

suspend fun createPost(token: Token, item: Item): Post { … } Object createPost(Token token, Item item, Continuation<Post> cont) { … } interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }

slide-19
SLIDE 19

Direct to CPS

slide-20
SLIDE 20

Direct code

suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

slide-21
SLIDE 21

Continuations

suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Initial continuation

slide-22
SLIDE 22

Continuations

suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Continuation

slide-23
SLIDE 23

Continuations

suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Continuation

Convert to CPS?

slide-24
SLIDE 24

Callbacks?

fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } }

slide-25
SLIDE 25

Labels

suspend fun postItem(item: Item) { // LABEL 0 val token = requestToken() // LABEL 1 val post = createPost(token, item) // LABEL 2 processPost(post) }

slide-26
SLIDE 26

Labels

suspend fun postItem(item: Item) { switch (label) { case 0: val token = requestToken() case 1: val post = createPost(token, item) case 2: processPost(post) } }

slide-27
SLIDE 27

suspend fun postItem(item: Item) { val sm = object : CoroutineImpl { … } switch (sm.label) { case 0: val token = requestToken() case 1: val post = createPost(token, item) case 2: processPost(post) } }

State

slide-28
SLIDE 28

fun postItem(item: Item, cont: Continuation) { val sm = object : CoroutineImpl { … } switch (sm.label) { case 0: requestToken(sm) case 1: createPost(token, item, sm) case 2: processPost(post) } }

CPS Transform

slide-29
SLIDE 29

fun postItem(item: Item, cont: Continuation) { val sm = … switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) case 2: processPost(post) } }

Save state

slide-30
SLIDE 30

fun postItem(item: Item, cont: Continuation) { val sm = object : CoroutineImpl { … } switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) case 2: processPost(post) } }

Callback

State Machine as Continuation

slide-31
SLIDE 31

fun postItem(item: Item, cont: Continuation) { val sm = object : CoroutineImpl { fun resume(…) { postItem(null, this) } } switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) … }

Callback

slide-32
SLIDE 32

fun postItem(item: Item, cont: Continuation) { val sm = cont as? ThisSM ?: object : ThisSM { fun resume(…) { postItem(null, this) } } switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: createPost(token, item, sm) … }

Callback

slide-33
SLIDE 33

fun postItem(item: Item, cont: Continuation) { val sm = … switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: val item = sm.item val token = sm.result as Token sm.label = 2 createPost(token, item, sm) … }

Restore state

slide-34
SLIDE 34

fun postItem(item: Item, cont: Continuation) { val sm = … switch (sm.label) { case 0: sm.item = item sm.label = 1 requestToken(sm) case 1: val item = sm.item val token = sm.result as Token sm.label = 2 createPost(token, item, sm) … }

Continue

slide-35
SLIDE 35

State Machine vs Callbacks

fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

slide-36
SLIDE 36

State Machine vs Callbacks

fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } } suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Reuse closure / state object Create new closure

slide-37
SLIDE 37

State Machine vs Callbacks

suspend fun postItems(items: List<Item>) { for (item in items) { val token = requestToken() val post = createPost(token, item) processPost(post) } }

Easy loops and higher-order functions

slide-38
SLIDE 38

State Machine vs Callbacks

fun postItems(items: List<Item>) { … } suspend fun postItems(items: List<Item>) { for (item in items) { val token = requestToken() val post = createPost(token, item) processPost(post) } }

A horrid callback mess Easy loops and higher-order functions

slide-39
SLIDE 39

Integration

slide-40
SLIDE 40

Zoo of futures on JVM

slide-41
SLIDE 41

interface Service { fun createPost(token: Token, item: Item): Call<Post> }

slide-42
SLIDE 42

interface Service { fun createPost(token: Token, item: Item): Call<Post> } suspend fun createPost(token: Token, item: Item): Post = serviceInstance.createPost(token, item).await()

slide-43
SLIDE 43

suspend fun <T> Call<T>.await(): T { … }

slide-44
SLIDE 44

Callbacks everywhere

suspend fun <T> Call<T>.await(): T { enqueue(object : Callback<T> {

  • verride fun onResponse(call: Call<T>, response: Response<T>) {

// todo }

  • verride fun onFailure(call: Call<T>, t: Throwable) {

// todo } }) }

slide-45
SLIDE 45

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> {

  • verride fun onResponse(call: Call<T>, response: Response<T>) {

if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) }

  • verride fun onFailure(call: Call<T>, t: Throwable) {

cont.resumeWithException(t) } }) }

slide-46
SLIDE 46

suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T

slide-47
SLIDE 47

suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T

Regular function Inspired by call/cc from Scheme

slide-48
SLIDE 48

Install callback

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> {

  • verride fun onResponse(call: Call<T>, response: Response<T>) {

if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) }

  • verride fun onFailure(call: Call<T>, t: Throwable) {

cont.resumeWithException(t) } }) }

slide-49
SLIDE 49

Install callback

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> {

  • verride fun onResponse(call: Call<T>, response: Response<T>) {

if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) }

  • verride fun onFailure(call: Call<T>, t: Throwable) {

cont.resumeWithException(t) } }) }

slide-50
SLIDE 50

Analyze response

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> {

  • verride fun onResponse(call: Call<T>, response: Response<T>) {

if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) }

  • verride fun onFailure(call: Call<T>, t: Throwable) {

cont.resumeWithException(t) } }) }

slide-51
SLIDE 51

Analyze response

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont -> enqueue(object : Callback<T> {

  • verride fun onResponse(call: Call<T>, response: Response<T>) {

if (response.isSuccessful) cont.resume(response.body()!!) else cont.resumeWithException(ErrorResponse(response)) }

  • verride fun onFailure(call: Call<T>, t: Throwable) {

cont.resumeWithException(t) } }) }

That’s all

slide-52
SLIDE 52

Out-of-the box integrations

kotlinx-coroutines-core jdk8 guava nio reactor rx1 rx2

Contributions are welcome

slide-53
SLIDE 53

Coroutine context

slide-54
SLIDE 54

What thread it resumes on?

suspend fun postItem(item: Item) { val token = requestToken() val post = createPost(token, item) processPost(post) }

Continuation

It depends!

slide-55
SLIDE 55

What thread it resumes on?

fun postItem(item: Item) { launch(UI) { val token = requestToken() val post = createPost(token, item) processPost(post) } }

Continuation

slide-56
SLIDE 56

Continuation Interceptor

interface ContinuationInterceptor : CoroutineContext.Element { companion object Key : CoroutineContext.Key<ContinuationInterceptor> fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> }

slide-57
SLIDE 57

Continuation Interceptor

interface ContinuationInterceptor : CoroutineContext.Element { companion object Key : CoroutineContext.Key<ContinuationInterceptor> fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> }

slide-58
SLIDE 58

Continuation Interceptor

interface ContinuationInterceptor : CoroutineContext.Element { companion object Key : CoroutineContext.Key<ContinuationInterceptor> fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> }

slide-59
SLIDE 59

Dispatched continuation

class DispatchedContinuation<in T>( val dispatcher: CoroutineDispatcher, val continuation: Continuation<T> ): Continuation<T> by continuation {

  • verride fun resume(value: T) {

dispatcher.dispatch(context, DispatchTask(…)) } … }

Dispatches execution to another thread

slide-60
SLIDE 60

Starting coroutines

slide-61
SLIDE 61

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T>

Coroutine builder

slide-62
SLIDE 62

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T>

A regular function

slide-63
SLIDE 63

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T>

slide-64
SLIDE 64

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T>

suspending lambda

slide-65
SLIDE 65

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(…) return future }

slide-66
SLIDE 66

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(…) return future }

slide-67
SLIDE 67

fun <T> future( context: CoroutineContext = DefaultDispatcher, block: suspend () -> T ): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(completion = object : Continuation<T> { … }) return future }

slide-68
SLIDE 68

fun <T> future(…): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(completion = object : Continuation<T> {

  • verride val context: CoroutineContext get() = context
  • verride fun resume(value: T) {

future.complete(value) }

  • verride fun resumeWithException(exception: Throwable) {

future.completeExceptionally(exception) } }) return future }

slide-69
SLIDE 69

fun <T> future(…): CompletableFuture<T> { val future = CompletableFuture<T>() block.startCoroutine(completion = object : Continuation<T> {

  • verride val context: CoroutineContext get() = context
  • verride fun resume(value: T) {

future.complete(value) }

  • verride fun resumeWithException(exception: Throwable) {

future.completeExceptionally(exception) } }) return future }

That’s all, folks!

slide-70
SLIDE 70

Job cancellation

slide-71
SLIDE 71

Launch coroutine builder

fun launch( context: CoroutineContext = DefaultDispatcher, block: suspend () -> Unit ): Job { … }

slide-72
SLIDE 72

Launching coroutine

val job = launch { … }

slide-73
SLIDE 73

val job = launch { … } job.join()

slide-74
SLIDE 74

val job = launch { … } job.join() job.cancel()

slide-75
SLIDE 75

Job

interface Job : CoroutineContext.Element { companion object Key : CoroutineContext.Key<Job> … }

slide-76
SLIDE 76

Using coroutine context

launch { val job = coroutineContext[Job]!! … }

slide-77
SLIDE 77

Using coroutine context

launch { val job = coroutineContext[Job]!! val interceptor = coroutineContext[CoroutineInterceptor]!! … }

slide-78
SLIDE 78

Timeouts

launch { withTimeout(10, TimeUnit.SECONDS) { … } }

slide-79
SLIDE 79

Cooperative cancellation

slide-80
SLIDE 80

Cooperative cancellation

launch { while (true) { … } }

slide-81
SLIDE 81

Cooperative cancellation

launch { while (isActive) { … } }

slide-82
SLIDE 82

Cooperative cancellation

launch { while (true) { delay(…) … } }

slide-83
SLIDE 83

Cancellable suspension

suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont -> enqueue(…) }

slide-84
SLIDE 84

Cancellable continuation

suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> -> enqueue(…) }

slide-85
SLIDE 85

Completion handler

suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> -> enqueue(…) cont.invokeOnCompletion { this@await.cancel() } }

slide-86
SLIDE 86

Completion handler

suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation<T> -> enqueue(…) cont.invokeOnCompletion { this@await.cancel() } }

slide-87
SLIDE 87

Communicating Sequential Processes (CSP)

slide-88
SLIDE 88

Shared Mutable State

@stefanobaghino

slide-89
SLIDE 89

The choice

Shared Mutable State Share by Communicating

slide-90
SLIDE 90

Example

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

slide-91
SLIDE 91

Main coroutine

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

slide-92
SLIDE 92

Channel

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

slide-93
SLIDE 93

Launch

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

Child coroutine

slide-94
SLIDE 94

Coroutine body

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

Sequential code!

slide-95
SLIDE 95

Send

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

slide-96
SLIDE 96

Close

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

slide-97
SLIDE 97

Receive for loop

fun main(args: Array<String>) = runBlocking<Unit> { val chan = Channel<Int>() launch(coroutineContext) { repeat(10) { i -> delay(100) chan.send(i) } chan.close() } launch(coroutineContext) { for (i in chan) { println(i) } } }

slide-98
SLIDE 98

Demo

slide-99
SLIDE 99

Actors

The other way to look at CSP

slide-100
SLIDE 100

The choice

Named channels Named coroutines

Actor == named coroutine & inbox channel

slide-101
SLIDE 101

Example

fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }

slide-102
SLIDE 102

Actor coroutine builder

fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }

slide-103
SLIDE 103

Actor body

fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }

Sequential!

slide-104
SLIDE 104

Interacting with an actor

fun main(args: Array<String>) = runBlocking<Unit> { val printer = actor<Int>(coroutineContext) { for (i in channel) { println(i) } } launch(coroutineContext) { repeat(10) { i -> delay(100) printer.send(i) } printer.close() } }

slide-105
SLIDE 105

References

slide-106
SLIDE 106

Guide to kotlinx.coroutines by example

https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md

  • Basics
  • Cancellation and Timeouts
  • Composition
  • Coroutine contexts
  • Channels
  • Shared Mutable State and Concurrency
  • Select expressions
slide-107
SLIDE 107

#kotlinconf17

relizarov elizarov at JetBrains Roman Elizarov

Thank you

Any questions?