Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RealmResults leak #1848

Open
santaevpavel opened this issue Oct 22, 2024 · 1 comment
Open

RealmResults leak #1848

santaevpavel opened this issue Oct 22, 2024 · 1 comment

Comments

@santaevpavel
Copy link

How frequently does the bug occur?

Sometimes

Description

In specific cases notification callback is not released that leads to memory leaks. A notification callback holds a reference to RealmResults that I believe holds reference to native realm results.

The leak happens when a Flow that returned from ObjectQuery::asFlow() is cancelled before callbackFlow() (source) execution reaches awaitClose() (source). It that case notification token that was received in RealmResults::registerForNotification won't be released.

This leak can be verified by capturing memory heap dump. NativeObjectReference, RealmResultsImpl instances left after closing everything and calling GC. Only a reference to callback holds reference to RealmResultsImpl.

Screenshot 2024-10-22 at 16 52 35

I think it can be fixed by surrounding withContext(dispatcher) { ... } by try catch that catches CancellationException and releases a notification token if needed:

return callbackFlow {
   //... 
  try {
      withContext(dispatcher) {
           //...
       }
   } catch (err: CancellationException) {
       token.value.cancel()
       throw err
   }
   awaitClose { 
         // ... 
   }
}
 

Stacktrace & log output

No response

Can you reproduce the bug?

Sometimes

Reproduction Steps

This bug can be reproduced by creating many observers and cancelling them in a loop.

    private fun observe() {
        lifecycleScope.launch {
            (0..100).forEach {
                val jobs = observeMany()
                delay(100)
                jobs.cancel()
                delay(100)
            }
       }
    }

    private fun observeMany(): Job {
        return lifecycleScope.launch {
            val flows = (0..100).map { idx ->
                realm!!.query(Entity0::class)
                    .query("column1 >= $0", 4000 + idx * 100)
                    .asFlow()
                    .map { it.list }
            }
            combine(flows) { arr -> arr }
                .collect {
                    Log.d("!!!!!", "Realm observed many Entity0")
                }
            }
    }

Version

3.0.0

What Atlas App Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

Android 15

Build environment

Android Studio version: 2023.3.1 Patch 2
Android Build Tools version: 8.3.2
Gradle version: 8.4

Copy link

sync-by-unito bot commented Oct 22, 2024

➤ PM Bot commented:

Jira ticket: RKOTLIN-1136

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant