blob: 39a9d23c0220aaf6ce603f4e9d07a6ad1c7c471d [file] [log] [blame]
/*
* 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();
}
}
}