Which conditions cause SQLiteDiskIOException code 3850 - android

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?

Related

After applying Android Studio 2.3.3 suggested changes (to remove warnings) I now get either of two errors. What to do?

Here's a little background. The process of querying the database (QueryDB) in my app begins in MainActivity.onCreate, where I have this code:
assets = getAssets(); // the SQLite database
DatabaseConnector
dbc = new DatabaseConnector(getApplicationContext(), assets);
dbc.setDbProcesslistener(this); // set way to know matches has been defined
dbc.findDBMatches();
And in the file in my question (named DatabaseConnector) I have:
void findDBMatches()
{
mContext.startService(new Intent(mContext, QueryDB.class));
}
Here's where the problem manifests itself. This code segment ...
public static class QueryDB extends IntentService
{
public QueryDB(String name)
{
super(name);
}
results in this error:
? E/libprocessgroup: failed to make and chown /acct/uid_10058: Read-only file system
? W/Zygote: createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?
com.dslomer64.servyhelperton E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.dslomer64.servyhelperton, PID: 335
java.lang.RuntimeException: Unable to instantiate service
com.dslomer64.servyhelperton.DatabaseConnector$QueryDB:
java.lang.InstantiationException: class
com.dslomer64.servyhelperton.DatabaseConnector$QueryDB has no zero argument constructor
The error doesn't tell me a line, but it does mention QueryDB. So I insert a zero-argument constructor for QueryDB and get an immediate error:
I circled extends IntentService because, in findDBMatches, I start a service for QueryDB and the first error message says Unable to instantiate service. However, in debugging, I found that execution didn't fail at the line mContext.startService(new Intent(mContext, QueryDB.class));. I had breakpoints set in the constructors for QueryDB but execution didn't go there.
I'm lost.
Before (foolishly) taking AS's advice about changes, the app worked fine. Now I could go back through the history and revert to the version before I began the changes, but there were plenty (of warnings) that I got rid of and I'd rather not do that. If anyone can, with such short snippets of code, suggest a fix, I can try it and maybe be good to go.
Further notes:
App won't work without QueryDB extending IntentService (get other immediate errors).
Note that mContext WAS declared static (which I now know not to do because of memory leaks) but it doesn't matter whether it's static or not. Same errors.
use super("QueryDB"); inside the constructor
public QueryDB {
super("QueryDB");
}

Query using MockContentResolver leads to NullPointerException

We have a JUnit test class which extends ActivityInstrumentationTestCase2<CommentActivity>. The test (and the class we're testing) use CommentContentProvider, which extends ContentProvider, to access the SQLite database, and we're getting a NullPointerException [full stack trace below] when running a query on the provider.
We instantiate a MockContentResolver as shown:
MockContentResolver mResolver;
public void setUp() {
super.setUp();
CommentContentProvider ccp = new CommentContentProvider();
mResolver = new MockContentResolver();
mResolver.addProvider(CommentContentProvider.AUTHORITY, ccp);
}
Later on, in our tests, when calling the following code, we get a NullPointerException:
Cursor mCursor = mResolver.query(Uri.parse(mUri), null, null, null, null);
We get the same result even if we wait to instantiate MockContentResolver until we have a copy of the activity under test:
mActivity = getActivity();
MockContentResolver mResolver = new MockContentResolver(mActivity);
We have verified that mActivity is not null.
A colleague stepped through the Android source (not installed on our system) and found that the proximate cause of the error is that getContext() returns null on the first line of ContentProvider.enforceReadPermissionInner().
We took a look at this question which originally seemed similar, but I think it was a different problem entirely.
This question is also a similar symptom of a problem, but they didn't instantiate their MockContentResolver. We are having problems instantiating ours.
Here's the stack trace we're getting:
java.lang.NullPointerException
at android.content.ContentProvider$Transport.enforceReadPermissionInner(ContentProvider.java:449)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:394)
at android.content.ContentProvider$Transport.query(ContentProvider.java:194)
at android.content.ContentResolver.query(ContentResolver.java:461)
at android.content.ContentResolver.query(ContentResolver.java:404)
at packagename.test.FooActivityTest.getNumCommentsForRecipient(FooActivityTest.java:84)
at packagename.test.FooActivityTest.testCommentEntryInternal(FooActivityTest.java:91)
at packagename.test.FooActivityTest.testCommentEntry1(FooActivityTest.java:108)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
at android.test.InstrumentationTestCase.access$000(InstrumentationTestCase.java:36)
at android.test.InstrumentationTestCase$2.run(InstrumentationTestCase.java:189)
at android.app.Instrumentation$SyncRunnable.run(Instrumentation.java:1719)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4998)
at java.lang.reflect.Method.invokeNative(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
at dalvik.system.NativeStart.main(Native Method)
How can we resolve this problem?
I had a similar problem when testing a content provider that internally relied on another content provider to write away some metadata.
First of all, you may be better off using the ProviderTestCase2 class, which will do most of the work for setting up the provider under test for you. It might make your life considerably easier. (For me this wasn't enough because it'll only help you with one provider, I needed two.)
If this is not possible for you, here's what did the trick for me:
Your query fails because your provider never had a context attached to it. You have to do this yourself, manually - which the documentation forgets to mention. Do this:
public void setUp() {
super.setUp();
CommentContentProvider ccp = new CommentContentProvider();
// Add this line to attach context:
ccp.attachInfo(mActivity, null);
mResolver = new MockContentResolver();
mResolver.addProvider(CommentContentProvider.AUTHORITY, ccp);
}
I'm not 100% sure which context to attach to keep your test isolated from the rest of the world, ProviderTestCase2 sets up a whole chain of mock contexts. If you're having issues, look at RenamingDelegatingContext and IsolatedContext, those are the ones ContentProviderTestCase2 uses. (Have a look at its setUp() method).
Hope this helps you!

net.sqlcipher.database.SQLiteException while opening database after resuming app

I'm getting the following error when using the encrypted SQLCipher database in my Android app, but only off and on:
net.sqlcipher.database.SQLiteException: not an error
at net.sqlcipher.database.SQLiteDatabase.dbopen(Native Method)
at net.sqlcipher.database.SQLiteDatabase.<init>(SQLiteDatabase.java:1950)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:900)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:947)
at net.sqlcipher.database.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:195)
at com.android.storage.DatabaseHelper.getReadable(DatabaseHelper.java:99)
...
I've got the proper files in the assets/ and libs/ folders because the database works fine most of the time. However, every once in awhile I'll see this error. I've seen this twice now on my phone and it's always been after resuming the app after hours of inactivity (I check for user's oauth token in db if it gets cleared from memory).
I call "SQLiteDatabase.loadLibs(this)" only from the Application::onCreate() method so my hunch is that this isn't getting called on a resume and is throwing the error. Does this sound possible? If so, where should I call loadLibs? A user could enter the app in any activity and I access the db if the token isn't in memory. I see my options as either calling loadLibs on each Activity::onCreate or calling it each time I attempt to open the db. Would it cause any harm or performance issues if I called it multiple times like this?
You might consider moving the SQLiteDatabase.loadLibs(this); to your application subclass of net.sqlcipher.database.SQLiteOpenHelper. You can then pass the static instance of your Application subclass as its argument. Something like the following might be an example:
public class SchemaManager extends net.sqlcipher.database.SQLiteOpenHelper {
private static SchemaManager instance;
public static synchronized SchemaManager getInstance() {
if(instance == null) {
SQLiteDatabase.loadLibs(YourApplication.getInstance());
instance = new SchemaManager(…)
}
return instance;
}
}
With regard to the exception that was provided, the Java routine calls into a JNI layer that calls sqlite3_open_v2, setting the soft heap limit and setting the busy timeout. I would suggest adding logging locally to verify you are passing a valid path and a non null passphrase when attempting to acquire the SQLiteDatabase instance when you get a crash. Calling SQLiteDatabase.loadLibs(this); multiple times shouldn't cause a noticeable performance impact, much of what occurs are calls to System.loadLibrary(…) which get mapped into Runtime.getRuntime().loadLibrary(…), once a dynamic library has been loaded, subsequent calls are ignored.

Android ORMLite 4.38 allowGeneratedIdInsert causing IllegalStateException

I'm targeting Android 2.2 and newer. This error was generated on a device running 4.x. I am using ORMLite 4.38 libraries.
I need to guarantee every record instance is unique for any number of devices. I was happy to see that ORMLite supports UUIDs as IDs. I've created a UUID - id abstract base class for my database record definitions. allowGeneratedIdInsert is the perfect solution. But this feature seems to cause an 'IllegalStateException: could not create data element in dao'. I tested by removing this annotation, and no issue. Put it back in...same issue. Put the base class stuff in one record definition...same issue.
LogCat also reports:
Caused by: java.sql.SQLException: Unable to run insert stmt on object - objectid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
public abstract class UUIDDaoEnabled<T> extends BaseDaoEnabled<T, UUID> {
//allowGeneratedIdInsert allows us to set UUID when this device db didn't create it
#DatabaseField(generatedId = true, allowGeneratedIdInsert=true)
private UUID id;
...
public void setUUIDFromSerializedSource(SerializedModelBinaryInputStream stream, Dao<T, UUID> dao) throws SQLException { //only place we can set UUIDs
if(id == null)
dao.refresh((T)this);
if(id != null)
throw new SQLException("Trying to set UUID on existing object");
id = stream.getCurrentUUID();
}
}
I'll specialize like so:
#DatabaseTable()
public class Type extends UUIDDaoEnabled<Type> { ... }
I can't explain this from the documentation for allowGeneratedIdInsert and generatedId. In fact the documentation for alloeGeneratedIdInsert says it overrides the default behavior of generatedId. It also says
This only works if the database supports this behavior
Yet, I have read in other posts that ORMLite 4.25 (?) and newer supports this behavior on Android devices. So, either that's not entirely true. Or I'm doing something stupid...anyone???
UPDATE: after thinking about it for a minute, I realized that neither allowGeneratedIdInsert support, nor inheritance can be the root cause, because I instantiate other objects based on the same abstract class. What I can't figure out is why one particular class is causing the issue. The only unique thing about the offending record type (compared to other types that create) is it is a many in a one to many, and it contains several to manies. Could these properties, combined with allowGenereatedIdInsert, be the root issue? Rather, I should ask, has anyone seen this issue in this circumstance?
UPDATE: nevermind the question. I can use updateId(...) instead of allowGeneratedIdInsert.
So I'm not sure about this but it looks to me that you are trying to insert an element twice into a table with the same UUID id. The exception is saying there is a constraints failure:
IllegalStateException: Could not create data element in dao
at BaseForeignCollection.add(BaseForeignCollection.java:57)
...
Caused by: SQLiteConstraintException: error code 19: constraint failed
If you call foreignCollection.add(...); it does the same thing as dao.create(...); -- and you can't do both of these with the same object. If you have an existing object that has already been created by the DAO and you want to associate it with another object, you should do something like:
// associate this object with another
existingObject.setForeignField(...);
// now update it in the db
existingObjectDao.update(existingObject);
You can't add it to the foreignField's foreign collection.
I had a similar problem. But it was caused by using create instead createOrUpdate to save the object.
It is also important to uninstall the application before changing this to ensure that the database has been removed and will not keep the old behavior.
Edit: createOrUpdate is very time expensive. It's better use just create with great amounts of data.
Edit 2:It is also bether to use a TransactionManager.callInTransaction.

How careful should I be with thread-safety when creating methods/activities which interact with SQLite database?

I am creating an app which allows for many different Activities to be started from a TabActivity(up to ~25). Most of the activities require data from the sqlite database, so when onCreate is run, an AsyncTask creates an SQLiteOpenHelper object(which will open a readable/writable database), runs a query, data is retrieved, and everything is then closed.
i was just testing messing around to see if i could break something, so i added every Activityto the TabActivity's TabHost. I then started mashing each tab as quickly as possible.
i noticed that very quickly i began to see in the LogCat: Caused by: android.database.sqlite.SQLiteException: database is locked: BEGIN EXCLUSIVE; and the app proceeded to die.
Typically there will only be about 4-6 tabs(i can just limit the user anyway) for the TabHost. I haven't been able to break anything with a small amount of tabs to mash, but i am still worried that maybe i am accessing the database in a poor way.
How can i prevent my SQLiteDatabase objects to cause a lock?
If i create a ContentProvider will that eliminate the possibility of database locking?
Do you have any suggestions for changes I could make for accessing data from an SQLiteDatabase?
I ended up taking the approach of using the Application class and storing 1 SQLiteOpenHelper and trying my best to keep it synchronized. This seems to be working great - i put all my 25 activities in the TabHost and mashed away on them with no errors.
I am calling ((SQLiteDbApplication)getApplication()).setDbHelper(new DBHelper(this, Constants.DB_NAME, null, Constants.DB_VERSION_CODE)); method(shown below) in every onCreate() in my activities
Any further suggestions to this approach or to the changes i made using this Application class?
import android.app.Application;
import android.database.sqlite.SQLiteDatabase;
public class SQLiteDbApplication extends Application {
private DBHelper dbHelper;
private SQLiteDatabase db;
public synchronized DBHelper getDbHelper() {
db = dbHelper.getDatabase();//returns the already opened database object
while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads());
return dbHelper;
}
public synchronized void closeDb() {
if(null != dbHelper)
dbHelper.close();
if(null != db)
db.close();
}
#Override
protected void finalize() throws Throwable {
if(null != dbHelper)
dbHelper.close();
if(null != db)
db.close();
super.finalize();
}
public synchronized void setDbHelper(DBHelper dbHelper) {
if(null == this.dbHelper) {
this.dbHelper = dbHelper;
this.dbHelper.setDb(this.dbHelper.getWritableDatabase());//creates and sets the database object via getWritableDatabase()
}
}
}
If you are to worried about all the database connections try to limit yourself to one SqliteOpenHelper and be sure to wrap a synchronization layer around it.
You can extend the application class and then call getApplication and cast the object you get into your application. Now you can store a SqliteOpenHelper in this application class and build your own thread safe access method to the database connection.
If you are using AsyncTask in all of your onCreate methods and you are experiencing problems with a lot of tabs these problems can also occur with a slower device, a faster user or a database that is grown big over the time of usage.
Depending on the use case of you app you can go the save way and go through all the effort and pain of threading and locking, or you can just publish the app with a number of tabs that never produced the error and be sure to catch the database exception and send yourself a notification (for example through google analytics) to test if the threading problem does occur in real life usage of the app.
All activity callbacks happen on the main thread, so in the scenario you describe there is no multi-threading going on, no matter how many activities or tabs you have.
ContentProvider doesn't provide any locking. In fact, it can introduce multithreading where you wouldn't already have it because it allows other processes to make calls in to your own process, and when that happens the call is dispatched from a separate thread in your process (not on the main UI thread).
Of course if you create your own threads, then you will also have multi-threading going on.

Categories

Resources