diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index 216c9c26424da..db0cac3eb9c7d 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -170,8 +170,8 @@ public final class SQLiteConnectionPool implements Closeable { // If timeout is set, setup idle connection handler // In case of MAX_VALUE - idle connections are never closed if (mConfiguration.idleConnectionTimeoutMs != Long.MAX_VALUE) { - setupIdleConnectionHandler(Looper.getMainLooper(), - mConfiguration.idleConnectionTimeoutMs); + setupIdleConnectionHandler( + Looper.getMainLooper(), mConfiguration.idleConnectionTimeoutMs, null); } } @@ -425,7 +425,7 @@ public final class SQLiteConnectionPool implements Closeable { mAvailablePrimaryConnection = connection; } wakeConnectionWaitersLocked(); - } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) { + } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize) { closeConnectionAndLogExceptionsLocked(connection); } else { if (recycleConnectionLocked(connection, status)) { @@ -456,6 +456,11 @@ public final class SQLiteConnectionPool implements Closeable { return true; } + @VisibleForTesting + public boolean hasAnyAvailableNonPrimaryConnection() { + return mAvailableNonPrimaryConnections.size() > 0; + } + /** * Returns true if the session should yield the connection due to * contention over available database connections. @@ -1061,9 +1066,11 @@ public final class SQLiteConnectionPool implements Closeable { * Set up the handler based on the provided looper and timeout. */ @VisibleForTesting - public void setupIdleConnectionHandler(Looper looper, long timeoutMs) { + public void setupIdleConnectionHandler( + Looper looper, long timeoutMs, Runnable onAllConnectionsIdle) { synchronized (mLock) { - mIdleConnectionHandler = new IdleConnectionHandler(looper, timeoutMs); + mIdleConnectionHandler = + new IdleConnectionHandler(looper, timeoutMs, onAllConnectionsIdle); } } @@ -1228,10 +1235,12 @@ public final class SQLiteConnectionPool implements Closeable { private class IdleConnectionHandler extends Handler { private final long mTimeout; + private final Runnable mOnAllConnectionsIdle; - IdleConnectionHandler(Looper looper, long timeout) { + IdleConnectionHandler(Looper looper, long timeout, Runnable onAllConnectionsIdle) { super(looper); mTimeout = timeout; + this.mOnAllConnectionsIdle = onAllConnectionsIdle; } @Override @@ -1247,6 +1256,9 @@ public final class SQLiteConnectionPool implements Closeable { + " after " + mTimeout); } } + if (mOnAllConnectionsIdle != null) { + mOnAllConnectionsIdle.run(); + } } } diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java index f1d27d4a13ab3..2b663bdb78615 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; +import java.util.concurrent.CountDownLatch; /** * Tests for {@link SQLiteConnectionPool} @@ -74,7 +75,7 @@ public class SQLiteConnectionPoolTest { Log.i(TAG, "Starting " + thread.getName()); thread.start(); SQLiteConnectionPool pool = SQLiteConnectionPool.open(mTestConf); - pool.setupIdleConnectionHandler(thread.getLooper(), 100); + pool.setupIdleConnectionHandler(thread.getLooper(), 100, null); SQLiteConnection c1 = pool.acquireConnection("pragma user_version", 0, null); assertEquals("First connection should be returned", 0, c1.getConnectionId()); pool.releaseConnection(c1); @@ -89,4 +90,31 @@ public class SQLiteConnectionPoolTest { pool.close(); thread.quit(); } + + @Test + public void testNonprimaryConnectionPoolRecycling() throws InterruptedException { + HandlerThread thread = new HandlerThread("test-close-idle-connections-thread"); + thread.start(); + SQLiteConnectionPool pool = SQLiteConnectionPool.open(mTestConf); + CountDownLatch latch = new CountDownLatch(1); + Runnable onIdleConnectionTimeout = () -> latch.countDown(); + pool.setupIdleConnectionHandler(thread.getLooper(), 1, onIdleConnectionTimeout); + + assertTrue("When the pool was just opened there should only be a primary connection.", + !pool.hasAnyAvailableNonPrimaryConnection()); + SQLiteConnection connection = pool.acquireConnection("pragma user_version", 0, null); + pool.releaseConnection(connection); + assertTrue("First time acquire should will return the primary connection.", + !pool.hasAnyAvailableNonPrimaryConnection()); + + // Wait for primary connection to time out + latch.await(); + + // Now that the primary is closed, acquiring again should open a non primary connection + connection = pool.acquireConnection("pragma user_version", 0, null); + pool.releaseConnection(connection); + assertTrue("There should be an available non primary connection in the pool.", + pool.hasAnyAvailableNonPrimaryConnection()); + pool.close(); + } }