Using MutableStateFlow as a thread-safe delegate? ...
# multiplatform
m
Using MutableStateFlow as a thread-safe delegate? TIL about
MutableStateFlow
. Because the
value
property is thread-safe, does it make sense to use
MutableStateFlow
as a multiplatform, thread-safe delegate, like this?
Copy code
class ThreadSafeMap<K, V>(
    vararg pairs: Pair<K, V>
) : MutableMap<K, V> by MutableStateFlow(
    mutableMapOf(*pairs)
).value
n
This will only make access to the reference of the map thread-safe, but that won't make the underlying
MutableMap
thread-safe since you're effectively exposing it to the caller as-is (through delegation). To make it thread-safe you'd have to implement the
MutableMap
interface yourself while making sure every operation you perform is thread-safe.
m
That’s what I suspected. It is possible to override functions of a delegate, but there isn’t a way to get access to the
MutableStateFlow
object when using delegation. (The delegate is the
MutableMap
it contains.) Is there a way to get access to the delegate object within the delegating class?
Here is part of a
MutableMap
implementation without delegation:
Copy code
class ThreadSafeMap<K, V>(
    vararg pairs: Pair<K, V>
): MutableMap<K, V> {

    private val _mapFlow = MutableStateFlow(mutableMapOf(*pairs))

    // Read-only property
    override val keys: MutableSet<K>
        get() = _mapFlow.value.entries

    // Function without a return value
    override fun clear(): Unit = _mapFlow.update { it.apply { clear() } }

    // Function that returns a value
    override fun remove(key: K): V? {
        var previousValue: V? = null
        // The lambda provided to MutableStateFlow.update()
        // needs to return the mutable map
        _mapFlow.update { it.apply { previousValue = remove(key) } }
        return previousValue
    }

    // etc.
}
I think this would work.
n
You'd think so since the
update
function of
MutableStateFlow
is atomic, but in reality, as long as you're storing and mutating a
MutableMap
in your
MutableStateFlow
you will find it hard to make it "thread-safe". A "simple" version of a thread-safe
MutableMap
could be implemented using locks from atomic-fu, or by storing an immutable
Map
in your
MutableStateFlow
and just updating that in your
update
function. Doing so you may as well use
atomic<T>
from atomic-fu, e.g. like in this snippet.
How will you be modifying the
Map
though? If you're only modifying it from a single thread, then you can just use an immutable
Map
stored in an
AtomicRef
and read it from as many threads as you like. You only need the "mutation thread safety" if you're actually mutating the
Map
from several threads
m
My use case is rarely-modified configuration and other information stored in global objects by the Klogging library. I have not been concerned about concurrent modification but it is possible: this GitHub issue has a discussion outlines the matter, where I offered my opinion and made a fix for JVM using
ConcurrentHashMap
. But I want to make Klogging multiplatform so I need a different solution. I had looked a bit at atomic-fu but wasn’t sure if was still experimental.
n
Considering kotlinx-atomicfu is used extensively in kotlinx.coroutines I would say you're no less experimental than if you use
MutableStateFlow
😄
I forgot to mention it, but in the code I provided in my snippet you can implement any read operation simply like this:
Copy code
fun get(key: K): V? = map.value.get(key)
Because the underlying
Map
is immutable (I have updated my snippet)