Query about freeze in Kotlin/Native from Kotlin Concurrancy HandsOn

Viewed 170

I'm following the Kotlin Hands On for Kotlin/Native Concurrency here. I'm having trouble in understanding the last example that why "The modified saveToDb function above now handles the background call, and only captures the function argument. That won't freeze the parent class"

class CountingModelSafer{
    var count = 0

    fun increment(){
        count++
        saveToDb(count)
    }

    private fun saveToDb(arg:Int) = background {
        println("Doing db stuff with $arg, in main $isMainThread")
    }
}

[Note that in these examples, inside background, the passed lambda is being frozen]

The below snippet causes the whole CountingModel to be frozen, but not the snippet above. Can some one help me understand why exactly that is so?

class CountingModel{
    var count = 0

    fun increment(){
        count++
        background {
            saveToDb(count)
        }
    }

    private fun saveToDb(arg:Int){
        //Do some db stuff
        println("Saving $arg to db")
    }
}
1 Answers

Sure. I wrote the sample, so let me attempt to explain.

        background {
            saveToDb(count)
        }

The background function takes a lambda argument. Lambdas can "capture" state and affect it. By that I mean, forgetting "freezing", you would normally expect that changing the value of count would change the source value. So, if we forget "freezing", the if count starts at 0, the following would result in count being 1:

        background {
            count++
        }

In order to make that happen, the lambda needs to have a reference to count, and to have a reference to count means you'll have a reverence to the class that holds it. More formally, the code should look like the following:

        background {
            this.count++
        }

We don't need to state this.count, but only because the this is assumed.

Lambdas need to access the state they reference, and none of this is magic. Lambdas have state, which is the objects they capture. Freezing state means all state that the freezee touches. In the case of lambda functions, that means anything the code references.

In this case:

    private fun saveToDb(arg:Int) = background {
        println("Doing db stuff with $arg, in main $isMainThread")
    }

arg:Int is a parameter. arg is frozen, but nothing in the lambda captures the parent class. If you wrote this.arg it would be an error. arg is local. Freezing it does not cascade up to the parent class.

I started writing an Intellij/AS plugin that would help warn you about these possible situations, as this is the kind of thing most likely to trip people up, but the Kotlin team told me they'd decided this memory model was going away. So, that plugin effort stopped. At some point next year this won't be an issue.

However, learning this memory model is useful. Devs tend to casually do things that are dangerous from a threading perspective. The Kotlin/Native restrictive threading model forces you to rethink how you architect your code. It's not necessarily a bad thing.

But I digress. The model is changing. In the meantime, be careful with your captured state.

Related