compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Memory leak in SnapshotStateObserver?

Open bassstorm opened this issue 3 years ago • 9 comments

Hi JB team,

I'm trying to get up with a desktop app which updates small part of UI quite frequently (e.g. a timer). As result of app's normal operation, memory consumption grows relatively quickly. Profiling points to SnapshotStateObserver, which seem doesn't release some state. However, I don't see a way how to control that or set a limit.

After just half an hour of work, memory trend is clear: image

On below screenshot you may see Live Bytes and Objects, the max numbers there constantly grow: image

Sample app source:

import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import kotlinx.datetime.Clock
import kotlin.concurrent.fixedRateTimer

fun main() = application {
    remember {
        fixedRateTimer(period = 50) { Holder.refreshClock() }
    }

    Window(onCloseRequest = ::exitApplication) {
        Text(Holder.clock.value)
    }
}

object Holder {
    val clock = mutableStateOf(Clock.System.now().toString())

    fun refreshClock() {
        clock.value = Clock.System.now().toString()
    }
}

OS: Manjaro Linux Compose: 1.1.0 OpenJDK: 11.0.15

Looking forward hearing back from you. Thanks!

bassstorm avatar Mar 20 '22 17:03 bassstorm

A bit of extra details to add.

In androidx.compose.runtime.snapshots.SnapshotStateObserver#observeReads, onValueChangedForScope does seem always to be a new instance for my mutable state change event. So it leads to a situation when applyMaps list grows infinitely.

bassstorm avatar Mar 24 '22 19:03 bassstorm

This appears similar to a problem I have been investigating, though my problem occurs on Android with Jetpack Compose. Some details here

XilinJia avatar Mar 29 '22 21:03 XilinJia

There is a related bug report for Jetpack Compose: https://issuetracker.google.com/issues/223222717

mnonnenmacher avatar Mar 30 '22 19:03 mnonnenmacher

@mnonnenmacher thanks for sharing, that's very good to know! Hopefully will be fixed soon in upstream.

bassstorm avatar Mar 30 '22 19:03 bassstorm

I believe the problem is in the UpdateEffect composable

On every invocation of the performUpdate snapshotObserver is subscribed with new lambda object:

fun performUpdate() {
    snapshotObserver.observeReads(
        Unit,
        onValueChangedForScope = { tasks.trySend(::performUpdate) }
    ) {
        currentUpdate()
    }
}

I replaced this declaration with the following and the memory leak disappeared:

lateinit var onValueChangedForScope: (Unit) -> Unit
fun performUpdate() {
    snapshotObserver.observeReads(
        Unit,
        onValueChangedForScope = onValueChangedForScope,
    ) {
        currentUpdate()
    }
}
onValueChangedForScope = { tasks.trySend(::performUpdate) }

pema4 avatar Apr 16 '22 08:04 pema4

I think I may be experiencing the same issue. My app slows down after time, so I started the Intellij debug view. After running the app for some time I paused it and took a look at the memory usage. I can see that there are lots of allocations of androidx.compose.runtime.collection.IdentityArraySet. See screenshot: Screenshot 2022-04-22 at 14 31 57

Is this expected? I am not sure. When looking at the stack trace of one of the objects I see the following:

Screenshot 2022-04-22 at 14 32 39

Thomas-Vos avatar Apr 22 '22 12:04 Thomas-Vos

I ran into the same problem。 image

wznshuai avatar May 20 '22 07:05 wznshuai

image

I have a similar problem, the snapshotmutablestateimpl has a memory leak problem, I'm a novice, and I don't know how to continue the analysis

wangjunqiangongithub avatar Jun 29 '22 10:06 wangjunqiangongithub

There's no real movement in related Google ticket: https://issuetracker.google.com/issues/223222717 Please vote or comment there if you can

bassstorm avatar Aug 28 '22 19:08 bassstorm

I posted the following already at the google issue tracker:

The memory leak is still present in Compose 1.2.0 with Kotlin 1.7.20. I profiled the code below (https://github.com/Ic-ks/compose-memory-leak) for 3 minutes with the Java Flight Recorder (the related .jfr file can be found here: https://issuetracker.google.com/action/issues/223222717/attachments/39334890?download=true). The overall allocated memory was more than 7GB. Heap usage steadily increased to 80 MB. It seems that SnapshotStateObserver does not clean its references properly.

val randomValueFlow = flow { while (true) emit(Math.random() * 100) }
    .onEach { delay(10) }
    .map { it.toInt() }
    .flowOn(Dispatchers.Default)

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        val randomValue by randomValueFlow.collectAsState(0L)
        Text(text = randomValue.toString(), fontSize = 20.sp)
    }
}

jfr

PS: .jfr files can be viewed with JDK Mission Control (https://github.com/openjdk/jmc)

Ic-ks avatar Oct 14 '22 17:10 Ic-ks

I have the same issue. Constant high-frequency UI updates cause memory leak. If the app is designed to be used for hours - there's no way to make it work without a restart.

yaroslavkulinich avatar Nov 04 '22 20:11 yaroslavkulinich

@pema4 Does your solution with gradle task patch work with Compose 1.2.0 version (maybe you already migrated)? Did you face any issues after patching UpdateEffect? I'm thinking to apply your approach, but it's nice to have some information about possible side-effects (if exist). Thanks

yaroslavkulinich avatar Nov 04 '22 20:11 yaroslavkulinich

@igordmn please, take a closer look at this problem. The repository with the reproducer published here https://github.com/Ic-ks/compose-memory-leak shows the problem. It would be perfect if the changes described by @pema4 were included directly in the jb-compose build. The problem for long-running apps with high-frequency UI updates is HUGE.

yaroslavkulinich avatar Nov 07 '22 14:11 yaroslavkulinich

Thanks for pointing at this issue, we'll take a look.

igordmn avatar Nov 07 '22 14:11 igordmn

Thanks to all for investigating this issue! And especially @pema4 for the fix.

It is fixed, and the fix will be in 1.3.0 (or in 1.2.2, we will decide soon).

igordmn avatar Nov 10 '22 02:11 igordmn

Terrific! Can't wait for release!

bassstorm avatar Nov 10 '22 19:11 bassstorm