leakcanary
leakcanary copied to clipboard
Leak with InstrumentationThread as GC Root
Description
While detecting leaks in Instrumentation tests, I noticed that one of the leak's GC Root is InstrumentationThread
.
Since InstrumentationThread
is only specific to non prod use cases, shall we add such GC Roots to an 'allow list' to reduce the noise ?
4601818 bytes retained by leaking objects
Signature: e5e92b020337ea7f858f125ca41051f949c581e5
┬───
│ GC Root: Thread object
│
├─ android.app.Instrumentation$InstrumentationThread instance
│ Leaking: NO (MessageQueue↓ is not leaking)
│ Thread name: 'Instr: com.xxxx.test.instrumentation.runner.xxxxAndroidJUnitRunner'
│ ↓ Instrumentation$InstrumentationThread.this$0
├─ com.xxxx.test.instrumentation.runner.xxxxAndroidJUnitRunner instance
│ Leaking: NO (MessageQueue↓ is not leaking)
│ mAppContext instance of android.app.ContextImpl
│ mInstrContext instance of android.app.ContextImpl
│ ↓ Instrumentation.mMessageQueue
├─ android.os.MessageQueue instance
│ Leaking: NO (MessageQueue#mQuitting is false)
│ HandlerThread: "main"
│ ↓ MessageQueue[1]
│ ~~~
├─ android.os.Message instance
│ Leaking: UNKNOWN
│ Retaining 4.6 MB in 76606 objects
│ Message.what = 1
│ Message.when = 117090332 (937 ms after heap dump)
│ Message.obj = null
│ Message.callback = instance @427103224 of android.view.contentcapture.MainContentCaptureSession$$ExternalSyntheticLambda4
│ Message.target = instance @427103304 of android.os.Handler
│ ↓ Message.callback
│ ~~~~~~~~
├─ android.view.contentcapture.MainContentCaptureSession$$ExternalSyntheticLambda4 instance
│ Leaking: UNKNOWN
│ Retaining 4.6 MB in 76604 objects
│ ↓ MainContentCaptureSession$$ExternalSyntheticLambda4.f$0
│ ~~~
├─ android.view.contentcapture.MainContentCaptureSession instance
│ Leaking: UNKNOWN
│ Retaining 4.6 MB in 76603 objects
│ mContext instance of com.xxxx.yyyy.MainActivity with mDestroyed = true
│ ↓ MainContentCaptureSession.mContext
│ ~~~~~~~~
╰→ com.xxxx.yyyy.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.xxxx.yyyy.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 4.6 MB in 76558 objects
key = a4cb94b3-8116-40a4-aa0c-8e14229c08ea
watchDurationMillis = 19139
retainedDurationMillis = 14139
mApplication instance of com.xxxx.yyyy.app.yyyyApplication
mBase instance of com.xxxx.yyyy.MainContextWrapper
====================================
Version Information
- LeakCanary version: 2.8.1
- Android OS version: SDK 31
- Gradle version: 6.9.1
The leaks can be caused by any reference in the leak chain, not just the root or a leaf. Similarly, there can be many paths from many roots to a leaf, LeakCanary only gives you the shortest, if you ignored that root you could very well get the same leak again with a different root.
This looks like a bug in android.view.contentcapture.MainContentCaptureSession
: it's lifecycle is tied to that of an activity that it's holding onto, and it seems like a delayed main thread message was posted to the main thread (Message.when = 117090332 (937 ms after heap dump)
) and the callback for that message (MainContentCaptureSession$$ExternalSyntheticLambda4
) holds a reference to MainContentCaptureSession.
The only postDelayed is MainContentCaptureSession#scheduleFlush
: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/contentcapture/MainContentCaptureSession.java;l=515;drc=19f65ec0986f2c2e06567db21396d6c0d3489db8
mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
It looks like MainContentCaptureSession#onDestroy
is canceling the flush message, so this shouldn't happen.
I don't know if this seems the flushFrequencyMs was too high, or if the flush keeps being rescheduled after onDestroy(), or something else..
You should file a ticket to AOSP, once this is investigated more deeply we can add it to the list of known library leaks.
If you upload a heapdump to Google drive and share a link here, we can probably figure out more.