Jelmer
Jelmer

Reputation: 1095

Memory leak when using coroutineScope

I have encountered an application that appears to be suffering from a memory leak when one of the methods that asynchronously calls an api uses a coroutineScope. The problem goes away when i omit it

The following piece of kotlin code reproduces a memory leak that I am seeing in an application:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking

class MyApp {

    suspend fun run() {
        myReport()
        // while this is sleeping trigger a heapdump with visual vm
        Thread.sleep(1000000000)
    }

    suspend fun myReport(): List<String> = runGaqlRequest().map { "some keyword" }

    suspend fun runGaqlRequest(): List<String> = io {
        listOf("A".repeat(300_000_000))  // very large string using 80% of a 1024mb heap
    }

    suspend inline fun <R> io(crossinline body: suspend () -> R): R =
        coroutineScope { async(Dispatchers.IO) { body() }.await() }
}

fun main() {
    val app = MyApp()

    runBlocking {
        app.run()
    }
}

The runGaqlRequest method simulates reading a response from an API endpoint. Here it will return a list with a single element in it. A huge 300.00.000 character string

The myReport method transforms each element in this list to the string "some keyword" so the output of this function is a list with a single element "some keyword" element

I invoke myReport from a run method, that then goes to sleep for a very long time

At this point I would expect the huge string to be eligible for garbage collection however when I use visualvm to create a heapdump of the process i see its still there and unable to be garbage collected

heap dump

When I change the runGaqlRequest method to this

    suspend fun runGaqlRequest(): List<String>  {
        return listOf("A".repeat(300_000_000))  // very large string using 80% of a 1024mb heap
    }

So it does not use the coroutineScope the problem goes away.

Edit: I created this repository that can reproduce the problem

https://github.com/jelmerk/reproduce-kotlin-problem

Upvotes: 3

Views: 666

Answers (1)

Vladimir Fisher
Vladimir Fisher

Reputation: 2719

Just try with

rootView.lifecycle().repeatOnLifecycle(Lifecycle.State.STARTED) {
     flow.collect { item -> ... }
}

Or if you inside fragment of activity then just lifecycle().repeatOnLifecycle(Lifecycle.State.STARTED)... without rootView.

Upvotes: 0

Related Questions