From 422dad0f5069a96c002faf31540bf471a7052585 Mon Sep 17 00:00:00 2001 From: Vasu Nori Date: Fri, 3 Sep 2010 16:03:08 -0700 Subject: [PATCH] android change to handle Change-Id: Idbeed81b5b7349059e467b33a8641abf0b4aaeff Change-Id: Icf221a8e8d4c281f7719875816835ad7dfe7f3d1 --- .../database/sqlite/SQLiteCompiledSql.java | 16 ++++ .../database/sqlite/SQLiteDatabase.java | 45 ++++++++++- .../database/sqlite/SQLiteProgram.java | 2 +- .../SQLiteUnfinalizedObjectsException.java | 31 +++++++ core/jni/android_database_SQLiteDatabase.cpp | 3 + .../SQLiteUnfinalizedExceptionTest.java | 81 +++++++++++++++++++ 6 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java create mode 100644 core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java index aa0a57d00cbc3..9541380f67c54 100644 --- a/core/java/android/database/sqlite/SQLiteCompiledSql.java +++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java @@ -144,6 +144,22 @@ import android.util.Log; } } + @Override public String toString() { + synchronized(this) { + StringBuilder buff = new StringBuilder(); + buff.append(" nStatement="); + buff.append(nStatement); + buff.append(", db="); + buff.append(mDatabase.getPath()); + buff.append(", db_connectionNum="); + buff.append(mDatabase.mConnectionNum); + buff.append(", sql="); + int len = mSqlStmt.length(); + buff.append(mSqlStmt.substring(0, (len > 100) ? 100 : len)); + return buff.toString(); + } + } + /** * Compiles SQL into a SQLite program. * diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 0137ea62edbad..6937da01f6458 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1066,7 +1066,7 @@ public class SQLiteDatabase extends SQLiteClosable { closePendingStatements(); releaseCustomFunctions(); // close this database instance - regardless of its reference count value - dbclose(); + closeDatabase(); if (mConnectionPool != null) { if (Log.isLoggable(TAG, Log.DEBUG)) { assert mConnectionPool != null; @@ -1075,7 +1075,7 @@ public class SQLiteDatabase extends SQLiteClosable { mConnectionPool.close(); } } finally { - unlock(); + unlock(); } } @@ -1099,6 +1099,47 @@ public class SQLiteDatabase extends SQLiteClosable { } } + /** + * package level access for testing purposes + */ + /* package */ void closeDatabase() throws SQLiteException { + try { + dbclose(); + } catch (SQLiteUnfinalizedObjectsException e) { + String msg = e.getMessage(); + String[] tokens = msg.split(",", 2); + int stmtId = Integer.parseInt(tokens[0]); + // get extra info about this statement, if it is still to be released by closeClosable() + Iterator> iter = mPrograms.entrySet().iterator(); + boolean found = false; + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + SQLiteClosable program = entry.getKey(); + if (program != null && program instanceof SQLiteProgram) { + SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql; + if (compiledSql.nStatement == stmtId) { + msg = compiledSql.toString(); + found = true; + } + } + } + if (!found) { + // the statement is already released by closeClosable(). is it waiting to be + // finalized? + if (mClosedStatementIds.contains(stmtId)) { + Log.w(TAG, "this shouldn't happen. finalizing the statement now: "); + closePendingStatements(); + // try to close the database again + closeDatabase(); + } + } else { + // the statement is not yet closed. most probably programming error in the app. + Log.w(TAG, "dbclose failed due to un-close()d SQL statements: " + msg); + throw e; + } + } + } + /** * Native call to close the database. */ diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 9b7d823636cbc..5e08aed1184c5 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -52,7 +52,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { /** * the SQLiteCompiledSql object for the given sql statement. */ - private SQLiteCompiledSql mCompiledSql; + /* package */ SQLiteCompiledSql mCompiledSql; /** * SQLiteCompiledSql statement id is populated with the corresponding object from the above diff --git a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java new file mode 100644 index 0000000000000..bcf95e2ef1fe2 --- /dev/null +++ b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 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; + +/** + * Thrown if the database can't be closed because of some un-closed + * Cursor or SQLiteStatement objects. Could happen when a thread is trying to close + * the database while another thread still hasn't closed a Cursor on that database. + * @hide + */ +public class SQLiteUnfinalizedObjectsException extends SQLiteException { + public SQLiteUnfinalizedObjectsException() {} + + public SQLiteUnfinalizedObjectsException(String error) { + super(error); + } +} diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp index 4b3f1c0b40a95..36e90899765fc 100644 --- a/core/jni/android_database_SQLiteDatabase.cpp +++ b/core/jni/android_database_SQLiteDatabase.cpp @@ -593,6 +593,9 @@ void throw_sqlite3_exception(JNIEnv* env, int errcode, case SQLITE_MISMATCH: exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException"; break; + case SQLITE_UNCLOSED: + exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException"; + break; default: exceptionClass = "android/database/sqlite/SQLiteException"; break; diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java new file mode 100644 index 0000000000000..cd2005dd16ff9 --- /dev/null +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 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.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.File; + +public class SQLiteUnfinalizedExceptionTest extends AndroidTestCase { + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + private static final String TABLE_NAME = "testCursor"; + @Override + protected void setUp() throws Exception { + super.setUp(); + + File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); + mDatabaseFile = new File(dbDir, "UnfinalizedExceptionTest.db"); + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + @SmallTest + public void testUnfinalizedExceptionNotExcpected() { + mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);"); + // the above statement should be in SQLiteDatabase.mPrograms + // and should automatically be finalized when database is closed + mDatabase.lock(); + try { + mDatabase.closeDatabase(); + } finally { + mDatabase.unlock(); + } + } + + @SmallTest + public void testUnfinalizedException() { + mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);"); + mDatabase.lock(); + mDatabase.closePendingStatements(); // clears the above from finalizer queue in mdatabase + mDatabase.unlock(); + ClassToTestSqlCompilationAndCaching.create(mDatabase, "select * from " + TABLE_NAME); + // since the above is NOT closed, closing database should fail + mDatabase.lock(); + try { + mDatabase.closeDatabase(); + fail("exception expected"); + } catch (SQLiteUnfinalizedObjectsException e) { + // expected + } finally { + mDatabase.unlock(); + } + } +}