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

RxSupport seems to leak a subscriber #945

Closed
dimsuz opened this issue Jul 7, 2015 · 2 comments
Closed

RxSupport seems to leak a subscriber #945

dimsuz opened this issue Jul 7, 2015 · 2 comments

Comments

@dimsuz
Copy link

dimsuz commented Jul 7, 2015

I started using square/leakcanary and occasional leaks rooted in retrofit started to appear.
I suspected my own code and tried to fix it, but failed and found nothing apparently leaking (I am also a good rx citizen and do subscribiton.unsubscribe() in onDestroy())
Then I tried to replace methods of retrofit-interfaced class with synchronous alternative, i.e. I changed

interface MyRetrofitApi {
  Observable<MyParsedClass> networkCall();
}
// later...
myApi.networkCall().map().concatMap().etc().subscribe();

to

interface MyRetrofitApi {
  MyParsedClass networkCall();
}
// later...
Observable o = Observable.defer({ Observable.just(myApi.networkCall()); });
o.map().concatMap().etc().subscribe();

And suddenly leaks stopped, leakcanary was now silent.

So I suspect this has something to do with retrofit and decided to report.
Below you can find a report from leak canary:

D  * com.dimsuz.app.ui.search.SearchActivity has leaked:
D  * GC ROOT thread java.lang.Thread.<Java Local> (named 'Retrofit-Idle')
D  * references java.util.concurrent.FutureTask$Sync.callable
D  * references java.util.concurrent.Executors$RunnableAdapter.task
D  * references retrofit.RxSupport$2.val$subscriber (anonymous class implements java.lang.Runnable)
D  * references rx.internal.operators.OperatorMap$1.val$o (anonymous class extends rx.Subscriber)
D  * references rx.internal.operators.OperatorOnErrorResumeNextViaFunction$1.val$child (anonymous class extends rx.Subscriber)
D  * references rx.internal.operators.OperatorMap$1.this$0 (anonymous class extends rx.Subscriber)
D  * references rx.internal.operators.OperatorMap.transformer
D  * references com.dimsuz.app.ui.search.SearchActivity$6$1.this$1 (anonymous class implements rx.functions.Func1)
D  * references com.dimsuz.app.ui.search.SearchActivity$6.this$0 (anonymous class implements rx.functions.Func1)
D  * leaks com.dimsuz.app.ui.search.SearchActivity instance
D  * Reference Key: 6bfa5b3d-e48e-4a6d-bd42-0f09a2878b95
D  * Device: Genymotion generic Google Nexus S - 4.1.1 - API 16 - 480x800 vbox86p
D  * Android Version: 4.1.1 API: 16 LeakCanary: 1.3.1
D  * Durations: watch=5018ms, gc=106ms, heap dump=168ms, analysis=5655ms
D  * Details:
D  * Instance of java.lang.Thread
D  |   static $staticOverhead = byte[] [id=0xa623e311;length=144;size=160]
D  |   static MAX_PRIORITY = 10
D  |   static MIN_PRIORITY = 1
D  |   static NANOS_PER_MILLI = 1000000
D  |   static NORM_PRIORITY = 5
D  |   static count = 208
D  |   static defaultUncaughtHandler = com.android.internal.os.RuntimeInit$UncaughtHandler [id=0xa6a789d0]
D  |   contextClassLoader = dalvik.system.PathClassLoader [id=0xa6a7f2c8]
D  |   vmThread = java.lang.VMThread [id=0xa6b69c80]
D  |   group = java.lang.ThreadGroup [id=0xa624b288]
D  |   uncaughtHandler = null
D  |   target = retrofit.Platform$Android$2$1 [id=0xa6b69bf0]
D  |   inheritableValues = null
D  |   interruptActions = java.util.ArrayList [id=0xa6b69c08]
D  |   localValues = java.lang.ThreadLocal$Values [id=0xa6b418f8]
D  |   name = java.lang.String [id=0xa6b5d828]
D  |   parkBlocker = java.util.concurrent.SynchronousQueue$TransferStack [id=0xa6b5f3f0]
D  |   id = 207
D  |   stackSize = 0
D  |   priority = 5
D  |   parkState = 3
D  |   hasBeenStarted = true
D  |   daemon = false
D  * Instance of java.util.concurrent.FutureTask$Sync
D  |   static $staticOverhead = byte[] [id=0xa6404759;length=120;size=136]
D  |   static CANCELLED = 4
D  |   static RAN = 2
D  |   static READY = 0
D  |   static RUNNING = 1
D  |   static serialVersionUID = -7828117401763700385
D  |   callable = java.util.concurrent.Executors$RunnableAdapter [id=0xa6b69448]
D  |   exception = null
D  |   result = null
D  |   runner = null
D  |   this$0 = java.util.concurrent.FutureTask [id=0xa6b69408]
D  |   head = null
D  |   tail = null
D  |   state = 2
D  |   exclusiveOwnerThread = null
D  * Instance of java.util.concurrent.Executors$RunnableAdapter
D  |   result = null
D  |   task = retrofit.RxSupport$2 [id=0xa6b69378]
D  * Instance of retrofit.RxSupport$2
D  |   this$0 = retrofit.RxSupport [id=0xa6b66a88]
D  |   val$interceptorTape = retrofit.RequestInterceptorTape [id=0xa6b69058]
D  |   val$invoker = retrofit.RestAdapter$RestHandler$1 [id=0xa6b67090]
D  |   val$subscriber = rx.internal.operators.OperatorMap$1 [id=0xa6b68998]
D  * Instance of rx.internal.operators.OperatorMap$1
D  |   this$0 = rx.internal.operators.OperatorMap [id=0xa6b676c0]
D  |   val$o = rx.internal.operators.OperatorOnErrorResumeNextViaFunction$1 [id=0xa6b68908]
D  |   cs = rx.internal.util.SubscriptionList [id=0xa6b68938]
D  |   op = rx.internal.operators.OperatorOnErrorResumeNextViaFunction$1 [id=0xa6b68908]
D  |   p = null
D  |   requested = -9223372036854775808
D  * Instance of rx.internal.operators.OperatorOnErrorResumeNextViaFunction$1
D  |   val$child = rx.internal.operators.OperatorMap$1 [id=0xa6b68560]
D  |   this$0 = rx.internal.operators.OperatorOnErrorResumeNextViaFunction [id=0xa6b67d88]
D  |   done = true
D  |   cs = rx.internal.util.SubscriptionList [id=0xa6b68938]
D  |   op = null
D  |   p = null
D  |   requested = -9223372036854775808
D  * Instance of rx.internal.operators.OperatorMap$1
D  |   this$0 = rx.internal.operators.OperatorMap [id=0xa6b680d0]
D  |   val$o = rx.internal.operators.OperatorConcat$ConcatInnerSubscriber [id=0xa6b684f8]
D  |   cs = rx.internal.util.SubscriptionList [id=0xa6b68530]
D  |   op = rx.internal.operators.OperatorConcat$ConcatInnerSubscriber [id=0xa6b684f8]
D  |   p = null
D  |   requested = -9223372036854775808
D  * Instance of rx.internal.operators.OperatorMap
D  |   transformer = com.dimsuz.app.ui.search.SearchActivity$6$1 [id=0xa6b680b8]
D  * Instance of com.dimsuz.app.ui.search.SearchActivity$6$1
D  |   this$1 = com.dimsuz.app.ui.search.SearchActivity$6 [id=0xa6a9eb40]
D  |   val$queryInfo = com.dimsuz.app.ui.search.AutoParcel_QueryRequest [id=0xa6b4d838]
D  * Instance of com.dimsuz.app.ui.search.SearchActivity$6
D  |   this$0 = com.dimsuz.app.ui.search.SearchActivity [id=0xa6b2b948]
D  * Instance of com.dimsuz.app.ui.search.SearchActivity
D  |   static $staticOverhead = byte[] [id=0xa6ab7a99;length=24;size=40]
D  |   static SAVED_STATE_QUERY_KEY = java.lang.String [id=0xa6b2b908]
D  |   adapter = com.dimsuz.app.ui.search.SearchResultsAdapter [id=0xa6afae98]
D  |   completedQueries = rx.subjects.BehaviorSubject [id=0xa6b2bcc8]
D  |   contentFrame = android.widget.FrameLayout [id=0xa6addae8]
D  |   firstDataFetchIndicator = com.dimsuz.app.ui.util.LoadIndicator [id=0xa6aeacb0]
D  |   listView = com.dimsuz.app.ui.widget.RxRecyclerView [id=0xa6afcba8]
D  |   pagedDataFetchIndicator = com.dimsuz.app.ui.util.AdapterDataLoadIndicator [id=0xa6afea70]
D  |   queryRequests = rx.subjects.BehaviorSubject [id=0xa6b2bc78]
D  |   searchView = com.dimsuz.app.ui.widget.SearchView [id=0xa6a771a0]
D  |   activityComponent = com.dimsuz.app.ui.base.DaggerActivityComponent [id=0xa6ad0668]
D  |   configSpec = com.dimsuz.app.ui.base.AutoParcel_BaseActivity_ConfigSpec [id=0xa6b2f138]
D  |   connectivityHelper = com.dimsuz.app.ui.base.InternetConnectivityHelper [id=0xa6a81bc8]
D  |   navDrawerToggle = android.support.v7.app.ActionBarDrawerToggle [id=0xa6a97d00]
D  |   navigationView = android.support.design.widget.NavigationView [id=0xa6af4cf0]
D  |   toolbar = android.support.v7.widget.Toolbar [id=0xa6ad20a0]
D  |   mDelegate = android.support.v7.app.AppCompatDelegateImplV14 [id=0xa6b2c578]
D  |   mAllLoaderManagers = android.support.v4.util.SimpleArrayMap [id=0xa6b35348]
D  |   mLoaderManager = null
D  |   mContainer = android.support.v4.app.FragmentActivity$2 [id=0xa6b2bc30]
D  |   mHandler = android.support.v4.app.FragmentActivity$1 [id=0xa6b2bb98]
D  |   mFragments = android.support.v4.app.FragmentManagerImpl [id=0xa6b2bbb8]
D  |   mCreated = true
D  |   mCheckedForLoaderManager = true
D  |   mLoadersStarted = false
D  |   mOptionsMenuInvalidated = false
D  |   mReallyStopped = true
D  |   mResumed = false
D  |   mRetaining = false
D  |   mStopped = true
D  |   mActionBar = null
D  |   mActivityInfo = android.content.pm.ActivityInfo [id=0xa6b2b180]
D  |   mAllLoaderManagers = android.util.SparseArray [id=0xa6b35280]
D  |   mApplication = com.dimsuz.app.PgApplication [id=0xa6ab4c28]
D  |   mWindowManager = android.view.Window$LocalWindowManager [id=0xa6b2c3a8]
D  |   mWindow = com.android.internal.policy.impl.PhoneWindow [id=0xa6b2be78]
D  |   mUiThread = java.lang.Thread [id=0xa624c4b0]
D  |   mComponent = android.content.ComponentName [id=0xa6b2b088]
D  |   mToken = android.os.BinderProxy [id=0xa6b2b120]
D  |   mCurrentConfig = android.content.res.Configuration [id=0xa6b2bdf0]
D  |   mDecor = null
D  |   mTitle = java.lang.String [id=0xa6acdcd8]
D  |   mDefaultKeySsb = null
D  |   mEmbeddedID = null
D  |   mSearchManager = null
D  |   mResultData = null
D  |   mFragments = android.app.FragmentManagerImpl [id=0xa6b2ba98]
D  |   mHandler = android.os.Handler [id=0xa6b2bb78]
D  |   mParent = null
D  |   mInstanceTracker = android.os.StrictMode$InstanceTracker [id=0xa6b2bb20]
D  |   mInstrumentation = android.app.Instrumentation [id=0xa6a7f1b8]
D  |   mIntent = android.content.Intent [id=0xa6b2b000]
D  |   mLastNonConfigurationInstances = null
D  |   mLoaderManager = null
D  |   mMenuInflater = null
D  |   mMainThread = android.app.ActivityThread [id=0xa6a79840]
D  |   mManagedCursors = java.util.ArrayList [id=0xa6b2bb08]
D  |   mManagedDialogs = null
D  |   mLoadersStarted = false
D  |   mIdent = 1399207524
D  |   mResultCode = 0
D  |   mFinished = true
D  |   mResumed = false
D  |   mEnableDefaultActionBarUp = false
D  |   mStartedActivity = false
D  |   mStopped = true
D  |   mTemporaryPause = false
D  |   mDefaultKeyMode = 0
D  |   mTitleColor = 0
D  |   mTitleReady = true
D  |   mConfigChangeFlags = 0
D  |   mCheckedForLoaderManager = true
D  |   mVisibleFromClient = true
D  |   mVisibleFromServer = true
D  |   mChangingConfigurations = false
D  |   mWindowAdded = true
D  |   mCalled = true
D  |   mBase = android.app.ContextImpl [id=0xa6b2bd00]
D  |   mInflater = com.android.internal.policy.impl.PhoneLayoutInflater [id=0xa6b2c278]
D  |   mTheme = android.content.res.Resources$Theme [id=0xa6b2c4f8]
D  |   mThemeResource = 2131230841
D  |   mBase = android.app.ContextImpl [id=0xa6b2bd00]
E  Leak result dropped because we already store 7 leak traces.
@JakeWharton
Copy link
Member

The RxJava support has been completely rewritten on master to be less of the giant hack it originally was. It will be included in the next version and no longer has a potential to leak when doing proper subscription management.

@dimsuz
Copy link
Author

dimsuz commented Jul 17, 2015

Wonderful news, thank you! I hope you won't forget to tweet or/and G+ when 2.0 hits the shelves :)

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

No branches or pull requests

2 participants