leakcanary icon indicating copy to clipboard operation
leakcanary copied to clipboard

Leak with InstrumentationThread as GC Root

Open sahil2441 opened this issue 2 years ago • 1 comments

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

sahil2441 avatar May 17 '22 21:05 sahil2441

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.

pyricau avatar May 18 '22 19:05 pyricau