$30 off During Our Annual Pro Sale. View Details »

Kotlin/Native Concurrency for Mobile Multiplatform

Kotlin/Native Concurrency for Mobile Multiplatform

If you're going to build shared Kotlin code with Kotlin/Native, you'll need to understand the concurrency and state model. In this talk, I give you the basics (and a bit more), plus next steps.

Kevin Galligan

June 05, 2020
Tweet

More Decks by Kevin Galligan

Other Decks in Technology

Transcript

  1. Kotlin Native Concurrency
    Kevin Galligan

    View Slide

  2. View Slide

  3. View Slide

  4. Copenhagen
    Denmark
    KOTLIN NATIVE
    CONCURRENCY
    EXPLAINED
    KEVIN GALLIGAN
    @kpgalligan

    View Slide

  5. View Slide

  6. Intro

    View Slide

  7. What is Kotlin Multiplatform?

    View Slide

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

    View Slide

  9. modern

    View Slide

  10. Saner Concurrency?
    concurrency is hard

    View Slide

  11. View Slide

  12. Kotlin Native model is controversial
    lots of disagreement

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. important to relax

    View Slide

  18. Kotlin Native Rules

    View Slide

  19. 1) Mutable State = one thread
    thread confined

    View Slide

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

    View Slide

  21. [email protected]
    @kpgalligan

    View Slide

  22. Internalize The Rules
    mutable = 1/immutable = many

    View Slide

  23. object BigGlobalState {
    var statusString = ""
    }

    View Slide

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

    View Slide

  25. JVM Trusts You
    native does not

    View Slide

  26. Runtime Verification

    View Slide

  27. Do Things Differently
    not “losing” anything

    View Slide

  28. (jvm strict mode would be nice)

    View Slide

  29. You can break rules too
    just not a great idea

    View Slide

  30. Stages of grief
    skip ahead to acceptance

    View Slide

  31. Core Concepts

    View Slide

  32. Worker
    kn concurrency queue

    View Slide

  33. Similar to ExecutorService
    Handler/MessageQueue/Looper on Android

    View Slide

  34. Worker
    kn concurrency queue

    View Slide

  35. Mutability & State

    View Slide

  36. 1) Mutable State = one thread
    simple state is simple

    View Slide

  37. 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}")
    }

    View Slide

  38. 1) Mutable State = one thread
    you can transfer

    View Slide

  39. 1) Mutable State = one thread
    you can transfer

    View Slide

  40. 2) Immutable state = many threads
    not source immutable

    View Slide

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

    View Slide

  42. freeze()
    runtime immutability designation

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  46. Crossing Threads

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  59. Functions Have State
    careful what you capture

    View Slide

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

    View Slide

  61. Global State
    some special rules

    View Slide

  62. var someData = SomeData("a", 1)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. Why Special Rules?
    you can access it from anywhere

    View Slide

  68. Debugging Issues

    View Slide

  69. InvalidMutabilityException
    changing frozen state

    View Slide

  70. Why is this frozen()
    common question

    View Slide

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

    View Slide

  72. ensureNeverFrozen()

    View Slide

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

    View Slide

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

    View Slide

  75. Rethinking architecture
    intentional mutability

    View Slide

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

    View Slide

  77. This can get weird
    subtle things

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  81. View Slide

  82. This may be frustrating
    but should make you think

    View Slide

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

    View Slide

  84. Concurrent Mutability

    View Slide

  85. Atomics
    mutating frozen state

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. Don’t use too many atomics
    AtomicRef can leak memory

    View Slide

  91. Worker dispatched state
    mutable state thread local

    View Slide

  92. State Thread
    IsolateState
    ???
    State Container
    Data

    View Slide

  93. State Thread
    IsolateState
    ???
    State Container
    ???
    Data

    View Slide

  94. open class IsolateState

    View Slide

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

    View Slide

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

    View Slide

  97. IsoArrayDeque
    IsoMutableCollection
    IsoMutableIterator
    IsoMutableList
    IsoMutableMap
    IsoMutableSet

    View Slide

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

    View Slide

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

    View Slide

  100. Multiplatform & State

    View Slide

  101. Multiplatform State

    View Slide

  102. freeze() is native only

    View Slide

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

    View Slide

  104. freeze() on iOS
    threading rules apply

    View Slide

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

    View Slide

  106. Concurrency Libraries

    View Slide

  107. Official Native kotlinx.coroutines
    single-threaded

    View Slide

  108. ktor on native
    called on main thread

    View Slide

  109. multithreaded coroutines!

    View Slide

  110. multithreaded coroutines?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  119. Flow, Channel, etc
    all freezable

    View Slide

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

    View Slide

  121. iOS Suspend Functions
    new for 1.4!

    View Slide

  122. Stately

    View Slide

  123. Stately (Probably) deprecated

    View Slide

  124. Stately
    v1! (ish)

    View Slide

  125. Modules
    • stately-common
    • stately-collections
    • stately-concurrency

    View Slide

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

    View Slide

  127. github.com/Autodesk/coroutineworker

    View Slide

  128. github.com/Badoo/reaktive

    View Slide

  129. Next Steps

    View Slide

  130. go.touchlab.co/kotlinlang

    View Slide

  131. go.touchlab.co/kndevto

    View Slide

  132. go.touchlab.co/knthreads

    View Slide

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

    View Slide

  134. github.com/touchlab/KaMPKit

    View Slide

  135. Thanks!
    @kpgalligan
    touchlab.co

    View Slide