https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
s

spierce7

01/21/2020, 8:06 PM
@elizarov @olonho A few questions (code on thread) 1. Should the property be frozen? The parent
this
isn’t frozen, and it seems like the property shouldn’t be frozen since it’s only ever touched by one thread. 2. I’m not seeing an exception when I attempt to mutate something that’s frozen. That seems odd. 3. I’m pretty sure I was seeing different behavior a month or so back (around Kotlinconf) where the property wasn’t frozen. Am I imagining this?
Copy code
fun main() {
    val context1: CoroutineContext = newSingleThreadContext("Context1")

    runBlocking(context1) {
        println("Example 1")

        val example1 = Example1(context1)

        println(example1.example1())
        println(example1.example1())
        println(example1.example1())

        println("Value: ${example1.value}")
        println("Is Value Frozen : ${example1.value.isFrozen}")
        println("Is Example1 Frozen: ${example1.isFrozen}")
    }
}

object Background {
    val context: CoroutineContext = newSingleThreadContext("Background") + NonCancellable

    suspend fun asyncRandomInt(): Int = withContext(context) {
        Random.nextInt()
    }
}

class Example1(val context: CoroutineContext) {
    var value = 0

    fun example1() = GlobalScope.launch(context) {
        val randomValue = Background.asyncRandomInt()
        value += randomValue
    }
}
Output:
Copy code
Example 1
StandaloneCoroutine{Active}@322ff4d8
StandaloneCoroutine{Active}@322fefd8
StandaloneCoroutine{Active}@322fe748
Value: 0
Is Value Frozen : true
Is Example1 Frozen: false
k

Kris Wong

01/21/2020, 8:15 PM
based on my understanding, the property should be frozen, the parent should be frozen, and it should throw an exception.
it's frozen because you create it on the main thread and then pass it to another thread.
can you put a log line after the call to
Background.asyncRandomInt()
d

Dico

01/21/2020, 8:49 PM
Shouldn't the
value
be a primitive type? Or does kotlin-native box all primitives?
Presumably a primitive value is always "frozen"
👌 1
s

Sam Schilling

01/21/2020, 11:07 PM
As Dico said, it looks like primitive values are always frozen. From https://kotlinlang.org/docs/reference/native/immutability.html:
Some naturally immutable objects such as 
kotlin.String
<http://kotlin.Int|kotlin.Int>
, and other primitive types, along with 
AtomicInt
 and 
AtomicReference
 are frozen by default.
For example, see the following:
Copy code
var test = false
print(test.isFrozen) // prints "true"
s

spierce7

01/21/2020, 11:21 PM
@Kris Wong it’s only ever touched by one thread.
@Sam Schilling That makes sense that it’s frozen then, because it’s a primitive.
That’s a good point
k

Kris Wong

01/21/2020, 11:23 PM
It doesn't matter if it's only touched by one thread. I wish it did.
Unless this changes on the native-mt branch and somehow I didn't notice.
s

spierce7

01/21/2020, 11:27 PM
@Kris Wong I’m glad to report that you are wrong
So now I’m getting results that I’d expect by not using a primitive type
Copy code
fun main() {
    val context1: CoroutineContext = newSingleThreadContext("Context1")

    runBlocking(context1) {
        println("Example 1")

        val example1 = Example1(context1)

        example1.example1()
        example1.example1()
        example1.example1()

        println("Data Value: ${example1.data.value}")
        println("Is MutableData Frozen : ${example1.data.isFrozen}")
        println("Is Example1 Frozen: ${example1.isFrozen}")
    }
}

object Background {
    val context: CoroutineContext = newSingleThreadContext("Background") + NonCancellable

    suspend fun asyncRandomInt(): Int = withContext(context) {
        5
    }
}

data class MutableData(
    var value: Int
)

class Example1(val context: CoroutineContext) {
    val data = MutableData(0)

    fun example1() = GlobalScope.launch(context) {
        val randomValue = Background.asyncRandomInt()
        data.value += randomValue
    }
}
Copy code
Example 1
Data Value: 0
Is MutableData Frozen : false
Is Example1 Frozen: false
Something is wrong though. Why is the data value
0
?
s

Sam Schilling

01/21/2020, 11:29 PM
Probably a race condition, you may be checking the value before the coroutines have a chance to run
k

Kris Wong

01/21/2020, 11:29 PM
We'll that's good news indeed. It will really simplify at least one of my classes.
s

spierce7

01/21/2020, 11:30 PM
oops - I wasn’t using a suspend function. stupid me fixing
s

Sam Schilling

01/21/2020, 11:30 PM
Try adding a delay before you check the values at the end, e.g.,
delay(500)
s

spierce7

01/21/2020, 11:31 PM
This gives the expected results:
Copy code
fun main() {
    val context1: CoroutineContext = newSingleThreadContext("Context1")

    runBlocking(context1) {
        println("Example 1")

        val example1 = Example1(context1)

        example1.example1()
        example1.example1()
        example1.example1()

        println("Data Value: ${example1.data.value}")
        println("Is MutableData Frozen : ${example1.data.isFrozen}")
        println("Is Example1 Frozen: ${example1.isFrozen}")
    }
}

object Background {
    val context: CoroutineContext = newSingleThreadContext("Background") + NonCancellable

    suspend fun asyncRandomInt(): Int = withContext(context) {
        5
    }
}

data class MutableData(
    var value: Int
)

class Example1(val context: CoroutineContext) {
    val data = MutableData(0)

    suspend fun example1() = withContext(context) {
        val randomValue = Background.asyncRandomInt()
        data.value += randomValue
    }
}
Copy code
Example 1
Data Value: 15
Is MutableData Frozen : false
Is Example1 Frozen: false
k

Kris Wong

01/21/2020, 11:35 PM
Oh, I didn't notice before that your
runBlocking
is using the same context as your
launch
Makes sense now
s

Sam Schilling

01/21/2020, 11:35 PM
Something worth noting, it looks like frozen state does not determine whether mutation attempts will succeed or fail, at least not for primitive types. Since they are frozen by default, you can mutate them freely until you switch threads.
Copy code
var foo = false
println("foo frozen: ${foo.isFrozen}")
foo = true
GlobalScope.launch {
    var bar = false
    println("bar frozen: ${bar.isFrozen}")
    bar = true
    println("set bar to $bar")
    foo = false
    println("set foo to $foo")
}
Copy code
foo frozen: true
bar frozen: true
set bar to true
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref@64f13698
k

Kris Wong

01/21/2020, 11:37 PM
That's very interesting. So they say they're frozen but behave as if they're not.
s

Sam Schilling

01/21/2020, 11:38 PM
Right. This kinda confuses me since I thought frozen implies immutability
You can even call
freeze()
on primitive types and mutation attempts still succeed:
Copy code
var foo = false
foo.freeze()
foo = true
println("foo set to $foo") // prints "foo set to true"
I would expect an
InvalidMutabilityException
in the above case but that doesn’t happen
(probably worth noting I’ve been testing all this on
1.3.3-native-mt
)
o

olonho

01/22/2020, 3:10 AM
primitive types are values, their fields cannot mutate (as they have no field)
what you change above is value stored in reference
foo
k

Kris Wong

01/22/2020, 2:05 PM
then why can't it be mutated from another thread?
d

Dico

01/22/2020, 3:02 PM
Because the reference is frozen.
Consider as if you're wrapping
foo
in a class. The compiler does that for you if you access it from another non-inlined or cross inlined scope/code block
k

Kris Wong

01/22/2020, 3:05 PM
this means developers have to remember that the behavior is different between primitives and other types. there seem to be all of these "special" conditions related to K/N and concurrency. it's a huge increase to the overall cognitive load.
d

Dico

01/22/2020, 3:08 PM
No, you're making wrong conclusions. Primitives are not mutable by definition (except for maybe AtomicReference if that's considered a primitive). There is no special rule here.
Therefore, whether they are frozen or not is irrelevant.
The behaviour of wrapping
foo
in a ref is the same on the jvm and probably javascript targets.
It's what kotlin does to enable you to not have to implement that boilerplate yourself.
Changing a variable is mutating a type that has no connection to the type of the variable.
k

Kris Wong

01/22/2020, 3:13 PM
regardless, my comment still stands
d

Dico

01/22/2020, 3:14 PM
?