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

Commit 94b87271 authored by Fyodor Kupolov's avatar Fyodor Kupolov Committed by Android (Google) Code Review
Browse files

Merge "Close idle sqlite connections"

parents 598225ae fd9c4a54
Loading
Loading
Loading
Loading
+112 −1
Original line number Diff line number Diff line
@@ -19,12 +19,19 @@ package android.database.sqlite;
import android.app.ActivityManager;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.OperationCanceledException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.util.PrefixPrinter;
import android.util.Printer;

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

import dalvik.system.CloseGuard;

import java.io.Closeable;
@@ -77,6 +84,15 @@ public final class SQLiteConnectionPool implements Closeable {
    // and logging a message about the connection pool being busy.
    private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds

    // TODO b/63398887 Move to SQLiteGlobal
    private static final long IDLE_CONNECTION_CLOSE_DELAY_MILLIS = SystemProperties
            .getInt("persist.debug.sqlite.idle_connection_close_delay", 30000);

    // TODO b/63398887 STOPSHIP.
    // Temporarily enabled for testing across a broader set of dogfood devices.
    private static final boolean CLOSE_IDLE_CONNECTIONS = SystemProperties
            .getBoolean("persist.debug.sqlite.close_idle_connections", true);

    private final CloseGuard mCloseGuard = CloseGuard.get();

    private final Object mLock = new Object();
@@ -94,6 +110,9 @@ public final class SQLiteConnectionPool implements Closeable {
            new ArrayList<SQLiteConnection>();
    private SQLiteConnection mAvailablePrimaryConnection;

    @GuardedBy("mLock")
    private IdleConnectionHandler mIdleConnectionHandler;

    // Describes what should happen to an acquired connection when it is returned to the pool.
    enum AcquiredConnectionStatus {
        // The connection should be returned to the pool as usual.
@@ -154,6 +173,11 @@ public final class SQLiteConnectionPool implements Closeable {
            mConfiguration.lookasideSlotSize = 0;
        }
        setMaxConnectionPoolSizeLocked();

        // Do not close idle connections for in-memory databases
        if (CLOSE_IDLE_CONNECTIONS && !configuration.isInMemoryDb()) {
            setupIdleConnectionHandler(Looper.getMainLooper(), IDLE_CONNECTION_CLOSE_DELAY_MILLIS);
        }
    }

    @Override
@@ -351,7 +375,13 @@ public final class SQLiteConnectionPool implements Closeable {
     */
    public SQLiteConnection acquireConnection(String sql, int connectionFlags,
            CancellationSignal cancellationSignal) {
        return waitForConnection(sql, connectionFlags, cancellationSignal);
        SQLiteConnection con = waitForConnection(sql, connectionFlags, cancellationSignal);
        synchronized (mLock) {
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionAcquired(con);
            }
        }
        return con;
    }

    /**
@@ -368,6 +398,9 @@ public final class SQLiteConnectionPool implements Closeable {
     */
    public void releaseConnection(SQLiteConnection connection) {
        synchronized (mLock) {
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionReleased(connection);
            }
            AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
            if (status == null) {
                throw new IllegalStateException("Cannot perform this operation "
@@ -509,6 +542,27 @@ public final class SQLiteConnectionPool implements Closeable {
        }
    }

    // Can't throw.
    private boolean closeAvailableConnectionLocked(int connectionId) {
        final int count = mAvailableNonPrimaryConnections.size();
        for (int i = count - 1; i >= 0; i--) {
            SQLiteConnection c = mAvailableNonPrimaryConnections.get(i);
            if (c.getConnectionId() == connectionId) {
                closeConnectionAndLogExceptionsLocked(c);
                mAvailableNonPrimaryConnections.remove(i);
                return true;
            }
        }

        if (mAvailablePrimaryConnection != null
                && mAvailablePrimaryConnection.getConnectionId() == connectionId) {
            closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
            mAvailablePrimaryConnection = null;
            return true;
        }
        return false;
    }

    // Can't throw.
    private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
        final int count = mAvailableNonPrimaryConnections.size();
@@ -532,6 +586,9 @@ public final class SQLiteConnectionPool implements Closeable {
    private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
        try {
            connection.close(); // might throw
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionClosed(connection);
            }
        } catch (RuntimeException ex) {
            Log.e(TAG, "Failed to close connection, its fate is now in the hands "
                    + "of the merciful GC: " + connection, ex);
@@ -963,6 +1020,22 @@ public final class SQLiteConnectionPool implements Closeable {
        }
    }

    /**
     * Set up the handler based on the provided looper and delay.
     */
    @VisibleForTesting
    public void setupIdleConnectionHandler(Looper looper, long delayMs) {
        synchronized (mLock) {
            mIdleConnectionHandler = new IdleConnectionHandler(looper, delayMs);
        }
    }

    void disableIdleConnectionHandler() {
        synchronized (mLock) {
            mIdleConnectionHandler = null;
        }
    }

    private void throwIfClosedLocked() {
        if (!mIsOpen) {
            throw new IllegalStateException("Cannot perform this operation "
@@ -1078,4 +1151,42 @@ public final class SQLiteConnectionPool implements Closeable {
        public RuntimeException mException;
        public int mNonce;
    }

    private class IdleConnectionHandler extends Handler {
        private final long mDelay;

        IdleConnectionHandler(Looper looper, long delay) {
            super(looper);
            mDelay = delay;
        }

        @Override
        public void handleMessage(Message msg) {
            // Skip the (obsolete) message if the handler has changed
            synchronized (mLock) {
                if (this != mIdleConnectionHandler) {
                    return;
                }
                if (closeAvailableConnectionLocked(msg.what)) {
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, "Closed idle connection " + mConfiguration.label + " " + msg.what
                                + " after " + mDelay);
                    }
                }
            }
        }

        void connectionReleased(SQLiteConnection con) {
            sendEmptyMessageDelayed(con.getConnectionId(), mDelay);
        }

        void connectionAcquired(SQLiteConnection con) {
            // Remove any pending close operations
            removeMessages(con.getConnectionId());
        }

        void connectionClosed(SQLiteConnection con) {
            removeMessages(con.getConnectionId());
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -1713,6 +1713,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
                    if (!mHasAttachedDbsLocked) {
                        mHasAttachedDbsLocked = true;
                        disableWal = true;
                        mConnectionPoolLocked.disableIdleConnectionHandler();
                    }
                }
                if (disableWal) {
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.database.sqlite;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import android.os.HandlerThread;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;

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

import java.io.File;

/**
 * Tests for {@link SQLiteConnectionPool}
 *
 * <p>Run with:  bit FrameworksCoreTests:android.database.sqlite.SQLiteConnectionPoolTest
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SQLiteConnectionPoolTest {
    private static final String TAG = "SQLiteConnectionPoolTest";

    private Context mContext;
    private File mTestDatabase;
    private SQLiteDatabaseConfiguration mTestConf;


    @Before
    public void setup() {
        mContext = InstrumentationRegistry.getContext();
        SQLiteDatabase db = SQLiteDatabase
                .openOrCreateDatabase(mContext.getDatabasePath("pool_test"), null);
        mTestDatabase = new File(db.getPath());
        Log.i(TAG, "setup: created " + mTestDatabase);
        db.close();
        mTestConf = new SQLiteDatabaseConfiguration(mTestDatabase.getPath(), 0);
    }

    @After
    public void teardown() {
        if (mTestDatabase != null) {
            Log.i(TAG, "teardown: deleting " + mTestDatabase);
            SQLiteDatabase.deleteDatabase(mTestDatabase);
        }
    }

    @Test
    public void testCloseIdleConnections() throws InterruptedException {
        HandlerThread thread = new HandlerThread("test-close-idle-connections-thread");
        Log.i(TAG, "Starting " + thread.getName());
        thread.start();
        SQLiteConnectionPool pool = SQLiteConnectionPool.open(mTestConf);
        pool.setupIdleConnectionHandler(thread.getLooper(), 100);
        SQLiteConnection c1 = pool.acquireConnection("pragma user_version", 0, null);
        assertEquals("First connection should be returned", 0, c1.getConnectionId());
        pool.releaseConnection(c1);
        SQLiteConnection c2 = pool.acquireConnection("pragma user_version", 0, null);
        assertTrue("Returned connection should be the same", c1 == c2);
        pool.releaseConnection(c2);
        Thread.sleep(200);
        SQLiteConnection c3 = pool.acquireConnection("pragma user_version", 0, null);
        assertTrue("New connection should be returned", c1 != c3);
        assertEquals("New connection should be returned", 1, c3.getConnectionId());
        pool.releaseConnection(c3);
        pool.close();
        thread.quit();
    }
}