Slide 1

Slide 1 text

Kotlin Native Concurrency Kevin Galligan

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Copenhagen Denmark KOTLIN NATIVE CONCURRENCY EXPLAINED KEVIN GALLIGAN @kpgalligan

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Intro

Slide 7

Slide 7 text

What is Kotlin Multiplatform?

Slide 8

Slide 8 text

kot·lin mul·ti·plat·form /ˌkätˈlin məltiˈplatfôrm,ˌkätˈlin məltīˈplatfôrm/ noun noun: kotlin multiplatform 1.optional, natively-integrated, open-source, code sharing platform, based on the popular, modern language kotlin. facilitates non-ui logic availability on many platforms.

Slide 9

Slide 9 text

modern

Slide 10

Slide 10 text

Saner Concurrency? concurrency is hard

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Kotlin Native model is controversial lots of disagreement

Slide 13

Slide 13 text

JVM model is controversial many other platforms don’t let you do that

Slide 14

Slide 14 text

JVM model is controversial many other platforms don’t let you do that

Slide 15

Slide 15 text

JVM model is controversial many other platforms don’t let you do that

Slide 16

Slide 16 text

You need to understand it libraries won’t hide all details

Slide 17

Slide 17 text

important to relax

Slide 18

Slide 18 text

Kotlin Native Rules

Slide 19

Slide 19 text

1) Mutable State = one thread thread confined

Slide 20

Slide 20 text

2) Immutable state = many threads no changes mean no problems

Slide 21

Slide 21 text

[email protected] @kpgalligan

Slide 22

Slide 22 text

Internalize The Rules mutable = 1/immutable = many

Slide 23

Slide 23 text

object BigGlobalState { var statusString = "" }

Slide 24

Slide 24 text

Don’t manage concurrent state no “synchronized”, “volatile”, etc

Slide 25

Slide 25 text

JVM Trusts You native does not

Slide 26

Slide 26 text

Runtime Verification

Slide 27

Slide 27 text

Do Things Differently not “losing” anything

Slide 28

Slide 28 text

(jvm strict mode would be nice)

Slide 29

Slide 29 text

You can break rules too just not a great idea

Slide 30

Slide 30 text

Stages of grief skip ahead to acceptance

Slide 31

Slide 31 text

Core Concepts

Slide 32

Slide 32 text

Worker kn concurrency queue

Slide 33

Slide 33 text

Similar to ExecutorService Handler/MessageQueue/Looper on Android

Slide 34

Slide 34 text

Worker kn concurrency queue

Slide 35

Slide 35 text

Mutability & State

Slide 36

Slide 36 text

1) Mutable State = one thread simple state is simple

Slide 37

Slide 37 text

class SimpleState { private var i = 0 val currVal: Int get() = i fun doStuff(arg: Int) { i += arg } } fun main(){ val s = SimpleState() s.doStuff(22) s.doStuff(33) println("My val is ${s.currVal}") }

Slide 38

Slide 38 text

1) Mutable State = one thread you can transfer

Slide 39

Slide 39 text

1) Mutable State = one thread you can transfer

Slide 40

Slide 40 text

2) Immutable state = many threads not source immutable

Slide 41

Slide 41 text

data class SomeData(val s:String, val i:Int) Mutable state?

Slide 42

Slide 42 text

freeze() runtime immutability designation

Slide 43

Slide 43 text

public fun T.freeze(): T {...}

Slide 44

Slide 44 text

Freeze • Recursively freezes everything • One-way (no “unfreeze”) • Data may be shared between threads Int String Float MoreData val strData var width SomeData val moreData var count

Slide 45

Slide 45 text

Int String Float Float String Int MoreData val strData var width SomeData val moreData var count

Slide 46

Slide 46 text

Crossing Threads

Slide 47

Slide 47 text

fun background(block:()->Unit)

Slide 48

Slide 48 text

fun background(block:()->Unit) block.freeze()

Slide 49

Slide 49 text

fun printStuffBg() { val someData = SomeData("Hello ", 2) background { println(someData) } }

Slide 50

Slide 50 text

fun printStuffBg() { val someData = SomeData("Hello ", 2) background { println(someData) } }

Slide 51

Slide 51 text

fun printStuffBg() { val someData = SomeData("Hello ", 2) background { println(someData) } }

Slide 52

Slide 52 text

fun printStuffBg() { val someData = SomeData("Hello ", 2) background { println(someData) } }

Slide 53

Slide 53 text

class CounterModel { var count = 0 fun countClicked() { count++ } }

Slide 54

Slide 54 text

class CounterModel { var count = 0 fun countClicked() { count++ background { saveToDb(count) } } }

Slide 55

Slide 55 text

class CounterModel { var count = 0 fun countClicked() { count++ background { saveToDb(count) } } }

Slide 56

Slide 56 text

class CounterModel { var count = 0 fun countClicked() { count++ background { saveToDb(count) } } }

Slide 57

Slide 57 text

class CounterModel { var count = 0 fun countClicked() { count++ background { saveToDb(count) } } }

Slide 58

Slide 58 text

class CounterModel { var count = 0 fun countClicked() { count++ background { saveToDb(count) } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel

Slide 59

Slide 59 text

Functions Have State careful what you capture

Slide 60

Slide 60 text

class CounterModel { var count = 0 fun countClicked() { count++ save(count) } private fun save(count:Int) = background { saveToDb(count) } }

Slide 61

Slide 61 text

Global State some special rules

Slide 62

Slide 62 text

var someData = SomeData("a", 1)

Slide 63

Slide 63 text

var someData = SomeData("a", 1) object GlobalObject { var data = SomeData("b", 2) }

Slide 64

Slide 64 text

@ThreadLocal var someData = SomeData("a", 1) @ThreadLocal object GlobalObject { var data = SomeData("b", 2) }

Slide 65

Slide 65 text

@SharedImmutable var someData = SomeData("a", 1) @ThreadLocal object GlobalObject { var data = SomeData("b", 2) }

Slide 66

Slide 66 text

var someData:SomeData set(v:SomeData)… get():SomeData = …

Slide 67

Slide 67 text

Why Special Rules? you can access it from anywhere

Slide 68

Slide 68 text

Debugging Issues

Slide 69

Slide 69 text

InvalidMutabilityException changing frozen state

Slide 70

Slide 70 text

Why is this frozen() common question

Slide 71

Slide 71 text

class CounterModel { var count = 0 fun countClicked() { count++ background { saveToDb(count) } } }

Slide 72

Slide 72 text

ensureNeverFrozen()

Slide 73

Slide 73 text

class CounterModel { init { ensureNeverFrozen() } var count = 0 fun countClicked() { count++ background { saveToDb(count) } } }

Slide 74

Slide 74 text

Int String Float Int MoreData val strData var width SomeData val moreData var count

Slide 75

Slide 75 text

Rethinking architecture intentional mutability

Slide 76

Slide 76 text

Main Thread Other Thread (maybe?) Data Data Data Content Model iOS View Model

Slide 77

Slide 77 text

This can get weird subtle things

Slide 78

Slide 78 text

class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }

Slide 79

Slide 79 text

class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() settings.set("Hey", "Bad idea") ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }

Slide 80

Slide 80 text

class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() settings.set("Hey", "Bad idea") ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

This may be frustrating but should make you think

Slide 83

Slide 83 text

class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }

Slide 84

Slide 84 text

Concurrent Mutability

Slide 85

Slide 85 text

Atomics mutating frozen state

Slide 86

Slide 86 text

class CounterModel { val count = AtomicInt(0) fun countClicked() { count.value++ background { saveToDb(count.value) } } }

Slide 87

Slide 87 text

class CounterModel { val count = AtomicInt(0) fun countClicked() { count.value++ background { saveToDb(count.value) } } }

Slide 88

Slide 88 text

val atom = AtomicReference(SomeData(“abc", 123))

Slide 89

Slide 89 text

class SomeModel { val atom = AtomicReference(SomeData(“abc", 123)) fun update(sd:SomeData){ atom.value = sd } init { freeze() } }

Slide 90

Slide 90 text

Don’t use too many atomics AtomicRef can leak memory

Slide 91

Slide 91 text

Worker dispatched state mutable state thread local

Slide 92

Slide 92 text

State Thread IsolateState ??? State Container Data

Slide 93

Slide 93 text

State Thread IsolateState ??? State Container ??? Data

Slide 94

Slide 94 text

open class IsolateState

Slide 95

Slide 95 text

open class IsolateState constructor(producer: () -> T)

Slide 96

Slide 96 text

open class IsolateState constructor(producer: () -> T) fun access(block: (T) -> R): R

Slide 97

Slide 97 text

IsoArrayDeque IsoMutableCollection IsoMutableIterator IsoMutableList IsoMutableMap IsoMutableSet

Slide 98

Slide 98 text

fun IsoMutableMap.checkFor2(first: K, second: K, ins: V)= access { map -> if (map.containsKey(first)) map.put(first, ins) else map.put(second, ins) }

Slide 99

Slide 99 text

Objective-C/C/C++ just do what you want!

Slide 100

Slide 100 text

Multiplatform & State

Slide 101

Slide 101 text

Multiplatform State

Slide 102

Slide 102 text

freeze() is native only

Slide 103

Slide 103 text

expect fun T.freeze(): T expect val T.isFrozen: Boolean expect fun Any.ensureNeverFrozen() expect annotation class Throws

Slide 104

Slide 104 text

freeze() on iOS threading rules apply

Slide 105

Slide 105 text

Swift/Objc can’t be frozen be aware of concurrency issues

Slide 106

Slide 106 text

Concurrency Libraries

Slide 107

Slide 107 text

Official Native kotlinx.coroutines single-threaded

Slide 108

Slide 108 text

ktor on native called on main thread

Slide 109

Slide 109 text

multithreaded coroutines!

Slide 110

Slide 110 text

multithreaded coroutines?

Slide 111

Slide 111 text

Switching Threads use withContext (or equivalents) data passed in (or captured) is frozen data returned is frozen

Slide 112

Slide 112 text

fun printStuffBg() { val someData = SomeData("Hello ", 2) background { println(someData) } }

Slide 113

Slide 113 text

suspend fun printStuffBg() { val someData = SomeData("Hello ", 2) withContext(workerDispatcher) { println(someData) } }

Slide 114

Slide 114 text

suspend fun printStuffBg() { val someData = SomeData("Hello ", 2) withContext(workerDispatcher) { println(someData) } }

Slide 115

Slide 115 text

suspend fun printStuffForeground() { val someData = SomeData("Hello ", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }

Slide 116

Slide 116 text

suspend fun printStuffForeground() { val someData = SomeData("Hello ", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }

Slide 117

Slide 117 text

suspend fun printStuffForeground() { val someData = SomeData("Hello ", 2) val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }

Slide 118

Slide 118 text

SomeData(s=Hello , Dogs!, i=3) I am frozen? true

Slide 119

Slide 119 text

Flow, Channel, etc all freezable

Slide 120

Slide 120 text

Main Thread Other Thread (maybe?) Data Data Data Content Model iOS View Model

Slide 121

Slide 121 text

iOS Suspend Functions new for 1.4!

Slide 122

Slide 122 text

Stately

Slide 123

Slide 123 text

Stately (Probably) deprecated

Slide 124

Slide 124 text

Stately v1! (ish)

Slide 125

Slide 125 text

Modules • stately-common • stately-collections • stately-concurrency

Slide 126

Slide 126 text

Modules • stately-isolate • stately-iso-collections • alpha. Feedback please!

Slide 127

Slide 127 text

github.com/Autodesk/coroutineworker

Slide 128

Slide 128 text

github.com/Badoo/reaktive

Slide 129

Slide 129 text

Next Steps

Slide 130

Slide 130 text

go.touchlab.co/kotlinlang

Slide 131

Slide 131 text

go.touchlab.co/kndevto

Slide 132

Slide 132 text

go.touchlab.co/knthreads

Slide 133

Slide 133 text

JetBrains Links • https://github.com/JetBrains/kotlin-native/blob/ master/IMMUTABILITY.md • https://github.com/JetBrains/kotlin-native/blob/ master/CONCURRENCY.md • https://www.youtube.com/watch?v=nw6YTfEyfO0

Slide 134

Slide 134 text

github.com/touchlab/KaMPKit

Slide 135

Slide 135 text

Thanks! @kpgalligan touchlab.co