Loading core/java/android/database/sqlite/DatabaseConnectionPool.java +85 −33 Original line number Diff line number Diff line Loading @@ -48,6 +48,9 @@ import java.util.Random; /** the main database connection to which this connection pool is attached */ private final SQLiteDatabase mParentDbObj; /** Random number generator used to pick a free connection out of the pool */ private Random rand; // lazily initialized /* package */ DatabaseConnectionPool(SQLiteDatabase db) { this.mParentDbObj = db; if (Log.isLoggable(TAG, Log.DEBUG)) { Loading Loading @@ -75,51 +78,68 @@ import java.util.Random; * @return the Database connection that the caller can use */ /* package */ SQLiteDatabase get(String sql) { if (Log.isLoggable(TAG, Log.DEBUG)) { doAsserts(); } SQLiteDatabase db = null; PoolObj poolObj = null; synchronized(mParentDbObj) { int poolSize = mPool.size(); if (Log.isLoggable(TAG, Log.DEBUG)) { assert sql != null; doAsserts(); } if (getFreePoolSize() == 0) { if (mMaxPoolSize == mPool.size()) { // no free ( = available) connections if (mMaxPoolSize == poolSize) { // maxed out. can't open any more connections. // let the caller wait on one of the pooled connections // preferably a connection caching the pre-compiled statement of the given SQL if (mMaxPoolSize == 1) { poolObj = mPool.get(0); } else { // get a random number between 0 and (mMaxPoolSize-1) poolObj = mPool.get( new Random(SystemClock.elapsedRealtime()).nextInt(mMaxPoolSize-1)); for (int i = 0; i < mMaxPoolSize; i++) { if (mPool.get(i).mDb.isSqlInStatementCache(sql)) { poolObj = mPool.get(i); break; } } if (poolObj == null) { // there are no database connections with the given SQL pre-compiled. // ok to return any of the connections. if (rand == null) { rand = new Random(SystemClock.elapsedRealtime()); } poolObj = mPool.get(rand.nextInt(mMaxPoolSize)); } } db = poolObj.mDb; } else { // create a new connection and add it to the pool, since we haven't reached // max pool size allowed int poolSize = getPoolSize(); db = mParentDbObj.createPoolConnection((short)(poolSize + 1)); poolObj = new PoolObj(db); mPool.add(poolSize, poolObj); } } else { // there are free connections available. pick one for (int i = mPool.size() - 1; i >= 0; i--) { // preferably a connection caching the pre-compiled statement of the given SQL for (int i = 0; i < poolSize; i++) { if (mPool.get(i).isFree() && mPool.get(i).mDb.isSqlInStatementCache(sql)) { poolObj = mPool.get(i); if (!poolObj.isFree()) { continue; break; } // it is free - but does its database object already have the given sql in its // statement-cache? db = poolObj.mDb; if (sql == null || db.isSqlInStatementCache(sql)) { // found a free connection we can use } if (poolObj == null) { // didn't find a free database connection with the given SQL already // pre-compiled. return a free connection (this means, the same SQL could be // pre-compiled on more than one database connection. potential wasted memory.) for (int i = 0; i < poolSize; i++) { if (mPool.get(i).isFree()) { poolObj = mPool.get(i); break; } // haven't found a database object which has the given sql in its // statement-cache } } db = poolObj.mDb; } assert poolObj != null; assert poolObj.mDb == db; Loading Loading @@ -181,13 +201,10 @@ import java.util.Random; return list; } /* package */ int getPoolSize() { synchronized(mParentDbObj) { return mPool.size(); } } private int getFreePoolSize() { /** * package level access for testing purposes only. otherwise, private should be sufficient. */ /* package */ int getFreePoolSize() { int count = 0; for (int i = mPool.size() - 1; i >= 0; i--) { if (mPool.get(i).isFree()) { Loading @@ -197,12 +214,29 @@ import java.util.Random; return count++; } /** * only for testing purposes */ /* package */ ArrayList<PoolObj> getPool() { return mPool; } @Override public String toString() { return "db: " + mParentDbObj.getPath() + ", threadid = " + Thread.currentThread().getId() + ", totalsize = " + mPool.size() + ", #free = " + getFreePoolSize() + ", maxpoolsize = " + mMaxPoolSize; StringBuilder buff = new StringBuilder(); buff.append("db: "); buff.append(mParentDbObj.getPath()); buff.append(", totalsize = "); buff.append(mPool.size()); buff.append(", #free = "); buff.append(getFreePoolSize()); buff.append(", maxpoolsize = "); buff.append(mMaxPoolSize); for (PoolObj p : mPool) { buff.append("\n"); buff.append(p.toString()); } return buff.toString(); } private void doAsserts() { Loading @@ -224,10 +258,21 @@ import java.util.Random; } } /** only used for testing purposes. */ /* package */ boolean isDatabaseObjFree(SQLiteDatabase db) { return mPool.get(db.mConnectionNum - 1).isFree(); } /** only used for testing purposes. */ /* package */ int getSize() { return mPool.size(); } /** * represents objects in the connection pool. * package-level access for testing purposes only. */ private static class PoolObj { /* package */ static class PoolObj { private final SQLiteDatabase mDb; private boolean mFreeBusyFlag = FREE; Loading Loading @@ -289,6 +334,13 @@ import java.util.Random; } } /** * only for testing purposes */ /* package */ synchronized int getNumHolders() { return mNumHolders; } @Override public String toString() { StringBuilder buff = new StringBuilder(); Loading core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java 0 → 100644 +364 −0 Original line number Diff line number Diff line /* * Copyright (C) 2006 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 android.content.Context; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.HashMap; public class DatabaseConnectionPoolTest extends AndroidTestCase { private static final String TAG = "DatabaseConnectionPoolTest"; private static final int MAX_CONN = 5; private static final String TEST_SQL = "select * from test where i = ? AND j = 1"; private static final String[] TEST_SQLS = new String[] { TEST_SQL, TEST_SQL + 1, TEST_SQL + 2, TEST_SQL + 3, TEST_SQL + 4 }; private SQLiteDatabase mDatabase; private File mDatabaseFile; private DatabaseConnectionPool mTestPool; @Override protected void setUp() throws Exception { super.setUp(); File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); mDatabaseFile = new File(dbDir, "database_test.db"); if (mDatabaseFile.exists()) { mDatabaseFile.delete(); } mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); assertNotNull(mDatabase); mDatabase.execSQL("create table test (i int, j int);"); mTestPool = new DatabaseConnectionPool(mDatabase); assertNotNull(mTestPool); } @Override protected void tearDown() throws Exception { mTestPool.close(); mDatabase.close(); mDatabaseFile.delete(); super.tearDown(); } @SmallTest public void testGetAndRelease() { mTestPool.setMaxPoolSize(MAX_CONN); // connections should be lazily created. assertEquals(0, mTestPool.getSize()); // MAX pool size should be set to MAX_CONN assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // get a connection SQLiteDatabase db = mTestPool.get(TEST_SQL); // pool size should be one - since only one should be allocated for the above get() assertEquals(1, mTestPool.getSize()); // no free connections should be available assertEquals(0, mTestPool.getFreePoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db)); // release the connection mTestPool.release(db); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(1, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertTrue(mTestPool.isDatabaseObjFree(db)); // release the same object again and expect IllegalStateException try { mTestPool.release(db); fail("illegalStateException expected"); } catch (IllegalStateException e ) { // expected. } } /** * get all connections from the pool and ask for one more. * should get one of the connections already got so far. */ @SmallTest public void testGetAllConnAndOneMore() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); ArrayList<SQLiteDatabase> dbObjs = new ArrayList<SQLiteDatabase>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQL); assertFalse(dbObjs.contains(db)); dbObjs.add(db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // pool is maxed out and no free connections. ask for one more connection SQLiteDatabase db1 = mTestPool.get(TEST_SQL); // make sure db1 is one of the existing ones assertTrue(dbObjs.contains(db1)); // pool size should remain at MAX_CONN assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db1 but since it is allocated 2 times, it should still remain 'busy' mTestPool.release(db1); assertFalse(mTestPool.isDatabaseObjFree(db1)); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release all connections for (int i = 0; i < MAX_CONN; i++) { mTestPool.release(dbObjs.get(i)); } // all objects in the pool should be freed now assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } /** * same as above except that each connection has different SQL statement associated with it. */ @SmallTest public void testConnRetrievalForPreviouslySeenSql() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); assertFalse(dbObjs.values().contains(db)); dbObjs.put(TEST_SQLS[i], db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // pool is maxed out and no free connections. ask for one more connection // use a previously seen SQL statement String testSql = TEST_SQLS[MAX_CONN - 1]; SQLiteDatabase db1 = mTestPool.get(testSql); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // make sure db1 is one of the existing ones assertTrue(dbObjs.values().contains(db1)); assertEquals(db1, dbObjs.get(testSql)); // do the same again SQLiteDatabase db2 = mTestPool.get(testSql); // make sure db1 is one of the existing ones assertEquals(db2, dbObjs.get(testSql)); // pool size should remain at MAX_CONN assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db1 but since the same connection is allocated 3 times, // it should still remain 'busy' mTestPool.release(db1); assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql))); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db2 but since the same connection is allocated 2 times, // it should still remain 'busy' mTestPool.release(db2); assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql))); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release all connections for (int i = 0; i < MAX_CONN; i++) { mTestPool.release(dbObjs.get(TEST_SQLS[i])); } // all objects in the pool should be freed now assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } private void executeSqlOnDatabaseConn(SQLiteDatabase db, String sql) { // execute the given SQL on the given database connection so that the prepared // statement for SQL is cached by the given database connection // this will help DatabaseConenctionPool figure out if a given SQL statement // is already cached by a database connection. db.execSQL(sql, new String[]{1+""}); } /** * get a connection for a SQL statement 'blah'. (connection_s) * make sure the pool has at least one free connection even after this get(). * and get a connection for the same SQL again. * this connection should be different from connection_s. * even though there is a connection with the given SQL pre-compiled, since is it not free * AND since the pool has free connections available, should get a new connection. */ @SmallTest public void testGetConnForTheSameSql() { mTestPool.setMaxPoolSize(MAX_CONN); SQLiteDatabase db = mTestPool.get(TEST_SQL); executeSqlOnDatabaseConn(db, TEST_SQL); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(1, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db)); SQLiteDatabase db1 = mTestPool.get(TEST_SQL); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db1)); assertFalse(db1.equals(db)); mTestPool.release(db); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); mTestPool.release(db1); assertEquals(2, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } /** * get the same connection N times and release it N times. * this tests DatabaseConnectionPool.PoolObj.mNumHolders */ @SmallTest public void testGetSameConnNtimesAndReleaseItNtimes() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); assertFalse(dbObjs.values().contains(db)); dbObjs.put(TEST_SQLS[i], db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // every connection in the pool should have numHolders = 1 for (int i = 0; i < MAX_CONN; i ++) { assertEquals(1, mTestPool.getPool().get(i).getNumHolders()); } // pool is maxed out and no free connections. ask for one more connection // use a previously seen SQL statement String testSql = TEST_SQLS[MAX_CONN - 1]; SQLiteDatabase db1 = mTestPool.get(testSql); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // make sure db1 is one of the existing ones assertTrue(dbObjs.values().contains(db1)); assertEquals(db1, dbObjs.get(testSql)); assertFalse(mTestPool.isDatabaseObjFree(db1)); DatabaseConnectionPool.PoolObj poolObj = mTestPool.getPool().get(db1.mConnectionNum - 1); int numHolders = poolObj.getNumHolders(); assertEquals(2, numHolders); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // get the same connection N times more int N = 100; for (int i = 0; i < N; i++) { SQLiteDatabase db2 = mTestPool.get(testSql); assertEquals(db1, db2); assertFalse(mTestPool.isDatabaseObjFree(db2)); // numHolders for this object should be now up by 1 int prev = numHolders; numHolders = poolObj.getNumHolders(); assertEquals(prev + 1, numHolders); } // release it N times for (int i = 0; i < N; i++) { mTestPool.release(db1); int prev = numHolders; numHolders = poolObj.getNumHolders(); assertEquals(prev - 1, numHolders); assertFalse(mTestPool.isDatabaseObjFree(db1)); } // the connection should still have 2 more holders assertFalse(mTestPool.isDatabaseObjFree(db1)); assertEquals(2, poolObj.getNumHolders()); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release 2 more times mTestPool.release(db1); mTestPool.release(db1); assertEquals(0, poolObj.getNumHolders()); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertTrue(mTestPool.isDatabaseObjFree(db1)); } @SmallTest public void testStressTest() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap<SQLiteDatabase, Integer> dbMap = new HashMap<SQLiteDatabase, Integer>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); assertFalse(dbMap.containsKey(db)); dbMap.put(db, 1); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // ask for lot more connections but since the pool is maxed out, we should start receiving // connections that we already got so far for (int i = MAX_CONN; i < 1000; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQL + i); assertTrue(dbMap.containsKey(db)); int k = dbMap.get(db); dbMap.put(db, ++k); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // print the distribution of the database connection handles received, should be uniform. for (SQLiteDatabase d : dbMap.keySet()) { Log.i(TAG, "connection # " + d.mConnectionNum + ", numHolders: " + dbMap.get(d)); } // print the pool info Log.i(TAG, mTestPool.toString()); // release all for (SQLiteDatabase d : dbMap.keySet()) { int num = dbMap.get(d); for (int i = 0; i < num; i++) { mTestPool.release(d); } } assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } } Loading
core/java/android/database/sqlite/DatabaseConnectionPool.java +85 −33 Original line number Diff line number Diff line Loading @@ -48,6 +48,9 @@ import java.util.Random; /** the main database connection to which this connection pool is attached */ private final SQLiteDatabase mParentDbObj; /** Random number generator used to pick a free connection out of the pool */ private Random rand; // lazily initialized /* package */ DatabaseConnectionPool(SQLiteDatabase db) { this.mParentDbObj = db; if (Log.isLoggable(TAG, Log.DEBUG)) { Loading Loading @@ -75,51 +78,68 @@ import java.util.Random; * @return the Database connection that the caller can use */ /* package */ SQLiteDatabase get(String sql) { if (Log.isLoggable(TAG, Log.DEBUG)) { doAsserts(); } SQLiteDatabase db = null; PoolObj poolObj = null; synchronized(mParentDbObj) { int poolSize = mPool.size(); if (Log.isLoggable(TAG, Log.DEBUG)) { assert sql != null; doAsserts(); } if (getFreePoolSize() == 0) { if (mMaxPoolSize == mPool.size()) { // no free ( = available) connections if (mMaxPoolSize == poolSize) { // maxed out. can't open any more connections. // let the caller wait on one of the pooled connections // preferably a connection caching the pre-compiled statement of the given SQL if (mMaxPoolSize == 1) { poolObj = mPool.get(0); } else { // get a random number between 0 and (mMaxPoolSize-1) poolObj = mPool.get( new Random(SystemClock.elapsedRealtime()).nextInt(mMaxPoolSize-1)); for (int i = 0; i < mMaxPoolSize; i++) { if (mPool.get(i).mDb.isSqlInStatementCache(sql)) { poolObj = mPool.get(i); break; } } if (poolObj == null) { // there are no database connections with the given SQL pre-compiled. // ok to return any of the connections. if (rand == null) { rand = new Random(SystemClock.elapsedRealtime()); } poolObj = mPool.get(rand.nextInt(mMaxPoolSize)); } } db = poolObj.mDb; } else { // create a new connection and add it to the pool, since we haven't reached // max pool size allowed int poolSize = getPoolSize(); db = mParentDbObj.createPoolConnection((short)(poolSize + 1)); poolObj = new PoolObj(db); mPool.add(poolSize, poolObj); } } else { // there are free connections available. pick one for (int i = mPool.size() - 1; i >= 0; i--) { // preferably a connection caching the pre-compiled statement of the given SQL for (int i = 0; i < poolSize; i++) { if (mPool.get(i).isFree() && mPool.get(i).mDb.isSqlInStatementCache(sql)) { poolObj = mPool.get(i); if (!poolObj.isFree()) { continue; break; } // it is free - but does its database object already have the given sql in its // statement-cache? db = poolObj.mDb; if (sql == null || db.isSqlInStatementCache(sql)) { // found a free connection we can use } if (poolObj == null) { // didn't find a free database connection with the given SQL already // pre-compiled. return a free connection (this means, the same SQL could be // pre-compiled on more than one database connection. potential wasted memory.) for (int i = 0; i < poolSize; i++) { if (mPool.get(i).isFree()) { poolObj = mPool.get(i); break; } // haven't found a database object which has the given sql in its // statement-cache } } db = poolObj.mDb; } assert poolObj != null; assert poolObj.mDb == db; Loading Loading @@ -181,13 +201,10 @@ import java.util.Random; return list; } /* package */ int getPoolSize() { synchronized(mParentDbObj) { return mPool.size(); } } private int getFreePoolSize() { /** * package level access for testing purposes only. otherwise, private should be sufficient. */ /* package */ int getFreePoolSize() { int count = 0; for (int i = mPool.size() - 1; i >= 0; i--) { if (mPool.get(i).isFree()) { Loading @@ -197,12 +214,29 @@ import java.util.Random; return count++; } /** * only for testing purposes */ /* package */ ArrayList<PoolObj> getPool() { return mPool; } @Override public String toString() { return "db: " + mParentDbObj.getPath() + ", threadid = " + Thread.currentThread().getId() + ", totalsize = " + mPool.size() + ", #free = " + getFreePoolSize() + ", maxpoolsize = " + mMaxPoolSize; StringBuilder buff = new StringBuilder(); buff.append("db: "); buff.append(mParentDbObj.getPath()); buff.append(", totalsize = "); buff.append(mPool.size()); buff.append(", #free = "); buff.append(getFreePoolSize()); buff.append(", maxpoolsize = "); buff.append(mMaxPoolSize); for (PoolObj p : mPool) { buff.append("\n"); buff.append(p.toString()); } return buff.toString(); } private void doAsserts() { Loading @@ -224,10 +258,21 @@ import java.util.Random; } } /** only used for testing purposes. */ /* package */ boolean isDatabaseObjFree(SQLiteDatabase db) { return mPool.get(db.mConnectionNum - 1).isFree(); } /** only used for testing purposes. */ /* package */ int getSize() { return mPool.size(); } /** * represents objects in the connection pool. * package-level access for testing purposes only. */ private static class PoolObj { /* package */ static class PoolObj { private final SQLiteDatabase mDb; private boolean mFreeBusyFlag = FREE; Loading Loading @@ -289,6 +334,13 @@ import java.util.Random; } } /** * only for testing purposes */ /* package */ synchronized int getNumHolders() { return mNumHolders; } @Override public String toString() { StringBuilder buff = new StringBuilder(); Loading
core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java 0 → 100644 +364 −0 Original line number Diff line number Diff line /* * Copyright (C) 2006 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 android.content.Context; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.HashMap; public class DatabaseConnectionPoolTest extends AndroidTestCase { private static final String TAG = "DatabaseConnectionPoolTest"; private static final int MAX_CONN = 5; private static final String TEST_SQL = "select * from test where i = ? AND j = 1"; private static final String[] TEST_SQLS = new String[] { TEST_SQL, TEST_SQL + 1, TEST_SQL + 2, TEST_SQL + 3, TEST_SQL + 4 }; private SQLiteDatabase mDatabase; private File mDatabaseFile; private DatabaseConnectionPool mTestPool; @Override protected void setUp() throws Exception { super.setUp(); File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); mDatabaseFile = new File(dbDir, "database_test.db"); if (mDatabaseFile.exists()) { mDatabaseFile.delete(); } mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); assertNotNull(mDatabase); mDatabase.execSQL("create table test (i int, j int);"); mTestPool = new DatabaseConnectionPool(mDatabase); assertNotNull(mTestPool); } @Override protected void tearDown() throws Exception { mTestPool.close(); mDatabase.close(); mDatabaseFile.delete(); super.tearDown(); } @SmallTest public void testGetAndRelease() { mTestPool.setMaxPoolSize(MAX_CONN); // connections should be lazily created. assertEquals(0, mTestPool.getSize()); // MAX pool size should be set to MAX_CONN assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // get a connection SQLiteDatabase db = mTestPool.get(TEST_SQL); // pool size should be one - since only one should be allocated for the above get() assertEquals(1, mTestPool.getSize()); // no free connections should be available assertEquals(0, mTestPool.getFreePoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db)); // release the connection mTestPool.release(db); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(1, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertTrue(mTestPool.isDatabaseObjFree(db)); // release the same object again and expect IllegalStateException try { mTestPool.release(db); fail("illegalStateException expected"); } catch (IllegalStateException e ) { // expected. } } /** * get all connections from the pool and ask for one more. * should get one of the connections already got so far. */ @SmallTest public void testGetAllConnAndOneMore() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); ArrayList<SQLiteDatabase> dbObjs = new ArrayList<SQLiteDatabase>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQL); assertFalse(dbObjs.contains(db)); dbObjs.add(db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // pool is maxed out and no free connections. ask for one more connection SQLiteDatabase db1 = mTestPool.get(TEST_SQL); // make sure db1 is one of the existing ones assertTrue(dbObjs.contains(db1)); // pool size should remain at MAX_CONN assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db1 but since it is allocated 2 times, it should still remain 'busy' mTestPool.release(db1); assertFalse(mTestPool.isDatabaseObjFree(db1)); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release all connections for (int i = 0; i < MAX_CONN; i++) { mTestPool.release(dbObjs.get(i)); } // all objects in the pool should be freed now assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } /** * same as above except that each connection has different SQL statement associated with it. */ @SmallTest public void testConnRetrievalForPreviouslySeenSql() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); assertFalse(dbObjs.values().contains(db)); dbObjs.put(TEST_SQLS[i], db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // pool is maxed out and no free connections. ask for one more connection // use a previously seen SQL statement String testSql = TEST_SQLS[MAX_CONN - 1]; SQLiteDatabase db1 = mTestPool.get(testSql); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // make sure db1 is one of the existing ones assertTrue(dbObjs.values().contains(db1)); assertEquals(db1, dbObjs.get(testSql)); // do the same again SQLiteDatabase db2 = mTestPool.get(testSql); // make sure db1 is one of the existing ones assertEquals(db2, dbObjs.get(testSql)); // pool size should remain at MAX_CONN assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db1 but since the same connection is allocated 3 times, // it should still remain 'busy' mTestPool.release(db1); assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql))); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db2 but since the same connection is allocated 2 times, // it should still remain 'busy' mTestPool.release(db2); assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql))); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release all connections for (int i = 0; i < MAX_CONN; i++) { mTestPool.release(dbObjs.get(TEST_SQLS[i])); } // all objects in the pool should be freed now assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } private void executeSqlOnDatabaseConn(SQLiteDatabase db, String sql) { // execute the given SQL on the given database connection so that the prepared // statement for SQL is cached by the given database connection // this will help DatabaseConenctionPool figure out if a given SQL statement // is already cached by a database connection. db.execSQL(sql, new String[]{1+""}); } /** * get a connection for a SQL statement 'blah'. (connection_s) * make sure the pool has at least one free connection even after this get(). * and get a connection for the same SQL again. * this connection should be different from connection_s. * even though there is a connection with the given SQL pre-compiled, since is it not free * AND since the pool has free connections available, should get a new connection. */ @SmallTest public void testGetConnForTheSameSql() { mTestPool.setMaxPoolSize(MAX_CONN); SQLiteDatabase db = mTestPool.get(TEST_SQL); executeSqlOnDatabaseConn(db, TEST_SQL); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(1, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db)); SQLiteDatabase db1 = mTestPool.get(TEST_SQL); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db1)); assertFalse(db1.equals(db)); mTestPool.release(db); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); mTestPool.release(db1); assertEquals(2, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } /** * get the same connection N times and release it N times. * this tests DatabaseConnectionPool.PoolObj.mNumHolders */ @SmallTest public void testGetSameConnNtimesAndReleaseItNtimes() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); assertFalse(dbObjs.values().contains(db)); dbObjs.put(TEST_SQLS[i], db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // every connection in the pool should have numHolders = 1 for (int i = 0; i < MAX_CONN; i ++) { assertEquals(1, mTestPool.getPool().get(i).getNumHolders()); } // pool is maxed out and no free connections. ask for one more connection // use a previously seen SQL statement String testSql = TEST_SQLS[MAX_CONN - 1]; SQLiteDatabase db1 = mTestPool.get(testSql); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // make sure db1 is one of the existing ones assertTrue(dbObjs.values().contains(db1)); assertEquals(db1, dbObjs.get(testSql)); assertFalse(mTestPool.isDatabaseObjFree(db1)); DatabaseConnectionPool.PoolObj poolObj = mTestPool.getPool().get(db1.mConnectionNum - 1); int numHolders = poolObj.getNumHolders(); assertEquals(2, numHolders); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // get the same connection N times more int N = 100; for (int i = 0; i < N; i++) { SQLiteDatabase db2 = mTestPool.get(testSql); assertEquals(db1, db2); assertFalse(mTestPool.isDatabaseObjFree(db2)); // numHolders for this object should be now up by 1 int prev = numHolders; numHolders = poolObj.getNumHolders(); assertEquals(prev + 1, numHolders); } // release it N times for (int i = 0; i < N; i++) { mTestPool.release(db1); int prev = numHolders; numHolders = poolObj.getNumHolders(); assertEquals(prev - 1, numHolders); assertFalse(mTestPool.isDatabaseObjFree(db1)); } // the connection should still have 2 more holders assertFalse(mTestPool.isDatabaseObjFree(db1)); assertEquals(2, poolObj.getNumHolders()); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release 2 more times mTestPool.release(db1); mTestPool.release(db1); assertEquals(0, poolObj.getNumHolders()); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertTrue(mTestPool.isDatabaseObjFree(db1)); } @SmallTest public void testStressTest() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap<SQLiteDatabase, Integer> dbMap = new HashMap<SQLiteDatabase, Integer>(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); assertFalse(dbMap.containsKey(db)); dbMap.put(db, 1); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // ask for lot more connections but since the pool is maxed out, we should start receiving // connections that we already got so far for (int i = MAX_CONN; i < 1000; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQL + i); assertTrue(dbMap.containsKey(db)); int k = dbMap.get(db); dbMap.put(db, ++k); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // print the distribution of the database connection handles received, should be uniform. for (SQLiteDatabase d : dbMap.keySet()) { Log.i(TAG, "connection # " + d.mConnectionNum + ", numHolders: " + dbMap.get(d)); } // print the pool info Log.i(TAG, mTestPool.toString()); // release all for (SQLiteDatabase d : dbMap.keySet()) { int num = dbMap.get(d); for (int i = 0; i < num; i++) { mTestPool.release(d); } } assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } }