root/content/public/android/java/src/org/chromium/content/browser/BindingManagerImpl.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. removeInitialBinding
  2. addStrongBinding
  3. removeStrongBinding
  4. dropBindings
  5. setInForeground
  6. setBoundForBackgroundPeriod
  7. isOomProtected
  8. clearConnection
  9. isConnectionCleared
  10. createBindingManager
  11. createBindingManagerForTesting
  12. addNewConnection
  13. setInForeground
  14. onSentToBackground
  15. onBroughtToForeground
  16. isOomProtected
  17. clearConnection
  18. isConnectionCleared

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser;

import android.util.Log;
import android.util.SparseArray;

import com.google.common.annotations.VisibleForTesting;

import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;

/**
 * Manages oom bindings used to bound child services.
 */
class BindingManagerImpl implements BindingManager {
    private static final String TAG = "BindingManager";

    // Delay of 1 second used when removing the initial oom binding of a process.
    private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000;

    // Delay of 1 second used when removing temporary strong binding of a process (only on
    // non-low-memory devices).
    private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 1 * 1000;

    // These fields allow to override the parameters for testing - see
    // createBindingManagerForTesting().
    private final long mRemoveInitialBindingDelay;
    private final long mRemoveStrongBindingDelay;
    private final boolean mIsLowMemoryDevice;

    /**
     * Wraps ChildProcessConnection keeping track of additional information needed to manage the
     * bindings of the connection. The reference to ChildProcessConnection is cleared when the
     * connection goes away, but ManagedConnection itself is kept (until overwritten by a new entry
     * for the same pid).
     */
    private class ManagedConnection {
        // Set in constructor, cleared in clearConnection().
        private ChildProcessConnection mConnection;

        // True iff there is a strong binding kept on the service because it is working in
        // foreground.
        private boolean mInForeground;

        // True iff there is a strong binding kept on the service because it was bound for the
        // application background period.
        private boolean mBoundForBackgroundPeriod;

        // When mConnection is cleared, oom binding status is stashed here.
        private boolean mWasOomProtected;

        /** Removes the initial service binding. */
        private void removeInitialBinding() {
            final ChildProcessConnection connection = mConnection;
            if (connection == null || !connection.isInitialBindingBound()) return;

            ThreadUtils.postOnUiThreadDelayed(new Runnable() {
                @Override
                public void run() {
                    if (connection.isInitialBindingBound()) {
                        connection.removeInitialBinding();
                    }
                }
            }, mRemoveInitialBindingDelay);
        }

        /** Adds a strong service binding. */
        private void addStrongBinding() {
            ChildProcessConnection connection = mConnection;
            if (connection == null) return;

            connection.addStrongBinding();
        }

        /** Removes a strong service binding. */
        private void removeStrongBinding() {
            final ChildProcessConnection connection = mConnection;
            // We have to fail gracefully if the strong binding is not present, as on low-end the
            // binding could have been removed by dropOomBindings() when a new service was started.
            if (connection == null || !connection.isStrongBindingBound()) return;

            // This runnable performs the actual unbinding. It will be executed synchronously when
            // on low-end devices and posted with a delay otherwise.
            Runnable doUnbind = new Runnable() {
                @Override
                public void run() {
                    if (connection.isStrongBindingBound()) {
                        connection.removeStrongBinding();
                    }
                }
            };

            if (mIsLowMemoryDevice) {
                doUnbind.run();
            } else {
                ThreadUtils.postOnUiThreadDelayed(doUnbind, mRemoveStrongBindingDelay);
            }
        }

        /**
         * Drops the service bindings. This is used on low-end to drop bindings of the current
         * service when a new one is created.
         */
        private void dropBindings() {
            assert mIsLowMemoryDevice;
            ChildProcessConnection connection = mConnection;
            if (connection == null) return;

            connection.dropOomBindings();
        }

        ManagedConnection(ChildProcessConnection connection) {
            mConnection = connection;
        }

        /**
         * Sets the visibility of the service, adding or removing the strong binding as needed. This
         * also removes the initial binding, as the service visibility is now known.
         */
        void setInForeground(boolean nextInForeground) {
            if (!mInForeground && nextInForeground) {
                addStrongBinding();
            } else if (mInForeground && !nextInForeground) {
                removeStrongBinding();
            }

            removeInitialBinding();
            mInForeground = nextInForeground;
        }

        /**
         * Sets or removes additional binding when the service is main service during the embedder
         * background period.
         */
        void setBoundForBackgroundPeriod(boolean nextBound) {
            if (!mBoundForBackgroundPeriod && nextBound) {
                addStrongBinding();
            } else if (mBoundForBackgroundPeriod && !nextBound) {
                removeStrongBinding();
            }

            mBoundForBackgroundPeriod = nextBound;
        }

        boolean isOomProtected() {
            // When a process crashes, we can be queried about its oom status before or after the
            // connection is cleared. For the latter case, the oom status is stashed in
            // mWasOomProtected.
            return mConnection != null ?
                    mConnection.isOomProtectedOrWasWhenDied() : mWasOomProtected;
        }

        void clearConnection() {
            mWasOomProtected = mConnection.isOomProtectedOrWasWhenDied();
            mConnection = null;
        }

        /** @return true iff the reference to the connection is no longer held */
        @VisibleForTesting
        boolean isConnectionCleared() {
            return mConnection == null;
        }
    }

    // This can be manipulated on different threads, synchronize access on mManagedConnections.
    private final SparseArray<ManagedConnection> mManagedConnections =
            new SparseArray<ManagedConnection>();

    // The connection that was most recently set as foreground (using setInForeground()). This is
    // used to add additional binding on it when the embedder goes to background. On low-end, this
    // is also used to drop process bidnings when a new one is created, making sure that only one
    // renderer process at a time is protected from oom killing.
    private ManagedConnection mLastInForeground;

    // Synchronizes operations that access mLastInForeground: setInForeground() and
    // addNewConnection().
    private final Object mLastInForegroundLock = new Object();

    // The connection bound with additional binding in onSentToBackground().
    private ManagedConnection mBoundForBackgroundPeriod;

    /**
     * The constructor is private to hide parameters exposed for testing from the regular consumer.
     * Use factory methods to create an instance.
     */
    private BindingManagerImpl(boolean isLowMemoryDevice, long removeInitialBindingDelay,
            long removeStrongBindingDelay) {
        mIsLowMemoryDevice = isLowMemoryDevice;
        mRemoveInitialBindingDelay = removeInitialBindingDelay;
        mRemoveStrongBindingDelay = removeStrongBindingDelay;
    }

    public static BindingManagerImpl createBindingManager() {
        return new BindingManagerImpl(SysUtils.isLowEndDevice(),
                REMOVE_INITIAL_BINDING_DELAY_MILLIS, DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS);
    }

    /**
     * Creates a testing instance of BindingManager. Testing instance will have the unbinding delays
     * set to 0, so that the tests don't need to deal with actual waiting.
     * @param isLowEndDevice true iff the created instance should apply low-end binding policies
     */
    public static BindingManagerImpl createBindingManagerForTesting(boolean isLowEndDevice) {
        return new BindingManagerImpl(isLowEndDevice, 0, 0);
    }

    @Override
    public void addNewConnection(int pid, ChildProcessConnection connection) {
        synchronized (mLastInForegroundLock) {
            if (mIsLowMemoryDevice && mLastInForeground != null) mLastInForeground.dropBindings();
        }

        // This will reset the previous entry for the pid in the unlikely event of the OS
        // reusing renderer pids.
        synchronized (mManagedConnections) {
            mManagedConnections.put(pid, new ManagedConnection(connection));
        }
    }

    @Override
    public void setInForeground(int pid, boolean inForeground) {
        ManagedConnection managedConnection;
        synchronized (mManagedConnections) {
            managedConnection = mManagedConnections.get(pid);
        }

        if (managedConnection == null) {
            Log.w(TAG, "Cannot setInForeground() - never saw a connection for the pid: " +
                    Integer.toString(pid));
            return;
        }

        synchronized (mLastInForegroundLock) {
            managedConnection.setInForeground(inForeground);
            if (inForeground) mLastInForeground = managedConnection;
        }
    }

    @Override
    public void onSentToBackground() {
        assert mBoundForBackgroundPeriod == null;
        synchronized (mLastInForegroundLock) {
            // mLastInForeground can be null at this point as the embedding application could be
            // used in foreground without spawning any renderers.
            if (mLastInForeground != null) {
                mLastInForeground.setBoundForBackgroundPeriod(true);
                mBoundForBackgroundPeriod = mLastInForeground;
            }
        }
    }

    @Override
    public void onBroughtToForeground() {
        if (mBoundForBackgroundPeriod != null) {
            mBoundForBackgroundPeriod.setBoundForBackgroundPeriod(false);
            mBoundForBackgroundPeriod = null;
        }
    }

    @Override
    public boolean isOomProtected(int pid) {
        // In the unlikely event of the OS reusing renderer pid, the call will refer to the most
        // recent renderer of the given pid. The binding state for a pid is being reset in
        // addNewConnection().
        ManagedConnection managedConnection;
        synchronized (mManagedConnections) {
            managedConnection = mManagedConnections.get(pid);
        }
        return managedConnection != null ? managedConnection.isOomProtected() : false;
    }

    @Override
    public void clearConnection(int pid) {
        ManagedConnection managedConnection;
        synchronized (mManagedConnections) {
            managedConnection = mManagedConnections.get(pid);
        }
        if (managedConnection != null) managedConnection.clearConnection();
    }

    /** @return true iff the connection reference is no longer held */
    @VisibleForTesting
    public boolean isConnectionCleared(int pid) {
        synchronized (mManagedConnections) {
            return mManagedConnections.get(pid).isConnectionCleared();
        }
    }
}

/* [<][>][^][v][top][bottom][index][help] */