Kotlin/Native - Isolated State
Tags: kotlin, kotlinmultiplatform, android, ios
Published: 2020-01-26 19:40:27

---
title: Kotlin/Native - Isolated State
published: true
description: You can have concurrent mutable state with reasonable performance by keeping it isolated to a single thread.
tags: kotlin, kotlinmultiplatform, android, ios
series: Kotlin Native Concurrent Mutable State
---

In the previous post we discussed transferring state. I started there because recently several people posted in the Kotlin slack discussing `DetachedObjectGraph`.

In my particular journey from JVM to Kotlin/Native (or KN), I went through a phase I think many new KN devs go through. In the JVM I was used to sharing mutable state between threads, and I assumed I must retain that ability at all costs. `DetachedObjectGraph` is one of the first things I started to focus on.

It seems like interest in KMP has picked up considerably this first month of 2020, and so we've had a lot of questions about `DetachedObjectGraph`. I have found that `DetachedObjectGraph` is of limited value, and once I understood the KN state model, I found I never needed it.

You will, however, occasionally need mutable state between threads.

## Stately Collections V1

Going back to mid/late 2018, we released Stately. 

{% github touchlab/Stately no-readme %}

Along with atomics, locks, and some other concurrency support stuff, there was also a set of concurrent collection classes. These internally used `AtomicRef` to implement collections. That meant I actually implemented a linked list and a hash map from scratch with `AtomicRef`.

These collections work, but performance is bad, their implementation is basic, and their use is inflexible. Also, just FYI, `AtomicRef` can leak memory, so you need to clear these collections when you're done with them.

As it turns out, if you just keep your state isolated, you can implement concurrent collections in an almost trivial manner, with far better performance.

## Stately Collections V2 (Isolated State)

The next version of Stately will have a class called `IsolateState`. You create an instance in a manner similar to how you interact with `DetachedObjectGraph`. Pass in a producer lambda. One critical difference. The value returned from that lambda cannot be frozen.

Once you create this instance of `IsolateState`, you interact with it by way of the `access` method. `access` takes a lambda, which takes one argument: the mutable state.

As an example, let's create a simple shared map:

```kotlin
object StateSample {
    val cacheMap = IsolateState { mutableMapOf<String, SomeData>() }
}

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

`cacheMap` is a simple mutable map, with `String` keys and `SomeData` values.

To be clear, according to KN state rules, `object StateSample` is frozen, so `cacheMap` is also frozen. The state it contains, the result of `mutableMapOf<String, SomeData>() `, is *not* frozen, and can never be (we call `ensureNeverFrozen()` internally).

Getting to that state and doing stuff with it is easy:

```kotlin
fun doStuff(){
    StateSample.cacheMap.access {map -> 
        map.put("Hello", SomeData("World"))
    }
}
```

Getting values from the state is also straightforward:

```kotlin
fun readStuff(){
    val sd = StateSample.cacheMap.access { it.get("Hello") }
    println("sd: $sd, isFrozen: ${sd.isFrozen()}")
}
```

Note, the value returned from `access` will be frozen, as is anything captured and passed into `access`.

There will be a separate `stately-iso-collections` module, but because isolated state is so flexible, I'm not sure how necessary a full `MutableMap` implementation, for example, would be as opposed to just using `access` to get at the methods you need. I didn't want to clutter the middle of the post, but I've included the entire implementation of a `MutableMap` using `IsolateState` at the bottom. It really is bordering on trivial.

One of the more inflexible aspects of any concurrent collection is the lack of atomic operations. Calling `size` will return a value, but if you want to insert a value based on that result, you could have race conditions.

Take the following example:

```kotlin
fun racyInABadWay(fmap:HashMap<String, SomeData>){
    val size = fmap.size
    fmap.put("i $size", SomeData("data $size")) // <- may result in missed values
}
```

Assume `fmap` is a concurrent map from Stately v1 collections. Between the call to get size and the call to put a value, the size could change. Your `put` call will be invalid. You could fix this by creating a `Lock` instance that's associated with `fmap`, then passing them both around, but that's ugly.

Using `IsolateState`, this is again trivial.

```kotlin
fun atomicOperations(isoMap: IsolateState<MutableMap<String, SomeData>>){
    isoMap.access { map ->
        map.put("i ${map.size}", SomeData("data ${map.size}"))
    }
}
```

Under the hood, all `access` calls run on the same thread, so while your block is running, nothing can change the map. You can implement arbitrarily complex atomic operations.

## Performace

Performance is kind of relative. Inserting 50k elements in a v1 Stately map vs v2 is pretty good.

```kotlin
    val times = 50_000
    val v1Time = measureTimeMillis {
        val fmap = frozenHashMap<String, SomeData>()
        repeat(times){c ->
            fmap.put("i $c", SomeData("data $c"))
        }
    }

    println("v1Time: $v1Time")

    val isoTime = measureTimeMillis {
        val isomap = IsolateState{ mutableMapOf<String, SomeData>()}
        repeat(times){c ->
            isomap.access { it.put("i $c", SomeData("data $c")) }
        }
    }

    println("isoTime: $isoTime")

```

The result:

```
v1Time: 13785
isoTime: 1937
```

Compared to using a regular `MutableMap`? Well, not as good.

```kotlin
    val normalTime = measureTimeMillis {
        val map = mutableMapOf<String, SomeData>()
        repeat(times){c ->
            map.put("i $c", SomeData("data $c"))
        }
    }

    println("normalTime: $normalTime")
```

```
isoTime: 1937
normalTime: 201
```

Of course, you're crossing thread contexts for each call to the `IsolateState` instance. There's a lot of overhead involved. Because `IsolateState` is inherently more flexible than a fixed concurrent map implementation, inserting in blocks is pretty easy to implement. The performance boost is dramatic.

```kotlin
    val isoBlockTime = measureTimeMillis {
        val isomap = IsolateState{ mutableMapOf<String, SomeData>()}
        val outerTimes = times / 1000
        repeat(outerTimes){ c ->
            val blockMap = mutableMapOf<String, SomeData>()
            repeat(1000){inner ->
                val putCount = (c * 1000) + inner
                blockMap.put("i $putCount", SomeData("data $putCount"))
            }

            isomap.access { it.putAll(blockMap) }
        }
    }

    println("isoBlockTime: $isoBlockTime")
```
```
normalTime: 201
isoBlockTime: 330
```

You've inserted 50k elements into a concurrent map structure in a time that's ~65% slower than a local mutable map. It's "slower", relatively, but that performance is fantastic all things considered. That's also without `inline` on any of the `IsolateState` methods, which may slightly improve the situation.

Can you insert in Stately v1's collections in blocks? Sure, but it's rigid, and because they're implemented with `AtomicRef`, the performance is dismal.

```kotlin
    val v1BlockTime = measureTimeMillis {
        val fmap = frozenHashMap<String, SomeData>()
        val outerTimes = times / 1000
        repeat(outerTimes){ c ->
            val blockMap = mutableMapOf<String, SomeData>()
            repeat(1000){inner ->
                val putCount = (c * 1000) + inner

                blockMap.put("i $putCount", SomeData("data $putCount"))
            }

            fmap.putAll(blockMap)
        }
    }

    println("v1BlockTime: $v1BlockTime")
```

It's the same as inserting values individually.

```
v1Time: 13785
isoTime: 1937
normalTime: 201
isoBlockTime: 330
v1BlockTime: 13623
```

To round out our timing comparison, if you use `DetachedObjectGraph` to insert 50k elements, things get much, much worse.

```kotlin
fun timeTest() {
    val times = 50_000
    val detachedTime = measureTimeMillis {
        val detachedMap = SharedDetachedObject { mutableMapOf<String, SomeData>() }
        detachedMap.freeze()
        repeat(times){c ->
            val sd = SomeData("data $c")
            detachedMap.access {
                it.put("i $c", sd)
            }
        }
    }

    println("detachedTime: $detachedTime")
}
```

```
detachedTime: 68334
```

That's about 35x slower than using `IsolateState`. It'll also get worse as the size grows because you lose the Big O advantage of a hash map. You could certainly optimize access to `DetachedObjectGraph` a bit, but what's the point?

In summary, use `IsolateState` for KMP concurrent state.

We've published an experimental version of Stately to try this out:

```groovy
implementation "co.touchlab:stately-isolate:0.10.2"
```

The documentation hasn't been updated, but you can see samples here:

{% github touchlab/StatelyIsoStateSample no-readme %}

We should have a more formal version soon, pending community feedback.

## 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). 

-----
## MutableMap Implementation

Here's the code for a `MutableMap` implemented with `IsolateState`. We're essentially just wrapping `mutableMapOf<K, V>()`.

```kotlin
class IsoMutableMap<K, V>(producer: () -> MutableMap<K, V> = { mutableMapOf() })
    : IsolateState<MutableMap<K, V>>(createState(producer)), MutableMap<K, V> {
    override val size: Int
        get() = access { it.size }

    override fun containsKey(key: K): Boolean = access { it.containsKey(key) }
    override fun containsValue(value: V): Boolean = access { it.containsValue(value) }
    override fun get(key: K): V? = access { it.get(key) }
    override fun isEmpty(): Boolean = access { it.isEmpty() }
    override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
        get() = access { IsoMutableSet(StateHolder(it.entries)) }
    override val keys: MutableSet<K>
        get() = access { IsoMutableSet(StateHolder(it.keys)) }
    override val values: MutableCollection<V>
        get() = access { IsoMutableCollection(StateHolder(it.values)) }
    override fun clear() = access { it.clear() }
    override fun put(key: K, value: V): V? = access { it.put(key, value) }
    override fun putAll(from: Map<out K, V>) = access { it.putAll(from) }
    override fun remove(key: K): V? = access { it.remove(key) }
}

open class IsoMutableCollection<T> internal constructor(stateHolder: StateHolder<MutableCollection<T>>) :
    IsolateState<MutableCollection<T>>(stateHolder), MutableCollection<T> {
    constructor(producer: () -> MutableCollection<T>) : this(createState(producer))
    override val size: Int
        get() = access { it.size }

    override fun contains(element: T): Boolean = access { it.contains(element) }
    override fun containsAll(elements: Collection<T>): Boolean = access { it.containsAll(elements) }
    override fun isEmpty(): Boolean = access { it.isEmpty() }
    override fun add(element: T): Boolean = access { it.add(element) }
    override fun addAll(elements: Collection<T>): Boolean = access { it.addAll(elements) }
    override fun clear() = access { it.clear() }
    override fun iterator(): MutableIterator<T> = access { IsoMutableIterator(StateHolder(it.iterator())) }
    override fun remove(element: T): Boolean = access { it.remove(element) }
    override fun removeAll(elements: Collection<T>): Boolean = access { it.removeAll(elements) }
    override fun retainAll(elements: Collection<T>): Boolean = access { it.retainAll(elements) }
}

class IsoMutableSet<T> internal constructor(stateHolder: StateHolder<MutableSet<T>>) :
    IsoMutableCollection<T>(stateHolder), MutableSet<T> {
    constructor(producer: () -> MutableSet<T> = { mutableSetOf() }) : this(createState(producer))
}

class IsoMutableIterator<T> internal constructor(stateHolder: StateHolder<MutableIterator<T>>) :
    IsolateState<MutableIterator<T>>(stateHolder), MutableIterator<T> {
    override fun hasNext(): Boolean = access { it.hasNext() }
    override fun next(): T = access { it.next() }
}
```