adrielcafe / bonsai

:deciduous_tree: A multiplatform tree view for Jetpack Compose
MIT License
366 stars 14 forks source link

Memory Leak possibly due to no proper `disposing` of `Composition` #10

Open Yash-Garg opened 2 years ago

Yash-Garg commented 2 years ago

I am facing a memory leak when using this library. I think it's probably due to the Composition used in bonsai/core/tree/Tree.kt.

There is a method for disposing the composition (composition.dispose()) which isn't getting called. It would be great if you can have a look at it.

Tried adding myComposeView.disposeComposition() in onStop() of Fragment, but got no luck.

[Docs Reference](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composition#dispose())

Leakcanary Stacktrace

D/LeakCanary: ​
D/LeakCanary: ====================================
D/LeakCanary: HEAP ANALYSIS RESULT
D/LeakCanary: ====================================
D/LeakCanary: 3 APPLICATION LEAKS
D/LeakCanary:D/LeakCanary: References underlined with "~~~" are likely causes.
D/LeakCanary: Learn more at https://squ.re/leaks.
D/LeakCanary:D/LeakCanary: 2794 bytes retained by leaking objects
D/LeakCanary: Signature: 294c14f86ef1f535d2d18c00e3aefe49abd3e220
D/LeakCanary: ┬───
D/LeakCanary: │ GC Root: Input or output parameters in native code
D/LeakCanary: │
D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
D/LeakCanary: │    Leaking: NO (SnapshotKt↓ is not leaking and A ClassLoader is never leaking)
D/LeakCanary: │    ↓ ClassLoader.runtimeInternalObjects
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: NO (SnapshotKt↓ is not leaking)
D/LeakCanary: │    ↓ Object[6667]
D/LeakCanary: ├─ androidx.compose.runtime.snapshots.SnapshotKt class
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking and a class is never leaking)
D/LeakCanary: │    ↓ static SnapshotKt.applyObservers
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking)
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: ├─ androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1 instance
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking)
D/LeakCanary: │    Anonymous subclass of kotlin.jvm.internal.Lambda
D/LeakCanary: │    ↓ Recomposer$recompositionRunner$2$unregisterApplyObserver$1.this$0
D/LeakCanary: ├─ androidx.compose.runtime.Recomposer instance
D/LeakCanary: │    Leaking: NO (CompositionImpl↓ is not leaking and Recomposer is in state Idle)
D/LeakCanary: │    ↓ Recomposer.knownCompositions
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: NO (CompositionImpl↓ is not leaking)
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: ├─ androidx.compose.runtime.CompositionImpl instance
D/LeakCanary: │    Leaking: NO (Composition not disposed)
D/LeakCanary: │    ↓ CompositionImpl.composer
D/LeakCanary: │                      ~~~~~~~~
D/LeakCanary: ├─ androidx.compose.runtime.ComposerImpl instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 9.1 kB in 48 objects
D/LeakCanary: │    ↓ ComposerImpl.parentProvider
D/LeakCanary: │                   ~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.compose.runtime.external.kotlinx.collections.immutable.implementations.immutableMap.PersistentHashMap
D/LeakCanary: │  instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 320 B in 8 objects
D/LeakCanary: │    ↓ PersistentHashMap.node
D/LeakCanary: │                        ~~~~
D/LeakCanary: ├─ androidx.compose.runtime.external.kotlinx.collections.immutable.implementations.immutableMap.TrieNode instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 296 B in 7 objects
D/LeakCanary: │    ↓ TrieNode.buffer
D/LeakCanary: │               ~~~~~~
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 272 B in 6 objects
D/LeakCanary: │    ↓ Object[45]
D/LeakCanary: │            ~~~~
D/LeakCanary: ├─ androidx.compose.runtime.StaticValueHolder instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 12 B in 1 objects
D/LeakCanary: │    ↓ StaticValueHolder.value
D/LeakCanary: │                        ~~~~~
D/LeakCanary: ├─ androidx.fragment.app.FragmentViewLifecycleOwner instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 173 B in 6 objects
D/LeakCanary: │    ↓ FragmentViewLifecycleOwner.mFragment
D/LeakCanary: │                                 ~~~~~~~~~
D/LeakCanary: ╰→ dev.yashgarg.qbit.ui.torrent.tabs.TorrentFilesFragment instance
D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because dev.yashgarg.qbit.ui.torrent.tabs.TorrentFilesFragment
D/LeakCanary: ​     received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
D/LeakCanary: ​     Retaining 2.8 kB in 105 objects
D/LeakCanary: ​     key = 729161f9-e2c4-4211-a526-d0a54992cd9e
D/LeakCanary: ​     watchDurationMillis = 5484
D/LeakCanary: ​     retainedDurationMillis = 483
D/LeakCanary:D/LeakCanary: 210 bytes retained by leaking objects
D/LeakCanary: Signature: cd1c7c9324750e1908bfb93e73a7daaccc8ddf30
D/LeakCanary: ┬───
D/LeakCanary: │ GC Root: Input or output parameters in native code
D/LeakCanary: │
D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
D/LeakCanary: │    Leaking: NO (SnapshotKt↓ is not leaking and A ClassLoader is never leaking)
D/LeakCanary: │    ↓ ClassLoader.runtimeInternalObjects
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: NO (SnapshotKt↓ is not leaking)
D/LeakCanary: │    ↓ Object[6667]
D/LeakCanary: ├─ androidx.compose.runtime.snapshots.SnapshotKt class
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking and a class is never leaking)
D/LeakCanary: │    ↓ static SnapshotKt.applyObservers
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking)
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: ├─ androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1 instance
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking)
D/LeakCanary: │    Anonymous subclass of kotlin.jvm.internal.Lambda
D/LeakCanary: │    ↓ Recomposer$recompositionRunner$2$unregisterApplyObserver$1.this$0
D/LeakCanary: ├─ androidx.compose.runtime.Recomposer instance
D/LeakCanary: │    Leaking: NO (CompositionImpl↓ is not leaking and Recomposer is in state Idle)
D/LeakCanary: │    ↓ Recomposer.knownCompositions
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: NO (CompositionImpl↓ is not leaking)
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: ├─ androidx.compose.runtime.CompositionImpl instance
D/LeakCanary: │    Leaking: NO (Composition not disposed)
D/LeakCanary: │    ↓ CompositionImpl.slotTable
D/LeakCanary: │                      ~~~~~~~~~
D/LeakCanary: ├─ androidx.compose.runtime.SlotTable instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 345 B in 17 objects
D/LeakCanary: │    ↓ SlotTable.slots
D/LeakCanary: │                ~~~~~
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 6.4 kB in 206 objects
D/LeakCanary: │    ↓ Object[32]
D/LeakCanary: │            ~~~~
D/LeakCanary: ├─ androidx.compose.ui.platform.DisposableSaveableStateRegistry instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 37 B in 2 objects
D/LeakCanary: │    ↓ DisposableSaveableStateRegistry.onDispose
D/LeakCanary: │                                      ~~~~~~~~~
D/LeakCanary: ├─ androidx.compose.ui.platform.DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1 instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 21 B in 1 objects
D/LeakCanary: │    Anonymous subclass of kotlin.jvm.internal.Lambda
D/LeakCanary: │    ↓ DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1.$androidxRegistry
D/LeakCanary: │                                                                                  ~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.savedstate.SavedStateRegistry instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 506 B in 19 objects
D/LeakCanary: │    ↓ SavedStateRegistry.components
D/LeakCanary: │                         ~~~~~~~~~~
D/LeakCanary: ├─ androidx.arch.core.internal.SafeIterableMap instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 483 B in 18 objects
D/LeakCanary: │    ↓ SafeIterableMap["androidx.lifecycle.internal.SavedStateHandlesProvider"]
D/LeakCanary: │                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.lifecycle.SavedStateHandlesProvider instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 251 B in 10 objects
D/LeakCanary: │    ↓ SavedStateHandlesProvider.viewModel$delegate
D/LeakCanary: │                                ~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ kotlin.SynchronizedLazyImpl instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 230 B in 9 objects
D/LeakCanary: │    ↓ SynchronizedLazyImpl._value
D/LeakCanary: │                           ~~~~~~
D/LeakCanary: ╰→ androidx.lifecycle.SavedStateHandlesVM instance
D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because androidx.lifecycle.SavedStateHandlesVM received
D/LeakCanary: ​     ViewModel#onCleared() callback)
D/LeakCanary: ​     Retaining 210 B in 8 objects
D/LeakCanary: ​     key = 375b335d-00db-4565-8c6d-0848ea7ca599
D/LeakCanary: ​     watchDurationMillis = 5485
D/LeakCanary: ​     retainedDurationMillis = 483
D/LeakCanary:D/LeakCanary: 15324 bytes retained by leaking objects
D/LeakCanary: Signature: 3074046c89ba82b393e6e7ba1f02c8a330683dbd
D/LeakCanary: ┬───
D/LeakCanary: │ GC Root: Input or output parameters in native code
D/LeakCanary: │
D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
D/LeakCanary: │    Leaking: NO (SnapshotKt↓ is not leaking and A ClassLoader is never leaking)
D/LeakCanary: │    ↓ ClassLoader.runtimeInternalObjects
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: NO (SnapshotKt↓ is not leaking)
D/LeakCanary: │    ↓ Object[6667]
D/LeakCanary: ├─ androidx.compose.runtime.snapshots.SnapshotKt class
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking and a class is never leaking)
D/LeakCanary: │    ↓ static SnapshotKt.applyObservers
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking)
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: ├─ androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1 instance
D/LeakCanary: │    Leaking: NO (Recomposer↓ is not leaking)
D/LeakCanary: │    Anonymous subclass of kotlin.jvm.internal.Lambda
D/LeakCanary: │    ↓ Recomposer$recompositionRunner$2$unregisterApplyObserver$1.this$0
D/LeakCanary: ├─ androidx.compose.runtime.Recomposer instance
D/LeakCanary: │    Leaking: NO (CompositionImpl↓ is not leaking and Recomposer is in state Idle)
D/LeakCanary: │    ↓ Recomposer.knownCompositions
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: NO (CompositionImpl↓ is not leaking)
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: ├─ androidx.compose.runtime.CompositionImpl instance
D/LeakCanary: │    Leaking: NO (Composition not disposed)
D/LeakCanary: │    ↓ CompositionImpl.parent
D/LeakCanary: │                      ~~~~~~
D/LeakCanary: ├─ androidx.compose.runtime.ComposerImpl$CompositionContextImpl instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 190 B in 6 objects
D/LeakCanary: │    ↓ ComposerImpl$CompositionContextImpl.this$0
D/LeakCanary: │                                          ~~~~~~
D/LeakCanary: ├─ androidx.compose.runtime.ComposerImpl instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 17.2 kB in 54 objects
D/LeakCanary: │    ↓ ComposerImpl.composition
D/LeakCanary: │                   ~~~~~~~~~~~
D/LeakCanary: ├─ androidx.compose.runtime.CompositionImpl instance
D/LeakCanary: │    Leaking: YES (Composition disposed)
D/LeakCanary: │    Retaining 15.3 kB in 388 objects
D/LeakCanary: │    ↓ CompositionImpl.observations
D/LeakCanary: ├─ androidx.compose.runtime.collection.IdentityScopeMap instance
D/LeakCanary: │    Leaking: YES (CompositionImpl↑ is leaking)
D/LeakCanary: │    Retaining 13.2 kB in 356 objects
D/LeakCanary: │    ↓ IdentityScopeMap.scopeSets
D/LeakCanary: ├─ androidx.compose.runtime.collection.IdentityArraySet[] array
D/LeakCanary: │    Leaking: YES (CompositionImpl↑ is leaking)
D/LeakCanary: │    Retaining 10.5 kB in 252 objects
D/LeakCanary: │    ↓ IdentityArraySet[4]
D/LeakCanary: ├─ androidx.compose.runtime.collection.IdentityArraySet instance
D/LeakCanary: │    Leaking: YES (CompositionImpl↑ is leaking)
D/LeakCanary: │    Retaining 148 B in 5 objects
D/LeakCanary: │    ↓ IdentityArraySet.values
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: YES (CompositionImpl↑ is leaking)
D/LeakCanary: │    Retaining 132 B in 4 objects
D/LeakCanary: │    ↓ Object[0]
D/LeakCanary: ├─ androidx.compose.runtime.RecomposeScopeImpl instance
D/LeakCanary: │    Leaking: YES (CompositionImpl↑ is leaking)
D/LeakCanary: │    Retaining 68 B in 3 objects
D/LeakCanary: │    ↓ RecomposeScopeImpl.block
D/LeakCanary: ├─ androidx.compose.ui.platform.ComposeView$Content$1 instance
D/LeakCanary: │    Leaking: YES (CompositionImpl↑ is leaking)
D/LeakCanary: │    Retaining 20 B in 1 objects
D/LeakCanary: │    Anonymous subclass of kotlin.jvm.internal.Lambda
D/LeakCanary: │    ↓ ComposeView$Content$1.$tmp1_rcvr
D/LeakCanary: ╰→ androidx.compose.ui.platform.ComposeView instance
D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because dev.yashgarg.qbit.ui.torrent.tabs.TorrentFilesFragment
D/LeakCanary: ​     received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
D/LeakCanary: ​     Retaining 2.4 kB in 39 objects
D/LeakCanary: ​     key = d15cfab8-cfee-4de6-82f2-9e5e4d4f6d02
D/LeakCanary: ​     watchDurationMillis = 5489
D/LeakCanary: ​     retainedDurationMillis = 488
D/LeakCanary: ​     View not part of a window view hierarchy
D/LeakCanary: ​     View.mAttachInfo is null (view detached)
D/LeakCanary: ​     View.mID = R.id.filesComposeView
D/LeakCanary: ​     View.mWindowAttachCount = 1
D/LeakCanary: ​     mContext instance of dev.yashgarg.qbit.MainActivity with mDestroyed = false
D/LeakCanary: ====================================
foodpoison commented 6 months ago

Hi I'm encountering memory leak of this library as well, do you need help fixing this issue?