Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3c366999 authored by Makoto Onuki's avatar Makoto Onuki Committed by android-build-merger
Browse files

Merge "Avoid creating multiple death recipients for same observer" into qt-dev

am: a478c5f9

Change-Id: Id856368d4da90c4efb660ee4598b7d2ae4a75dc7
parents 5d7d866d a478c5f9
Loading
Loading
Loading
Loading
+147 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;

/**
 * Multiplexes multiple binder death recipients on the same binder objects, so that at the native
 * level, we only need to keep track of one death recipient reference. This will help reduce the
 * number of JNI strong references.
 *
 * test with: atest FrameworksCoreTests:BinderDeathDispatcherTest
 */
public class BinderDeathDispatcher<T extends IInterface> {
    private static final String TAG = "BinderDeathDispatcher";

    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private final ArrayMap<IBinder, RecipientsInfo> mTargets = new ArrayMap<>();

    @VisibleForTesting
    class RecipientsInfo implements DeathRecipient {
        final IBinder mTarget;

        /**
         * Recipient list. If it's null, {@link #mTarget} has already died, but in that case
         * this RecipientsInfo instance is removed from {@link #mTargets}.
         */
        @GuardedBy("mLock")
        @Nullable
        ArraySet<DeathRecipient> mRecipients = new ArraySet<>();

        private RecipientsInfo(IBinder target) {
            mTarget = target;
        }

        @Override
        public void binderDied() {
            final ArraySet<DeathRecipient> copy;
            synchronized (mLock) {
                copy = mRecipients;
                mRecipients = null;

                // Also remove from the targets.
                mTargets.remove(mTarget);
            }
            if (copy == null) {
                return;
            }
            // Let's call it without holding the lock.
            final int size = copy.size();
            for (int i = 0; i < size; i++) {
                copy.valueAt(i).binderDied();
            }
        }
    }

    /**
     * Add a {@code recipient} to the death recipient list on {@code target}.
     *
     * @return # of recipients in the recipient list, including {@code recipient}. Or, -1
     * if {@code target} is already dead, in which case recipient's
     * {@link DeathRecipient#binderDied} won't be called.
     */
    public int linkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) {
        final IBinder ib = target.asBinder();
        synchronized (mLock) {
            RecipientsInfo info = mTargets.get(ib);
            if (info == null) {
                info = new RecipientsInfo(ib);

                // First recipient; need to link to death.
                try {
                    ib.linkToDeath(info, 0);
                } catch (RemoteException e) {
                    return -1; // Already dead.
                }
                mTargets.put(ib, info);
            }
            info.mRecipients.add(recipient);
            return info.mRecipients.size();
        }
    }

    public void unlinkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) {
        final IBinder ib = target.asBinder();

        synchronized (mLock) {
            final RecipientsInfo info = mTargets.get(ib);
            if (info == null) {
                return;
            }
            if (info.mRecipients.remove(recipient) && info.mRecipients.size() == 0) {
                info.mTarget.unlinkToDeath(info, 0);
                mTargets.remove(info.mTarget);
            }
        }
    }

    public void dump(PrintWriter pw, String indent) {
        synchronized (mLock) {
            pw.print(indent);
            pw.print("# of watched binders: ");
            pw.println(mTargets.size());

            pw.print(indent);
            pw.print("# of death recipients: ");
            int n = 0;
            for (RecipientsInfo info : mTargets.values()) {
                n += info.mRecipients.size();
            }
            pw.println(n);
        }
    }

    @VisibleForTesting
    public ArrayMap<IBinder, RecipientsInfo> getTargetsForTest() {
        return mTargets;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -756,4 +756,8 @@ public class ArrayUtils {
            return String.valueOf(value);
        }
    }

    public static @Nullable <T> T firstOrNull(T[] items) {
        return items.length > 0 ? items[0] : null;
    }
}
+265 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.internal.os;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.FileDescriptor;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class BinderDeathDispatcherTest {
    private static class MyTarget implements IInterface, IBinder {
        public boolean isAlive = true;
        public DeathRecipient mRecipient;

        @Override
        public String getInterfaceDescriptor() throws RemoteException {
            return null;
        }

        @Override
        public boolean pingBinder() {
            return false;
        }

        @Override
        public boolean isBinderAlive() {
            return isAlive;
        }

        @Override
        public IInterface queryLocalInterface(String descriptor) {
            return null;
        }

        @Override
        public void dump(FileDescriptor fd, String[] args) throws RemoteException {

        }

        @Override
        public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {

        }

        @Override
        public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver)
                throws RemoteException {

        }

        @Override
        public boolean transact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            return false;
        }

        @Override
        public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException {
            // In any situation, a single binder object should only have at most one death
            // recipient.
            assertThat(mRecipient).isNull();

            if (!isAlive) {
                throw new DeadObjectException();
            }

            mRecipient = recipient;
        }

        @Override
        public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
            if (!isAlive) {
                return false;
            }
            assertThat(mRecipient).isSameAs(recipient);
            mRecipient = null;
            return true;
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        public void die() {
            isAlive = false;
            if (mRecipient != null) {
                mRecipient.binderDied();
            }
            mRecipient = null;
        }

        public boolean hasDeathRecipient() {
            return mRecipient != null;
        }
    }

    @Test
    public void testRegisterAndUnregister() {
        BinderDeathDispatcher<MyTarget> d = new BinderDeathDispatcher<>();

        MyTarget t1 = new MyTarget();
        MyTarget t2 = new MyTarget();
        MyTarget t3 = new MyTarget();

        DeathRecipient r1 = mock(DeathRecipient.class);
        DeathRecipient r2 = mock(DeathRecipient.class);
        DeathRecipient r3 = mock(DeathRecipient.class);
        DeathRecipient r4 = mock(DeathRecipient.class);
        DeathRecipient r5 = mock(DeathRecipient.class);

        // Start hooking up.

        // Link 3 recipients to t1 -- only one real recipient will be set.
        assertThat(d.linkToDeath(t1, r1)).isEqualTo(1);
        assertThat(d.getTargetsForTest().size()).isEqualTo(1);

        assertThat(d.linkToDeath(t1, r2)).isEqualTo(2);
        assertThat(d.linkToDeath(t1, r3)).isEqualTo(3);
        assertThat(d.getTargetsForTest().size()).isEqualTo(1);

        // Unlink two -- the real recipient is still set.
        d.unlinkToDeath(t1, r1);
        d.unlinkToDeath(t1, r2);

        assertThat(t1.hasDeathRecipient()).isTrue();
        assertThat(d.getTargetsForTest().size()).isEqualTo(1);

        // Unlink the last one. The real recipient is also unlinked.
        d.unlinkToDeath(t1, r3);
        assertThat(t1.hasDeathRecipient()).isFalse();
        assertThat(d.getTargetsForTest().size()).isEqualTo(0);

        // Set recipients to t1, t2 and t3. t3 has two.
        assertThat(d.linkToDeath(t1, r1)).isEqualTo(1);
        assertThat(d.linkToDeath(t2, r1)).isEqualTo(1);
        assertThat(d.linkToDeath(t3, r1)).isEqualTo(1);
        assertThat(d.linkToDeath(t3, r2)).isEqualTo(2);


        // They should all have a real recipient.
        assertThat(t1.hasDeathRecipient()).isTrue();
        assertThat(t2.hasDeathRecipient()).isTrue();
        assertThat(t3.hasDeathRecipient()).isTrue();

        assertThat(d.getTargetsForTest().size()).isEqualTo(3);

        // Unlink r1 from t3. t3 still has r2, so it should still have a real recipient.
        d.unlinkToDeath(t3, r1);
        assertThat(t1.hasDeathRecipient()).isTrue();
        assertThat(t2.hasDeathRecipient()).isTrue();
        assertThat(t3.hasDeathRecipient()).isTrue();
        assertThat(d.getTargetsForTest().size()).isEqualTo(3);

        // Unlink r2 from t3. Now t3 has no real recipient.
        d.unlinkToDeath(t3, r2);
        assertThat(t3.hasDeathRecipient()).isFalse();
        assertThat(d.getTargetsForTest().size()).isEqualTo(2);
    }

    @Test
    public void testRegisterAndKill() {
        BinderDeathDispatcher<MyTarget> d = new BinderDeathDispatcher<>();

        MyTarget t1 = new MyTarget();
        MyTarget t2 = new MyTarget();
        MyTarget t3 = new MyTarget();

        DeathRecipient r1 = mock(DeathRecipient.class);
        DeathRecipient r2 = mock(DeathRecipient.class);
        DeathRecipient r3 = mock(DeathRecipient.class);
        DeathRecipient r4 = mock(DeathRecipient.class);
        DeathRecipient r5 = mock(DeathRecipient.class);

        // Hook them up.

        d.linkToDeath(t1, r1);
        d.linkToDeath(t1, r2);
        d.linkToDeath(t1, r3);

        // r4 is linked then unlinked. It shouldn't be notified.
        d.linkToDeath(t1, r4);
        d.unlinkToDeath(t1, r4);

        d.linkToDeath(t2, r1);

        d.linkToDeath(t3, r3);
        d.linkToDeath(t3, r5);

        assertThat(d.getTargetsForTest().size()).isEqualTo(3);

        // Kill the targets.

        t1.die();
        verify(r1, times(1)).binderDied();
        verify(r2, times(1)).binderDied();
        verify(r3, times(1)).binderDied();
        verify(r4, times(0)).binderDied();
        verify(r5, times(0)).binderDied();

        assertThat(d.getTargetsForTest().size()).isEqualTo(2);

        reset(r1, r2, r3, r4, r5);

        t2.die();
        verify(r1, times(1)).binderDied();
        verify(r2, times(0)).binderDied();
        verify(r3, times(0)).binderDied();
        verify(r4, times(0)).binderDied();
        verify(r5, times(0)).binderDied();

        assertThat(d.getTargetsForTest().size()).isEqualTo(1);

        reset(r1, r2, r3, r4, r5);

        t3.die();
        verify(r1, times(0)).binderDied();
        verify(r2, times(0)).binderDied();
        verify(r3, times(1)).binderDied();
        verify(r4, times(0)).binderDied();
        verify(r5, times(1)).binderDied();

        assertThat(d.getTargetsForTest().size()).isEqualTo(0);

        // Try to register to a dead object -> should return -1.
        assertThat(d.linkToDeath(t1, r1)).isEqualTo(-1);

        assertThat(d.getTargetsForTest().size()).isEqualTo(0);
    }
}
+47 −6
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
@@ -56,6 +57,7 @@ import android.os.ShellCallback;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -63,6 +65,7 @@ import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BinderDeathDispatcher;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -83,6 +86,9 @@ public final class ContentService extends IContentService.Stub {
    static final String TAG = "ContentService";
    static final boolean DEBUG = false;

    /** Do a WTF if a single observer is registered more than this times. */
    private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000;

    public static class Lifecycle extends SystemService {
        private ContentService mService;

@@ -135,6 +141,12 @@ public final class ContentService extends IContentService.Stub {
    private SyncManager mSyncManager = null;
    private final Object mSyncManagerLock = new Object();

    private static final BinderDeathDispatcher<IContentObserver> sObserverDeathDispatcher =
            new BinderDeathDispatcher<>();

    @GuardedBy("sObserverLeakDetectedUid")
    private static final ArraySet<Integer> sObserverLeakDetectedUid = new ArraySet<>(0);

    /**
     * Map from userId to providerPackageName to [clientPackageName, uri] to
     * value. This structure is carefully optimized to keep invalidation logic
@@ -236,6 +248,13 @@ public final class ContentService extends IContentService.Stub {
                pw.println();
                pw.print(" Total number of nodes: "); pw.println(counts[0]);
                pw.print(" Total number of observers: "); pw.println(counts[1]);

                sObserverDeathDispatcher.dump(pw, " ");
            }
            synchronized (sObserverLeakDetectedUid) {
                pw.println();
                pw.print("Observer leaking UIDs: ");
                pw.println(sObserverLeakDetectedUid.toString());
            }

            synchronized (mCache) {
@@ -1345,20 +1364,42 @@ public final class ContentService extends IContentService.Stub {
            private final Object observersLock;

            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
                                 int _uid, int _pid, int _userHandle) {
                                 int _uid, int _pid, int _userHandle, Uri uri) {
                this.observersLock = observersLock;
                observer = o;
                uid = _uid;
                pid = _pid;
                userHandle = _userHandle;
                notifyForDescendants = n;
                try {
                    observer.asBinder().linkToDeath(this, 0);
                } catch (RemoteException e) {

                final int entries = sObserverDeathDispatcher.linkToDeath(observer, this);
                if (entries == -1) {
                    binderDied();
                } else if (entries == TOO_MANY_OBSERVERS_THRESHOLD) {
                    boolean alreadyDetected;

                    synchronized (sObserverLeakDetectedUid) {
                        alreadyDetected = sObserverLeakDetectedUid.contains(uid);
                        if (!alreadyDetected) {
                            sObserverLeakDetectedUid.add(uid);
                        }
                    }
                    if (!alreadyDetected) {
                        String caller = null;
                        try {
                            caller = ArrayUtils.firstOrNull(AppGlobals.getPackageManager()
                                    .getPackagesForUid(uid));
                        } catch (RemoteException ignore) {
                        }
                        Slog.wtf(TAG, "Observer registered too many times. Leak? cpid=" + pid
                                + " cuid=" + uid
                                + " cpkg=" + caller
                                + " url=" + uri);
                    }
                }

            }

            @Override
            public void binderDied() {
                synchronized (observersLock) {
@@ -1454,7 +1495,7 @@ public final class ContentService extends IContentService.Stub {
            // If this is the leaf node add the observer
            if (index == countUriSegments(uri)) {
                mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
                        uid, pid, userHandle));
                        uid, pid, userHandle, uri));
                return;
            }

@@ -1498,7 +1539,7 @@ public final class ContentService extends IContentService.Stub {
                if (entry.observer.asBinder() == observerBinder) {
                    mObservers.remove(i);
                    // We no longer need to listen for death notifications. Remove it.
                    observerBinder.unlinkToDeath(entry, 0);
                    sObserverDeathDispatcher.unlinkToDeath(observer, entry);
                    break;
                }
            }
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import com.android.server.content.ContentService.ObserverCall;
import com.android.server.content.ContentService.ObserverNode;

/**
 * bit FrameworksServicesTests:com.android.server.content.ObserverNodeTest
 * atest FrameworksServicesTests:com.android.server.content.ObserverNodeTest
 */
@SmallTest
public class ObserverNodeTest extends AndroidTestCase {