Skip to content

Fb mem leak + remove BuildConfig.DEBUG #442

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

Merged
merged 9 commits into from
Dec 7, 2016
Merged

Fb mem leak + remove BuildConfig.DEBUG #442

merged 9 commits into from
Dec 7, 2016

Conversation

SUPERCILEX
Copy link
Collaborator

Yes! This is the last leak I've encountered so once this gets merged, FirebaseUI will finally be leak free!

The leak will occur when the startLogin() method is called in FacebookProvider:

In com.firebase.uidemo:1.0:1.
* com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity has leaked:
* GC ROOT static com.firebase.ui.auth.provider.FacebookProvider.sCallbackManager
* references com.facebook.internal.CallbackManagerImpl.callbacks
* references java.util.HashMap.table
* references array java.util.HashMap$HashMapEntry[].[2]
* references java.util.HashMap$HashMapEntry.value
* references com.facebook.login.LoginManager$1.val$callback (anonymous implementation of com.facebook.internal.CallbackManagerImpl$Callback)
* references com.firebase.ui.auth.provider.FacebookProvider.mCallbackObject
* leaks com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity instance
* Retaining: 191 KB.
* Reference Key: 55966931-5b62-493b-8e16-caac09a1e1ff
* Device: Google google Pixel XL marlin
* Android Version: 7.1 API: 25 LeakCanary: 1.5 00f37f5
* Durations: watch=5010ms, gc=165ms, heap dump=17341ms, analysis=167801ms

* Details:
* Class com.firebase.ui.auth.provider.FacebookProvider
|   static PUBLIC_PROFILE = java.lang.String@318108768 (0x12f5f460)
|   static $classOverhead = byte[200]@316165809 (0x12d84eb1)
|   static TAG = java.lang.String@318108816 (0x12f5f490)
|   static sCallbackManager = com.facebook.internal.CallbackManagerImpl@318028928 (0x12f4bc80)
|   static ERROR = java.lang.String@1873785240 (0x6fafb198)
|   static EMAIL = java.lang.String@1873788504 (0x6fafbe58)
|   static ERROR_MSG = java.lang.String@318106240 (0x12f5ea80)
* Instance of com.facebook.internal.CallbackManagerImpl
|   static staticCallbacks = java.util.HashMap@318101192 (0x12f5d6c8)
|   static $classOverhead = byte[124]@317921025 (0x12f31701)
|   callbacks = java.util.HashMap@318101232 (0x12f5d6f0)
|   shadow$_klass_ = com.facebook.internal.CallbackManagerImpl
|   shadow$_monitor_ = 0
* Instance of java.util.HashMap
|   static MAXIMUM_CAPACITY = 1073741824
|   static serialVersionUID = 362498820763181265
|   static DEFAULT_LOAD_FACTOR = 0.75
|   static EMPTY_TABLE = java.util.HashMap$HashMapEntry[0]@1869973792 (0x6f758920)
|   static $classOverhead = byte[416]@1870814817 (0x6f825e61)
|   static DEFAULT_INITIAL_CAPACITY = 4
|   entrySet = null
|   loadFactor = 0.75
|   modCount = 1
|   size = 1
|   table = java.util.HashMap$HashMapEntry[4]@318600288 (0x12fd7460)
|   threshold = 3
|   keySet = null
|   values = null
|   shadow$_klass_ = java.util.HashMap
|   shadow$_monitor_ = 0
* Array of java.util.HashMap$HashMapEntry[]
|   [0] = null
|   [1] = null
|   [2] = java.util.HashMap$HashMapEntry@320443704 (0x13199538)
|   [3] = null
* Instance of java.util.HashMap$HashMapEntry
|   static $classOverhead = byte[144]@1870809137 (0x6f824831)
|   hash = -1016872218
|   key = java.lang.Integer@321518864 (0x1329fd10)
|   next = null
|   value = com.facebook.login.LoginManager$1@321518880 (0x1329fd20)
|   shadow$_klass_ = java.util.HashMap$HashMapEntry
|   shadow$_monitor_ = 0
* Instance of com.facebook.login.LoginManager$1
|   static $classOverhead = byte[112]@318927105 (0x13027101)
|   this$0 = com.facebook.login.LoginManager@321518912 (0x1329fd40)
|   val$callback = com.firebase.ui.auth.provider.FacebookProvider@318028944 (0x12f4bc90)
|   shadow$_klass_ = com.facebook.login.LoginManager$1
|   shadow$_monitor_ = 0
* Instance of com.firebase.ui.auth.provider.FacebookProvider
|   static PUBLIC_PROFILE = java.lang.String@318108768 (0x12f5f460)
|   static $classOverhead = byte[200]@316165809 (0x12d84eb1)
|   static TAG = java.lang.String@318108816 (0x12f5f490)
|   static sCallbackManager = com.facebook.internal.CallbackManagerImpl@318028928 (0x12f4bc80)
|   static ERROR = java.lang.String@1873785240 (0x6fafb198)
|   static EMAIL = java.lang.String@1873788504 (0x6fafbe58)
|   static ERROR_MSG = java.lang.String@318106240 (0x12f5ea80)
|   mCallbackObject = com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity@317961024 (0x12f3b340)
|   mScopes = java.util.ArrayList@317966128 (0x12f3c730)
|   shadow$_klass_ = com.firebase.ui.auth.provider.FacebookProvider
|   shadow$_monitor_ = 0
* Instance of com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity
|   static TAG = java.lang.String@317892016 (0x12f2a5b0)
|   static RC_ACCOUNT_LINK = 3
|   static $classOverhead = byte[3980]@317984769 (0x12f41001)
|   static RC_EMAIL_FLOW = 2
|   mIdpProviders = java.util.ArrayList@318095096 (0x12f5bef8)
|   mSaveSmartLock = com.firebase.ui.auth.util.signincontainer.SaveSmartLock@317761712 (0x12f0a8b0)
|   mActivityHelper = com.firebase.ui.auth.ui.ActivityHelper@317966032 (0x12f3c6d0)
|   mDelegate = android.support.v7.app.AppCompatDelegateImplN@315592272 (0x12cf8e50)
|   mEatKeyUpEvent = false
|   mResources = null
|   mThemeId = 2131427471
|   mCreated = true
|   mFragments = android.support.v4.app.FragmentController@317648800 (0x12eeefa0)
|   mHandler = android.support.v4.app.FragmentActivity$1@317878176 (0x12f26fa0)
|   mMediaController = null
|   mNextCandidateRequestIndex = 0
|   mOptionsMenuInvalidated = false
|   mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@317966008 (0x12f3c6b8)
|   mReallyStopped = true
|   mRequestedPermissionsFromFragment = false
|   mResumed = false
|   mRetaining = false
|   mStopped = true
|   mStartedActivityFromFragment = false
|   mStartedIntentSenderFromFragment = false
|   mActionBar = null
|   mActionModeTypeStarting = 0
|   mActivityInfo = android.content.pm.ActivityInfo@315592560 (0x12cf8f70)
|   mActivityTransitionState = android.app.ActivityTransitionState@317895952 (0x12f2b510)
|   mApplication = com.firebase.uidemo.auth.LeakCatcher@314863488 (0x12c46f80)
|   mCalled = true
|   mChangeCanvasToTranslucent = false
|   mChangingConfigurations = false
|   mComponent = android.content.ComponentName@317968496 (0x12f3d070)
|   mConfigChangeFlags = 0
|   mCurrentConfig = android.content.res.Configuration@316460640 (0x12dcce60)
|   mDecor = null
|   mDefaultKeyMode = 0
|   mDefaultKeySsb = null
|   mDestroyed = true
|   mDoReportFullyDrawn = false
|   mEatKeyUpEvent = false
|   mEmbeddedID = null
|   mEnableDefaultActionBarUp = false
|   mEnterTransitionListener = android.app.SharedElementCallback$1@1877714360 (0x6feba5b8)
|   mExitTransitionListener = android.app.SharedElementCallback$1@1877714360 (0x6feba5b8)
|   mFinished = true
|   mFragments = android.app.FragmentController@317648752 (0x12eeef70)
|   mHandler = android.os.Handler@317878112 (0x12f26f60)
|   mHasCurrentPermissionsRequest = false
|   mIdent = 147695503
|   mInstanceTracker = android.os.StrictMode$InstanceTracker@317648768 (0x12eeef80)
|   mInstrumentation = android.app.Instrumentation@314618280 (0x12c0b1a8)
|   mIntent = android.content.Intent@317972656 (0x12f3e0b0)
|   mLastNonConfigurationInstances = null
|   mMainThread = android.app.ActivityThread@314708224 (0x12c21100)
|   mManagedCursors = java.util.ArrayList@317965456 (0x12f3c490)
|   mManagedDialogs = null
|   mMenuInflater = null
|   mParent = null
|   mReferrer = java.lang.String@317973104 (0x12f3e270)
|   mResultCode = 0
|   mResultData = null
|   mResumed = false
|   mSearchEvent = null
|   mSearchManager = null
|   mStartedActivity = false
|   mStopped = true
|   mTaskDescription = android.app.ActivityManager$TaskDescription@317878144 (0x12f26f80)
|   mTemporaryPause = false
|   mTitle = java.lang.String@315021120 (0x12c6d740)
|   mTitleColor = 0
|   mTitleReady = true
|   mToken = android.os.BinderProxy@318009440 (0x12f47060)
|   mTranslucentCallback = null
|   mUiThread = java.lang.Thread@1956798296 (0x74a25f58)
|   mVisibleBehind = false
|   mVisibleFromClient = true
|   mVisibleFromServer = true
|   mVoiceInteractor = null
|   mWindow = com.android.internal.policy.PhoneWindow@314849920 (0x12c43a80)
|   mWindowAdded = true
|   mWindowManager = android.view.WindowManagerImpl@317965768 (0x12f3c5c8)
|   mInflater = com.android.internal.policy.PhoneLayoutInflater@317892592 (0x12f2a7f0)
|   mOverrideConfiguration = null
|   mResources = android.content.res.Resources@317888680 (0x12f298a8)
|   mTheme = android.content.res.Resources$Theme@318025856 (0x12f4b080)
|   mThemeResource = 2131427471
|   mBase = android.app.ContextImpl@316514480 (0x12dda0b0)
|   shadow$_klass_ = com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity
|   shadow$_monitor_ = 1073744196
* Excluded Refs:
| Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
| Thread:FinalizerWatchdogDaemon (always)
| Thread:main (always)
| Thread:LeakCanary-Heap-Dump (always)
| Class:java.lang.ref.WeakReference (always)
| Class:java.lang.ref.SoftReference (always)
| Class:java.lang.ref.PhantomReference (always)
| Class:java.lang.ref.Finalizer (always)
| Class:java.lang.ref.FinalizerReference (always)

Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
# Conflicts:
#	auth/src/main/java/com/firebase/ui/auth/provider/FacebookProvider.java
…-leak

# Conflicts:
#	auth/src/main/java/com/firebase/ui/auth/provider/FacebookProvider.java
// a HashMap in the CallbackManager instance, sCallbackManager.
// Because FacebookProvider which contains an instance of an activity, mCallbackObject,
// is contained in sCallbackManager, that activity will not be garbage collected.
// Thus, we have leaked an Activity.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep this explanation? It's a bit lengthy and could probably be replaced with // Fixes activity leaks.

Here is the Facebook code in question:

CallbackManager.Factory.create()

public static CallbackManager create() {
    return new CallbackManagerImpl();
}
loginManager.registerCallback(sCallbackManager, ***this***);

->

/**
 * Registers a login callback to the given callback manager.
 * @param callbackManager The callback manager that will encapsulate the callback.
 * @param callback The login callback that will be called on login completion.
 */
public void registerCallback(
final CallbackManager callbackManager,
final FacebookCallback<LoginResult> callback) {
    if (!(callbackManager instanceof CallbackManagerImpl)) {
        throw new FacebookException("Unexpected CallbackManager, " +
                                            "please use the provided Factory.");
    }
    ((CallbackManagerImpl) callbackManager).registerCallback( // sCallbackManager is storing callback into itself
            CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode(),
            new CallbackManagerImpl.Callback() {
                @Override
                public boolean onActivityResult(int resultCode, Intent data) {
                    return LoginManager.this.onActivityResult(
                            resultCode,
                            data,
                            ***callback***);
                }
            }
    );
}

->

private Map<Integer, CallbackManagerImpl.Callback> callbacks = new HashMap<>();

public void registerCallback(int requestCode, Callback callback) {
    Validate.notNull(callback, "callback");
    callbacks.put(requestCode, ***callback***); // Callback is now in callbacks HashMap
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the super-detailed investigation. I am fine with the long comment, it makes people more scared to undo your fix.

Copy link
Contributor

@samtstern samtstern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again!

} else {
try {
String email = object.getString("email");
mCallbackObject.onSuccess(createIDPResponse(loginResult, email));
FacebookProvider.this.onSuccess(email, loginResult);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this branch has FacebookProvider.this but the failure branch does not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope.

// a HashMap in the CallbackManager instance, sCallbackManager.
// Because FacebookProvider which contains an instance of an activity, mCallbackObject,
// is contained in sCallbackManager, that activity will not be garbage collected.
// Thus, we have leaked an Activity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the super-detailed investigation. I am fine with the long comment, it makes people more scared to undo your fix.

Signed-off-by: Alex Saveau <[email protected]>
@samtstern samtstern merged commit 7944823 into firebase:version-1.1.0-dev Dec 7, 2016
@SUPERCILEX SUPERCILEX deleted the fb-mem-leak branch December 7, 2016 15:49
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

Successfully merging this pull request may close these issues.

2 participants