I encountered an unexpected (for me) behaviour wit...
# coroutines
w
I encountered an unexpected (for me) behaviour with coroutines and I’m not quite sure if I should report it. Basically sometimes Kotlin generates code that holds onto some variables even if I null them out in another coroutine. That was unexpected for me, as I was writing the following test:
Copy code
inCoroutine {
    var foo: Foo? = Foo()
    val fooRef = WeakReference(foo)

    triggerAndWaitForGc()
    assert(fooRef.get() != null)

    println(foo) // Test passes if this is removed

    inCoroutine {
        foo = null
        triggerAndWaitForGc()
        assert(fooRef.get() == null)
    }
}
So first I hold
Foo
in a strong reference (variable) and assert
WeakRef
also has a reference to it. But later I set
foo = null
, at which point I expect that there’s no strong reference to
Foo
and it can be GC-ed. This doesn’t happen in the snippet above, because
println(foo)
generates the following bit:
Copy code
Foo var9 = (Foo)foo.element;
var5 = false;
System.out.println(var9);
and
var9
is never cleared, so there will be a strong reference to
Foo
as long as that coroutine is active (?). Is this expected? Snippet with full code: https://pl.kotl.in/25D0_X5w8
t
here is your snippet with all coroutine stuff removed, it does the same thing. https://pl.kotl.in/2HVrmReY8
w
Hah, that’s right 😄 I was fixated on coroutines for some reason 😕
I’m pretty confident this is expected, but I don’t understand why Kotlin needs this reference in a separate variable. Or why it doesn’t null it out after using. Should I ask this somewhere else, now that you pointed out it’s not related to coroutines? 🤔
t
my guess is the VM optimizes away the reference to foo in the local scope because it’s never used (just assigned). But the local scope of the block holding foo of course runs till after the embedded inCoroutine block. Weak references are not guaranteed to be cleared by the GC (let alone by any type of collection) imagine you put a breakpoint at
}
from you first block, you would still expect to see the value of “foo” there since it’s in scope
(it should be null of course)
I’m not sure but I doubt it’s Kotlin related, more GC
w
I kind of rule out GC being indeterministic, and I do think it’s more of a Kotlin question, because it’s Kotlin that generates code which clearly holds the reference in a variable. I also ruled out VM optimisations because using
foo
but in the same scope doesn’t have this issue
t
where do you see this reference? I guess you can easily write the same code in Java for comparison
w
where do you see this reference?
in IJ when I open Kotlin Bytecode window and hit Decompile
I guess you can easily write the same code in Java for comparison
As it turns out, I can’t:
t
ah it’s been a while 🙂
it might indeed be kotlin related then, if it does not synchronize back the value of the local variable if it’s not accessed in bytecode. but again, there are no hard guuaranees about a weak reference being cleared. obviously if you put another
println(foo)
before the end of the block, it correctly prints
null
w
Yes, that’s right. Well the thing is, with the generated bytecode weak reference will never be cleared (as long as we’re in that function)