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

Commit 6c354da9 authored by Vasu Nori's avatar Vasu Nori
Browse files

read old version of data and use multiple connections to db

cts tests are in Change-Id: Ifcc89b4ff484c7c810fd2d450ded212a43360dda
dependency on: Change-Id: I938c42afc3fb50f5296d01c55ffcf4a102d8b0cb

1. Use sqlite's work-in-progress writeahead logging feature to read old
     versions of data and thus increase concurrency of readers
     even when there is a writer on the database
2. New API executeQueriesInParallel() sets up a database connecion pool
     automatically created and managed by sqlite java layer
3. To increase reader concurrency, add an option to do BEGIN IMMEDIATE xaction
     instead of BEGIN EXCLUSIVE

Change-Id: I3ce55a8a7cba538f01f731736e7de8ae1e2a8a1f
parent c465f9c4
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -60294,6 +60294,17 @@
 visibility="public"
>
</method>
<method name="beginTransactionNonExclusive"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="beginTransactionWithListener"
 return="void"
 abstract="false"
@@ -60307,6 +60318,19 @@
<parameter name="transactionListener" type="android.database.sqlite.SQLiteTransactionListener">
</parameter>
</method>
<method name="beginTransactionWithListenerNonExclusive"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="transactionListener" type="android.database.sqlite.SQLiteTransactionListener">
</parameter>
</method>
<method name="close"
 return="void"
 abstract="false"
@@ -60363,6 +60387,17 @@
<parameter name="whereArgs" type="java.lang.String[]">
</parameter>
</method>
<method name="enableWriteAheadLogging"
 return="void"
 abstract="false"
 native="false"
 synchronized="true"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="endTransaction"
 return="void"
 abstract="false"
@@ -60938,6 +60973,19 @@
<exception name="SQLException" type="android.database.SQLException">
</exception>
</method>
<method name="setConnectionPoolSize"
 return="void"
 abstract="false"
 native="false"
 synchronized="true"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="size" type="int">
</parameter>
</method>
<method name="setLocale"
 return="void"
 abstract="false"
+309 −0
Original line number Diff line number Diff line
/*
 * 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.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. It is set based on the amount of memory the device has.
     * TODO: set this with 'a system call' which returns the amount of memory the device has
     */
    private static final int DEFAULT_CONNECTION_POOL_SIZE = 1;

    /** the pool size set for this {@link SQLiteDatabase} */
    private volatile int mMaxPoolSize = DEFAULT_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;

    /* 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 */ void close() {
        synchronized(mParentDbObj) {
            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 */ SQLiteDatabase get(String sql) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            doAsserts();
        }

        SQLiteDatabase db = null;
        PoolObj poolObj = null;
        synchronized(mParentDbObj) {
            if (getFreePoolSize() == 0) {
                if (mMaxPoolSize == mPool.size()) {
                    // maxed out. can't open any more connections.
                    // let the caller wait on one of the pooled connections
                    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));
                    }
                    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--) {
                    poolObj = mPool.get(i);
                    if (!poolObj.isFree()) {
                        continue;
                    }
                    // 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
                        break;
                    }
                    // haven't found a database object which has the given sql in its
                    // statement-cache
                }
            }

            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 */ void release(SQLiteDatabase db) {
        PoolObj poolObj;
        synchronized(mParentDbObj) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                assert db.mConnectionNum > 0;
                doAsserts();
                assert mPool.get(db.mConnectionNum - 1).mDb == db;
            }

            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 */ ArrayList<SQLiteDatabase> getConnectionList() {
        ArrayList<SQLiteDatabase> list = new ArrayList<SQLiteDatabase>();
        synchronized(mParentDbObj) {
            for (int i = mPool.size() - 1; i >= 0; i--) {
                list.add(mPool.get(i).mDb);
            }
        }
        return list;
    }

    /* package */ int getPoolSize() {
        synchronized(mParentDbObj) {
            return mPool.size();
        }
    }

    private int getFreePoolSize() {
        int count = 0;
        for (int i = mPool.size() - 1; i >= 0; i--) {
            if (mPool.get(i).isFree()) {
                count++;
            }
        }
        return count++;
    }

    @Override
    public String toString() {
        return "db: " + mParentDbObj.getPath() +
                ", threadid = " + Thread.currentThread().getId() +
                ", totalsize = " + mPool.size() + ", #free = " + getFreePoolSize() +
                ", maxpoolsize = " + mMaxPoolSize;
    }

    private void doAsserts() {
        for (int i = 0; i < mPool.size(); i++) {
            mPool.get(i).verify();
            assert mPool.get(i).mDb.mConnectionNum == (i + 1);
        }
    }

    /* package */ void setMaxPoolSize(int size) {
        synchronized(mParentDbObj) {
            mMaxPoolSize = size;
        }
    }

    /* package */ int getMaxPoolSize() {
        synchronized(mParentDbObj) {
            return mMaxPoolSize;
        }
    }

    /**
     * represents objects in the connection pool.
     */
    private 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;
            }
        }

        @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();
        }
    }
}
+290 −36

File changed.

Preview size limit exceeded, changes collapsed.

+26 −17
Original line number Diff line number Diff line
@@ -63,8 +63,8 @@ enum {

static jfieldID offset_db_handle;

static char *createStr(const char *path) {
    int len = strlen(path);
static char *createStr(const char *path, short extra) {
    int len = strlen(path) + extra;
    char *str = (char *)malloc(len + 1);
    strncpy(str, path, len);
    str[len] = NULL;
@@ -85,7 +85,7 @@ static void registerLoggingFunc(const char *path) {
    }

    LOGV("Registering sqlite logging func \n");
    int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path));
    int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path, 0));
    if (err != SQLITE_OK) {
        LOGE("sqlite_config failed error_code = %d. THIS SHOULD NEVER occur.\n", err);
        return;
@@ -176,13 +176,17 @@ done:
    if (handle != NULL) sqlite3_close(handle);
}

static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName) {
static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName, short connNum) {
    char const *path = env->GetStringUTFChars(databaseName, NULL);
    if (path == NULL) {
        LOGE("Failure in getDatabaseName(). VM ran out of memory?\n");
        return NULL; // VM would have thrown OutOfMemoryError
    }
    char *dbNameStr = createStr(path);
    char *dbNameStr = createStr(path, 4);
    if (connNum > 999) { // TODO: if number of pooled connections > 999, fix this line.
      connNum = -1;
    }
    sprintf(dbNameStr + strlen(path), "|%03d", connNum);
    env->ReleaseStringUTFChars(databaseName, path);
    return dbNameStr;
}
@@ -192,10 +196,10 @@ static void sqlTrace(void *databaseName, const char *sql) {
}

/* public native void enableSqlTracing(); */
static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName)
static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
{
    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
    sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName));
    sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName, connType));
}

static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
@@ -204,13 +208,13 @@ static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
}

/* public native void enableSqlProfiling(); */
static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName)
static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
{
    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
    sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName));
    sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName,
            connType));
}


/* public native void close(); */
static void dbclose(JNIEnv* env, jobject object)
{
@@ -251,7 +255,8 @@ static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString)
    jsize sqlLen = env->GetStringLength(sqlString);

    if (sql == NULL || sqlLen == 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "You must supply an SQL string");
        jniThrowException(env, "java/lang/IllegalArgumentException",
                "You must supply an SQL string");
        return;
    }

@@ -261,7 +266,8 @@ static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString)

    if (err != SQLITE_OK) {
        char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
        LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle), handle, sql8);
        LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle),
                handle, sql8);
        throw_sqlite3_exception(env, handle, sql8);
        env->ReleaseStringUTFChars(sqlString, sql8);
        return;
@@ -272,10 +278,12 @@ static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString)

    if (stepErr != SQLITE_DONE) {
        if (stepErr == SQLITE_ROW) {
            throw_sqlite3_exception(env, "Queries cannot be performed using execSQL(), use query() instead.");
            throw_sqlite3_exception(env,
                    "Queries cannot be performed using execSQL(), use query() instead.");
        } else {
            char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
            LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle), handle, sql8);
            LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle),
                    handle, sql8);
            throw_sqlite3_exception(env, handle, sql8);
            env->ReleaseStringUTFChars(sqlString, sql8);

@@ -455,8 +463,8 @@ static JNINativeMethod sMethods[] =
    /* name, signature, funcPtr */
    {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
    {"dbclose", "()V", (void *)dbclose},
    {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing},
    {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling},
    {"enableSqlTracing", "(Ljava/lang/String;S)V", (void *)enableSqlTracing},
    {"enableSqlProfiling", "(Ljava/lang/String;S)V", (void *)enableSqlProfiling},
    {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL},
    {"lastInsertRow", "()J", (void *)lastInsertRow},
    {"lastChangeCount", "()I", (void *)lastChangeCount},
@@ -482,7 +490,8 @@ int register_android_database_SQLiteDatabase(JNIEnv *env)
        return -1;
    }

    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase", sMethods, NELEM(sMethods));
    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase",
            sMethods, NELEM(sMethods));
}

/* throw a SQLiteException with a message appropriate for the error in handle */
+274 −0

File added.

Preview size limit exceeded, changes collapsed.