Kotlin/Native - Transferring State
Tags: kotlin, kotlinmultiplatform, android, ios
Published: 2020-01-26 16:28:58

---
title: Kotlin/Native - Transferring State
published: true
description: About transferring mutable state between threads.
tags: kotlin, kotlinmultiplatform, android, ios
series: Kotlin Native Concurrent Mutable State
---

Kotlin/Native (KN) has special rules around state and concurrency. We cover them in usable detail in [Practical Kotlin Native Concurrency](https://dev.to/touchlab/practical-kotlin-native-concurrency-ac7) and in more detail in [KotlinConf 2019: Kotlin Native Concurrency Explained](https://www.youtube.com/watch?v=oxQ6e1VeH4M).

There are basically two rules. Mutable state can only be accessed by one thread at a time, and immutable state can be shared. When sharing state we freeze it, and mutable state generally stays thread confined. However, you *can* pass mutable state between threads. In all of my discussions, I mostly ignore that possibility, because it's fairly impractical and we don't use it anywhere (in production), but today we'll discuss it a bit.

## Transferring State

In order to transfer mutable state between threads, you need to make sure there are no external references to it. The `Worker.execute` function and the `DetachedObjectGraph` constructor both take a `producer` function argument. The purpose of this function is to "produce" the state that you want to transfer, in such a way that all external references can be omitted.

That is the first complication and critical concept to understand. You'll often want to add mutable data to a `DetachedObjectGraph` from existing state, but this is syntactically difficult.

```kotlin
    @Test
    fun failLocal(){
        val d = Dat("Hello")
        assertFails {
            DetachedObjectGraph {d}
        }
    }

    data class Dat(val s:String)
```

The `DetachedObjectGraph` constructor takes a lambda producer argument. In it we try to return `d`, but `d` is still referenced from outside the lambda, specifically here in the local `val d`, so the transfer fails.

This case is overly simplistic. You can "solve" it with the following.

```kotlin
    @Test
    fun directReturn(){
        DetachedObjectGraph {Dat("Hello")}
    }

    data class Dat(val s:String)
```

The `Dat` instance returned from the producer has no external references, so you can transfer it. However, use your imagination and extrapolate how difficult it will be to maintain mutable state in a `DetachedObjectGraph` and pass it around. It quickly becomes impractical.

I would abbreviate `DetachedObjectGraph` with DOG, but `DetachedObjectGraph` is not your friend, and dogs are, so I will use the full name `DetachedObjectGraph` and clutter your visual space because we don't like them. Anyway...

You can build some managed access, with something like the following.

```kotlin
class SharedDetachedObject<T:Any>(producer: () -> T) {
    private val adog :AtomicReference<DetachedObjectGraph<Any>?>
    private val lock = Lock()

    init {
        val detachedObjectGraph = DetachedObjectGraph { producer() as Any }.freeze()
        adog = AtomicReference(detachedObjectGraph.freeze())
    }

    fun <R> access(block: (T) -> R): R = lock.withLock{
        val holder = FreezableAtomicReference<Any?>(null)
        val producer = { grabAccess(holder, block) as Any }
        adog.value = DetachedObjectGraph(TransferMode.SAFE, producer).freeze()
        val retult = holder.value!!
        holder.value = null
        retult as R
    }

    private fun <R> grabAccess(holder:FreezableAtomicReference<Any?>, block: (T) -> R):T{
        val attach = adog.value!!.attach()
        val t = attach as T
        holder.value = block(t)
        return t
    }

    fun clear(){
        adog.value?.attach()
    }
}
```

You can find this code in a new [Stately branch](https://github.com/touchlab/Stately/blob/kpg/iso_state/stately-isolate/src/nativeCommonMain/kotlin/co/touchlab/stately/native/SharedDetachedObject.kt#L12).

This is somewhat complex to look at, but the concept is relatively simple. You create an instance of `SharedDetachedObject` with a producer, similar to the previous examples. The state returned is kept in a `DetachedObjectGraph`, and you can access that state with the `access` method. From that method you can return a value, although make absolutely sure it isn't the mutable state you're holding in the `DetachedObjectGraph`.

```kotlin
val detachedObject = SharedDetachedObject { mutableListOf("a", "b")}

repeat(50_000){rcount ->
    detachedObject.access {
        val element = "row $rcount"
        it.add(element)
        element
    }
}
```

The code above creates a mutable list that can be accessed in a shared way, from multiple threads. Anything leaving the `access` lambda that's still referenced from inside it should be frozen, of course (`String` gets special treatment in KN. It's always frozen).

OK! Transferring state may be syntactically messy, but it works, right! Sure, but there's another important consideration.

## Performance

Transferring state requires that you inspect all of the state you want to transfer, to ensure nobody external is referencing it. *All* of the state. In the example above, that means on the last run, all 50k entries. Doing that isn't free.

Imagine a common use case. Building a HashMap cache. One of the primary benefits of a HashMap is read and write time. In the good case, they're constant time. Keeping a map in a `DetachedObjectGraph` sounds like a good idea, but each access of the map is followed by detaching so you can put it back in the `DetachedObjectGraph` for another thread to use. That turns constant time into linear time. A linear time hash map is a shitty hash map.

In our simple list case above, the time per-insert grows as the list gets larger.

We have a new set of concurrent/mutable state holders coming out for Stately. You can see the [current branch here](https://github.com/touchlab/Stately/tree/kpg/iso_state). In the next post we'll cover how to use the new state objects.

## Hiring!

[Touchlab is hiring!](https://go.touchlab.co/tldevto) Looking for Android-focused mobile developers, and also for experienced or very interested Kotlin Multiplatform devs. We *really* need to find a solid Android dev at this precise moment, though. Just FYI. Remote friendly (US-only, for now).