I’ve got a `kotlin.native.concurrent.InvalidMutabi...
# kotlin-native
r
I’ve got a
kotlin.native.concurrent.InvalidMutabilityException
that I can’t explain.
Copy code
companion object {

        internal var xs: Set<X> by atomic(HashSet())
            private set

        fun registerX(x: X) {
            xs = xs + x
        }

    }
atomic
being:
Copy code
inline fun <reified T> atomic(initial: T) = AtomicDelegate(initial)
AtomicDelegate
being:
Copy code
class AtomicDelegate<T>(initial: T) : ReadWriteProperty<Any?, T> {

    private val atomic = AtomicReference(initial)

    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
        atomic.get()

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        atomic.set(value)
    }

}
And
AtomicReference
here is:
Copy code
internal actual class AtomicReference<T> actual constructor(initial: T) {

    private val atomic =
        kotlin.native.concurrent.FreezableAtomicReference(initial)

    actual fun get(): T =
        atomic.value

    actual fun set(newValue: T) {
        atomic.compareAndSet(get(), newValue)
    }

}
The app crashes when
registerX
is called:
Copy code
Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashSet@221c288
Where in my code am I attempting to modify a frozen
HashSet
? I’m pretty sure I’m just creating a new local one, modifying it, then assigning it which makes it frozen (that’s what the
+
does)
m
Where do you actually freeze the set, though?
I'd assume that delegate's
setValue
should look like this:
Copy code
atomic.set(value.freeze())
r
Well I supposed it got frozen automatically when assigned. The error states it’s frozen. So I suppose that means it’s been frozen before? Is my assumption that local variables aren’t frozen false?
I’m clearly not understanding something because I’m sure I’m not trying to mutate anything frozen. I’m actually not mutating anything in this code
m
AtomicReference expects frozen values, you need to freeze them before assigning them to atomicReference
Not sure if that's the exact cause of your problem, but it could be
r
I forgot to mention that
AtomicReference
is actually a custom thing, it’s a multiplatform expect, I’m adding it to my main message
So I should use
atomic.compareAndSet(get(), newValue.freeze())
instead of
atomic.compareAndSet(get(), newValue)
m
I think so
I see you're trying to make common KMP code K/N-friendly, how's that going?
Btw, Stately has KMP implementations of AtomicReference, freeze(), etc. Might be helpful https://github.com/touchlab/Stately/
d
The only place I see a mutation is
xs + x
. Is that the entire stacktrace?
r
There’s no stacktrace in native
@Marko Mitic pretty good overall, just a bunch of
object
`var`s who need to use my
atomic
delegate, but other than that it’s kinda straightforward. We already have a production Android/iOS app with common backends
@Dominaezzz this mutation occurs on a local instance, so it’s not frozen, right? The source of
+
(from kotlin):
Copy code
public operator fun <T> Set<T>.plus(element: T): Set<T> {
    val result = LinkedHashSet<T>(mapCapacity(size + 1))
    result.addAll(this)
    result.add(element)
    return result
}
add
and
addAll
are the only mutations I see, and they should work because
result
should not be frozen, unless I don’t understand something
d
They shouldn't be frozen but it says a
HashSet
is being mutated.
m
Yeah, Stately doesn't freeze the reference because native AtomicReference doesn't either, they both expect object passed to be frozen already
r
I replaced
xs = xs + x
with
xs = setOf(x, *xs.toTypedArray())
and it seems to work. So the local hash map created by
+
is frozen?
r
Yeah that’s basically what I have now
The only problem I have left is that I can’t implement an
unregisterX
function, I don’t see how I can do that without modifying collections