/* * Copyright (C) 20010 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.res.Resources; import android.os.SystemClock; import android.util.Log; import java.util.ArrayList; import java.util.HashSet; import java.util.Random; /** * A connection pool to be used by readers. * Note that each connection can be used by only one reader at a time. */ /* package */ class DatabaseConnectionPool { private static final String TAG = "DatabaseConnectionPool"; /** The default connection pool size. */ private volatile int mMaxPoolSize = Resources.getSystem().getInteger(com.android.internal.R.integer.db_connection_pool_size); /** The connection pool objects are stored in this member. * TODO: revisit this data struct as the number of pooled connections increase beyond * single-digit values. */ private final ArrayList<PoolObj> mPool = new ArrayList<PoolObj>(mMaxPoolSize); /** 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)) { Log.d(TAG, "Max Pool Size: " + mMaxPoolSize); } } /** * close all database connections in the pool - even if they are in use! */ /* package */ synchronized void close() { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Closing the connection pool on " + mParentDbObj.getPath() + toString()); } for (int i = mPool.size() - 1; i >= 0; i--) { mPool.get(i).mDb.close(); } mPool.clear(); } /** * get a free connection from the pool * * @param sql if not null, try to find a connection inthe pool which already has cached * the compiled statement for this sql. * @return the Database connection that the caller can use */ /* package */ synchronized SQLiteDatabase get(String sql) { SQLiteDatabase db = null; PoolObj poolObj = null; int poolSize = mPool.size(); if (Log.isLoggable(TAG, Log.DEBUG)) { assert sql != null; doAsserts(); } if (getFreePoolSize() == 0) { // 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 { for (int i = 0; i < mMaxPoolSize; i++) { if (mPool.get(i).mDb.isInStatementCache(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 db = mParentDbObj.createPoolConnection((short)(poolSize + 1)); poolObj = new PoolObj(db); mPool.add(poolSize, poolObj); } } else { // there are free connections available. pick one // 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.isInStatementCache(sql)) { poolObj = mPool.get(i); break; } } 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; } } } db = poolObj.mDb; } assert poolObj != null; assert poolObj.mDb == db; poolObj.acquire(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "END get-connection: " + toString() + poolObj.toString()); } return db; // TODO if a thread acquires a connection and dies without releasing the connection, then // there could be a connection leak. } /** * release the given database connection back to the pool. * @param db the connection to be released */ /* package */ synchronized void release(SQLiteDatabase db) { if (Log.isLoggable(TAG, Log.DEBUG)) { assert db.mConnectionNum > 0; doAsserts(); assert mPool.get(db.mConnectionNum - 1).mDb == db; } PoolObj poolObj = mPool.get(db.mConnectionNum - 1); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "BEGIN release-conn: " + toString() + poolObj.toString()); } if (poolObj.isFree()) { throw new IllegalStateException("Releasing object already freed: " + db.mConnectionNum); } poolObj.release(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "END release-conn: " + toString() + poolObj.toString()); } } /** * Returns a list of all database connections in the pool (both free and busy connections). * This method is used when "adb bugreport" is done. */ /* package */ synchronized ArrayList<SQLiteDatabase> getConnectionList() { ArrayList<SQLiteDatabase> list = new ArrayList<SQLiteDatabase>(); for (int i = mPool.size() - 1; i >= 0; i--) { list.add(mPool.get(i).mDb); } return list; } /** * 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()) { count++; } } return count++; } /** * only for testing purposes */ /* package */ ArrayList<PoolObj> getPool() { return mPool; } @Override public String toString() { 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() { for (int i = 0; i < mPool.size(); i++) { mPool.get(i).verify(); assert mPool.get(i).mDb.mConnectionNum == (i + 1); } } /** only used for testing purposes. */ /* package */ synchronized void setMaxPoolSize(int size) { mMaxPoolSize = size; } /** only used for testing purposes. */ /* package */ synchronized int getMaxPoolSize() { return mMaxPoolSize; } /** 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. */ /* package */ static class PoolObj { private final SQLiteDatabase mDb; private boolean mFreeBusyFlag = FREE; private static final boolean FREE = true; private static final boolean BUSY = false; /** the number of threads holding this connection */ // @GuardedBy("this") private int mNumHolders = 0; /** contains the threadIds of the threads holding this connection. * used for debugging purposes only. */ // @GuardedBy("this") private HashSet<Long> mHolderIds = new HashSet<Long>(); public PoolObj(SQLiteDatabase db) { mDb = db; } private synchronized void acquire() { if (Log.isLoggable(TAG, Log.DEBUG)) { assert isFree(); long id = Thread.currentThread().getId(); assert !mHolderIds.contains(id); mHolderIds.add(id); } mNumHolders++; mFreeBusyFlag = BUSY; } private synchronized void release() { if (Log.isLoggable(TAG, Log.DEBUG)) { long id = Thread.currentThread().getId(); assert mHolderIds.size() == mNumHolders; assert mHolderIds.contains(id); mHolderIds.remove(id); } mNumHolders--; if (mNumHolders == 0) { mFreeBusyFlag = FREE; } } private synchronized boolean isFree() { if (Log.isLoggable(TAG, Log.DEBUG)) { verify(); } return (mFreeBusyFlag == FREE); } private synchronized void verify() { if (mFreeBusyFlag == FREE) { assert mNumHolders == 0; } else { assert mNumHolders > 0; } } /** * only for testing purposes */ /* package */ synchronized int getNumHolders() { return mNumHolders; } @Override public String toString() { StringBuilder buff = new StringBuilder(); buff.append(", conn # "); buff.append(mDb.mConnectionNum); buff.append(", mCountHolders = "); synchronized(this) { buff.append(mNumHolders); buff.append(", freeBusyFlag = "); buff.append(mFreeBusyFlag); for (Long l : mHolderIds) { buff.append(", id = " + l); } } return buff.toString(); } } }