SQLCipher throwing exception in insert - android

I got an exception in my code.
10-10 22:05:33.866 5725 5725 E AndroidRuntime: Caused by: net.sqlcipher.database.SQLiteDiskIOException: error code 10: disk I/O error
10-10 22:05:33.866 5725 5725 E AndroidRuntime: at net.sqlcipher.database.SQLiteStatement.native_execute(Native Method)
10-10 22:05:33.866 5725 5725 E AndroidRuntime: at net.sqlcipher.database.SQLiteStatement.execute(SQLiteStatement.java:58)
10-10 22:05:33.866 5725 5725 E AndroidRuntime: at net.sqlcipher.database.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:2074)
This has only happened once (so far) among thousands of successful inserts and selects, so I suspect there is some kind of race condition between different threads in the same process trying to access the same database.
The actual code is unimportant, this is just a plain insert of a new record, and the on conflict parameter is saying to ignore any conflict:
ContentValues values = new ContentValues();
// values.put(); calls go here, nothing strange about the data
db.insertWithOnConflict("Sample", null, values, SQLiteDatabase.CONFLICT_IGNORE);
Architectually I have a singleton object in my Service encapsulating a single continuously open connection to the database with a cache sitting in front of it. Multiple threads call the object from different places in the Service to insert data, these are typically listeners which are logging things such as GPS position, battery state, etc. Reading typically comes from the cache and does not hit the database.
My first thought was that perhaps I need to synchronize the class through which database access flow (it smells like two different threads might be trying to insert at almost exactly the same time, which in practice would happen rarely), but this answer seems to be saying that I should be able to do this with no problem. "Inserts, updates, deletes and reads are generally OK from multiple threads" Of note the exception says "I/O error" not "database locked".
Update: I added logcat traces and exception catching around every database access. After that I got two more exceptions. In both cases only a single database access was in progress. Both were simple inserts. The first one threw an exception 1.2 seconds after the insertion began, which seems unbelievably high. The second only took 38ms which looks to be about average.
The issues is not running out of storage: I have 27GB free. I have a hard time believing it is an issue with the flash either, as I am not seeing any other errors with write to it. I have also not seen any issues like this with an older app that uses the same SQLCipher library but which closes the database after each insert.
Update 2: In the exception handler I added code to immediately retry the insert, and so far I have not received a second exception at this point:
try {
ret = db.insertWithOnConflict("Sample", null, values, SQLiteDatabase.CONFLICT_IGNORE);
} catch (Exception e) {
Log.d(logtag,"exception:"+e);
try {
ret = db.insertWithOnConflict("Sample", null, values, SQLiteDatabase.CONFLICT_IGNORE);
} catch (Exception e1) {
Log.d(logtag,"exception2:"+e1);
}
return 0;
}

Related

Android Realm Bad Transaction Log

I am using Realm to persist data in my Android app and just got this crash with Realm. The circumstances: I am using RealmResults in a Fragment. There is a trigger to switch to another set of data, but it has not yet been saved to Realm. So I read the new from a file, save it to Realm then re-run my query to overwrite the prior RealmResults in the same Fragment.
io.realm.exceptions.RealmError: Unrecoverable error. Bad transaction log in /Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 210
at io.realm.internal.SharedRealm.nativeRefresh(Native Method)
at io.realm.internal.SharedRealm.refresh(SharedRealm.java:281)
at io.realm.HandlerController.realmChanged(HandlerController.java:450)
at io.realm.HandlerController.handleMessage(HandlerController.java:121)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6117)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
I found this response (https://github.com/realm/realm-java/issues/3702) in response to the same crash but I do not understand the concept of the response:
Bad transaction log could indicate problems with the file somehow.
I see you are using Sync. Note that you cannot open an old Realm in Sync mode. You need to move all data there. Trying to open an old Realm as syncable will result in the error you are seeing.
Can someone point me in the right direction? What does "open an old Realm in Sync mode" mean? Do I need to close Realm in my Fragment and get a new Realm instance before overwriting my RealmResults? This is highly intermittent, so it's not a matter of simply trying it once and seeing if it fixes the problem.
UPDATE: Finally fixed by not opening any Realm instance in MainActivity. So I have to check for schema migration in one of its ViewPager fragments. (FWIW the crash occurs when beginTransaction() is called in an AsyncTask of a Fragment, though I can see from my error logging that the AsyncTask continues to work after the crash). I think the problem is related to using Realm in an AsyncTask of a Fragment. The reason I think this is because I have another Activity with a ViewPager, but no problems with Realm, I assume because those ViewPager fragments do not utilize Realm + AsyncTask.
Although I no longer believe my crash is related to attempted schema migration, as requested, here is how I do that:
RealmConfiguration config = new RealmConfiguration.Builder()
.name("my_realm.realm")
.schemaVersion(MyConstants.MY_REALM_SCHEMA_VERSION)
.modules(new MyRealmModule())
.build();
try {
Realm.migrateRealm(config, new MigrationMyRealm());
System.out.println("debug tag, migrated realm");
} catch (FileNotFoundException e) {
System.out.println("debug tag, there is no realm to migrate");
} catch (IllegalStateException e) {
// realm is already open -- cannot migrate it
System.out.println("debug tag, could not migrate realm because it is open");
}

Android Sqlite SQLiteAbortException does not work

My application uses some complex sql statements so I've gotten in the probably bad habit of using execsql often, even when I could/should probably use something like insert or update for simple things(below).
However, I am wondering why this is not working
String query = "UPDATE OR ABORT " + myTable + " SET " + .... column names and values + WHERE ...
try {
db.execSQL(query);
} catch (SQLiteAbortException e) {
Log.i(TAG, "error in the update");
I've tried this with data that should clearly fail (ie, there is no matching record to update) but I am not hitting the catch. Initially I had used UPDATE OR FAIL and a SQLiteConstraintException but when that was not caught I tried the SQLiteAbortException which specifically states
An exception that indicates that the SQLite program was aborted. This
can happen either through a call to ABORT in a trigger, or as the
result of using the ABORT conflict clause.
What am I missing here?
Update: just adding this for reference on INSERT/UPDATE OR ABORT/FAIL If SQLiteAbortException is not the way, how to catch an update abort/fail?
I just tried to find an explanation for that behavior in the Java source code part of Android API level 19, but the only place where SQLiteAbortException is (re-)thrown is in a static helper class named DatabaseUtils. It gets originally thrown from native code using a method in android_database_SQLiteCommon.cpp. It is thrown when SQLite returned error code 4, which is named SQLITE_ABORT and documented as:
/* Callback routine requested an abort */
I strongly assume a C callback function is meant with that. You can register callbacks with SQLite and they seem to have some level of control. So it appears to me that error code which gets translated into an SQLiteAbortException has little to do with an SQL ABORT clause. in fact, I highly doubt that that exception ever gets thrown, since I don't think that the native part of Android's SQLite database driver hooks callbacks into SQLite that abort requests.
The SQLite documentation of ABORT is also specific which kind of error code to expect:
ABORT
When an applicable constraint violation occurs, the ABORT resolution algorithm aborts
the current SQL statement with an SQLITE_CONSTRAINT error (...snip)
So according to the documentation of SQLite and what the native Android source code actually does, the SQLiteConstraintException is to be expected in that case and apparently Android's documentation of SQLiteAbortException is not entirely correct.

Which conditions cause SQLiteDiskIOException code 3850

I have an app that uses ContentProvider to access SQLite. An instance of SQLiteOpenHelper is created in providers onCreate:
#Override
public boolean onCreate() {
final Context context = getContext();
mDBHelper = new MyDatabase(context);
return true;
}
SQLiteDatabase instances retrieved in methods insert/update/delete/query are not manually closed. None of these methods are marked synchronized. ContentProvider is accessed from multiple threads started from UI and services.
Sample stacktrace:
android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 3850)
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:734)
at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
at android.database.sqlite.SQLiteDatabase.updateWithOnConflict(SQLiteDatabase.java:1574)
at android.database.sqlite.SQLiteDatabase.update(SQLiteDatabase.java:1520)
at com.sample.provider.MyProvider.update(SourceFile:0)
at android.content.ContentProvider$Transport.update(ContentProvider.java:260)
at android.content.ContentResolver.update(ContentResolver.java:1040)
Things worked fine up to the moment when I added a class that serializes writes to certain tables using a Handler initialized by Looper from HandlerThread. After this I started seeing plenty of SQLiteDiskIOExceptions with error codes 3850 and 0 (not an error?). Interestingly 90% of these crashes occur with Nexus 4 and on a handful of other devices.
I have been running Unit tests trying to simulate the condition but have been unable to reproduce the problem. There are other questions that already discuss related issues (e.g. here: Synchronize access to Content Provider) but to me the original cause for this error seems still a bit unclear. So what really are the reasons for error 3850?

Intermittent NPE when inserting data into SQLite

I'm getting an NullPointerException when I insert values into to my SQLite table on Android and I don't understand why. I'm testing ContentValues and the database instance for null.
This is the insertion code:
public void insertOrIgnore(ContentValues values) {
SQLiteDatabase db = this.dbHelper.getWritableDatabase();
try {
//I added these null value checks to stop NPE, but doesn't help.
if (values != null && db != null) {
db.insertWithOnConflict(TABLE, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
} catch (SQLiteException e) {
} finally {
if (db != null) {
db.close();
}
}
}
where
public static final String TABLE = "albums";
Most of the time this code works with the data added to the database as expected. However, it sometimes and rarely generates the below error. The stack trace is from ACRA and I have not been able to isolate under what conditions this error occurs. I'm looking for pointers as to why this happens and what the conditions are. My knowledge of SQLite is beginner level.
java.lang.NullPointerException
at android.database.sqlite.SQLiteStatement.releaseAndUnlock(SQLiteStatement.java:290)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:96)
at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:2025)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1965)
at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:690)
at android.database.sqlite.SQLiteDatabase.beginTransactionNonExclusive(SQLiteDatabase.java:605)
at android.database.sqlite.SQLiteStatement.acquireAndLock(SQLiteStatement.java:247)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:112)
at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1844)
at com.mydomain.myapp.albums.AlbumsData.insertOrIgnore(AlbumsData.java:89)
Line 89 is the db.insertWithOnConflict(...) call shown above.
I'm not looking for an answer with complete code necessarily but rather a pointer and explanation as to what's going wrong so I can begin to fix it myself.
EDIT:
The stack trace shows the NPE originates from line 290 of SQLiteStatement (v 4.03):
setNativeHandle(mDatabase.mNativeHandle);
So it seems the database instance is null. How can it become null during a transaction when I tested for null at the beginning of the transaction?
As mentioned here SQLiteDatabase close() function causing NullPointerException when multiple threads
The reason for your bug could be that you close the database at some point. Probably concurrently while the task that fails was not finished.
I've followed the stacktrace a bit and this is what roughly happens:
AlbumsData.insertOrIgnore(AlbumsData.java:89)
You call insertWithOnConflict, which builds the resulting sql string ("INSERT OR IGNORE INTO...") then wraps that together with the values from your ContentValues into a SQLiteStatement.
SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1844) - The resulting statement is to be executed now
SQLiteStatement.executeInsert(SQLiteStatement.java:112) - before the actual insert can happen, the database needs to acquire a lock.
SQLiteStatement.acquireAndLock(SQLiteStatement.java:247) - some checks happen here, the database object is as far as I can see not null at that point. Code decides that it has to start a transaction. The database object itself is as far as I can see not locked at that point.
SQLiteDatabase.beginTransactionNonExclusive(SQLiteDatabase.java:605) - just forwarding
SQLiteDatabase.beginTransaction(SQLiteDatabase.java:690) - after some checks (not sure if database has to exist here) it will try to execute execSQL("BEGIN IMMEDIATE;")
SQLiteDatabase.execSQL(SQLiteDatabase.java:1965) - just forward
SQLiteDatabase.executeSql(SQLiteDatabase.java:2025) - builds another SQLiteStatement out of "BEGIN IMMEDIATE;. This one should be executed now
SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:96) - starts with checking the database lock, this seems to be okay and the database should not be null here. The statement is then executed and finally the database is to be unlocked again.
SQLiteStatement.releaseAndUnlock(SQLiteStatement.java:290) - cleans up some stuff and in the end fails with NPE because the database is null.
Line numbers don't match so there are probably vendor modifications / additions in that code.
As you can see, the code crashes before actually using the data you supplied. It was about to do
BEGIN TRANSACTION IMMEDIATE; -- crash
INSERT INTO table (...) VALUES (...);
-- (end transaction)
That makes it in my opinion a framework bug. The database object that is internally handled there should not be able to be null somewhere down the line, especially when it seems that it was not null further up in the stack.
I also think that it is possible that another hidden exception could be the root cause for this. There are a lot of try { /* do stuff */ } finally { /* clean up */ } blocks within the code and the finally part will be executed even if the try part throws an exception. Now the finally block could cause another exception and the result is AFAIK that the original exception is replaced by the new exception from the finally block.
Especially executeUpdateDelete() is like
try {
acquireAndLock(WRITE);
// actual statement execution
} finally {
releaseAndUnlock();
}
if the database is closed at that point, acquireAndLock or any code in the try part could fail and that could leave the database object at null which causes releaseAndUnlock to fail again. You should get the same stacktrace.
Apart from that, don't do empty catch blocks like catch (SQLiteException e) { /* empty */ }. Log them with ACRA if possible / you don't do that already.
This NPE appears to be from a custom ROM as the Android source code is pointing to different Methods than the ones you receive in the LogCat. What I do for such cases is that: if the rate of these exceptions is very rare, I ignore them as it is difficult to know what custom ROM is running on the phone and more difficult to get the source code of this custom ROM to know where the problem is.
Not many users are using custom ROMs, so if you extensively tested your App on different phones with different SDKs and the rate of the Exceptions you get is not that significant, you can ignore them. Otherwise, you can take a shoot in the dark and speculate what can be in this custom ROM that is causing NPE (personally, I think it is not worth the effort).

How to handle SQLiteException in Android activity?

I am looking at how to handle exceptions in Android.
In the update() function in the sample code for the Notepad Content Provider, it calls getWriteableDatabase(), which can potentially throw an SQLiteException.
I notice that the NoteEditor Activity saveNote() function has the following code:
// Commit all of our changes to persistent storage. When the update completes
// the content provider will notify the cursor of the change, which will
// cause the UI to be updated.
try {
getContentResolver().update(mUri, values, null, null);
} catch (NullPointerException e) {
Log.e(TAG, e.getMessage());
}
What happens if an SQLiteException occurs?. I want to be able to catch this exception in the Activity and display an appropriate message to the user (via a toast or something similar).
I thought I could do by adding an extra catch for SQLiteException. However, I read the following info in the Google docs:
"Remember that the Android system must be able to communicate the Exception across process boundaries. Android can do this for the following exceptions that may be useful in >handling query errors:
IllegalArgumentException (You may choose to throw this if your provider receives an >invalid content URI)
NullPointerException"
So I am now confused - can I catch the SQLiteException or not?
Whenever possible, you should catch the Exception in the class or component in which the Exception occurred. Use some sort of broadcast mechanism or return value semantics to report errors across processes.
It's admittedly a tricky situation. Some people say that if an SQLite (or other Exception) occurs in a ContentProvider, the provider should propagate the exception upwards instead of returning null in the Cursor. However, this generally won't work across processes! On the other hand, returning null doesn't give you a lot of information.
A limited set of Exceptions do traverse process boundaries, but SQLiteException isn't among them - still they might be useful/appropriate.

Categories

Resources