Slide 1

Slide 1 text

Server-side Kotlin with Coroutines Roman Elizarov relizarov

Slide 2

Slide 2 text

Speaker: Roman Elizarov • Professional developer since 2000 • Previously developed high-perf trading software @ Devexperts • Teach concurrent & distributed programming @ St. Petersburg ITMO University • Chief judge @ Northern Eurasia Contest / ICPC • Now team lead in Kotlin Libraries @ JetBrains elizarov @ relizarov

Slide 3

Slide 3 text

Kotlin – Programming Language for

Slide 4

Slide 4 text

Kotlin – Programming Language for This talk Server-side

Slide 5

Slide 5 text

Backend evolution Starting with “good old days”

Slide 6

Slide 6 text

ET 1 ET 2 ET N … Executor Threads DB Old-school client-server monolith Clients

Slide 7

Slide 7 text

ET 1 ET 2 ET N … Executor Threads DB Incoming request Clients

Slide 8

Slide 8 text

ET 1 ET 2 ET N … Executor Threads DB Blocks tread ! Clients

Slide 9

Slide 9 text

ET 1 ET 2 ET N … Executor Threads DB Sizing threads – easy Clients N = number of DB connections

Slide 10

Slide 10 text

Services

Slide 11

Slide 11 text

ET 1 ET 2 ET N … Executor Threads DB Old-school client-server monolith Clients

Slide 12

Slide 12 text

ET 1 ET 2 ET N … Executor Threads DB Now with Services Service Clients

Slide 13

Slide 13 text

ET 1 ET 2 ET N … Executor Threads Services everywhere … Service K Service 1 Service 2 Clients

Slide 14

Slide 14 text

ET 1 ET 2 ET N … Executor Threads Sizing threads – not easy … N = ????? Service K Service 1 Service 2 Clients

Slide 15

Slide 15 text

Complex business logic fun placeOrder(order: Order): Response { … }

Slide 16

Slide 16 text

Complex business logic fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) … }

Slide 17

Slide 17 text

Complex business logic fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) … }

Slide 18

Slide 18 text

Complex business logic fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } … }

Slide 19

Slide 19 text

Complex business logic fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } return validateOrder(order, margin) }

Slide 20

Slide 20 text

Complex business logic fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } return validateOrder(order, margin) }

Slide 21

Slide 21 text

What if a service is slow? fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } return validateOrder(order, margin) } !

Slide 22

Slide 22 text

ET 2 ET N … Executor Threads Clients Blocks threads … Service K Service 1 Service 2 ET 1! "

Slide 23

Slide 23 text

ET 2 ET N … Executor Threads Clients Blocks threads … Service K Service 1 Service 2 ET 1! " !

Slide 24

Slide 24 text

ET 2 ET N … Executor Threads Clients Blocks threads … Service K Service 1 Service 2 ET 1! " ! !

Slide 25

Slide 25 text

Code that waits

Slide 26

Slide 26 text

Asynchronous programming Writing code that waits

Slide 27

Slide 27 text

ET 2 ET N … Executor Threads Clients Instead of blocking… … Service K Service 1 Service 2 ET 1! "

Slide 28

Slide 28 text

ET 2 ET N … Executor Threads Release the thread … Service K Service 1 Service 2 ET 1 ! Clients

Slide 29

Slide 29 text

Clients ET 2 ET N … Executor Threads Resume operation later … Service K Service 1 Service 2 ET 1 !

Slide 30

Slide 30 text

But how? fun loadMargin(account: Account): Margin

Slide 31

Slide 31 text

But how? •Callbacks fun loadMargin(account: Account, callback: (Margin) -> Unit)

Slide 32

Slide 32 text

But how? •Callbacks •Futures/Promises fun loadMargin(account: Account): Future

Slide 33

Slide 33 text

But how? •Callbacks •Futures/Promises/Reactive fun loadMargin(account: Account): Mono

Slide 34

Slide 34 text

But how? •Callbacks •Futures/Promises/Reactive •async/await async fun loadMargin(account: Account): Task

Slide 35

Slide 35 text

But how? •Callbacks •Futures/Promises/Reactive •async/await •Kotlin Coroutines suspend fun loadMargin(account: Account): Margin

Slide 36

Slide 36 text

Learn more KotlinConf (San Francisco) 2017 GOTO Copenhagen 2018

Slide 37

Slide 37 text

Suspend behind the scenes suspend fun loadMargin(account: Account): Margin

Slide 38

Slide 38 text

Suspend behind the scenes suspend fun loadMargin(account: Account): Margin fun loadMargin(account: Account, cont: Continuation) But why callback and not future?

Slide 39

Slide 39 text

Performance! •Future is a synchronization primitive •Callback is a lower-level primitive •Integration with async IO libraries is easy

Slide 40

Slide 40 text

Integration suspend fun loadMargin(account: Account): Margin

Slide 41

Slide 41 text

Integration suspend fun loadMargin(account: Account): Margin = suspendCoroutine { cont -> // install callback & use cont to resume }

Slide 42

Slide 42 text

Integration at scale Going beyond slide-ware

Slide 43

Slide 43 text

ET 2 ET N … Executor Threads Clients Release thread? … Service K Service 1 Service 2 ET 1! "

Slide 44

Slide 44 text

Blocking server fun placeOrder(order: Order): Response { // must return response }

Slide 45

Slide 45 text

Asynchronous server fun placeOrder(order: Order): Mono { // may return without response }

Slide 46

Slide 46 text

Convenient? fun placeOrder(order: Order): Mono { // response from placed order cache return Mono.just(response) }

Slide 47

Slide 47 text

Server integrated with coroutines suspend fun placeOrder(order: Order): Response { // response from placed order cache return response }

Slide 48

Slide 48 text

Server not integrated with coroutines fun placeOrder(order: Order) = GlobalScope.mono { // response from placed order cache return@mono response } Coroutine builder

Slide 49

Slide 49 text

The server shall support asynchrony is some way

Slide 50

Slide 50 text

Suspend suspend fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } return validateOrder(order, margin) }

Slide 51

Slide 51 text

Suspend suspend fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } return validateOrder(order, margin) } Invoke suspending funs

Slide 52

Slide 52 text

Suspend is convenient suspend fun placeOrder(order: Order): Response { val account = accountService.loadAccout(order.accountId) val margin = if (account.isOptionsAccount) { marginService.loadMargin(account) } else { defaultMargin } return validateOrder(order, margin) } Invoke suspending funs Write regular code!

Slide 53

Slide 53 text

Suspend is efficient suspend fun placeOrder(order: Order): Response { val account = accountService.loadAccount(order.accountId) val margin = marginService.loadMargin(account) return validateOrder(order, margin) } One object allocated

Slide 54

Slide 54 text

Futures/Promises/Reactive – less efficient fun placeOrder(order: Order): Mono = accountService.loadAccountAsync(order.accountId) .flatMap { account -> marginService.loadMargin(account) } .map { margin -> validateOrder(order, margin) } Lambda allocated* Future allocated Lambda allocated Future allocated

Slide 55

Slide 55 text

Let’s go deeper fun placeOrder(params: Params): Mono { // check pre-conditions return actuallyPlaceOrder(order) } fun actuallyPlaceOrder(order: Order): Mono

Slide 56

Slide 56 text

Let’s go deeper (with coroutines) suspend fun placeOrder(params: Params): Response { // check pre-conditions return actuallyPlaceOrder(order) } suspend fun actuallyPlaceOrder(params: Params): Response Tail call optimization Tail call

Slide 57

Slide 57 text

Call stack with coroutines Coroutine Builder placeOrder actuallyPlaceOrder moreLogic marginService.loadMargin suspendCoroutine

Slide 58

Slide 58 text

Call stack with coroutines Coroutine Builder placeOrder actuallyPlaceOrder moreLogic marginService.loadMargin suspendCoroutine unwind Continuation in heap

Slide 59

Slide 59 text

Scaling with coroutines With thread pools

Slide 60

Slide 60 text

ET 2 ET N … Executor Threads Clients Thread pools ET 1

Slide 61

Slide 61 text

ET 2 ET N … Executor Threads Clients Thread pools ET 1 Service 1 Threads ST 2 ST M1 … S1 1 N = number of CPU cores M 1 = depends

Slide 62

Slide 62 text

IO-bound (blocking) fun loadAccount(order: Order): Account { // some blocking code here.... }

Slide 63

Slide 63 text

IO-bound suspend fun loadAccount(order: Order): Account { // some blocking code here.... }

Slide 64

Slide 64 text

IO-bound withContext suspend fun loadAccount(order: Order): Account = withContext(dispatcher) { // some blocking code here.... }

Slide 65

Slide 65 text

IO-bound withContext suspend fun loadAccount(order: Order): Account = withContext(dispatcher) { // some blocking code here.... } val dispatcher = Executors.newFixedThreadPool(M2).asCoroutineDispatcher()

Slide 66

Slide 66 text

CPU-bound code fun validateOrder(order: Order, margin: Margin): Response { // perform CPU-consuming computation }

Slide 67

Slide 67 text

CPU-bound code suspend fun validateOrder(order: Order, margin: Margin): Response = withContext(compute) { // perform CPU-consuming computation } val compute = Executors.newFixedThreadPool(M3).asCoroutineDispatcher()

Slide 68

Slide 68 text

ET 2 ET N … Executor Threads Clients Fine-grained control and encapsulation ET 1 Service 1 Threads S1 1 ST M1 Service 2 Threads S1 1 ST M 2 Service 3 Threads S1 1 ST M 3 Async IO-bound CPU-bound Never blocked

Slide 69

Slide 69 text

But there’s more!

Slide 70

Slide 70 text

Cancellation

Slide 71

Slide 71 text

withTimeout suspend fun placeOrder(order: Order): Response = withTimeout(1000) { // code before loadMargin(account) // code after }

Slide 72

Slide 72 text

withTimeout propagation suspend fun placeOrder(order: Order): Response = withTimeout(1000) { // code before loadMargin(account) // code after } suspend fun loadMargin(account: Account): Margin = suspendCoroutine { cont -> // install callback & use cont to resume }

Slide 73

Slide 73 text

withTimeout propagation suspend fun placeOrder(order: Order): Response = withTimeout(1000) { // code before loadMargin(account) // code after } suspend fun loadMargin(account: Account): Margin = suspendCancellableCoroutine { cont -> // install callback & use cont to resume }

Slide 74

Slide 74 text

withTimeout propagation suspend fun placeOrder(order: Order): Response = withTimeout(1000) { // code before loadMargin(account) // code after } suspend fun loadMargin(account: Account): Margin = suspendCancellableCoroutine { cont -> // install callback & use cont to resume cont.invokeOnCancellation { … } }

Slide 75

Slide 75 text

Concurrency Multiple things at the same time

Slide 76

Slide 76 text

Example fun placeOrder(order: Order): Response { val account = accountService.loadAccount(order) val margin = marginService.loadMargin(order) return validateOrder(order, account, margin) }

Slide 77

Slide 77 text

Example fun placeOrder(order: Order): Response { val account = accountService.loadAccount(order) val margin = marginService.loadMargin(order) return validateOrder(order, account, margin) } No data dependencies

Slide 78

Slide 78 text

Concurrency with async (futures) fun placeOrder(order: Order): Response { val account = accountService.loadAccountAsync(order) val margin = marginService.loadMarginAsync(order) return validateOrder(order, account.await(), margin.await()) }

Slide 79

Slide 79 text

Concurrency with async (futures) fun placeOrder(order: Order): Response { val account = accountService.loadAccountAsync(order) val margin = marginService.loadMarginAsync(order) return validateOrder(order, account.await(), margin.await()) }

Slide 80

Slide 80 text

Concurrency with async (futures) fun placeOrder(order: Order): Response { val account = accountService.loadAccountAsync(order) val margin = marginService.loadMarginAsync(order) return validateOrder(order, account.await(), margin.await()) } Fails?

Slide 81

Slide 81 text

Concurrency with async (futures) fun placeOrder(order: Order): Response { val account = accountService.loadAccountAsync(order) val margin = marginService.loadMarginAsync(order) return validateOrder(order, account.await(), margin.await()) } Fails? Leaks!

Slide 82

Slide 82 text

Structured concurrency

Slide 83

Slide 83 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) }

Slide 84

Slide 84 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) }

Slide 85

Slide 85 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) }

Slide 86

Slide 86 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) } Fails?

Slide 87

Slide 87 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) } Fails? Cancels

Slide 88

Slide 88 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) } Fails? Cancels Cancels

Slide 89

Slide 89 text

Concurrency with coroutines suspend fun placeOrder(order: Order): Response = coroutineScope { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } validateOrder(order, account.await(), margin.await()) } Waits for completion of all children

Slide 90

Slide 90 text

Enforcing structure

Slide 91

Slide 91 text

Without coroutine scope? suspend fun placeOrder(order: Order): Response { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } return validateOrder(order, account.await(), margin.await()) }

Slide 92

Slide 92 text

Without coroutine scope? suspend fun placeOrder(order: Order): Response { val account = async { accountService.loadAccount(order) } val margin = async { marginService.loadMargin(order) } return validateOrder(order, account.await(), margin.await()) } ERROR: Unresolved reference.

Slide 93

Slide 93 text

Extensions of CoroutineScope fun CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred

Slide 94

Slide 94 text

Convention fun CoroutineScope.bg(params: Params) = launch { // … } Launches new coroutine

Slide 95

Slide 95 text

Types as documentation fun foo(params: Params): Response suspend fun foo(params: Params): Response fun CoroutineScope.foo(params: Params): Response Fast, local Remote, or slow Side effect - bg

Slide 96

Slide 96 text

Types are enforced fun foo(params: Params): Response suspend fun foo(params: Params): Response fun CoroutineScope.foo(params: Params): Response Not allowed But must provide scope explicitly Using coroutineScope { … } Fast, local Remote, or slow Side effect - bg

Slide 97

Slide 97 text

Green threads / fibers Alternative way to async

Slide 98

Slide 98 text

Green threads / Fibers ET 2 ET N … Executor Threads ET 1 F 2 F M … Fibers F 1 ~ Coroutines Hidden from developer

Slide 99

Slide 99 text

Fibers promise •Develop just like with threads •Everything is effectively suspendable

Slide 100

Slide 100 text

Marking with suspend pays off at scale

Slide 101

Slide 101 text

Thread switching And how to avoid it

Slide 102

Slide 102 text

ET 2 ET N … Executor Threads Clients Threads ET 1 Service 1 Threads S1 1 ST M1 Service 2 Threads S1 1 ST M 2 Service 3 Threads S1 1 ST M 3

Slide 103

Slide 103 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1

Slide 104

Slide 104 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1!

Slide 105

Slide 105 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1! ET N+1

Slide 106

Slide 106 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1! ET N+1 ! ET N+2

Slide 107

Slide 107 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1! ET N+1 ! … ET M! ET N+2 ET N+M

Slide 108

Slide 108 text

withContext for IO suspend fun loadAccount(order: Order): Account = withContext(dispatcher) { // some blocking code here.... } val dispatcher = Executors.newFixedThreadPool(M2).asCoroutineDispatcher()

Slide 109

Slide 109 text

withContext for Dispatсhers.IO suspend fun loadAccount(order: Order): Account = withContext(Dispatchers.IO) { // some blocking code here.... } No thread switch from Dispatchers.Default pool

Slide 110

Slide 110 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1 Dispatchers.Default

Slide 111

Slide 111 text

ET 2 ET N … Executor Threads Clients Solution – shared thread pool ET 1! ET N+1 ! … ET M! ET N+2 ET N+M Dispatchers.Default Dispatchers.IO

Slide 112

Slide 112 text

Coroutines and data streams

Slide 113

Slide 113 text

Returning many responses suspend fun foo(params: Params): Response One response suspend fun foo(params: Params): List Many responses suspend fun foo(params: Params): ???? Many responses async?

Slide 114

Slide 114 text

Channel receive() send()

Slide 115

Slide 115 text

Producer Builder fun CoroutineScope.foo(): ReceiveChannel = produce { for (i in 1..10) { send(i) delay(100) } } Channel type Can be async

Slide 116

Slide 116 text

Consumer fun CoroutineScope.foo(): ReceiveChannel = produce { for (i in 1..10) { send(i) delay(100) } } fun main() = runBlocking { for (x in foo()) { println(x) } }

Slide 117

Slide 117 text

Where’s the catch? fun CoroutineScope.foo(): ReceiveChannel = produce { for (i in 1..10) { send(i) delay(100) } } fun main() = runBlocking { for (x in foo()) { println(x) } }

Slide 118

Slide 118 text

Where’s the catch? fun CoroutineScope.foo(): ReceiveChannel = produce { for (i in 1..10) { send(i) delay(100) } } fun main() = runBlocking { for (x in foo()) { println(x) } } Creates coroutine

Slide 119

Slide 119 text

Try this! fun CoroutineScope.foo(): ReceiveChannel = produce { for (i in 1..10) { send(i) delay(100) } } fun main() = runBlocking { foo() } Waits for completion of children !

Slide 120

Slide 120 text

Kotlin Flows Disclaimer: available in preview only, not stable yet

Slide 121

Slide 121 text

Flow example fun bar(): Flow = flow { for (i in 1..10) { emit(i) delay(100) } } ~ Asynchronous sequence

Slide 122

Slide 122 text

Flow example fun bar(): Flow = flow { for (i in 1..10) { emit(i) delay(100) } } fun main() = runBlocking { bar().collect { x -> println(x) } }

Slide 123

Slide 123 text

Try this! fun bar(): Flow = flow { for (i in 1..10) { emit(i) delay(100) } } fun main() = runBlocking { bar() } Flow is cold: describes the data, does not run it until collected !

Slide 124

Slide 124 text

Flow example fun bar(): Flow = flow { for (i in 1..10) { emit(i) delay(100) } } fun main() = runBlocking { bar() .map { it * it } .toList() } Write regular code! Similar to collections / sequences

Slide 125

Slide 125 text

Thank you Want to learn more? Questions? elizarov @ Roman Elizarov relizarov

Slide 126

Slide 126 text

No content